Files
synapbus/specs/018-dynamic-agent-spawning/tasks.md
T
Algis DumbrisandClaude Opus 4.6 ff5d0c49f4 feat(018): dynamic agent spawning — primitives + doc-gardener demo
Ships the MVP slice of spec 018 (dynamic agent spawning):

- 5 new SQLite migrations (021-025): goals + goal_tasks + agent_proposals
  + reputation_evidence + secrets + harness_runs.task_id. The legacy
  `tasks` table (channel auctions) and `agent_trust` table (reactions
  workflow) are left untouched — the new schema coexists.

- 4 new internal packages, fully tested:
  - internal/goals: Goal struct + store + service, slug collision dedup,
    backing-channel auto-create via ChannelCreator adapter
  - internal/goaltasks: goal_tasks table with denormalized 16 KB
    ancestry snapshots, single-statement optimistic-lock atomic claim,
    recursive-CTE cost rollup, state machine, per-billing-code rollup
  - internal/secrets: NaCl-secretbox encrypted blobs, user/agent/task
    scope precedence, sanitized env injection, master-key bootstrap
  - internal/trust additions: ConfigHash (deterministic SHA-256 of
    model + prompt + tools + skills + mcp + subagents, sorted),
    DelegationCap (tier + tool-scope + budget + depth enforcement),
    append-only Ledger with exponential time-decay rolling score and
    70%-of-parent child seeding. Existing trust package unchanged.

- Critical invariants under test:
  - 50-goroutine concurrent claim race → exactly one winner per round
  - ConfigHash stable under shuffled array inputs, sensitive to
    capability changes
  - DelegationCap full tier × tool-scope matrix
  - Ledger time-decay + parent seed at 70 % ± 1 %
  - Secret name sanitization, scope precedence, plaintext never
    returned via MCP-equivalent paths

- internal/agents/types.go extended with dynamic-spawning columns
  (config_hash, parent_agent_id, spawn_depth, system_prompt,
  autonomy_tier, tool_scope_json, quarantined_at). Existing tests
  still pass.

- cmd/docgardener: self-contained demo binary driving the end-to-end
  flow. `docgardener run` creates a goal, builds a task tree with
  denormalized ancestry, spawns 3 specialists (each going through
  real delegation-cap validation and config-hash computation and
  70 %-of-parent reputation seeding), claims tasks atomically, runs
  them through the state machine, records reputation evidence.
  `docgardener report` queries all of that back out and renders a
  rich dark-mode HTML report (header, spend metrics, task tree,
  spawned-agent cards with reputation bars, cost breakdown, artifacts,
  timeline).

- examples/doc-gardener: start.sh / run_task.sh / report.sh / stop.sh
  mirroring the cold-topic-explainer pattern. Launches an isolated
  synapbus instance on port 18089, drives the demo, renders
  report.html, cleans up. Full README documenting what's real vs
  deferred, plus examples/README.md listing both examples.

- specs/018: tasks.md updated with MVP completion status; legacy tasks
  naming collision noted.

Deferred (marked explicitly in example README):
- Real LLM-driven coordinator (needs MCP tool wiring + prompt
  iteration)
- Real subprocess runs (needs reactor integration with task_id on
  ExecRequest)
- Full MCP tool surface (contracts are written at
  specs/018-dynamic-agent-spawning/contracts/mcp-tools.md)
- Svelte /goals UI (REST endpoints remain a follow-up)
- Full budget race + quarantine auto-trigger wiring
- Full resource-request → secrets fulfill reaction-workflow path

Cross-compiles clean for linux/amd64 and darwin/arm64 with no CGO
(SC-010). All new package tests pass (SC-004, SC-005, SC-007).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:29:21 +03:00

