Repository Tasks

Package deterministic app workflows as typed repository modules with validated input, named assertions, and structured output.

CLI v0.23.1

A repository task is trusted local TypeScript that performs one known, repeatable workflow against a live Ansight app session. A user, agent, or CLI invokes the task by its exact ID; it never runs in the background.

Use a task when the required steps and tool calls are already understood—for example, query every map annotation and assert that the expected areas exist. Use a workspace test when a model should navigate and reason through a complete user journey.

Create a task

Task modules live beneath ansight/tasks. A module’s relative path becomes its ID: ansight/tasks/map/validate-areas.ts becomes map.validate-areas.

Create the support files and a starter task with the CLI:

ansight workspace init .
ansight workspace add task . home.verify \
  --app-id com.example.app \
  --title "Verify signed-in home" \
  --description "Checks that the signed-in home screen is visible."

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

Extract a task from a session

Use timeline extraction when a recorded session contains a specific interaction you want to preserve as a repeatable task. The extraction is a starting draft, not an assertion that the recorded behavior was correct.

Find the recorded session and inspect the evidence around the behavior:

ansight session list --app-id com.example.app
ansight session show <session-id>
ansight session touches <session-id>
ansight session trees <session-id>
ansight session images <session-id>

Select a tight interval and pass its bounds as non-negative seconds from session start or as ISO-8601 timestamps:

ansight task extract <session-id> \
  --start 12.5 \
  --end 24.8 \
  --workspace . \
  --title "Complete checkout"

By default, the CLI writes a slugged TypeScript file to ansight/tasks/<suggested-name>.ts. Use --output <path> to choose an exact destination. Existing files are preserved unless you explicitly pass --force (--overwrite is an alias). --repository <path> is accepted as an alias for --workspace <path>.

The extractor:

  • groups the selected touch events into gestures;
  • generates taps only when nearby visual-tree evidence provides a stable automation ID or text selector;
  • converts drags into directional swipe actions;
  • reports long presses, multi-touch, incomplete gestures, ambiguous targets, and missing selectors as REVIEW: diagnostics; and
  • adds a provisional final UI-stability assertion.

Open the generated file and resolve every diagnostic. Confirm or encode the required starting state, simplify selectors where necessary, and replace the provisional stability check with named assertions for the product outcome you actually care about. Extraction reads the stored session; the live app is only needed when you test the generated task.

npx tsc -p ansight/tasks/tsconfig.json
ansight task run <generated-task-id> \
  --app-id com.example.app \
  --repository . \
  --session-id <connected-session-id>

The extraction command and every option are also listed in the CLI command reference.

Complete task example

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

import type { TaskDefinition, TaskInvocation } from "./ansight-task.d.ts";

type VerifyHomeInput = { automationId: string };

export const task = {
  "schemaVersion": 1,
  "title": "Verify signed-in home",
  "description": "Checks that the signed-in home screen is visible.",
  "feature": "onboarding",
  "keywords": ["login", "home"],
  "inputSchema": {
    "type": "object",
    "properties": {
      "automationId": {
        "type": "string",
        "default": "signed-in-home"
      }
    },
    "additionalProperties": false
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "matchCount": { "type": "number" }
    },
    "required": ["matchCount"],
    "additionalProperties": false
  },
  "timeoutSeconds": 30,
  "maximumActions": 1
} satisfies TaskDefinition;

export default async function verifySignedInHome(
  { input, ansight, check }: TaskInvocation<VerifyHomeInput>
): Promise<{ matchCount: number }> {
  const result = await ansight.ui.assert({
    automationId: input.automationId,
    exists: true,
    expectedVisible: true
  });

  check.equal(result.passed, true, "signed-in-home-visible");
  return { matchCount: result.matchCount };
}

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 scope. The selected Studio workspace normally supplies it.
titleYesShort discovery label.
descriptionYesComplete workflow and authoritative result.
featureNoFocused discovery category such as map or onboarding.
keywordsNoUp to 32 focused search terms.
inputSchemaNoSchema and defaults for the object passed as input.
outputSchemaNoDiscoverable description of the returned value.
hostToolsNoExtra registered host tools used only through ansight.callTool; standard methods need no declaration.
timeoutSecondsNoWhole-run timeout from 1–300 seconds. Default: 120.
maximumActionsNoMaximum Ansight/app tool calls from 1–100. Default: 64.

outputSchema is descriptive in contract version 1. Studio exposes it during discovery but does not reject a returned value that differs from the schema.

Input validation

Task input is always an object. Contract version 1 validates the root properties, required, and additionalProperties fields, plus scalar property type, enum, default, minimum, and maximum values. Defaults are applied before the function starts.

