Harness spec 2.0.0: background work is a job — commands and subagents alike, seen and steered

One registry per process, every job owned by a session: bash's background commands (and a command
still running at its timeout, which now goes to the background instead of being killed) and
subagents started with task background: true. job_list, job_output, job_stop (the former
bash_list, bash_output, bash_kill) and the new job_send and report; a finished job wakes the owning
session in every front end with its marked note. The TUI counts jobs, lists them (/jobs), opens a
command's output, enters a subagent and messages it, moves a command to the background (ctrl+b)
and asks before quitting with jobs running. The link gains the jobs capability. Subagents get a
session row of their own, the spec's preamble and agent texts, and their errors reach the report.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
HomerandClaude Fable 5.1 committed 2026-10-20 16:17:00 +00:00
1 parent aaa3e3a44f
commit 2183040db8
75 files changed
+3614 -437

No files matched your search

+29 -2
View File
@@ -21,8 +21,20 @@ export interface ToolContext {
instructionsSeen?: Set<string>
/** Ask the user questions (the ask_user tool). Absent where nobody can answer. */
question?: (req: import("./ask_user.ts").QuestionRequest, callId?: string) => Promise<import("./ask_user.ts").QuestionReply>
/** Run a subagent (the task tool). Absent inside a subagent: no recursion. */
spawn?: (agent: string, prompt: string, signal: AbortSignal, callId?: string, opts?: { worktree?: boolean; description?: string; model?: string }) => Promise<import("./task.ts").SubagentResult>
/** Start a subagent (the task tool): a handle on its job, whose `done` is its report. `signal`:
* the parent's turn, which stops a synchronous subagent with it — never wired for a background
* one. Absent inside a subagent: no recursion. */
spawn?: (agent: string, prompt: string, opts: SpawnOptions) => Promise<SubagentHandleInfo>
/** Background jobs (jobs/registry.ts): the registry, and whose jobs these tools see — the
* session's, or a subagent's own (`job`: the subagent's job, which `report` speaks for). */
jobs?: { registry: import("../jobs/registry.ts").JobRegistry; owner(): { sessionId: string; job?: import("../jobs/registry.ts").Job } }
/** false: a foreground command is killed at its timeout rather than moved to the background —
* the TUI's own `!` commands, which no model owns. Default true. */
bashTimeline?: boolean
/** Aborted when the person wants the foreground command of this step in the background now
* (the TUI's ctrl+b): bash hands it to the registry as it would at its timeout. Set by the
* engine for the step's calls. */
backgroundNow?: AbortSignal
/** The id of the call being run (set by the engine per call). */
callId?: string
/** The project directory (.agent), when the project has one. */
@@ -68,6 +80,21 @@ export interface ToolContext {
}
}
export interface SpawnOptions {
signal?: AbortSignal
callId?: string
worktree?: boolean
description?: string
model?: string
background?: boolean
}
/** What the task tool gets back from spawn: the job's id and the report when it ends. */
export interface SubagentHandleInfo {
id: string
done: Promise<import("./task.ts").SubagentResult>
}
export interface ToolResult {
output: string
/** One-line summary for the UI: `src/app.ts +12 −3`, `exit 0 · 0.4s`. */