Three improvements that turn the doc-gardener demo from "runs but stays in 'draft' forever" into a goal that properly transitions through its lifecycle and renders a completion summary on /goals/<id>. ### 1. complete_goal MCP tool (#59, #62) New tool surface: complete_goal(goal_id, status, summary, completion_message_id?) The critic calls this from inside the sandbox after it sends its FINAL: DM. Records the one-paragraph human-readable summary on the goal row plus a pointer to the message that carried the FINAL text, so the Web UI /goals/<id> page has both the verdict and a deep link to the full findings JSON. Status parameter accepts completed | stuck | cancelled. Idempotent when called with the current status. Rejects callers owned by a different human than the goal owner. Plumbing: - New migration 026_goals_completion_summary.sql adds two columns to goals: completion_summary TEXT, completion_message_id INTEGER (FK messages.id, ON DELETE SET NULL). - internal/goals/types.go: new CompletionSummary + CompletionMessageID fields on Goal struct. - internal/goals/store.go: Get/List Scan both new columns; SetCompletion(goalID, status, summary, messageID) helper that updates status+summary+message_id atomically and populates completed_at for terminal states. - internal/goals/service.go: Complete(ctx, goalID, status, summary, messageID) wraps the store method with legalTransition gating. legalTransition expanded so draft can jump straight to completed (no mandatory "active" hop required). - internal/mcp/goals_tools.go: completeGoalTool definition + handleCompleteGoal handler. Tool count 6 → 7. - internal/api/goals_handler.go: surfaces completion_summary, completion_message_id, and completed_at on both list and detail endpoints so the Svelte /goals UI can render them. ### 2. Draft → active auto-transition in propose_task_tree (#60) handleProposeTaskTree now flips the goal from draft to active at the end. Previously the coordinator would call create_goal + propose_task_tree and dispatch inspector, but the goal stayed in draft forever because nothing transitioned it. Now the mere fact of having a task tree means the goal is active. Safe: the transition is best-effort and ignores the legal-transition error when the goal is already beyond draft. ### 3. REVISE round cap (#61) Two-layer enforcement: - Server-side: examples/doc-gardener/start.sh drops max_trigger_depth from 8 to 4. Each REVISE round costs 2 hops (critic→inspector + inspector→critic), so depth=4 caps the loop at roughly 2 rounds before the reactor refuses further dispatches. - Prompt-side: inspector now includes revision_round (starting at 0, incremented when it sees a REVISE: input) in its findings JSON. Critic reads revision_round and force-FINALs when >= 1. Prompt explicitly tells the critic to call complete_goal after sending FINAL, so the goal row gets a proper completion_summary. ### 4. run_task.sh terminal-state detection Rewrote the poll loop to watch goals.status/completion_summary as the definitive "done" signal rather than parsing DM bodies. Keeps a message-based fallback for TRIVIAL/CANNOT paths that don't create a goal. Treats "Received system trigger..." and "Coalesced trigger..." as informational (they're `__coalesced__` reactor synthetic events leaking through the coordinator reply, not real user-facing output). Bare coordinator replies are terminal only when no goal was created. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SynapBus examples
Runnable demos of SynapBus features. Each example is self-contained under its own directory, launches an isolated synapbus instance on a distinct port, and cleans up after itself.
| Example | Feature | Real LLM? | Port |
|---|---|---|---|
cold-topic-explainer/ |
Reactive agent triggers + subprocess harness — three Gemini agents (decomposer → writer → critic) collaborate via DMs to produce a 3-paragraph explainer, with real LLM calls end-to-end. | ✅ yes (gemini CLI) |
18088 |
doc-gardener/ |
Dynamic agent spawning (spec 018) — a coordinator meta-agent decomposes a goal into a task tree, spawns specialists with config_hash-rooted trust + delegation-cap enforcement, runs them through the state machine, generates a rich HTML report. |
❌ v1 is synthetic (primitives demo); real LLM coordinator is a follow-up PR | 18089 |
Quick start
Pick an example, cd into it, and follow its README. In general:
cd examples/<name>
./start.sh # rebuild + launch an isolated synapbus instance
./run_task.sh # drive the demo flow
./report.sh # (where applicable) render an HTML report
./stop.sh # shut down
Both examples use the same layout for consistency:
examples/<name>/
├── start.sh # build & launch
├── run_task.sh # execute the demo flow
├── stop.sh # shut down
├── report.sh # (doc-gardener only) render HTML report
├── bin/
│ ├── synapbus # built from the current checkout
│ └── <helper> # example-specific driver binary
├── configs/ # per-agent JSON configs (harness_config, prompts, etc.)
├── data/ # isolated SQLite DB + attachment store + sockets
├── synapbus.log # server stdout+stderr
└── README.md # example-specific docs
What each example proves
- cold-topic-explainer proves that the SynapBus reactor + subprocess harness can drive a real multi-agent loop with three distinct LLMs, with depth and budget guards, OpenTelemetry tracing, and harness_runs accounting.
- doc-gardener proves that the dynamic-agent-spawning data primitives —
goals,goal_taskswith denormalized ancestry, atomic optimistic-lock claim,config_hash-keyed reputation ledger, delegation-cap enforcement, per-billing-code cost rollup — work end-to-end against real SQLite, and feed a rich HTML report.
The two examples are complementary: cold-topic-explainer exercises the runtime path (reactor → harness → LLM → DMs), doc-gardener exercises the work-tracking path (goals → tasks → trust → report). A future example will combine them into a full LLM-driven coordinator loop.
Global prereqs
- Go 1.25+
sqlite3,curl,jqon$PATH- A free TCP port per example (see table above)
- For
cold-topic-explaineronly:geminiCLI authenticated viagemini auth login
Troubleshooting
- Port already in use: set
SYNAPBUS_PORT=18090 ./start.sh(each example honors the env var). - Web UI is blank: rebuild the embedded Svelte SPA with
make webfrom the repo root once, then re-run./start.sh. - Stale binary: delete the example's
bin/directory and rerun./start.shto force a rebuild.