Use a TypeScript type with TaskInvocation<TInput> so the editor understands the validated input shape. The input and run objects passed to the function are read-only.

Function API

The default function receives { run, input, ansight, app, check }:

  • run identifies the run, task, App ID, exact session, repository, timeout, and action limit.
  • input contains validated input with defaults applied.
  • ansight exposes session-pinned Studio host methods.
  • app exposes standardized app methods and callTool for user-defined tools.
  • check records named assertions and determines whether the task passed.

Call a Studio-owned task tool through its feature API:

await ansight.ui.assert({ automationId: "home", exists: true });

Current-session inspection and running-app controls marked as task-callable are grouped under ansight.session, ansight.ui, ansight.logs, ansight.artifacts, ansight.screenshots, ansight.telemetry, and the other feature APIs. For example, ansight_wait_for_ui is exposed as ansight.ui.waitFor(...) without descriptor boilerplate, while ansight.session.getProperties<TProperties>() returns the selected session’s custom properties in a named SessionPropertiesResult<TProperties> contract. App registration, pairing, cloud, repository management, session mutation, audit, host diagnostics, import/export, and profiling remain host-only. ansight.callTool(name, args) remains available for extra host tools named in hostTools; Studio validates those names against the active task registry while loading the repository.

Call a custom tool published by the live app with:

await app.callTool("example.map.query_surface_contents", {
  annotationKind: "point"
});

Standard app tools instead have discoverable API suites such as app.artifacts.request(...), app.data.query(...), app.maui.getVisualTree(...), app.react.getComponentTree(...), app.flutter.inspectWidget(...), and app.capacitor.querySelector(...). These methods and custom app.callTool(...) IDs need no descriptor declaration.

Task-callable host methods discard caller-supplied sessionId, appId, deviceId, and bundleIdentifier values and inject the task session. App tools are also pinned to the selected live session and must be live, authorized, and available there. Destructive app tools use their normal authorization and approval policies.

Await calls serially. An undeclared ansight.callTool host call, overlapping call, failed tool guard, or exceeded maximumActions limit rejects the task. Calls retain normal audit and timeout handling.

Diagnostic result retention

A successful file_descriptors.list_open call also creates a durable open-file-descriptors.json artifact snapshot on the selected session timeline. The artifact preserves the complete app-tool response. The compact file_descriptors.count_open and file_descriptors.get_usage calls remain ordinary app-tool responses, so a task can return only their raw count or usage summary. Continuous open-handle and JNI-reference counts remain telemetry; JNI reference graphs are not currently exposed as an app tool.

Named assertions and status

The check API provides:

  • check.ok(actual, id, message?)
  • check.equal(actual, expected, id, message?)
  • check.notEqual(actual, expected, id, message?)
  • check.includes(actual, expected, id, message?)
  • check.fail(id, message, actual?)

Use stable, unique assertion IDs. A failed assertion stops the function immediately. Returning output is not enough to pass: a task is Passed only when at least one named assertion passes and none fail. A normal return without assertions is Inconclusive.

Other terminal statuses are Failed, Error, TimedOut, Cancelled, and Rejected. The result includes validated input, returned output, duration, assertions, standard error, and a per-tool-call audit.

Test a task in Studio

  1. Open the Test workspace and select the app beneath the active workspace.
  2. Open or create a module under ansight/tasks.
  3. Review Studio’s descriptor, schema, API, and assertion snippets.
  4. Select an open simulator session for the same App ID.
  5. Choose Save & test task.
  6. Review the terminal status, named assertions, tool calls, and standard error.

The task editor validates the static module shape before saving. A live task run uses the saved source and the exact selected session.

Discover and run from the CLI

Inspect tasks without running them:

ansight task list --app-id com.example.app --repository .

Task execution requires a resident host and an already connected app session:

ansight host run

ansight task run home.verify \
  --app-id com.example.app \
  --repository . \
  --input '{"automationId":"signed-in-home"}'

Use --device-id or --session-id to select an exact connected target, and --input-file for larger input. Without either target option, Ansight selects the most recently updated connected session whose App ID matches exactly.

Execution and security boundaries

Each run starts a fresh Node process. Module loading has a separate five-second bound, the process environment is reduced, and the OpenAI key is never added to the task environment. Tool results, returned output, standard error, action count, and total duration are bounded.

Capability declarations restrict calls back into Ansight; they do not sandbox filesystem, process, or network access available to trusted local Node code. Review a repository before running its tasks.

A repository can expose up to 256 tasks, and each executable module is limited to 1 MiB.