Enrich your timeline with custom app data

Capture app state automatically at meaningful events so you can explain what happened with more than screenshots and logs.

Preserve what your app knew when an error or important event occurred. Repository triggers request custom app data in response to app or session events, placing the captured evidence beside screenshots, touches, logs, telemetry, and visual trees in the Ansight timeline.

This gives humans and agents the missing context they need to understand how hidden app state changed throughout a session. Use a trigger to request diagnostics when an error log arrives, snapshot relevant database state after a write or migration, or preserve map annotations, route state, and viewport after a map event.

The CLI matches the App ID, exact event kind, and declarative conditions before running the trigger, which can return no action or one app-tool action. Version 2 also supports a host screenshot, sandbox file capture, or annotation action without the SDK. Use a task when a user or agent should explicitly start the workflow. Triggers are not schedulers, background listeners, or arbitrary TypeScript predicates.

See Loading and Session Lifecycle for the startup flow chart, when descriptors load, and what session.capture.started does and does not guarantee.

Create a trigger

Trigger modules live beneath ansight/triggers. A module’s relative path becomes its ID: ansight/triggers/diagnostics/capture-errors.ts becomes diagnostics.capture-errors.

Create a starter trigger with the CLI:

ansight workspace add trigger . diagnostics.capture-errors \
  --app-id com.example.app \
  --event-kind session.log.received

This also initializes the workspace support files if they do not exist yet. The example below adds conditions and an action to that starter trigger.

You can also review discovered triggers from the local player’s Tests area.

Review the complete TypeScript declarations and trigger descriptor schema online, or download them from the source viewer.

Complete trigger example

Every trigger exports a static trigger descriptor and one default function:

import type {
  TriggerContext,
  TriggerDefinition
} from "./ansight-trigger.d.ts";

type LogPayload = {
  priority: string;
  sourceEventId?: string;
  message?: string;
};

export const trigger = {
  "schemaVersion": 1,
  "eventKind": "session.log.received",
  "eventSchema": {
    "type": "object",
    "properties": {
      "priority": { "type": "string" },
      "sourceEventId": { "type": "string" },
      "message": { "type": "string" }
    },
    "required": ["priority"],
    "additionalProperties": true
  },
  "conditions": [
    {
      "field": "payload.priority",
      "operator": "equals",
      "value": "Error"
    }
  ],
  "functionTimeoutMs": 100,
  "actionTimeoutSeconds": 20,
  "retry": {
    "maxAttempts": 3,
    "initialDelayMs": 250,
    "backoffMultiplier": 2,
    "maxDelayMs": 2000
  }
} satisfies TriggerDefinition;

export default async function captureError(
  { event, app }: TriggerContext<LogPayload>
) {
  return app.artifacts.request({
    providerId: "example.diagnostics",
    artifactId: "state",
    arguments: {
      sourceEventId: event.payload.sourceEventId ?? "",
      message: event.payload.message ?? ""
    }
  });
}

The descriptor must remain a JSON-compatible object literal. The host extracts it without importing or executing the module, so do not use variables, spreads, functions, or computed values inside the descriptor.

Descriptor fields

FieldRequiredMeaning
schemaVersionNo1 (default) requires an SDK provider; 2 supports capability requirements and host evidence actions.
requiresNoVersion 2: required capabilities and executable appTools IDs, checked before the handler runs.
enabledNoWhether the trigger may match events and execute. Defaults to true; disabled triggers remain visible in inspection and connected catalogs.
appIdNoOptional exact App ID when the repository has a registered app scope; otherwise required.
eventKindYesExact normalized host event kind.
eventSchemaNoDescriptive schema for event.payload. It does not control matching.
conditionsNoUp to 16 declarative conditions, all of which must match.
functionTimeoutMsNoTrigger function bound from 10–1,000 ms. Default: 100 ms.
actionTimeoutSecondsNoSeparate returned-action bound from 1–300 seconds. Default: 30 seconds.
retryNoHost-owned retry policy with at most five total attempts.

eventSchema helps editors, agents, and module inspection understand the payload. Contract version 1 does not reject events that differ from this schema. Use conditions for authoritative matching.

Event kinds

Choose one exact eventKind for each trigger, then narrow it with conditions. Each event includes its App ID, optional session ID, timestamp, and a payload specific to the event family.

