job
Wait for or cancel background jobs managed by the session async runtime.
Source
- Entry:
packages/coding-agent/src/tools/job.ts - Model-facing prompt:
packages/coding-agent/src/prompts/tools/job.md - Key collaborators:
packages/coding-agent/src/async/job-manager.ts— job registry, cancellation, delivery suppression.packages/coding-agent/src/async/support.ts— feature gating for background jobs.packages/coding-agent/src/tools/bash.ts— explicit async bash and auto-backgrounded bash jobs.packages/coding-agent/src/task/index.ts— async task-job scheduling.packages/coding-agent/src/sdk.ts— automatic follow-up delivery for unsuppressed completions.packages/coding-agent/src/config/settings-schema.ts—async.pollWaitDurationoptions.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
poll |
string[] |
No | Job ids to watch. Cannot be combined with list. If omitted (and cancel is also omitted), the tool watches all running jobs. If provided, missing ids are silently filtered out before waiting. |
cancel |
string[] |
No | Job ids to cancel before any polling. Missing ids are reported as not_found; non-running ids as already_completed. |
list |
boolean |
No | Return an immediate snapshot of every job spawned by the calling agent (running + completed within retention) without waiting. Read-only — cannot be combined with poll or cancel. |
Outputs
The tool returns one text block plus details.
content[0].text: markdown-like plain text sections assembled by#buildResult(...):## Cancelled (N)for cancel outcomes.## Completed (N)for non-running jobs, including storedresultTextanderrorText.## Still Running (N)for jobs still inrunning.
details.jobs: array of snapshots:id: stringtype: "bash" | "task"status: "running" | "completed" | "failed" | "cancelled"label: stringdurationMs: number- optional
resultText,errorText
details.cancelledappears only whencancelwas passed; each item is{ id, status }where status is"cancelled" | "not_found" | "already_completed".
Streaming behavior:
- During a polling wait,
execute(...)emitsonUpdate(...)every 500 ms with an empty text block and freshdetails.jobssnapshots. - Final return is single-shot after a completion, timeout, abort, or immediate fast path.
Read-only snapshot path:
- Calling
jobwithlist: truereturns a markdown summary of every job spawned by the calling agent (running + completed within retention) without waiting.
Flow
JobTool.createIf(...)inpackages/coding-agent/src/tools/job.tsonly exposes the tool whenisBackgroundJobSupportEnabled(...)returns true for eitherasync.enabledorbash.autoBackground.enabled.execute(...)fetchessession.asyncJobManager. If absent, it returnsAsync execution is disabled; no background jobs are available.cancelids are processed first:manager.getJob(id)missing →not_found.- existing job with
status !== "running"→already_completed. - running job →
manager.cancel(id), which setsjob.status = "cancelled", aborts the controller, and schedules eviction.
- Polling mode is chosen with
const shouldPoll = requestedPollIds !== undefined || cancelIds.length === 0:- only
cancelpresent → return immediately, no wait. - explicit
poll, or no args at all → proceed to watch jobs.
- only
- Watch set resolution:
- explicit
poll→ map ids throughmanager.getJob(...)and drop missing ones. - no
polland nocancel→manager.getRunningJobs().
- explicit
- Empty watch set returns immediately:
- if cancellations happened, return snapshots for the cancelled ids that still exist.
- else return either
No matching jobs found for IDs: ...orNo running background jobs to wait for.
- If every watched job is already non-running,
#buildResult(...)returns immediately without waiting. - Otherwise the tool waits on
Promise.race(...)across:- every watched running job's
job.promise, - a timeout promise for
async.pollWaitDuration, - the tool-call abort signal when present.
- every watched running job's
- Before waiting, it calls
manager.watchJobs(watchedJobIds). This suppresses automatic completion delivery for those ids while they are being watched. - If
onUpdateexists, a 500 ms interval sends progress snapshots from#snapshotJobs(...); one snapshot is emitted immediately before entering the race. - In
finally, the tool always callsmanager.unwatchJobs(...), clears the timeout, and stops the progress interval. #buildResult(...)deduplicates jobs, snapshots current manager state, then callsmanager.acknowledgeDeliveries(...)for every non-running job in the result. That suppresses later automatic follow-up delivery for the same completions and removes queued deliveries for those ids.- The final text groups jobs by non-running vs still-running state. A timeout is not an error path; it simply returns the current snapshot.
Modes / Variants
- Poll all running jobs: call with neither
pollnorcancel. - Poll explicit ids: call with
pollonly. - Cancel only: call with
cancelonly; cancellations happen and the tool returns immediately. - Cancel then poll: call with both. Cancellations are applied first, then the tool watches the remaining resolved
pollids. - Read-only inspection: call with
list: truefor the same snapshot data without waiting on completion.
Spawn paths that produce jobs:
packages/coding-agent/src/tools/bash.tsasync: truealways registers atype: "bash"job withAsyncJobManager.register(...)and returns a start message.- auto-background mode (
bash.autoBackground.enabled) starts the same managed job path for non-PTY commands, waits up tomin(bash.autoBackground.thresholdMs, timeoutMs - 1000), and if the command is still running returns a background-job start result instead of inline command output.
packages/coding-agent/src/task/index.ts- when
async.enabledis on, the chosen agent is not blocking, andtasks.length > 0, each task item is registered as atype: "task"job.
- when
Lifecycle and exact state names:
- Conceptual scheduling path:
pending(only task-progress bookkeeping before work starts) →running→completed/failed; cancellation changes a running async job tocancelled. - Exact
AsyncJob.statusvalues inpackages/coding-agent/src/async/job-manager.ts:"running" | "completed" | "failed" | "cancelled". - Exact per-task progress values in
packages/coding-agent/src/task/types.ts:"pending" | "running" | "completed" | "failed" | "aborted".
Side Effects
- Filesystem
- None in
job.tsitself. - Jobs being observed may already have written artifacts/results through their own tool runtimes.
- None in
- Session state (transcript, memory, jobs, checkpoints, registries)
- Reads and mutates
session.asyncJobManagerstate. watchJobs(...)/unwatchJobs(...)toggle delivery suppression for the watched ids.acknowledgeDeliveries(...)marks completed ids as suppressed and removes queued deliveries for them.cancel(...)aborts running jobs through each job'sAbortController.
- Reads and mutates
- User-visible prompts / interactive UI
- Polling emits periodic
onUpdatesnapshots every 500 ms. - Automatic job completion follow-ups are generated by
packages/coding-agent/src/sdk.tsonly for unsuppressed deliveries.
- Polling emits periodic
- Background work / cancellation
- Waiting uses a timeout plus optional tool-call abort signal.
- Cancelling a job does not synchronously await teardown; it flips state, aborts, and returns control to the manager/job promise.
Limits & Caps
- Poll wait duration comes from
async.pollWaitDurationinpackages/coding-agent/src/config/settings-schema.ts:- allowed values:
5s,10s,30s,1m,5m - default:
30s
- allowed values:
- Progress update cadence while polling:
PROGRESS_INTERVAL_MS = 500inpackages/coding-agent/src/tools/job.ts. - Async job retention default:
DEFAULT_RETENTION_MS = 5 * 60 * 1000inpackages/coding-agent/src/async/job-manager.ts. - Manager fallback max-running limit:
DEFAULT_MAX_RUNNING_JOBS = 15inpackages/coding-agent/src/async/job-manager.ts. - Session wiring clamps
async.maxJobsto1..100before constructing the manager inpackages/coding-agent/src/sdk.ts; settings default is100inpackages/coding-agent/src/config/settings-schema.ts. - Async completion delivery retry backoff in
packages/coding-agent/src/async/job-manager.ts:- base
500ms - max
30_000ms - jitter
< 200ms - exponent capped at 8 doublings
- base
Errors
- Tool-disabled path is returned as normal text, not thrown:
Async execution is disabled; no background jobs are available. - Polling a nonexistent id is not an exception:
- with
pollonly, missing ids are dropped; if none remain the tool returnsNo matching jobs found for IDs: .... - with
cancel, each missing id is reported asnot_foundindetails.cancelledand text.
- with
- Cancelling a non-running job is not an exception; it reports
already_completedeven if the actual status iscompleted,failed, orcancelled. - Tool-call abort during polling stops waiting and returns a final snapshot through
#buildResult(...); it does not cancel watched jobs. - Failures inside the underlying async work are stored on the job (
status: "failed",errorText) and reported in normal tool output, not rethrown byjob. - Calling
list: trueagainst an empty manager returns a normal empty-list result rather than throwing; missing ids passed topollare silently filtered.
Notes
jobwaits for the first watched running job to settle, not for all watched jobs. If others remainrunning, they are reported under## Still Running; the caller must invokejobagain to continue waiting.- Delivery suppression is the key difference between snapshot and automatic delivery:
- snapshots (
jobcalls withpollorlist: true) read current manager state; - follow-up delivery comes from
AsyncJobManager.#enqueueDelivery(...)andsdk.tsonJobComplete; - watched or acknowledged ids are suppressed via
isDeliverySuppressed(...).
- snapshots (
manager.cancel(id)setsstatus = "cancelled"before the underlying promise settles. The job function may later populateresultTextorerrorText;job-manager.tspreserves that text but does not transition the status away fromcancelled.- Retention eviction removes the job record, suppression flags, and watch flag together. After eviction, both
jobcalls andlist: truesnapshots behave as if the id never existed.