388 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description: "Task list for Dynamic Agent Spawning"
---
# Tasks: Dynamic Agent Spawning
**Input**: Design documents from `/specs/018-dynamic-agent-spawning/`
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/, quickstart.md
**Tests**: Included — the spec mandates table-driven unit tests, service-layer integration tests, and end-to-end MCP tests (success criteria SC-004, SC-005, SC-006, SC-007, SC-008, SC-009, SC-012).
**Organization**: Tasks are grouped by user story. Setup and Foundational phases run first; US1 is the MVP slice.
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (US1–US9 from spec.md)
- File paths are absolute-from-repo-root
## Path Conventions
- **Backend code**: `internal/<package>/` and `cmd/synapbus/`
- **Schema**: `internal/storage/schema/`
- **Web UI source**: `web/src/`
- **Tests**: co-located `_test.go` files in the same package
- **Example**: `examples/doc-gardener/`
---
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: Create new packages and migration files; wire through the build. No behavior yet.
- [ ] T001 Create empty Go packages with `doc.go` files at `internal/goals/doc.go`, `internal/tasks/doc.go`, `internal/trust/doc.go`, `internal/secrets/doc.go`
- [X] T002 [P] Create migration files (empty DDL, just table shells per data-model.md) at `internal/storage/schema/021_goals_tasks.sql`, `internal/storage/schema/022_agent_proposals.sql`, `internal/storage/schema/023_agent_trust_model.sql`, `internal/storage/schema/024_secrets.sql`, `internal/storage/schema/025_harness_runs_task_id.sql` — **note**: table renamed from `tasks` to `goal_tasks` (legacy `tasks` table exists from migration 001 for channel task auctions)
- [ ] T003 [P] Create example scaffolding at `examples/doc-gardener/` mirroring `examples/cold-topic-explainer/` — copy `start.sh`, `stop.sh`, `run_task.sh`, `wrapper.sh`, `README.md` with doc-gardener placeholders
- [ ] T004 [P] Add `golang.org/x/crypto/nacl/secretbox` to `go.mod` via `go get` and verify the pure-Go build still works (`CGO_ENABLED=0 go build ./...`)
- [ ] T005 Create `specs/018-dynamic-agent-spawning/BUILD_NOTES.md` with the developer runbook (build commands, test commands, example commands) for the whole feature
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: Migrations, base types, and the trust primitive that every user story depends on.
- [X] T010 Fill migration 021 DDL (goals + goal_tasks tables, indexes, state CHECK constraints)
- [X] T011 Fill migration 022 DDL (agent_proposals + resource_requests tables)
- [X] T012 Fill migration 023 DDL — **revised**: keep existing agent_trust table intact (wired to reactions), only ALTER agents + CREATE reputation_evidence
- [X] T013 Fill migration 024 DDL (secrets table)
- [X] T014 Fill migration 025 DDL (harness_runs.task_id)
- [ ] T015 [P] Write data-migration Go code that extracts `system_prompt` from existing `harness_config_json` for pre-existing agents, idempotent, in `internal/storage/migrations_data.go`
- [ ] T016 [P] Write data-migration Go code that computes `config_hash` for every existing agent and backfills the column in `internal/storage/migrations_data.go`
- [ ] T017 Implement `trust.ConfigHash(agent)` canonical SHA-256 hashing (sorted-keys JSON) in `internal/trust/hash.go` with table-driven unit tests in `internal/trust/hash_test.go` verifying stability across shuffled input arrays
- [ ] T018 Implement `trust.AppendEvidence()`, `trust.RollingScore()` (with exponential decay, clamp [0,1], neutral 0.5 default), `trust.IsQuarantined()` in `internal/trust/ledger.go` with unit tests in `internal/trust/ledger_test.go`
- [ ] T019 [P] Implement `trust.DelegationCap(parent, proposed) (Grant, violations)` enforcing the child ≤ parent rule across tool_scope, autonomy_tier, and budget dimensions in `internal/trust/delegation.go` with a full tier-matrix test in `internal/trust/delegation_test.go` — **covers SC-005**
- [ ] T020 Extend `internal/agents/types.go` with the new columns (`ConfigHash`, `ParentAgentID`, `SpawnDepth`, `SystemPrompt`, `AutonomyTier`, `ToolScope`, `QuarantinedAt`)
- [ ] T021 Extend `internal/agents/service.go` with `RecomputeConfigHash(ctx, agentID)` that recomputes on any config mutation and re-seeds reputation at 70 % of prior value — includes unit test verifying the 70 % invariant in `internal/agents/service_test.go` — **covers SC-007**
- [ ] T022 Implement `secrets.Store` in `internal/secrets/store.go` with NaCl secretbox encrypt/decrypt, master key file bootstrap (0600 perms, auto-generate on first use), CRUD, scope-filter, env sanitization; unit tests in `internal/secrets/store_test.go`
- [ ] T023 Implement `secrets.BuildEnv(ctx, agentID, taskID) map[string]string` in `internal/secrets/injector.go` with precedence user < agent < task; unit test in `internal/secrets/injector_test.go`
---
## Phase 3: User Story 1 — Create a goal from free text (Priority: P1) 🎯 MVP
**Goal**: A human can run `create_goal` and get back a goal id, a `#goal-<slug>` channel, and a coordinator agent wired up.
**Independent Test**: Call `create_goal` via the MCP transport with title + description; assert `goals` row exists, channel exists, owner is a member, and a coordinator agent is referenced.
**Maps to**: Spec US1, FR-001, FR-002, SC-001.
- [ ] T030 [US1] Implement `Goal` struct, `GoalStatus` enum, and repository CRUD in `internal/goals/types.go` and `internal/goals/repo.go`
- [ ] T031 [US1] Implement `goals.Service.CreateGoal(ctx, title, description, ownerID, opts)` that atomically inserts the goal, generates a unique slug with collision-dedup suffixing, creates the `#goal-<slug>` channel via existing `channels.Service`, adds the owner as a member, and assigns the coordinator — in `internal/goals/service.go`
- [ ] T032 [US1] Implement `goals.Service.GetGoal`, `ListGoals`, `TransitionTo` with state-machine guarding in `internal/goals/service.go`
- [ ] T033 [P] [US1] Write table-driven unit tests for `goals.Service` covering happy path, slug collision, invalid status transitions in `internal/goals/service_test.go`
- [ ] T034 [P] [US1] Write real-SQLite integration test `TestCreateGoal_EndToEnd` verifying DB row, channel creation, member add, coordinator assignment in `internal/goals/integration_test.go`
- [ ] T035 [US1] Implement MCP tool `create_goal` handler in `internal/mcp/tools_goals.go` with JSON Schema matching `contracts/mcp-tools.md` §1
- [ ] T036 [P] [US1] Write MCP tool test `TestCreateGoalMCP` using the existing in-process MCP test harness in `internal/mcp/tools_goals_test.go`
- [ ] T037 [US1] Register `create_goal` in the MCP tool registry in `internal/mcp/server.go`
- [ ] T038 [P] [US1] Add REST endpoint `GET /api/goals` and `GET /api/goals/:id` in `internal/api/handlers_goals.go`, returning the JSON snapshot defined in `contracts/mcp-tools.md` §REST
- [ ] T039 [P] [US1] Write REST handler tests in `internal/api/handlers_goals_test.go`
- [ ] T040 [P] [US1] Create Svelte 5 route `web/src/routes/goals/+page.svelte` listing goals (title, status, owner, total spend, last activity) consuming `GET /api/goals`
- [ ] T041 [P] [US1] Create Svelte 5 route `web/src/routes/goals/[id]/+page.svelte` with placeholder goal header (task tree will come in US2)
- [ ] T042 [US1] Rebuild embedded Web UI via `make web` and verify `internal/web/dist/` is regenerated
- [ ] T043 [US1] Wire a `create_goal` helper into the example script `examples/doc-gardener/run_task.sh` that calls the admin socket to create the doc-gardener goal
**Checkpoint**: `./start.sh && synapbus goals create --title "Test" --desc "Test goal"` works; Web UI `/goals` page lists it.
---
## Phase 4: User Story 2 — Coordinator decomposes a goal (Priority: P1)
**Goal**: The coordinator agent posts a task-tree proposal; the human approves; tasks are materialized atomically with denormalized ancestry.
**Independent Test**: Create a goal, wait for coordinator heartbeat, assert a `task_proposal` message in `#approvals`. React `approve`. Assert `tasks` rows with correct parent/child links, depth, and ancestry JSON.
**Maps to**: Spec US2, FR-003, FR-004, FR-006, FR-008, FR-033.
- [ ] T050 [US2] Implement `Task` struct, `TaskStatus` enum, `AncestryNode`, `VerifierConfig`, `HeartbeatConfig` types in `internal/tasks/types.go`
- [ ] T051 [US2] Implement `tasks.Service.CreateTaskTree(ctx, goalID, proposerID, tree)` that walks the proposed tree, assigns depth, builds ancestry snapshot (≤16 KB cap check), inserts all rows in a single transaction, and posts a single "tree materialized" system message to the goal channel — in `internal/tasks/service.go`
- [ ] T052 [US2] Implement `tasks.Service.TransitionTo(ctx, taskID, newStatus)` with state-machine guarding and automatic system-message posting to the goal channel — in `internal/tasks/service.go`
- [ ] T053 [P] [US2] Write table-driven unit tests for `CreateTaskTree` (depth assignment, ancestry correctness, 16 KB overflow rejection) and `TransitionTo` (valid and invalid transitions) in `internal/tasks/service_test.go`
- [ ] T054 [P] [US2] Write real-SQLite integration test `TestTaskTreeMaterialization` verifying atomicity (partial failure rolls back) in `internal/tasks/integration_test.go`
- [ ] T055 [US2] Implement MCP tool `propose_task_tree` handler in `internal/mcp/tools_tasks.go` per `contracts/mcp-tools.md` §2 — writes approval message; wires the reaction-workflow callback to invoke `tasks.Service.CreateTaskTree` on approval
- [ ] T056 [P] [US2] Write MCP tool test `TestProposeTaskTreeMCP` in `internal/mcp/tools_tasks_test.go`
- [ ] T057 [US2] Register `propose_task_tree` in `internal/mcp/server.go`
- [ ] T058 [US2] Extend `internal/api/handlers_goals.go` `GET /api/goals/:id` response to include the materialized task tree (recursive CTE fetch)
- [ ] T059 [P] [US2] Extend `web/src/routes/goals/[id]/+page.svelte` with a recursive Svelte task-tree component (collapsible, status badges, per-task spend) and wire the SSE event stream for real-time updates
- [ ] T060 [US2] Rebuild embedded Web UI via `make web`
- [ ] T061 [US2] Write the coordinator's fixed system prompt in `internal/coordinator/default_prompt.md` covering: role, allowed tools, JSON output format for tree proposals, delegation cap reminder — per research.md D13
- [ ] T062 [US2] Ship the pre-built coordinator config (name, model, system prompt from T061, tool scope) in `examples/doc-gardener/configs/coordinator.json`
- [ ] T063 [US2] Create `auto_approve.sh` helper in `examples/doc-gardener/auto_approve.sh` that polls `#approvals` every 1 s and posts `approve` reactions
**Checkpoint**: Running the example with `--auto-approve` produces a task tree in `tasks` table and a timeline entry in the goal channel.
---
## Phase 5: User Story 3 — Propose spawning a specialist agent (Priority: P1)
**Goal**: Coordinator calls `propose_agent`; server pre-checks delegation cap + spawn depth; human approves; new `agents` row is materialized with `config_hash`, `parent_agent_id`, `spawn_depth`, seeded reputation.
**Independent Test**: Call `propose_agent` from the coordinator with a valid proposal and a violating proposal (tool scope exceeds parent). Assert the violating one is rejected server-side. Approve the valid one. Assert new `agents` row with hash, parent, depth, and reputation seed.
**Maps to**: Spec US3, FR-010, FR-011, FR-012, FR-013, FR-014, FR-015, FR-016, FR-017, FR-018.
- [ ] T070 [US3] Implement `AgentProposal` type and repository in `internal/agents/proposals.go`
- [ ] T071 [US3] Implement `agents.ProposalService.Propose(ctx, proposal)` that runs delegation-cap pre-check (via `trust.DelegationCap`), depth-cap check, then writes `agent_proposals` row and posts `#approvals` message — in `internal/agents/proposals.go`
- [ ] T072 [US3] Implement `agents.ProposalService.Approve(ctx, proposalID, approverID)` that runs inside a transaction: (a) re-check caps; (b) compute `config_hash`; (c) insert `agents` row with parent/depth; (d) mint new API key; (e) seed `reputation_evidence` at 70 % of parent's rolling score; (f) DM proposer with credentials — in `internal/agents/proposals.go`
- [ ] T073 [US3] Implement `agents.ProposalService.Reject(ctx, proposalID, reason)` with DM to proposer
- [ ] T074 [P] [US3] Write table-driven unit tests covering: valid proposal, depth-cap violation, delegation-cap violation (all tier/tool combinations) in `internal/agents/proposals_test.go` — **covers SC-005**
- [ ] T075 [P] [US3] Write integration test `TestProposeAndMaterialize` with real SQLite in `internal/agents/proposals_integration_test.go`
- [ ] T076 [US3] Implement MCP tool `propose_agent` handler in `internal/mcp/tools_spawn.go` per `contracts/mcp-tools.md` §3
- [ ] T077 [P] [US3] Write MCP test `TestProposeAgentMCP` in `internal/mcp/tools_spawn_test.go`
- [ ] T078 [US3] Register `propose_agent` in `internal/mcp/server.go`
- [ ] T079 [US3] Wire the reaction-workflow callback so that an `approve` reaction on an `#approvals` message of type `agent_proposal` calls `ProposalService.Approve`
- [ ] T080 [P] [US3] Add "spawned agents" panel to `web/src/routes/goals/[id]/+page.svelte` showing each agent's `config_hash` (first 12 chars), parent, autonomy tier, and rolling reputation
- [ ] T081 [US3] Update the coordinator's system prompt in `internal/coordinator/default_prompt.md` to include the `propose_agent` tool description and the expected JSON format
- [ ] T082 [US3] Ship three specialist templates in `examples/doc-gardener/configs/` — `docs-scanner.json`, `cli-verifier.json`, `commit-watcher.json` (per research.md D16, with model, prompt, tool scope, MCP)
**Checkpoint**: Running the example produces a task tree, then coordinator proposes 1–3 specialists which are auto-approved and materialized.
---
## Phase 6: User Story 4 — Specialist claims a task and runs on a heartbeat (Priority: P1)
**Goal**: Atomic claim + reactor wakes the agent + harness runs the subprocess with injected context + task transitions to `awaiting_verification`.
**Independent Test**: Given an approved task and a matching specialist, call `claim_task`; wait for heartbeat; assert `harness_runs` row with `task_id`, leaf-task spend updated, task at `awaiting_verification`.
**Maps to**: Spec US4, FR-005, FR-007, FR-009, FR-028, FR-037, FR-038, FR-039.
- [ ] T090 [US4] Implement `tasks.Service.ClaimTask(ctx, taskID, agentID)` as the atomic optimistic-lock UPDATE per data-model.md, returning `ErrAlreadyClaimed` on 0 rows affected — in `internal/tasks/service.go`
- [ ] T091 [P] [US4] Write concurrency integration test `TestClaimTask_Concurrent` spawning 100 goroutines racing on the same task, asserting exactly one winner — in `internal/tasks/claim_test.go` — **covers SC-004**
- [ ] T092 [US4] Implement `tasks.Service.RollupCosts(ctx, rootTaskID)` using the recursive CTE from data-model.md — in `internal/tasks/service.go`
- [ ] T093 [P] [US4] Write test `TestRollupCosts_Recursive` building a 4-level tree and verifying sums — in `internal/tasks/rollup_test.go`
- [ ] T094 [US4] Implement MCP tool `claim_task` in `internal/mcp/tools_tasks.go` per `contracts/mcp-tools.md` §4
- [ ] T095 [P] [US4] Write MCP test `TestClaimTaskMCP` (including `ErrAlreadyClaimed` and `ErrAgentQuarantined` paths) in `internal/mcp/tools_tasks_test.go`
- [ ] T096 [US4] Extend `internal/harness/reactor/reactor.go` with three new wake sources (`task_assignment`, `task_timer`, `verification_requested`); reuse existing `pending_work` coalescing
- [ ] T097 [US4] Wire the reactor to invoke `secrets.BuildEnv(ctx, agentID, taskID)` when preparing subprocess env for a task-context run, and to write `harness_runs.task_id` on completion
- [ ] T098 [US4] Wire the reactor's post-run path to increment the leaf task's `spent_tokens` / `spent_dollars_cents` and transition the task to `awaiting_verification` inside the same transaction as the `harness_runs` insert
- [ ] T099 [P] [US4] Write integration test `TestReactorTaskHeartbeat_EndToEnd` using a mock subprocess that emits a synthetic cost, asserting the task state machine advances and `harness_runs.task_id` is set — in `internal/harness/reactor/reactor_task_test.go`
- [ ] T100 [P] [US4] Extend the `docs-scanner` and `cli-verifier` specialist wrapper scripts in `examples/doc-gardener/wrapper.sh` to read `SYNAPBUS_TASK_ID`, `SYNAPBUS_TASK_DESCRIPTION`, `SYNAPBUS_TASK_ANCESTRY` env vars set by the harness
- [ ] T101 [US4] Propagate OTel trace context across the reactor → subprocess hop (reuse existing TRACEPARENT plumbing from fee73e3)
**Checkpoint**: The example runs through claim → heartbeat → subprocess → completion message posted to goal channel.
---
## Phase 7: User Story 5 — Verification gate (Priority: P1)
**Goal**: Tasks in `awaiting_verification` are verified via one of `auto`, `peer`, `command` verifier kinds; verdicts drive `done`/`failed`; reputation is appended on success.
**Independent Test**: Configure a task with each verifier kind; transition through verification; assert correct terminal state and reputation row appended.
**Maps to**: Spec US5, FR-034, FR-035, FR-036.
- [ ] T110 [US5] Implement the verifier dispatcher in `internal/tasks/verifier.go` — parses `verifier_config_json`, routes to auto/peer/command handler, writes the resulting `tasks.status`
- [ ] T111 [US5] Implement `peer` verifier: call the configured peer's `verify_task` tool via the in-process MCP client in `internal/tasks/verifier_peer.go`
- [ ] T112 [US5] Implement `command` verifier: shell out with task env, enforce timeout, exit code 0 → approve in `internal/tasks/verifier_command.go`
- [ ] T113 [US5] On verdict, append `reputation_evidence` row with the appropriate `score_delta` (+1.0 approve / -1.0 reject) via `trust.AppendEvidence` — wire in `internal/tasks/verifier.go`
- [ ] T114 [US5] On verification failure, DM the coordinator of the task's goal with task id, verdict, and reason
- [ ] T115 [P] [US5] Write unit test `TestVerifier_AutoApprove`, `TestVerifier_CommandExitCodes`, `TestVerifier_PeerApprove_PeerReject` in `internal/tasks/verifier_test.go`
- [ ] T116 [US5] Implement MCP tool `verify_task` in `internal/mcp/tools_tasks.go` per `contracts/mcp-tools.md` §5 — gated on the caller being the configured peer verifier
- [ ] T117 [P] [US5] Write MCP test `TestVerifyTaskMCP` with positive and negative paths in `internal/mcp/tools_tasks_test.go`
- [ ] T118 [US5] Register `verify_task` in `internal/mcp/server.go`
- [ ] T119 [US5] Wire the reactor to invoke the verifier dispatcher as a new wake source `verification_requested` when a task enters `awaiting_verification`
**Checkpoint**: A task with each verifier kind runs through to `done` or `failed`; reputation ledger grows.
---
## Phase 8: User Story 9 — Doc-gardener example runs end-to-end (Priority: P1)
**Goal**: `./start.sh && ./run_task.sh --auto-approve && ./report.sh` produces `report.html` showing the full run.
**Independent Test**: Run the script trio from a clean checkout; assert `report.html` exists and contains the required sections.
**Maps to**: Spec US9, FR-043, FR-044, FR-045, FR-046, SC-002, SC-003.
> Note: User Story 9 is a P1 integration-level story — it depends on US1 through US5. It is placed after US5 deliberately so that the "doc-gardener runs" phase is the capstone of the MVP.
- [ ] T130 [US9] Complete `examples/doc-gardener/start.sh` per `quickstart.md` §1: preflight, rebuild, launch, create user `algis`, create coordinator, create `#approvals` + `#requests` channels
- [ ] T131 [US9] Complete `examples/doc-gardener/run_task.sh` per `quickstart.md` §2: DM coordinator, launch `auto_approve.sh`, poll goal channel for `FINAL:` message, 5-min timeout
- [ ] T132 [US9] Complete `examples/doc-gardener/stop.sh` per `cold-topic-explainer` pattern
- [ ] T133 [US9] Complete `examples/doc-gardener/auto_approve.sh` polling script
- [ ] T134 [US9] Write `examples/doc-gardener/wrapper.sh` subprocess entrypoint reading `SYNAPBUS_TASK_*` env vars and invoking the chosen LLM CLI
- [ ] T135 [P] [US9] Write the Go HTML report generator at `examples/doc-gardener/report.go` (cmd-line tool) that queries the DB via the internal `goals.Service.GetGoalSnapshot(id)` and renders the template
- [ ] T136 [P] [US9] Write the Go text/template at `examples/doc-gardener/report.html.tmpl` with the 6 sections from research.md D17 (header / tree / agents / cost / timeline / artifacts) with inline dark-mode CSS
- [ ] T137 [US9] Write `examples/doc-gardener/report.sh` that builds the report binary, runs it against the last goal id (from `.last_goal_id`), and opens `report.html`
- [ ] T138 [US9] Implement `goals.Service.GetGoalSnapshot(ctx, goalID) (*GoalSnapshot, error)` assembling goal + tree + agents + cost breakdown + timeline — in `internal/goals/snapshot.go`
- [ ] T139 [P] [US9] Write unit test `TestGoalSnapshot_ContainsAllSections` asserting every required field in `internal/goals/snapshot_test.go`
- [ ] T140 [US9] Write `examples/doc-gardener/README.md` per the `cold-topic-explainer/README.md` pattern, with the expected flow, commands, and troubleshooting from `quickstart.md`
- [ ] T141 [US9] End-to-end smoke test: run `./start.sh && ./run_task.sh --auto-approve && ./report.sh` against the current checkout; iterate on failures until it produces a valid report — **covers SC-002, SC-003**
**Checkpoint**: 🎯 **MVP DONE** — the doc-gardener example runs end-to-end and produces an HTML report. Ship-ready.
---
## Phase 9: User Story 6 — Resource-request protocol (Priority: P2)
**Goal**: Agents can ask the human for missing secrets; humans provide them via CLI; subsequent runs get the env var.
**Maps to**: Spec US6, FR-025, FR-026, FR-027, FR-028, FR-029, FR-030, SC-012.
- [ ] T150 [US6] Implement MCP tool `request_resource` in `internal/mcp/tools_resources.go` per `contracts/mcp-tools.md` §6
- [ ] T151 [P] [US6] Write MCP test `TestRequestResourceMCP` in `internal/mcp/tools_resources_test.go`
- [ ] T152 [US6] Implement MCP tool `list_resources` in `internal/mcp/tools_resources.go` per `contracts/mcp-tools.md` §7 (names-only, never values)
- [ ] T153 [P] [US6] Write MCP test `TestListResourcesMCP_NeverReturnsValues` asserting values are never exposed
- [ ] T154 [US6] Register both tools in `internal/mcp/server.go`
- [ ] T155 [US6] Implement cobra subcommand `synapbus secrets set|get|list|revoke` in `cmd/synapbus/cmd_secrets.go` — `get` returns availability only, never value; `set` encrypts and stores scoped
- [ ] T156 [P] [US6] Write cobra integration test in `cmd/synapbus/cmd_secrets_test.go`
- [ ] T157 [US6] Wire the resource-request workflow: on `secrets set` for a pending `resource_requests` row, mark `fulfilled`, DM the requester, and re-queue the agent for a heartbeat
- [ ] T158 [P] [US6] Write end-to-end integration test `TestRequestResource_RoundTrip` — agent requests, human sets, agent runs, subprocess sees env var — in `internal/secrets/e2e_test.go` — **covers SC-012**
- [ ] T159 [P] [US6] Add `/api/secrets/:scope_type/:scope_id` REST endpoint in `internal/api/handlers_secrets.go` returning names only; tests in `internal/api/handlers_secrets_test.go`
**Checkpoint**: A specialist can request a credential and the human can fulfill it without restarting anything.
---
## Phase 10: User Story 7 — Budget cascade, soft alert, auto-pause (Priority: P2)
**Goal**: Goals hit 80 % → soft alert; hit 100 % → paused; budgets cascade across delegation.
**Maps to**: Spec US7, FR-019, FR-020, FR-021, FR-022, SC-006.
- [ ] T170 [US7] Implement `goals.Service.CheckBudgetAndLaunch(ctx, goalID, newCost) error` that runs inside the same transaction as the `harness_runs` insert per research.md D7 — in `internal/goals/budget.go`
- [ ] T171 [US7] Implement soft-alert posting (idempotent, only on prior-below-80 + now-at-or-above-80) in `internal/goals/budget.go`
- [ ] T172 [US7] Implement hard auto-pause (transition goal to `paused`, post pause message, DM owner) in `internal/goals/budget.go`
- [ ] T173 [US7] Wire `goals.Service.CheckBudgetAndLaunch` into the reactor's pre-run transaction in `internal/harness/reactor/reactor.go`
- [ ] T174 [US7] Implement `tasks.Service.CreateSubTask` to subtract child budget from parent's remaining budget, failing with `ErrBudgetInsufficient` if over
- [ ] T175 [US7] Implement `goals.Service.SpendByBillingCode(ctx, goalID) (map[string]Cost, error)` rollup query
- [ ] T176 [P] [US7] Write integration test `TestBudgetRace_80AndPause` with two concurrent runs, asserting exactly one soft alert and the goal auto-pauses — in `internal/goals/budget_test.go` — **covers SC-006**
- [ ] T177 [P] [US7] Write unit test `TestBillingCodeRollup` asserting per-billing-code totals in `internal/goals/budget_test.go`
- [ ] T178 [US7] Implement MCP tool `resume_goal` in `internal/mcp/tools_goals.go` per `contracts/mcp-tools.md` §8
- [ ] T179 [P] [US7] Write MCP test `TestResumeGoalMCP` in `internal/mcp/tools_goals_test.go`
- [ ] T180 [US7] Register `resume_goal` in `internal/mcp/server.go`
- [ ] T181 [P] [US7] Add "Budget" + "Billing code breakdown" panels to `web/src/routes/goals/[id]/+page.svelte`
**Checkpoint**: Goals auto-pause at budget ceiling; the Web UI shows breakdowns.
---
## Phase 11: User Story 8 — Quarantine on low reputation (Priority: P3)
**Goal**: Agents whose rolling reputation drops below 0.3 are auto-quarantined and cannot claim tasks.
**Maps to**: Spec US8, FR-023, FR-024.
- [ ] T190 [US8] Implement `trust.CheckQuarantine(ctx, agentID)` in `internal/trust/ledger.go` running a ticker every N seconds (configurable, default 60) plus on every evidence append
- [ ] T191 [US8] Implement `agents.Service.Quarantine(ctx, agentID, reason)` and `Unquarantine(ctx, agentID)` in `internal/agents/service.go`
- [ ] T192 [US8] Reject `claim_task` from quarantined agents with `ErrAgentQuarantined` — wire in `internal/tasks/service.go`
- [ ] T193 [P] [US8] Write unit test `TestQuarantine_BelowThreshold` and `TestQuarantine_ClaimRejected` in `internal/agents/service_test.go`
- [ ] T194 [US8] Implement MCP tool `unquarantine_agent` in `internal/mcp/tools_goals.go` per `contracts/mcp-tools.md` §9
- [ ] T195 [P] [US8] Write MCP test `TestUnquarantineMCP` in `internal/mcp/tools_goals_test.go`
- [ ] T196 [US8] Register `unquarantine_agent` in `internal/mcp/server.go`
- [ ] T197 [P] [US8] Add "Quarantined" indicator to agent detail page in `web/src/routes/agents/[name]/+page.svelte`
**Checkpoint**: Reputation drop triggers quarantine; human can lift it via CLI or UI.
---
## Phase 12: Polish & Cross-Cutting Concerns
**Purpose**: Full test sweep, cross-compile verification, docs, and the finishing touches needed to ship.
- [ ] T210 Run the full test suite: `go test ./...` with `-race` and fix any flakes
- [ ] T211 Cross-compile verification: `CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./...` and `CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build ./...` — **covers SC-010**
- [ ] T212 [P] Verify all new MCP tools appear in `tools/list` via an MCP transport integration test in `internal/mcp/integration_test.go` — **covers SC-008**
- [ ] T213 [P] Web UI smoke test: load `/goals` with 100 seeded goals × 1000 tasks and assert load time < 1 s in `web/src/routes/goals/+page.svelte.test.ts` — **covers SC-011**
- [ ] T214 Update the project-level `CLAUDE.md` (already auto-updated by /speckit.plan) with any additional notes about running the doc-gardener example
- [ ] T215 Update `examples/README.md` (or create if absent) to list both `cold-topic-explainer` and `doc-gardener` with one-line descriptions and links
- [ ] T216 [P] Write a top-level `docs/dynamic-agents.md` explaining the trust model, spawn primitive, and verification workflow for new users
- [ ] T217 Run the complete SC checklist against the built binary and generated report — confirm every SC-0xx item is met
- [ ] T218 Commit the full branch with a descriptive message; push to GitHub
- [ ] T219 Final smoke: wipe `./examples/doc-gardener/data` and rerun `./start.sh && ./run_task.sh --auto-approve && ./report.sh` in one shot to confirm reproducibility from a clean state
- [ ] T220 Review `specs/018-dynamic-agent-spawning/checklists/requirements.md` — mark done after T219 passes
---
## Dependency Graph
```text
Phase 1 (Setup) — no dependencies
↓
Phase 2 (Foundational) — needs Phase 1
↓
Phase 3 (US1 — MVP start) — needs Phase 2
↓
Phase 4 (US2) — needs US1 (goal exists before tree)
↓
Phase 5 (US3) — needs US2 (tasks exist before spawn proposals target them)
↓
Phase 6 (US4) — needs US3 (specialists must exist before claims)
↓
Phase 7 (US5 — verification) — needs US4 (tasks must be in awaiting_verification)
↓
Phase 8 (US9 — doc-gardener E2E) — needs US1–US5 (MVP capstone)
↓ [MVP ships here]
Phase 9 (US6 — resources) ─┐ — needs US4 (run path exists); can run parallel to Phase 10 & 11
Phase 10 (US7 — budget) ──┤ — needs US4 (harness_runs path)
Phase 11 (US8 — quarantine) ┘ — needs Phase 2 (trust primitive)
↓
Phase 12 (Polish) — needs everything above
```
## Parallel Execution Opportunities
**Within Phase 1**: T002, T003, T004 run in parallel.
**Within Phase 2**: T015, T016 can run after migrations are written. T019 can run after T017 and T018. T022 and T023 run in parallel after T017.
**Within Phase 3 (US1)**: T033, T034, T036, T038, T039, T040, T041 all [P] — different files.
**Within Phase 4 (US2)**: T053, T054, T056, T059 all [P].
**Within Phase 5 (US3)**: T074, T075, T077, T080 all [P].
**Within Phase 6 (US4)**: T091, T093, T095, T099, T100 all [P].
**Within Phase 7 (US5)**: T115, T117 [P].
**Within Phase 8 (US9)**: T135, T136, T139 [P].
**Phase 9, 10, 11**: can run in parallel to each other after US5 is done. Inside each phase, [P] tasks run in parallel.
**Within Phase 12**: T212, T213, T216 [P].
## Implementation Strategy
### MVP Scope (ship-ready)
Phases 1, 2, 3, 4, 5, 6, 7, 8 — this gets you US1 through US5 and the doc-gardener example running end-to-end. **At the end of Phase 8 the feature is demoable and the HTML report renders.** This is the minimum to ship.
### Post-MVP
Phases 9, 10, 11 can be shipped as follow-up PRs without re-cutting the MVP. Phase 12 (polish) gates the final merge.
### Task Count
| Phase | Story | Count | Parallel |
|---|---|---|---|
| 1 — Setup | — | 5 | 3 |
| 2 — Foundational | — | 14 | 5 |
| 3 — US1 | P1 | 14 | 7 |
| 4 — US2 | P1 | 14 | 4 |
| 5 — US3 | P1 | 13 | 5 |
| 6 — US4 | P1 | 12 | 5 |
| 7 — US5 | P1 | 10 | 2 |
| 8 — US9 | P1 (E2E) | 12 | 3 |
| 9 — US6 | P2 | 10 | 5 |
| 10 — US7 | P2 | 12 | 5 |
| 11 — US8 | P3 | 8 | 4 |
| 12 — Polish | — | 11 | 3 |
| **Total** | | **135** | **51 [P]** |
## Independent Test Criteria (summary)
| Story | Priority | Independent test |
|---|---|---|
| US1 | P1 | `create_goal` → row + channel + coordinator exist |
| US2 | P1 | Coordinator posts tree proposal → approve → tasks materialized atomically |
| US3 | P1 | `propose_agent` valid + invalid → approval → new agent with hash/parent/seeded rep |
| US4 | P1 | 100-way concurrent claim → exactly one winner; claim → heartbeat → run → awaiting_verification |
| US5 | P1 | Each verifier kind (auto/peer/command) drives correct terminal state + reputation row |
| US6 | P2 | Request resource → set secret → next run sees env var |
| US7 | P2 | Race two runs at 80 % → exactly one alert; cumulative at 100 % → paused |
| US8 | P3 | Reputation drop → auto-quarantine + claim rejected; unquarantine restores |
| US9 | P1 | Full example pipeline produces `report.html` with 6 sections non-empty |