Event familyUse it for
Application eventsApp-defined milestones and foreground/background changes
PairingEnrollment progress and connection failures
Capture lifecycleSession connection, disconnection, and finalization
Session transfersNotifications that evidence arrived
Captured logsMatching an individual log message or severity
TrendsFailed checks, regressions, and recovery
Unknown eventsDiagnostic fallback kinds

Application events

app.event

Fires for each application event reported by the SDK, such as a completed login or a map-load milestone. Use it to capture app state at a meaningful point in a workflow. Filter payload.label, payload.eventType, payload.details, or payload.channelId; payload.clientName identifies the app.

This event carries the original application-event fields even when the player also shows that event in its timeline or log view. For example, match payload.label against your app’s map.loaded event.

app.lifecycle.changed

Fires when the reported app lifecycle state changes, such as entering the foreground or background. Use it for diagnostics around app activation or suspension. The payload contains previousState, currentState, changedAtUtc, and clientName; for example, filter payload.currentState to Foreground.

A foreground transition does not guarantee that a screen has finished loading. Use an app-defined readiness event when that distinction matters.

Pairing events

Pairing payloads contain clientName, remoteAddress, configId, reasonCode, and reasonMessage. Some values can be absent, and a rejected request may not have a session. Use these events for enrollment diagnostics; a live app-tool action requires an established connection.

app.pairing.discoveryReceived

Fires after an enrollment connection request passes authorization and receives a reserved session ID. Use it to track connection setup. It does not indicate that every discovery packet was accepted or that the app is connected yet.

app.pairing.accepted

Fires when enrollment is accepted and a live-session endpoint has been issued. Use it to confirm pairing progress. Wait for session.capture.started before assuming the app has opened that connection.

app.pairing.rejected

Fires when authorization is rejected or a live-session endpoint cannot be provided. Inspect payload.reasonCode and payload.reasonMessage to diagnose the failure. Avoid returning an app-tool action when no live session exists.

Capture lifecycle

These events describe the capture connection. Their payload contains clientName, status, and message. See the session lifecycle flow chart for their place in the overall startup and capture sequence.

session.capture.started

Fires when the app opens its session connection. Use it for connection-start diagnostics. It can fire again on reconnect and does not guarantee that app tools, the first screenshot, or the initial screen are ready.

session.capture.updated

This name is reserved in the current contract for capture-status updates. The current CLI does not emit it during normal capture. Use a specific transfer, log, or lifecycle event for a trigger that must run today.

session.capture.stopped

Fires when the session connection closes or is replaced by a newer connection. Use it to observe disconnection. Final capture work may still be running, and the disconnected app can no longer supply a final live snapshot.

session.capture.finalized

Fires after capture shutdown, including native-log and external-screenshot capture, has finished. Use it for work that depends on the completed capture. Automatic Trends analysis is queued at this point; this event does not mean Trends results are already available.

Session transfers

Transfer events announce arriving evidence. They carry clientName, itemCount, message, and an optional capturedAtUtc, rather than the complete evidence content. Use them when arrival itself matters. Use the dedicated app-event or log event when conditions need fields from an individual item.

session.transfer.telemetry

Fires when metric channels are registered or telemetry samples arrive. itemCount reports the channel or sample count. Use Trends definitions for metric budgets; this payload does not expose individual metric values.

session.transfer.log

Fires when client log evidence arrives. Use it to observe log transfer activity. Choose session.log.received to match severity, source, or message text.

session.transfer.appEvent

Fires when a batch of application events arrives; network-request arrivals also use this transfer kind. Use it for transfer diagnostics. Choose app.event to match an original SDK event’s label or details.

session.transfer.appProfile

Fires when device/app profile evidence arrives. Use it to observe profile availability. The notification summarizes the transfer; it does not include the complete profile or guarantee every app tool is ready.

session.transfer.screenshot

Fires when a screenshot frame is received from the app. Use it to correlate app state with incoming visual evidence. The notification contains no image bytes and should not be treated as a notification for every capture source.

session.transfer.visualTree

Fires when an incoming visual-tree snapshot is saved. Use it to observe new UI structure evidence. itemCount counts the snapshot, not its nodes; the payload does not contain the tree itself.

session.transfer.touchInput

Fires when touch input records arrive. Use it to correlate app state with input activity. itemCount counts records, which may represent parts of a gesture; the notification does not describe a completed semantic tap or swipe.

session.transfer.annotatedFeedback

Fires after annotated feedback from the app has been successfully imported. Use it to collect related app diagnostics for that feedback. The notification identifies the transfer rather than embedding the annotation bundle.

