Task Definition and Execution API

Task descriptors, target constraints, invocation data, capability requirements, composition, and named assertions.

Use this reference when defining a task and its success criteria. Start with the Task API overview for a complete minimal module and the distinction between platform, device kind, execution mode, and framework.

TaskDefinition

FieldTypeRequiredContract
schemaVersion1 | 2NoDefaults to version 1, which requires an SDK app-tool provider. Use version 2 for capability-aware execution.
requiresExecutionRequirementsNoVersion 2 only: required capabilities and executable appTools IDs, checked before execution.
enabledbooleanNoDefaults to true. Disabled tasks remain discoverable and reject execution.
appIdstringNoExact optional app scope.
titlestringYesShort discovery label.
descriptionstringYesComplete workflow and authoritative result.
startingStatestringNoAdvisory prerequisites, 1–1000 characters. The task must check them.
successStatestringNoAdvisory state established or preserved on success, 1–1000 characters. Back it with named assertions.
platformsTaskPlatform[]Noios, android, or both. Omit when platform-independent.
deviceKindsTaskDeviceKind[]Novirtual, physical, or both. Omit when either kind works.
frameworksTaskFramework[]NoCompatible app frameworks. Omit when framework-independent.
featurestringNoFocused discovery domain such as map.
keywordsstring[]NoAt most 32 discovery terms.
inputSchemaJsonSchemaNoValidates the object supplied as input and applies defaults.
outputSchemaJsonSchemaNoDescribes returned output; version 1 does not enforce it.
hostToolsstring[]NoExtra host tools callable through ansight.callTool; maximum 32. Standard typed APIs such as ansight.keyboard need no declaration.
timeoutSecondsnumberNoWhole run: 1–300 seconds; default 120.
maximumActionsnumberNoHost, app, discovery, and child-task calls: 1–100; default 64.

The descriptor must be a static object literal. Do not use functions, variables, spreads, or computed values inside it.

TaskInvocation<TInput>

PropertyTypeMeaning
runReadonly<TaskRun>Host-owned run identity and effective limits.
inputReadonly<TInput>Validated input after schema defaults are applied.
secretsget(alias), require(alias)Resolve only values granted to this run. get returns undefined for an ungranted alias; require throws.
ansightAnsightHostSession-pinned host evidence and control methods.
appTaskAppContextStandard app-tool suites and custom-tool bridge.
expectTaskExpectPlaywright-style named assertions that establish pass or failure.

TaskRun

PropertyTypeMeaning
runIdstringUnique run ID.
executionMode"sdk" | "device"The selected session’s capture and execution mode.
taskIdstringRepository-relative task ID.
appIdstringExact app scope enforced by the host.
sessionIdstringLive session enforced for every call.
repositoryRootPathstringAbsolute connected repository root.
timeoutSecondsnumberEffective whole-task timeout.
maximumActionsnumberEffective action limit.

Declare supported targets

Target metadata describes the task’s intended compatibility; it is not task input and does not select or launch a device. A workspace test that references the task with taskId validates these declarations against its selected target and connected session. Keep runtime prerequisites in requires and task code.

export const task = {
  schemaVersion: 2,
  title: "Verify a MAUI page on Android",
  description: "Check the selected MAUI page on an Android device.",
  platforms: ["android"],
  deviceKinds: ["virtual", "physical"],
  frameworks: ["dotnet-maui"],
  startingState: "The app is signed in and the home page is visible.",
  successState: "The home page remains visible with its expected content.",
  requires: { appTools: ["maui.get_current_page"] }
} satisfies TaskDefinition;

This descriptor fragment uses TaskDefinition from ./ansight-task.d.ts. Its state hints help discovery and planning; they neither navigate the app nor establish a passing result. The task function still needs named assertions.

TaskPlatform is "ios" | "android". TaskDeviceKind is "virtual" | "physical": virtual means iOS Simulator or Android Emulator. TaskFramework accepts dotnet-maui, dotnet-ios, dotnet-android, react-native, flutter, capacitor, ios-uikit, ios-swiftui, android-views, android-compose, and native-unknown.

Omitting a target constraint does not make an unavailable API work. See the compatibility model and device support matrix.

Editor hovers carry @supportedPlatforms, @unsupportedPlatforms, @supportedDeviceKinds, @unsupportedDeviceKinds, @supportedFrameworks, and @unsupportedFrameworks tags on platform-sensitive declarations. These describe compatibility; they do not grant a capability or replace the selected provider’s runtime checks. Method-specific notes identify current declaration differences.

Execution capabilities

Use descriptor version 2 for tasks that support external sessions. Version 1 tasks retain their SDK requirement. Declare essential capabilities in requires so the host can refuse an incompatible session before running task code:

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

