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
| Field | Required | Meaning |
|---|---|---|
schemaVersion | No | Contract version. Omitted descriptors use version 1. |
appId | No | Optional exact App ID scope. The selected Studio workspace normally supplies it. |
title | Yes | Short discovery label. |
description | Yes | Complete workflow and authoritative result. |
feature | No | Focused discovery category such as map or onboarding. |
keywords | No | Up to 32 focused search terms. |
inputSchema | No | Schema and defaults for the object passed as input. |
outputSchema | No | Discoverable description of the returned value. |
hostTools | No | Extra registered host tools used only through ansight.callTool; standard methods need no declaration. |
timeoutSeconds | No | Whole-run timeout from 1–300 seconds. Default: 120. |
maximumActions | No | Maximum 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 }:
runidentifies the run, task, App ID, exact session, repository, timeout, and action limit.inputcontains validated input with defaults applied.ansightexposes session-pinned Studio host methods.appexposes standardized app methods andcallToolfor user-defined tools.checkrecords 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
- Open the Test workspace and select the app beneath the active workspace.
- Open or create a module under
ansight/tasks. - Review Studio’s descriptor, schema, API, and assertion snippets.
- Select an open simulator session for the same App ID.
- Choose Save & test task.
- 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.