Captured logs

session.log.received

Fires for an individual captured log entry. Use it to request diagnostics when a particular error or warning appears. The payload contains streamId, message, priority, source, tag, sourceEventId, processId, and threadId.

For example, combine payload.priority equal to Error with payload.message containing a specific failure. Narrow the conditions so routine logs do not repeatedly trigger expensive capture work.

This event includes native system logs captured for the app: android-logcat on Android and apple-unified-log on iOS simulators, as well as the sdk stream. Filter payload.streamId to choose a stream. Native log capture must be enabled; the trigger sees only entries retained for the selected session.

For an externally monitored app, use a version 2 trigger and declare the host capability its action needs. For example, capture a screenshot on a native error:

export const trigger = {
  "schemaVersion": 2,
  "appId": "com.example.notes",
  "eventKind": "session.log.received",
  "requires": { "capabilities": ["ui.screenshot"] },
  "conditions": [
    { "field": "payload.streamId", "operator": "equals", "value": "android-logcat" },
    { "field": "payload.priority", "operator": "equals", "value": "Error" },
    { "field": "payload.tag", "operator": "equals", "value": "Notes" }
  ]
};

export default ({ ansight }) => ansight.screenshots.capture({});

Use apple-unified-log and the tag shown in your captured logs for iOS. Host file and screenshot actions work without the SDK; app-internal tools still require the SDK and are refused when unavailable. Linking the trusted workspace to the App ID enables its triggers for future matching log entries.

These events report Trends analysis outcomes. Payload fields include definitionId, spanGroup, message, trendsStatus, historyStatus, and historyComparison; fields vary with the outcome. They can arrive after the app disconnects, so a live app-tool action may no longer be available.

Fires when a required Trends check fails or errors. A report-persistence error can also produce this event with definitionId set to trends.persistence. Use the definition ID and status to focus follow-up on the failed check.

Fires when Trends history reports a regression against the configured baseline. Use it to follow up on deteriorating behavior across comparable runs. Inspect definitionId, spanGroup, and historyComparison to identify the affected comparison.

Fires when Trends history reports recovery from a regression. Use it to record that the affected comparison has recovered; it does not mean every Trends check for the app passed.

Unknown events

These are fallback names for an unrecognized event subtype in each family. They are not wildcards and do not subscribe to every event in that family.

app.pairing.unknown

An unrecognized pairing subtype, carrying the pairing diagnostic fields.

session.capture.unknown

An unrecognized capture subtype, carrying capture status and message fields.

session.transfer.unknown

An unrecognized transfer subtype, carrying transfer counts and timing fields.

An unrecognized Trends subtype, carrying the available outcome fields.

Conditions

The CLI evaluates conditions before running the trigger. They can inspect these event envelope fields:

  • eventId
  • kind
  • occurredAtUtc
  • appId
  • sessionId
  • correlationId
  • causationId
  • nested payload paths beginning with payload.

Supported operators are equals, notEquals, contains, startsWith, endsWith, exists, and notExists. String comparisons can set ignoreCase: true. Every condition is ANDed; there is no executable predicate.

value is required except for exists and notExists:

"conditions": [
  {
    "field": "payload.label",
    "operator": "equals",
    "value": "MapPage"
  },
  {
    "field": "payload.details",
    "operator": "contains",
    "value": "navigation settled",
    "ignoreCase": true
  },
  {
    "field": "sessionId",
    "operator": "exists"
  }
]

Keep conditions as narrow as possible. A broad high-frequency match can start too many trigger runs and exhaust available execution capacity.

Function and returned action

The default function receives { run, event, app }:

  • run identifies the trigger run, attempt, repository, queue time, and effective limits.
  • event is the immutable normalized event envelope that matched.
  • Standardized suite methods such as app.artifacts.request(arguments) create actions for core app tools.
  • app.callTool(toolId, arguments) creates an action for a user-defined tool.

These methods do not call the app from inside the module. Return their result so the host can validate the action, preserve the matched event’s session and correlation identity, apply app grants, audit the request, and execute it under actionTimeoutSeconds.

A trigger can return null, return nothing, or return one action. It cannot call host-owned UI tools or execute multiple app actions. The connected app validates custom tool IDs and arguments through its normal authorization policy.