export const task = {
  schemaVersion: 2,
  title: "Capture notes database",
  description: "Save the simulator app's notes database to the session.",
  platforms: ["ios"],
  deviceKinds: ["virtual"],
  requires: { capabilities: ["files.capture"] }
} satisfies TaskDefinition;

export default async function run({ ansight, expect }: TaskInvocation) {
  const captured = await ansight.files.capture({
    root: "data",
    path: "Documents/notes.sqlite"
  });
  expect(captured.snapshotId, { id: "database-copy-retained" }).toBeTruthy();
  return captured;
}

That example uses an iOS data-container path. An Android app might instead store the file at files/notes.sqlite or databases/notes.sqlite.

await ansight.capabilities.snapshot() returns sessionId, executionMode, live, appAvailable, executable appTools, and a capabilities map. Each capability contains available, provider, and an unavailable reason. Use await ansight.capabilities.require({ capabilities: ["files.read"] }) for an optional branch that needs extra support. Available keys include ui.semantic, ui.screenshot, lifecycle, host.spans, evidence.read, evidence.logs, evidence.telemetry, evidence.artifacts, logs.native, files.read, files.capture, and app.tools; actual availability is session-specific.

app.available is false without a connected SDK provider. Calling any SDK app.* operation then rejects with code: "capability_unavailable", the missing capability, executionMode, and retryable: false. SDK-required operations include setup guidance. For a task that needs a particular app tool, declare requires: { appTools: ["files.list_directory"] } rather than assuming the suite exists. Missing declared requirements refuse the task; they do not skip the operation and report success.

Each requirements array accepts at most 64 non-empty identifiers, each at most 160 characters without control characters. Unknown or unavailable required capabilities reject the run; use an exact executable tool ID for appTools.

A capability snapshot is not a complete per-method compatibility table. For example, lifecycle does not promise physical-iPhone backgrounding, and evidence.read does not promise every evidence API in device mode. Check the host method index and each method’s support restrictions. An available evidence reader also does not prove that its data was captured.

Task composition

ansight.tasks.list(args?) searches the same repository and App ID as the calling task. It accepts query, feature, and maxResults; focused queries use the same metadata, input-schema, synonym, and typo matching as ansight task list.

Use the exact discovered ID to invoke another task against the same live session:

type ChildOutput = { selectedArea: string };

const catalog = await ansight.tasks.list({
  query: "select map area",
  feature: "map"
});
const selected = catalog.tasks.find(candidate => candidate.taskId === "map.select-area");
if (!selected) throw new Error("The map selection task is unavailable.");

const child = await ansight.tasks.run<ChildOutput>({
  taskId: selected.taskId,
  input: { area: "Secret Garden" }
});
expect(child.output?.selectedArea, { id: "area-selected" }).toBe("Secret Garden");

run resolves only when the child task is Passed; every other child status rejects the call and therefore fails the caller unless its code deliberately handles the error. The child keeps its own run ID, assertions, timeout, action budget, tool-call audit, and persisted result. The parent records ansight.tasks.run as one action, and its remaining deadline caps the complete child call. Parent and child assertions are not merged, so the parent must still record at least one named assertion for its own outcome.

The host reloads the caller’s exact workspace for discovery and execution, pins every child to the caller’s App ID and session, rejects self-calls and recursive cycles, and permits at most eight task execution levels in one call chain. A child cannot select another repository, app, session, or device.

expect: named assertions

MatcherBehavior
expect(actual, { id, message? }).toBe(expected)Uses Object.is, matching Playwright’s strict identity matcher.
.toEqual(expected)Uses strict recursive deep equality.
.toBeTruthy() / .toBeFalsy()Checks JavaScript truthiness.
.toBeDefined() / .toBeUndefined() / .toBeNull()Checks nullish states explicitly.
.toContain(expected)Checks string inclusion or direct array membership.
.toContainEqual(expected)Checks deep-equal array membership.
.not.<matcher>Negates the following matcher.
expect.soft(actual, metadata).<matcher>Records a failure and continues the task function.

Assertion IDs must be stable and unique. At least one assertion must pass and none may fail for the task status to be Passed. Hard failures stop the task function; soft failures continue. A recorded failure remains authoritative even if task code catches the hard assertion error. Returning normally without an assertion produces Inconclusive.

Execution rules

  • Await host and app calls serially; overlapping calls are rejected.
  • Standard host APIs need no hostTools declaration.
  • ansight.tasks.list and ansight.tasks.run need no hostTools declaration.
  • ansight.callTool accepts only names declared in task.hostTools.
  • app.callTool targets app-defined tools and needs no descriptor declaration.
  • Every host, app, discovery, and child-task call counts toward the caller’s maximumActions.
  • Tasks can access only the enforced live session through Ansight APIs.
  • Repository TypeScript is trusted local code, not a filesystem or network sandbox.