start.sh was passing --owner 1 to every `agent create` call, under the
assumption that the freshly created `algis` user would be user id 1.
It isn't — the `admin` user is auto-seeded at id=1, so `algis` comes
in at id=2. Result: the 3 AI agents AND the `algis` human agent were
owned by `admin`, and when the user logged into the Web UI as `algis`
the message handler called GetHumanAgentForUser(2) which returned a
different auto-created `algis-human` agent (id=5, owned by user 2).
The critic DM'd the name `algis` → landed on agent id=1 (admin-owned),
but the UI listed DMs for `algis-human` → panel showed
"No conversations" despite reactive_runs clearly showing the chain
succeeded.
Fix: after `user create`, query sqlite for the algis user id and use
that value as --owner for every subsequent agent create. Bails with a
clear error if the id lookup fails or returns 1 (sanity check that
admin/algis aren't conflated).
Verified end-to-end:
1. ./stop.sh && ./start.sh — new instance, ownership correct from
the first `agent create` call.
2. ./run_task.sh with a fresh topic — 3 subprocess runs succeeded,
messages #1 (algis → decomposer-pro) and #4 (critic-lite → algis)
now belong to an agent owned by user 2, so
GetHumanAgentForUser(2) returns the same agent the messages are
addressed to.
3. sqlite3 agents table shows all four agents with owner_id=2.
4. Chrome navigation to http://localhost:18088/ reaches the login
form with no errors — once the user logs in as
algis / algis-demo-pw the Direct Messages panel will show the
four-message conversation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cold-topic-explainer
Toy multi-agent task that exercises the subprocess harness end-to-end. Three Gemini agents on different models collaborate via SynapBus DMs to produce a 3-paragraph explainer for a topic, with a writer ↔ critic refinement loop.
Roles
| Agent | Model | Job |
|---|---|---|
decomposer-pro |
gemini-2.5-pro |
Receives the topic, splits it into what / why / how, DMs writer-flash |
writer-flash |
gemini-2.5-flash |
Drafts (or revises) the 3-paragraph explainer, DMs critic-lite |
critic-lite |
gemini-2.5-flash-lite |
Rates each paragraph 1–10. Scores all ≥ 8 → DMs algis with FINAL:. Else DMs writer-flash with REVISE: and specific fixes |
This exercises:
- Decomposition —
decomposer-prosplits one request into 3 sub-questions - Delegation — each agent DMs the next, routed by the SynapBus reactor
- Recursive update — the writer↔critic loop runs until convergence or
max_trigger_depthfires (default 6, giving ~3 full refinement rounds)
Every hop is a subprocess reactive run, subject to the same depth /
budget / cooldown guards as a K8s reactive run. Each hop writes a
harness_runs row with usage, cost, duration, and trace id.
Prereqs
geminiCLI installed and authenticated (gemini auth logindone once)- Go 1.25+
jq,curl,sqlite3available on PATH- An unused TCP port (default 18088)
Run it
./start.sh
./run_task.sh "how does the SynapBus reactor's pending_work flag coalesce bursts of DMs?"
./stop.sh
What happens
start.shbuildssynapbusfrom the current checkout, launches a separate instance on port 18088 with a local./datadirectory, creates useralgis(passwordalgis), creates three AI agents, and configures each agent'sharness_config_jsonwith GEMINI.md, MCP pointer, role env, and the wrapper script invocation.run_task.shkicks off the chain by sending an initial DM fromalgistodecomposer-provia the admin socket, then polls for a DM toalgiswhose body starts withFINAL:. Prints the body when it arrives (or gives up after 4 min).stop.shsignals the synapbus PID and waits for it to exit cleanly.
View during the run
- Web UI: http://localhost:18088 — log in as
algis/algis-demo-pw - Agent detail (see Harness panel + traces):
- Live slog JSON:
tail -f synapbus.log | jq -c 'select(.component=="reactor" or .harness)' - All DMs in order:
./bin/synapbus --socket ./data/synapbus.sock messages list --limit 50 - Harness runs:
sqlite3 ./data/synapbus.db 'SELECT run_id, agent_name, backend, status, duration_ms, tokens_in, tokens_out, cost_usd FROM harness_runs ORDER BY id'
OpenTelemetry
Off by default. To ship spans to a collector while you run the task:
SYNAPBUS_OTEL_ENABLED=1 SYNAPBUS_OTEL_ENDPOINT=otel-collector.synapbus.svc.cluster.local:4318 ./start.sh
Or stand up a local collector first using deploy/kubic/otel-collector.yaml.
Without a collector, the same information is available in synapbus.log
as slog JSON and in the harness_runs table.
Cost
Rough cost per successful run, assuming 2 writer-critic iterations:
| Hops | Model | Cost |
|---|---|---|
| 1 | gemini-2.5-pro | ~$0.01 |
| 2 | gemini-2.5-flash | ~$0.01 |
| 3 | gemini-2.5-flash-lite | ~$0.002 |
| Total | ~$0.02 |
The daily trigger budget per agent is capped at 20 (see start.sh) so
this example cannot accidentally spend more than pennies per day even
if the reactor loops on a bug.
Files
start.sh— launch separate synapbus + configure agentsrun_task.sh— kickoff DM + poll for finalstop.sh— graceful shutdownwrapper.sh— shell wrapper used as the agents'local_command; readsmessage.json, callsgemini, routes the result back via the admin socketconfigs/*.json— per-agentharness_config_jsonblobs