For replay enrichment, prefer an artifact provider whose snapshot is small, bounded, and understandable without live app context. Give the artifact a stable provider and artifact ID, include the source event or domain revision in its arguments or metadata, and capture only the state needed to explain the event. This makes successive database, map, navigation, cache, or domain-state snapshots comparable on the session timeline without turning the trigger into a continuous polling mechanism.

Diagnostic result retention

A successful file_descriptors.list_open action also creates a durable open-file-descriptors.json artifact snapshot on the matched session timeline. Count and usage actions remain compact trigger-run results. Continuous open-handle and JNI-reference counts remain telemetry; JNI reference graphs are not currently exposed as an app tool.

Timeouts and retry

The function and returned action have separate bounds. A slow app tool should use actionTimeoutSeconds; do not expand the tight TypeScript function bound to cover action execution. Module loading has its own five-second safety limit.

Retry configuration is host-owned:

FieldRangeDefault when retries are enabled
maxAttempts1–5, including the first attempt1
initialDelayMs10–60,000 ms250 ms
backoffMultiplier1–102
maxDelayMsAt least initialDelayMs, at most 60,000 msEight times the initial delay, capped at 60,000 ms

Retry delays are invalid when maxAttempts is 1. Only Failed and TimedOut attempts retry. Succeeded, Rejected, Cancelled, and exhausted attempts are terminal. Every attempt starts with fresh module state while retaining the same run, event, and correlation identity.

Connect triggers through the local host

Trigger code is trusted local automation and is never enabled merely because it exists in the repository.

  1. Review the repository’s trigger code, event kinds, actions, and limits.
  2. Open Apps in the local player and select the registered App ID.
  3. Link Agent workspace to the repository root. Linking enables repository automation and the host attempts to connect its trigger catalog.
  4. Inspect the connection and any validation warnings. Use Connect to reconnect after fixing or updating definitions.

The connection is scoped to the exact App ID. The host remembers the opt-in and restores the connection while that app remains linked to the same workspace. Choose Disconnect to stop future matches.

Registering a codebase through ansight app register --codebase or ansight workspace init --app-id also enables this workspace link. A separate manual Connect step is not required for a valid enabled link.

A connected device cannot nominate or change the repository path. The selected registered host app owns that association.

Inspect and connect from the CLI

Inspecting validates the repository without executing or connecting triggers:

ansight repo automation inspect com.example.app .

CLI trigger connections require a resident host. Repository automation is enabled by default:

ansight host run

From another terminal, connect and inspect the active catalog:

ansight repo automation connect com.example.app .
ansight repo automation list

Disconnect or inspect recent runs with:

ansight repo automation disconnect com.example.app
ansight repo automation runs com.example.app --limit 100

Linking an Agent workspace restores its enabled trigger connection at host startup. For a one-off repository that is not linked to an app, pass the repository option:

ansight host run \
  --automation-repository /path/to/repository

When a repository is configured directly without a registered app scope, every trigger descriptor must declare its appId.

Inspect a trigger through the CLI

Validate the repository catalog without connecting it:

ansight repo automation inspect com.example.app /path/to/repository --json

Use the returned trigger IDs, descriptors, module paths, and validation messages as the authoritative discovery result.

Run history and diagnosis

In Apps, select the App ID and inspect Triggers → Recent runs. Each attempt records:

  • trigger ID, run ID, attempt number, and retry state
  • matched event, event ID, correlation ID, and session ID
  • start time, completion time, and duration
  • status, message, and process exit code
  • returned action and app-tool result
  • standard error

Select a run for its complete trace. History survives host restart and is stored as bounded local JSONL. The CLI can return up to 500 recent app-scoped attempts.

No run trace means the module never started. Check the App ID connection, exact event kind, every condition, and bounded queue admission. Other common states:

  • Rejected: the action returned an invalid shape or required a live session the event did not have.
  • Failed: inspect the execution message, standard error, and tool result.
  • TimedOut: identify whether the function bound or separate action bound expired.
  • Runtime unavailable: run ansight doctor and follow the runtime setup guide.

Security and limits

App-tool actions retain the connected app’s grants, critical-tool policy, current availability, cancellation, audit, and artifact persistence. A trigger cannot grant itself more access.

Capability declarations restrict calls back into Ansight; they do not sandbox filesystem, process, or network access available to trusted local workspace code. Review every module before connecting the repository.

A repository can expose up to 256 triggers, and each executable module is limited to 1 MiB. An invalid trigger definition rejects the repository trigger catalog, so run inspection after every change and reconnect to load updates.