2026-07-09 21:22:54 +08:00
# Background Task Runtime
2026-07-19 22:50:49 +08:00
Types shared by long-running producers, `ctx.tasks` , and task control surfaces. The [runtime Agent Note ](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md ) owns the design; this page records the literal shapes from [`packages/tasks/tasks/src/types.ts` ](../../packages/tasks/tasks/src/types.ts ).
2026-07-09 21:22:54 +08:00
## Ids and status
2026-07-15 21:45:30 +08:00
`TaskId` is a [branded id ](core.md#branded-ids ) generated as `<kind>-N` . Access control relies on owner authorization, not id secrecy. `TaskKind` derives from a merge-extensible map; the registry treats kinds as opaque id namespaces.
```ts type-equiv
2026-07-19 12:25:40 +08:00
/**
* Producer-defined task kinds. Plugins extend this map by declaration merging;
* the registry treats every value as an opaque id namespace.
*/
2026-07-15 21:45:30 +08:00
interface TaskKindMap {
bash: 'bash'
subagent: 'subagent'
}
` ``
` TaskStatus` is ` 'running' | 'stopping' | 'completed' | 'killed' | 'failed'`; producer-specific facts belong in ` TaskSnapshot.detail`.
2026-07-09 21:22:54 +08:00
2026-07-15 21:08:58 +08:00
## Producer contract
2026-07-09 21:22:54 +08:00
2026-07-15 21:08:58 +08:00
` TaskStart` declares identity and a starter. The runtime finishes preflight before calling ` run()` and commits without a later failable step. Producers own execution resources; the runtime owns identity, access, and lifecycle state.
2026-07-09 21:22:54 +08:00
` ``ts type-equiv
2026-07-19 12:25:40 +08:00
/**
* Producer declaration passed to {@link TaskService.start}. The runtime
* preflights access and cleanup before invoking {@link run}; the producer owns
* execution resources while the runtime owns identity and lifecycle state.
*/
2026-07-09 21:53:48 +08:00
interface TaskStart {
2026-07-15 21:45:30 +08:00
/** Producer kind — also the id prefix (` bash`, ` subagent`, …). */
kind: TaskKind
2026-07-09 21:22:54 +08:00
/** One-line model-facing label (the command; the delegation description). */
label: string
/**
2026-07-15 21:08:58 +08:00
* Owning live agent. Access is fenced by its session id, and agent disposal
* cancels and awaits the task. The instance must be the one currently
2026-07-15 21:45:30 +08:00
* registered under its agent id. Omitting the owner creates an unowned task,
* open to any caller until service disposal.
2026-07-09 21:22:54 +08:00
*/
2026-07-15 21:45:30 +08:00
owner?: Agent
2026-07-09 21:53:48 +08:00
/**
2026-07-15 21:08:58 +08:00
* Start the work after preflight and synchronously return its hooks. Called
* once; a throw leaves nothing registered, and the producer must clean up any
* partially started resources.
2026-07-09 21:53:48 +08:00
*/
run(): TaskHooks
}
` ``
2026-07-15 21:08:58 +08:00
` TaskHooks.done` is the quiescence boundary. Optional ` readOutput` distinguishes consuming stream tasks from final-output-only tasks.
2026-07-09 21:53:48 +08:00
` ``ts type-equiv
2026-07-19 12:25:40 +08:00
/** Hooks through which the runtime controls and observes producer work. */
2026-07-09 21:53:48 +08:00
interface TaskHooks {
2026-07-09 21:22:54 +08:00
/**
2026-07-15 21:08:58 +08:00
* Request termination. Must be synchronous, idempotent, and eventually settle
* {@link done}; throws propagate. The optional reason is forwarded verbatim.
2026-07-09 21:22:54 +08:00
*/
cancel(reason?: string): void
/**
2026-07-15 21:08:58 +08:00
* Resolves after the producer releases its resources, not merely when work
* finishes. Must not reject; the runtime converts a rejection to ` failed`.
* If teardown cancellation throws, the runtime may force-fail only the
* registry record without claiming that the work stopped.
2026-07-09 21:22:54 +08:00
*/
done: Promise<TaskOutcome>
/**
2026-07-15 21:08:58 +08:00
* Consume output produced since the previous call. The producer formats
* truncation and spill notices. Absence marks a final-output-only task; each
* task has one consuming cursor.
2026-07-09 21:22:54 +08:00
*/
readOutput?(): string
}
` ``
` ``ts type-equiv
2026-07-19 12:25:40 +08:00
/** Terminal result supplied by a producer through {@link TaskHooks.done}. */
2026-07-09 21:22:54 +08:00
interface TaskOutcome {
/** How the task ended: finished (` completed`), cancelled (` killed`), or broke (` failed`). */
status: 'completed' | 'killed' | 'failed'
/** Kind-specific detail rendered into status lines ('exit code: 3', 'max-tokens'). */
detail?: string
2026-07-15 21:08:58 +08:00
/** Final output for tasks without ` readOutput`; stream tasks leave it unset. */
2026-07-09 21:22:54 +08:00
output?: string
}
` ``
2026-07-15 21:08:58 +08:00
## Consumer views
2026-07-09 21:22:54 +08:00
2026-07-15 21:08:58 +08:00
Snapshots are fresh read-only projections. ` ownerSession` carries the shared ` SessionId` used for authorization; completion listeners separately receive the exact owner object used for lifecycle cleanup. ` reported` suppresses a completion notice after another surface has delivered or committed to deliver the terminal state.
2026-07-09 21:22:54 +08:00
` ``ts type-equiv
2026-07-19 12:25:40 +08:00
/**
* A read-only projection of one task, safe to hand to listeners and tools —
* a fresh object per call, never live registry state.
*/
2026-07-09 21:22:54 +08:00
interface TaskSnapshot {
/** The registry-issued id (` <kind>-N`). */
id: TaskId
/** The producer kind the task was registered with. */
2026-07-15 21:45:30 +08:00
kind: TaskKind
2026-07-09 21:22:54 +08:00
/** The producer-supplied one-line label. */
label: string
/**
2026-07-15 21:08:58 +08:00
* Owner session id used for authorization and correlation; absent for
* unowned tasks. Completion listeners receive the exact {@link Agent}
* separately through {@link TaskDoneListener}.
2026-07-09 21:22:54 +08:00
*/
2026-07-12 17:10:02 +08:00
ownerSession?: SessionId
2026-07-09 21:22:54 +08:00
/** Current lifecycle state. */
status: TaskStatus
/** Kind-specific status detail, present once the producer supplied one (usually terminal). */
detail?: string
/** Epoch ms when the task was registered. */
startedAt: number
/** Epoch ms when the task settled; absent while ` running`/` stopping`. */
finishedAt?: number
/**
2026-07-15 21:08:58 +08:00
* True when a kill, read, or wait has reported or committed to report the
* terminal state. Completion surfaces suppress redundant notices when set.
2026-07-09 21:22:54 +08:00
*/
reported: boolean
}
` ``
` ``ts type-equiv
2026-07-19 12:25:40 +08:00
/** Output and post-read state returned by {@link TaskService.read}. */
2026-07-09 21:22:54 +08:00
interface TaskRead {
/**
* Stream kinds: the consuming delta since the previous read. Final-output
* kinds: empty while live, the terminal {@link TaskOutcome.output} (or
* empty) once settled — idempotent, never consumed.
*/
text: string
/** The task's state at read time. */
snapshot: TaskSnapshot
}
` ``
2026-07-15 21:08:58 +08:00
## Service behavior
2026-07-09 21:22:54 +08:00
2026-07-15 21:08:58 +08:00
[` TaskService`](../../packages/tasks/tasks/src/index.ts) provides atomic ` start`, caller-scoped ` get` and ` list`, ` read`, ` kill`, bounded ` wait`, contained ` onTaskDone` listeners, and the ` attachSurface` availability fence. Authorization compares owner sessions; owner cleanup selects the exact registered ` Agent` instance. See [` dsh-tasks`](../../packages/tasks/tasks/README.md) for the package contract and [` dsh-tool-tasks`](../../packages/tasks/tool-tasks/README.md) for the model-facing surface.