I slept through my beta launch and delivered on time
At 23:47 the night before I planned to flip the public repo, I had four Claude Code
sessions running in four different tmux panes, each in a different
project on my machine. Three were workers. One was an orchestrator. None of them
were me.
I went to bed.
When I woke up, ten commits had landed on main. All green. None of
them mine. Forty-eight commits in over fifty hours. I never wrote down what each
session was doing.
This is the story of how that worked, and the one part where it nearly didn't.
The moment that earned the post
Mid-sprint, on the bench pane, after 1902 turns of conversation grinding through
benchmark cells, I tested the recovery loop deliberately. I ran
/hydrate-distill. The local daemon summarised the entire conversation
into one staged injection. I ran /clear. The conversation wiped. I ran
/hydrate-last. Thirty seconds later, the new session emitted this:
The bench was mid-run when last seen. Cell 1 (Hydrate, N=10)
was scored 11/20 and Cell 2 (claude-mem) was underway. Two logs
to check:
- Hydrate arm: /tmp/bench-hy-run.log on mini
- claude-mem arm: /tmp/bench-cm-run.log on mini
In-flight sub-session (do not re-dispatch):
sse-project-detail-pane → pane dev, dispatched 07:20 UTC, 1h30m ETA.
Next up after bench completes:
1. wave2-pack-onboarding-measure
2. wave3-cloudflare-email-routing
3. Various wave3 post-launch items
The status line read ↑ 14 tok saved. The full recovery cost fourteen
tokens against the local daemon. Re-pasting the equivalent conversation would have
cost around two hundred thousand.
That is the ratio the rest of the pattern depends on.
How it works
The orchestrator ran in its own tmux pane on Opus, because it makes the
judgement calls. Four worker panes sat alongside it:
dev: Claude Code on Sonnet, in~/Documents/Dev/go/hydrate. The daemon, the CLI, the hooks, the MCP server.web: Claude Code on Sonnet, in~/Documents/Dev/gethydrate-dev. Marketing pages, docs, blog, install snippets.bench: Claude Code on Sonnet, in~/Documents/Dev/go/hydrate-benchmark, driving Docker on a Mac mini at192.168.68.100viaDOCKER_HOST=ssh://....term: a bare shell, no model. For git, curl, and SSH operations that did not need one.
Most multi-agent demos run one orchestrator and N workers in the same project. This was three workers in three different project repos on disk, with no shared filesystem mounts and no message broker between them. The bus was the local hook every Hydrate user already runs.
The fifth actor was hydrate-server, my own memory daemon, running locally
on the same workstation. Every pane reached it through the two hooks every Hydrate
user already has installed: UserPromptSubmit (read state before every
prompt) and Stop (capture session decisions on completion). It owned a
single SQLite file at ~/.hydrate/data.db.
The orchestrator's only mechanism for controlling the workers was
tmux send-keys. It typed prompts directly into each worker's input field.
No socket, no API, no message broker. The two-call dispatch pattern took three minutes
to debug on the first attempt (text first, sleep half a second, then Enter as a
separate keystroke; sending both in one call lets the TUI swallow the Enter as
input-field state) and worked for every dispatch after that.
Sharing facts across three project repos
This is the part that is novel, and the part that made the whole sprint work.
Hydrate's normal job is to share memory inside one project. A fact written in a
session lands in the local SQLite store. The UserPromptSubmit hook
surfaces relevant facts in the next prompt automatically. For the launch, I wrote
orchestration facts at the global scope instead, with no project tag, under an
[ORCH:*] naming convention:
project_id = NULL(global scope)pinned = 1tier = 'always'category = 'orchestration'text = '[ORCH:<name>:<key>] <value>'
Globally-scoped pinned facts surface in every session's prompt on the same machine, no matter which project the session is working in. That single line of server logic, the same one shipping to every Hydrate user, is what made the cross-project coordination free. The dev pane could read everything the web pane had landed. The web pane could read everything the bench pane was measuring. None of them paid extra tokens to learn it. The orchestrator never asked a worker "what are you doing right now." It wrote the dispatch as a fact, and the worker's next prompt already contained it.
The state writes looked like this:
hydrate orchestrator set wave3-vs-claude-mem in-progress
hydrate orchestrator set wave3-vs-claude-mem-notes "cell 2/10"
hydrate orchestrator dispatch --to web \
--brief docs/plans/.../HANDOFF-sse-facts-pane.md \
--eta 2h \
--acceptance "per-row SSE frames; user edit not clobbered" \
sse-facts-pane
The subcommand (cmd/hydrate/orchestrator.go, around three hundred and
fifty lines plus tests) is a thin typed wrapper over the existing canon-fact API. The
Stop hook does the other half of the job: when a worker session ends, its transcript
is processed and any durable decisions are extracted as facts automatically. Even if
a worker forgot to call hydrate orchestrator set, its decisions still
landed in the store and surfaced in the next orchestrator prompt. I did not have to
chase anyone for reports.
What the orchestrator dispatched
The fifty hours split into four kinds of work:
- Last-minute features. The biggest chunk. Ten dashboard panes that needed converting from pull to live-push over SSE, including two with awkward shapes:
/factsis interactive (per-row push frames so an in-progress edit on one row does not clobber another), and/project/{id}is parametric (project-scoped filter pulled from the URL slug). - Bugs surfaced during the run. Not on the checklist when I wrote the plan.
/hydrate-lastwas returning identity facts instead of in-flight state. MCP was sending an emptyX-API-Keyagainst the portfile fallback.SQLITE_BUSYon rapid orchestrator-dispatch calls.hydrate server startwas silently missing from the CLI. Auth middleware was blocking dashboard browser navigation in one specific case. And one that would have been a hard launch-blocker if missed: cross-project distill bleed, where a test session surfaced credentials from a different project. Fixed within the hour the orchestrator noticed it. - Install tests in on-demand Docker containers. The bench pane spun up clean-account containers on demand and ran the launch gate through them:
brew install,hydrate doctor,hydrate install-hooks, then a real Claude Code session calling/hydrate-last. Confirmed each step end to end, then tore the container down. Scripted, not babysat. - Benchmarks on the fly. The same bench pane spun up recordable scenario containers on the Mac mini, ran the vs-claude-mem head-to-head across nine cells, scored each one, and wrote the results back into canon. The homepage benchmark numbers had source data behind them by the time the launch landed.
What broke, twice
The first failure was on the bench pane. It had been grinding through benchmark cells
for hours. Context climbed past seventy percent. Then eighty. The status line lit up
with distill now! — a nudge from the fatigue-signals pane I had shipped
two hours earlier on the same plan. I watched it cross eighty-three percent. Then
auto-compaction fired. The session reset.
I had built PreCompact and SessionStart hooks days before
for exactly this case, but I had not actually tested them on a real fatigued session.
They had not been exercised in anger. Now they were.
The replacement session loaded the snapshot. The bench worker picked up the cell-by-cell grading loop. The benchmark continued without intervention. The compact-survival features predicted their own validation event. I had built the recovery loop hours earlier with no plan to exercise it. The sprint exercised it for me, on the same feature it had just shipped.
The second failure was funnier and more my fault. The orchestrator was sending a
status update to the dev pane that started with
/overview shipped at <sha>.... Claude Code's TUI parsed the leading
/overview as a slash command. The worker responded "Unknown command:
/overview", then /exit ran in the input history, and the session closed.
Filed as a pattern-doc gotcha: never lead a tmux send-keys payload with
/<word>. Wrap in "Dispatch:" or "Status:" before any text that
might start with a slash.
In both cases the recovery surface was the same. Open a fresh session in the dead
pane, run /hydrate-last, get the structured briefing shown earlier,
resume.
The waves
The orchestrator split the plan into four batches:
| Wave | Tasks | Calendar time | What happened |
|---|---|---|---|
| 1 | 4 | One afternoon | First proof of concept. Validated the dispatch pattern. |
| 2 | 10 | ~24 hours | Recall-bias bug surfaced and fixed. Distill / clear / last cycle tested live. |
| Overnight | 10 (autonomous) | 8 hours unattended | Dev queue 100% consumed. Web 100% consumed. Bench mid-run. I was asleep. |
| SSE-everywhere | 12 (parallelised) | 78 minutes | Ten dashboard panes converted from pull to push. Plan estimated ten hours serial. |
Forty-eight commits between the orchestrator subcommand landing
(26f1f9d) and full SSE-everywhere completion (3baaf6e).
Fifty hours of calendar time. Most of it ran while I was asleep or in other meetings.
What it cost
The orchestrator session ran continuously on Opus for the duration. Worker panes ran on Sonnet for implementation and switched to Opus only for the judgement-heavy passes (writing, design calls). Early read on the token accounting: orchestrator-to-worker ratio settled at around ten percent. Coordination overhead was an order of magnitude smaller than the work the panes did. The dominant cost was always implementation, not coordination.
The reason that ratio held: the orchestrator's input was the pre-prompt hook, which is free at the point of read, and its output was short dispatch prompts plus a running ops log. It never paid to re-derive state from scratch.
What would have happened without this
A week of evenings. Lost momentum. The kind of launch slip that a founder remembers years later as the moment they wished they'd had a team.
Instead, I had a hook the product already shipped, three worker sessions that did not need a standup, and a beta that shipped on time. The state lived in SQLite where I could not lose it. The hook surfaced it on every prompt the worker made. Coordination cost nothing extra.
Hydrate's homepage says memory is a layer, not a feature. This sprint was the test.