Repository Triggers

Match app and session events in the host, run bounded repository handlers, return one app-tool action, and inspect durable attempt history.

CLI v0.23.1

A repository trigger reacts automatically to one normalized app or session event. Its main purpose is to enrich session replay and the timeline with an app-owned state snapshot at the moment that state is most useful. The C# host matches the App ID, exact event kind, and declarative conditions before it starts the TypeScript module. The trigger function can return no action or one app-tool action.

Use a trigger for bounded capture work such as requesting diagnostics when an error log arrives, snapshotting relevant database state after a write or migration, or preserving map annotations, route state, and viewport after a map event. Those artifacts sit beside screenshots, touches, logs, telemetry, and visual trees, allowing a human or agent to reconstruct how hidden app state changed throughout a session during post-mortem inspection. Use a task when a user or agent should explicitly start the workflow. Triggers are not schedulers, background listeners, or arbitrary TypeScript predicates.

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 the support files and a starter trigger with the CLI:

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

You can also create and edit triggers in Studio’s Test workspace.

Complete trigger example

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

import type {
  TriggerContext,
  TriggerDefinition
} from "./ansight-automation.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. Studio extracts it without importing or executing the module, so do not use variables, spreads, functions, or computed values inside the descriptor.

Descriptor fields

FieldRequiredMeaning
schemaVersionNoContract version. Omitted descriptors use version 1.
appIdNoOptional exact App ID. Required when an embedded host supplies repository paths without a Studio workspace.
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

Contract version 1 publishes these normalized event kinds:

  • App lifecycle and application events: app.lifecycle.changed, app.event
  • Pairing events: app.pairing.discoveryReceived, app.pairing.accepted, app.pairing.rejected
  • Capture lifecycle: session.capture.started, session.capture.updated, session.capture.stopped, session.capture.finalized
  • Session transfers: session.transfer.telemetry, session.transfer.log, session.transfer.appEvent, session.transfer.appProfile, session.transfer.screenshot, session.transfer.visualTree, session.transfer.touchInput, session.transfer.annotatedFeedback
  • Captured log entries: session.log.received
  • Quality controls: quality.check.failed, quality.trend.regressed, quality.trend.recovered

Raw SDK application events use app.event, even when Studio also projects them into a session timeline or log view. Match app.event when the trigger needs the original application-event payload.

Conditions

Conditions are evaluated in C# before Node starts. 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 many short-lived processes and consume the bounded trigger queue.

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 Studio-owned host 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 a fresh process while retaining the same run, event, and correlation identity.

Connect triggers in Studio

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

  1. Open Workspace → Apps.
  2. Select the registered App ID.
  3. Link Agent workspace to the repository root.
  4. Review the discovered trigger IDs, event kinds, actions, and limits.
  5. Choose Connect and accept the local-code trust warning.

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

A connected device cannot nominate or change the repository path. The selected Studio 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 with repository automation enabled:

ansight host run --enable-repository-automations

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

CLI connections last for the resident host’s lifetime. To restore them at host startup, pass both options:

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

An embedded host can configure repository paths directly. In that mode, every trigger descriptor must declare appId because no selected Studio workspace supplies the scope.

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 Workspace → 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 Studio 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: install Node or configure an embedded host with an absolute JavaScript executable path.

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 Node 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.