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

+8
View File
@@ -35,6 +35,14 @@ test("a call under a former name runs the tool it now is, with its arguments", (
expect(canonicalToolNames(["read", "webfetch", "notes"])).toEqual(["read", "web_fetch", "notes_search", "note_view", "note_manage"])
})
test("spec 2.0.0: bash_output, bash_list and bash_kill are job_output, job_list and job_stop, in calls and in permission rules", () => {
expect(resolveCall("bash_output", { id: "job1" })).toEqual({ name: "job_output", raw: { id: "job1" } })
expect(resolveCall("bash_list", {}).name).toBe("job_list")
expect(resolveCall("bash_kill", { id: "job2" }).name).toBe("job_stop")
expect(canonicalToolNames(["bash", "bash_output"])).toEqual(["bash", "job_output"])
expect(renamePermissionKeys({ bash_output: "deny", bash_kill: "ask", job_list: "allow" })).toEqual({ job_output: "deny", job_stop: "ask", job_list: "allow" })
})
test("a todo call is a plan_write of one phase called Work, its statuses translated", () => {
const r = resolveCall("todo", { todos: [{ content: "Read the code", status: "completed" }, { content: "Fix it", status: "in_progress" }, "Test it", { content: "Old idea", status: "cancelled" }] })
expect(r.name).toBe("plan_write")