@animalabs/connectome-host 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/.env.example +20 -0
  2. package/.github/workflows/publish.yml +65 -0
  3. package/ARCHITECTURE.md +355 -0
  4. package/CHANGELOG.md +30 -0
  5. package/HEADLESS-FLEET-PLAN.md +330 -0
  6. package/README.md +189 -0
  7. package/UNIFIED-TREE-PLAN.md +242 -0
  8. package/bun.lock +288 -0
  9. package/docs/AGENT-MEMORY-GUIDE.md +214 -0
  10. package/docs/AGENT-ONBOARDING.md +541 -0
  11. package/docs/ATTENTION-AND-GATING.md +120 -0
  12. package/docs/DEPLOYMENTS.md +130 -0
  13. package/docs/DEV-ENVIRONMENT.md +215 -0
  14. package/docs/LIBRARY-PIPELINE.md +286 -0
  15. package/docs/LOCUS-ROUTING-DESIGN.md +115 -0
  16. package/docs/claudeai-evacuation.md +228 -0
  17. package/docs/debug-context-api.md +208 -0
  18. package/docs/webui-deployment.md +219 -0
  19. package/package.json +33 -0
  20. package/recipes/SETUP.md +308 -0
  21. package/recipes/TRIUMVIRATE-SETUP.md +467 -0
  22. package/recipes/claude-export-revive.json +19 -0
  23. package/recipes/clerk.json +157 -0
  24. package/recipes/knowledge-miner.json +174 -0
  25. package/recipes/knowledge-reviewer.json +76 -0
  26. package/recipes/mcpl-editor-test.json +38 -0
  27. package/recipes/prompts/transplant-addendum.md +17 -0
  28. package/recipes/triumvirate.json +63 -0
  29. package/recipes/webui-fleet-test.json +27 -0
  30. package/recipes/webui-test.json +16 -0
  31. package/recipes/zulip-miner.json +87 -0
  32. package/scripts/connectome-doctor +373 -0
  33. package/scripts/evacuator.ts +956 -0
  34. package/scripts/import-claudeai-export.ts +747 -0
  35. package/scripts/lib/line-reader.ts +53 -0
  36. package/scripts/test-historical-thinking.ts +148 -0
  37. package/scripts/warmup-session.ts +336 -0
  38. package/src/agent-name.ts +58 -0
  39. package/src/commands.ts +1074 -0
  40. package/src/headless.ts +528 -0
  41. package/src/index.ts +867 -0
  42. package/src/logging-adapter.ts +208 -0
  43. package/src/mcpl-config.ts +199 -0
  44. package/src/modules/activity-module.ts +270 -0
  45. package/src/modules/channel-mode-module.ts +260 -0
  46. package/src/modules/fleet-module.ts +1705 -0
  47. package/src/modules/fleet-types.ts +225 -0
  48. package/src/modules/lessons-module.ts +465 -0
  49. package/src/modules/mcpl-admin-module.ts +345 -0
  50. package/src/modules/retrieval-module.ts +327 -0
  51. package/src/modules/settings-module.ts +143 -0
  52. package/src/modules/subagent-module.ts +2074 -0
  53. package/src/modules/subscription-gc-module.ts +322 -0
  54. package/src/modules/time-module.ts +85 -0
  55. package/src/modules/tui-module.ts +76 -0
  56. package/src/modules/web-ui-curve-page.ts +249 -0
  57. package/src/modules/web-ui-module.ts +2595 -0
  58. package/src/recipe.ts +1003 -0
  59. package/src/session-manager.ts +278 -0
  60. package/src/state/agent-tree-reducer.ts +431 -0
  61. package/src/state/fleet-tree-aggregator.ts +195 -0
  62. package/src/strategies/frontdesk-strategy.ts +364 -0
  63. package/src/synesthete.ts +68 -0
  64. package/src/tui.ts +2200 -0
  65. package/src/types/bun-ffi.d.ts +6 -0
  66. package/src/web/protocol.ts +648 -0
  67. package/test/agent-name.test.ts +62 -0
  68. package/test/agent-tree-fleet-launch.test.ts +64 -0
  69. package/test/agent-tree-reducer-parity.test.ts +194 -0
  70. package/test/agent-tree-reducer.test.ts +443 -0
  71. package/test/agent-tree-rollup.test.ts +109 -0
  72. package/test/autobio-progress-snapshot.test.ts +40 -0
  73. package/test/claudeai-export-importer.test.ts +386 -0
  74. package/test/evacuator.test.ts +332 -0
  75. package/test/fleet-commands.test.ts +244 -0
  76. package/test/fleet-no-subfleets.test.ts +147 -0
  77. package/test/fleet-orchestration.test.ts +353 -0
  78. package/test/fleet-recipe.test.ts +124 -0
  79. package/test/fleet-route.test.ts +44 -0
  80. package/test/fleet-smoke.test.ts +479 -0
  81. package/test/fleet-subscribe-union-e2e.test.ts +123 -0
  82. package/test/fleet-subscribe-union.test.ts +85 -0
  83. package/test/fleet-tree-aggregator-e2e.test.ts +110 -0
  84. package/test/fleet-tree-aggregator.test.ts +215 -0
  85. package/test/frontdesk-strategy.test.ts +326 -0
  86. package/test/headless-describe.test.ts +182 -0
  87. package/test/headless-smoke.test.ts +190 -0
  88. package/test/logging-adapter.test.ts +87 -0
  89. package/test/mcpl-admin-module.test.ts +213 -0
  90. package/test/mcpl-agent-overlay.test.ts +126 -0
  91. package/test/mock-headless-child.ts +133 -0
  92. package/test/recipe-cache-ttl.test.ts +37 -0
  93. package/test/recipe-env.test.ts +157 -0
  94. package/test/recipe-path-resolution.test.ts +170 -0
  95. package/test/subagent-async-timeout.test.ts +381 -0
  96. package/test/subagent-fork-materialise.test.ts +241 -0
  97. package/test/subagent-peek-scoping.test.ts +180 -0
  98. package/test/subagent-peek-zombie.test.ts +148 -0
  99. package/test/subagent-reaper-in-flight.test.ts +229 -0
  100. package/test/subscription-gc-module.test.ts +136 -0
  101. package/test/warmup-handoff.test.ts +150 -0
  102. package/test/web-ui-bind.test.ts +51 -0
  103. package/test/web-ui-module.test.ts +246 -0
  104. package/test/web-ui-protocol.test.ts +0 -0
  105. package/tsconfig.json +14 -0
  106. package/web/bun.lock +357 -0
  107. package/web/index.html +12 -0
  108. package/web/package.json +24 -0
  109. package/web/src/App.tsx +1931 -0
  110. package/web/src/Context.tsx +158 -0
  111. package/web/src/ContextDocument.tsx +150 -0
  112. package/web/src/Files.tsx +271 -0
  113. package/web/src/Lessons.tsx +164 -0
  114. package/web/src/Mcpl.tsx +310 -0
  115. package/web/src/Stream.tsx +119 -0
  116. package/web/src/TreeSidebar.tsx +283 -0
  117. package/web/src/Usage.tsx +182 -0
  118. package/web/src/main.tsx +7 -0
  119. package/web/src/styles.css +26 -0
  120. package/web/src/tree.ts +268 -0
  121. package/web/src/wire.ts +120 -0
  122. package/web/tsconfig.json +21 -0
  123. package/web/vite.config.ts +32 -0
@@ -0,0 +1,130 @@
1
+ # Connectome Deployments — Operations
2
+
3
+ **Status:** Working notes. Generic operations patterns for running Connectome
4
+ agents. For setting up the *code stack* (repos/branches/symlinks), see
5
+ [`DEV-ENVIRONMENT.md`](./DEV-ENVIRONMENT.md); for standing up a new agent
6
+ end-to-end, see [`AGENT-ONBOARDING.md`](./AGENT-ONBOARDING.md).
7
+
8
+ ---
9
+
10
+ ## TL;DR
11
+
12
+ An agent deployment is the Connectome stack (`forking-knowledge-miner` host +
13
+ `agent-framework` + MCPL servers) plus a per-agent install dir. Agents run
14
+ **headless** (`bun forking-knowledge-miner/src/index.ts <recipe> --headless`),
15
+ each as its own OS user, supervised by launchd (macOS) or systemd-user (Linux).
16
+
17
+ > **Model integrity matters.** Each agent has a *correct* model and must stay on
18
+ > it — feeding one model's chronicle to another is treated as a continuity
19
+ > violation. If an agent is ever contaminated onto the wrong model, delete the
20
+ > contaminated interlude and re-ingest the correct-model archive.
21
+
22
+ ---
23
+
24
+ ## Hosts
25
+
26
+ - **local (macOS)** — supervised with launchd (`gui/$(id -u)/…`). Dev checkouts
27
+ live under `~/connectome-local/*` and are symlinked into the host (see
28
+ DEV-ENVIRONMENT).
29
+ - **VPS (Linux)** — systemd **user** services (lingering enabled). If `sudo` is
30
+ unavailable on the box, run everything as user services. A box reached only
31
+ via a login user that differs from the agent user can be driven over SSH (or
32
+ the terminal-sessions MCP).
33
+
34
+ ---
35
+
36
+ ## Shared infra (per host)
37
+
38
+ ### Shell — terminal-sessions daemon
39
+ The shell tool is a per-agent MCP frontend talking to one shared, token-auth'd
40
+ session daemon. Each agent's recipe passes `SESSION_SERVER_TOKEN` (from its
41
+ `.env`); without it the daemon drops the socket ("Connection lost").
42
+
43
+ - Bind the daemon to **loopback** (`127.0.0.1:<port>`), never a public
44
+ interface. Reach it (and any loopback service) by SSH-tunnelling through a
45
+ login account, not by exposing a port.
46
+ - Tokens differ per host — each daemon is independent; don't share tokens
47
+ across machines. Values live in the `.env` files — **not** in this doc.
48
+
49
+ ### Debug context API
50
+ `GET /debug/context` (transparent: no inference/writes) is served by the `webui`
51
+ module. Keep it on **loopback** and reach it via SSH tunnel. See
52
+ [`debug-context-api.md`](./debug-context-api.md).
53
+
54
+ ---
55
+
56
+ ## Operating an agent
57
+
58
+ **Recipe / config / data live in the install dir:**
59
+ - `recipes/<agent>.json` — agent def, model, MCP servers (absolute paths to each
60
+ `dist/`), wake-gate policies, modules.
61
+ - `.env` — `ANTHROPIC_API_KEY`, `DISCORD_TOKEN`, `DISCORD_GUILD_ID`,
62
+ `DISCORD_MCPL_DEBUG_LOG`, `DISCORD_SUBSCRIPTIONS_FILE`, `SESSION_SERVER_TOKEN`,
63
+ `HEARTBEAT_CONFIG_FILE`.
64
+ - `data/` — chronicle store + per-session `config/gate.json` (wake policies;
65
+ append-only reconcile, so reorder the **live** file, not just the recipe).
66
+
67
+ **Start / stop / restart:**
68
+
69
+ | | local (launchd) | VPS (systemd-user) |
70
+ |---|---|---|
71
+ | restart | `launchctl kickstart -k gui/$(id -u)/<label>` | `systemctl --user restart <unit>` |
72
+ | stop | `launchctl bootout gui/$(id -u)/<label>` | `systemctl --user stop <unit>` |
73
+ | start | `launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/<label>.plist` | `systemctl --user start <unit>` |
74
+
75
+ Labels/units follow `cc.<agent>.agent` (launchd) / `<agent>-agent.service`
76
+ (systemd). (launchd: a plain `kill`/`stop` respawns via KeepAlive — use `bootout`
77
+ to stop, `kickstart -k` to restart.)
78
+
79
+ **Logs / observability:**
80
+ - discord-mcpl debug log: `data/discord-mcpl-debug.log` (incoming, attachments,
81
+ `handlePublish`, wake metadata).
82
+ - host stdout/stderr: launchd → `data/launchd-stdout.log` / `launchd-stderr.log`;
83
+ systemd → `journalctl --user -u <unit>`.
84
+ - routing decisions on stderr: `[routeSpeech] …`, `[routing] …`.
85
+ - raw model calls: `data/llm-calls.*.jsonl` (forensic; large).
86
+ - ⚠️ **Inference failures are under-logged in headless** — a model-call 400/error
87
+ emits only a trace (no stderr line) + a buried JSONL field. An
88
+ `[inference-failed]` chronicle marker + stderr line helps surface these.
89
+
90
+ ---
91
+
92
+ ## Wake gating (loop fix)
93
+
94
+ Each recipe's `modules.wake` policies, in order (first-match-wins):
95
+ `heartbeat-wake → [discord-send-failed-skip] → discord-explicit-mention →
96
+ discord-bot-skip → discord-direct-address → discord-ambient → cli-input`.
97
+ Net effect: a bot wakes a peer bot **only by an explicit @mention** (not a
98
+ reply), which breaks auto-reply loops; humans still wake via mention/reply/DM;
99
+ ambient channel chatter enters context without waking.
100
+
101
+ ---
102
+
103
+ ## Current divergences / caveats
104
+
105
+ - **Dev boxes can drift ahead of prod.** A prod box may run some `@animalabs/*`
106
+ as *installed* copies (or dist-patched in place) rather than checkouts; a
107
+ dist patch reverts if the package is reinstalled. The clean state is the
108
+ symlinked-checkout layout (per DEV-ENVIRONMENT).
109
+ - **Backups** accumulate per install dir: `data.preremediation-*`,
110
+ `data.bak-*`, per-session `*.bak-*`, and recipe `*.bak-*`. Safe to prune once a
111
+ change is confirmed good.
112
+
113
+ ## Known pending fixes (forward work)
114
+
115
+ 1. **Image handling** — discord-mcpl base64-inlines attachments; an image whose
116
+ base64 > 5 MB 400s the *entire* request. Need: downsample-on-ingest to a
117
+ byte/pixel budget, fail-fast `[image too large]` note instead of a broken
118
+ block, and the budgeting strategy to account for image payload. (Stopgap
119
+ script: `scripts/strip-oversized-images.mjs` removes oversized blocks from a
120
+ stopped agent's chronicle.)
121
+ 2. **Inference-failure surfacing** — see the ⚠️ above.
122
+
123
+ ## Helper scripts (per install dir)
124
+
125
+ - `scripts/ingest-multiuser.mjs` — multi-user chat export → chronicle (roster-
126
+ attributed participants).
127
+ - `scripts/compress-fresh.mjs` — drain the compression queue offline before
128
+ launch (uses the recipe's `compressionModel`).
129
+ - `scripts/strip-oversized-images.mjs` — remove >5 MB image blocks from the
130
+ chronicle (agent must be stopped).
@@ -0,0 +1,215 @@
1
+ # Connectome Dev Environment — Setup Guide
2
+
3
+ **Status:** Working notes, uncommitted. Snapshot of the dev layout as of 2026-05-30.
4
+ **Goal:** Reproduce the current development state — every component as a local git
5
+ checkout, wired together so the host runs against editable source.
6
+
7
+ ---
8
+
9
+ ## TL;DR
10
+
11
+ The whole stack is a **pseudo-monorepo of sibling git checkouts** under one parent
12
+ directory (canonically `~/connectome-local/`). The `@animalabs/*` libraries are
13
+ **symlinked into each other's `node_modules`** so there is exactly ONE instance of
14
+ each at runtime (sharing a single `membrane` instance is mandatory — two copies
15
+ break `instanceof` / module singletons). Every package's `package.json` `main`
16
+ points at `dist/…`, so **each package must be `tsc`-built** before the host runs.
17
+
18
+ Each agent lives in a separate **install dir** (`<agent>-cm`, e.g. `example-cm`)
19
+ that holds a recipe + `.env` + chronicle data and references the code by absolute path.
20
+
21
+ ---
22
+
23
+ ## Projects & branches (current state)
24
+
25
+ All cloned as siblings under `~/connectome-local/`:
26
+
27
+ | Dir | GitHub repo | Branch | Ver | Role |
28
+ |---|---|---|---|---|
29
+ | `forking-knowledge-miner` | `anima-research/connectome-host` | `feat/tui-mem-stats-and-strategy-passthrough` | 0.3.0 | the host app (run via **bun**) |
30
+ | `agent-framework` | `anima-research/agent-framework` | `fix/lazy-register-locus-and-send-failure-marker` | 0.5.0 | host runtime: gate, MCPL orchestration, locus routing, `think` |
31
+ | `discord-mcpl` | `anima-research/discord-mcpl` | `main` | 0.1.0 | Discord surface (MCPL server) |
32
+ | `heartbeat-mcpl` | `anima-research/heartbeat-mcpl` | `main` | 0.1.0 | periodic self-wake (MCPL server) |
33
+ | `terminal-sessions-mcp` | `antra-tess/terminal-sessions-mcp` ⚠️ | `main` | 1.0.1 | shell: session daemon (ws://localhost:3100) + per-agent MCP stdio frontend |
34
+ | `membrane` | `antra-tess/membrane` ⚠️ | `fix/before-request-hook-on-stream-paths` | 0.5.47 | LLM client lib — **single shared instance required** |
35
+ | `context-manager` | `anima-research/context-manager` | `feat/adaptive-resolution` | 0.4.0 | context compilation / autobiographical memory |
36
+ | `chronicle` | `anima-research/chronicle` | `main` | 0.1.1 | record / chronicle store |
37
+ | `mcpl-core-ts` | `anima-research/mcpl-core-ts` | `main` | 0.1.0 | MCPL protocol types (discord-mcpl path dep) |
38
+
39
+ > ⚠️ **Org note:** `terminal-sessions-mcp` and `membrane` still live under the
40
+ > personal `antra-tess` org, not `anima-research`. Consider migrating them for
41
+ > consistency (as was done for discord-mcpl / heartbeat-mcpl).
42
+
43
+ > The host repo is `connectome-host` on GitHub but is historically checked out into
44
+ > a directory named `forking-knowledge-miner`. Keep that dir name — recipes and the
45
+ > `@animalabs/agent-framework` symlink target it.
46
+
47
+ ---
48
+
49
+ ## 1. Clone
50
+
51
+ ```bash
52
+ mkdir -p ~/connectome-local && cd ~/connectome-local
53
+
54
+ git clone -b feat/tui-mem-stats-and-strategy-passthrough \
55
+ git@github.com:anima-research/connectome-host.git forking-knowledge-miner
56
+ git clone -b fix/lazy-register-locus-and-send-failure-marker \
57
+ git@github.com:anima-research/agent-framework.git
58
+ git clone -b main git@github.com:anima-research/discord-mcpl.git
59
+ git clone -b main git@github.com:anima-research/heartbeat-mcpl.git
60
+ git clone -b main git@github.com:antra-tess/terminal-sessions-mcp.git
61
+ git clone -b fix/before-request-hook-on-stream-paths \
62
+ git@github.com:antra-tess/membrane.git
63
+ git clone -b feat/adaptive-resolution \
64
+ git@github.com:anima-research/context-manager.git
65
+ git clone -b main git@github.com:anima-research/chronicle.git
66
+ git clone -b main git@github.com:anima-research/mcpl-core-ts.git
67
+ ```
68
+
69
+ ---
70
+
71
+ ## 2. Install + build (bottom-up)
72
+
73
+ Each package's `main` is `dist/…`, so all must be built with `tsc`. Build leaf
74
+ libs first, then the framework, then the host:
75
+
76
+ ```
77
+ membrane → chronicle → context-manager → agent-framework → connectome-host
78
+ mcpl-core-ts → discord-mcpl
79
+ heartbeat-mcpl
80
+ terminal-sessions-mcp
81
+ ```
82
+
83
+ In each: `npm install && npm run build` (build script is `tsc`).
84
+
85
+ > `discord-mcpl` depends on `@connectome/mcpl-core` via `file:../mcpl-core-ts`
86
+ > (a path dep), so build `mcpl-core-ts` first.
87
+
88
+ ---
89
+
90
+ ## 3. Wire single-instance symlinks (the crucial part)
91
+
92
+ The host imports `@animalabs/{agent-framework,membrane,context-manager,chronicle}`,
93
+ and `agent-framework` *also* imports `@animalabs/{membrane,context-manager}`. These
94
+ must resolve to the SAME physical copy, or you get two `membrane` instances and
95
+ subtle breakage. Replace the npm-installed copies with symlinks to the siblings.
96
+
97
+ ```bash
98
+ # Host resolves the framework + shared libs from the sibling checkouts:
99
+ cd ~/connectome-local/forking-knowledge-miner/node_modules/@animalabs
100
+ for d in agent-framework membrane context-manager; do
101
+ rm -rf "$d"; ln -sfn "../../../$d" "$d"
102
+ done
103
+ # (chronicle can remain the npm-installed copy — versions match — or symlink it too.)
104
+
105
+ # agent-framework shares the SAME membrane / context-manager instance as the host:
106
+ cd ~/connectome-local/agent-framework/node_modules/@animalabs
107
+ for d in membrane context-manager chronicle; do
108
+ rm -rf "$d"
109
+ ln -sfn "../../../forking-knowledge-miner/node_modules/@animalabs/$d" "$d"
110
+ done
111
+ ```
112
+
113
+ Verify each symlink resolves and reports the expected version:
114
+ ```bash
115
+ for d in membrane context-manager chronicle; do
116
+ echo -n "$d -> "; readlink -f "$d"; grep '"version"' "$d/package.json" | head -1
117
+ done
118
+ ```
119
+
120
+ > This is the layout in use on a checkout-based box. On a fresh box, `npm
121
+ > install` inside `agent-framework` will pull its own `@animalabs/*` copies
122
+ > first — run it, THEN replace them with the symlinks above.
123
+
124
+ ---
125
+
126
+ ## 4. Shell daemon (terminal-sessions-mcp)
127
+
128
+ The shell tool is two parts: a **per-agent MCP stdio frontend** (spawned by the
129
+ recipe) that connects over WebSocket to a **shared session daemon**. Run the daemon
130
+ as a supervised, token-protected service on `localhost:3100`:
131
+
132
+ ```bash
133
+ node ~/connectome-local/terminal-sessions-mcp/dist/src/server/start-session-server.js \
134
+ --host localhost --headless --token <SESSION_SERVER_TOKEN>
135
+ ```
136
+ - Supervise it: launchd `com.terminal-sessions.server` (macOS) /
137
+ systemd-user `terminal-sessions.service` (Linux, needs `loginctl enable-linger`).
138
+ - The token is required — each agent's shell frontend must present it as
139
+ `SESSION_SERVER_TOKEN` or the daemon closes the socket ("Connection lost").
140
+ - Each host/box runs its OWN daemon + token (don't share tokens across machines).
141
+
142
+ ---
143
+
144
+ ## 5. Agent install dir (e.g. `<agent>-cm`)
145
+
146
+ Separate from the code — holds config + data, references code by absolute path.
147
+
148
+ ```
149
+ <agent>-cm/
150
+ recipes/<agent>.json # agent def, mcpServers (ABSOLUTE paths to each dist/),
151
+ # wake-gate policies, modules, strategy
152
+ .env # secrets + paths (see below)
153
+ data/ # chronicle store + per-session config/gate.json
154
+ launch.sh # optional convenience launcher
155
+ ```
156
+
157
+ **`.env` keys:**
158
+ ```
159
+ ANTHROPIC_API_KEY=...
160
+ DISCORD_TOKEN=...
161
+ DISCORD_GUILD_ID=... # comma-separated; empty = all guilds
162
+ DISCORD_MCPL_DEBUG_LOG=<abs>/data/discord-mcpl-debug.log
163
+ DISCORD_SUBSCRIPTIONS_FILE=<abs>/data/discord-subscriptions.json
164
+ SESSION_SERVER_TOKEN=<same token the shell daemon was started with>
165
+ HEARTBEAT_CONFIG_FILE=<abs>/data/heartbeat-config.json
166
+ ```
167
+
168
+ **Run the host (headless), supervised:**
169
+ ```bash
170
+ bun ~/connectome-local/forking-knowledge-miner/src/index.ts \
171
+ ~/connectome-local/<agent>-cm/recipes/<agent>.json --headless
172
+ ```
173
+ - macOS: launchd agent (e.g. `cc.<agent>.agent`) — stop with `launchctl bootout`,
174
+ restart with `launchctl kickstart -k gui/$(id -u)/<label>`.
175
+ - Linux: systemd-user (e.g. `<agent>-agent.service`) —
176
+ `systemctl --user restart <unit>`; needs lingering enabled.
177
+
178
+ ---
179
+
180
+ ## 6. Runtimes
181
+
182
+ - **bun** — runs the host (`bun src/index.ts …`).
183
+ - **node ≥ 20** — runs the MCPL servers (node 22 also fine). nvm fine locally.
184
+
185
+ ---
186
+
187
+ ## Gotchas (carry these over)
188
+
189
+ 1. **Recipe MCP paths are absolute.** Each `mcpServers.*.args[0]` points at a
190
+ `dist/src/index.js` by absolute path — they must match the new env's checkout
191
+ location, or templatize them.
192
+ 2. **`gate.json` is per-session and append-only.** The recipe's `modules.wake`
193
+ seeds a fresh session's `data/sessions/<id>/config/gate.json` verbatim, but on a
194
+ *resumed* session the gate only *appends* recipe policies it doesn't already
195
+ have (by name) — it does NOT reorder. Since the gate is first-match-wins, to
196
+ change wake ordering you must edit the **live** `gate.json`, not just the recipe.
197
+ 3. **Single `membrane` instance** (see §3) — the #1 source of weird runtime errors
198
+ if you skip the symlink wiring and end up with two copies.
199
+ 4. **Prod vs dev divergence.** Production boxes may run `@animalabs/*` as
200
+ npm-installed pinned copies rather than checkouts. "Mirroring current dev
201
+ state" means the symlinked-checkout layout in this doc.
202
+ 5. **Two repos still under `antra-tess`** (`membrane`, `terminal-sessions-mcp`) —
203
+ not yet migrated to `anima-research`.
204
+
205
+ ---
206
+
207
+ ## Architecture cross-references
208
+
209
+ - `docs/LOCUS-ROUTING-DESIGN.md` — host-owned output routing (why stickiness/
210
+ `think` live in the framework, not in a surface adapter).
211
+ - Output routing: `agent-framework/src/framework.ts` (turn completion →
212
+ `ChannelRegistry.routeSpeech`), `src/mcpl/channel-registry.ts`.
213
+ - Wake gate: `agent-framework/src/gate/event-gate.ts` (+ recipe `modules.wake`).
214
+ - Discord surface signals (`isExplicitMention` / `isReplyToBot` / `isBot`):
215
+ `discord-mcpl/src/server.ts`.
@@ -0,0 +1,286 @@
1
+ # The Library Pipeline
2
+
3
+ A guide to running connectome-host as a self-dispatching, three-agent knowledge pipeline: **Clerk**, **Miner**, and **Reviewer** coexisting as independent processes, coordinating through a shared filesystem.
4
+
5
+ This is a non-obvious way to use connectome-host. Most recipes are one-shot: you start a session, talk to the agent, it does the work. The library pipeline is different — three long-running sessions answer to filesystem events, each one waking when the previous one produces output. Nobody types prompts at the mining or review agents; the Clerk types prompts at them by dropping files.
6
+
7
+ ## What it is
8
+
9
+ Three agents, three recipes, three roles:
10
+
11
+ | Agent | Recipe | Role |
12
+ |-------|--------|------|
13
+ | **Clerk** | `clerk.json` | Sits on a Zulip channel. Answers questions from the library. Files a ticket when the library falls short. |
14
+ | **Miner** | `knowledge-miner.json` | Deep research across Zulip / Notion / GitLab. Produces draft reports with confidence markers. |
15
+ | **Reviewer** | `knowledge-reviewer.json` | Critic pass over miner output. Produces SME checklists and review notes. |
16
+
17
+ The three are coupled by **file events**, not by IPC, HTTP, or a message queue. Each agent's output directory is another agent's watched input. A write on one end wakes an inference on the other.
18
+
19
+ ```
20
+ ┌────────────────────────── Zulip #tracker-miner-f ──────────────────────────┐
21
+ │ │
22
+ ▼ │
23
+ ┌────────┐ ┌─────────┐ ┌──────────┐ │
24
+ │ CLERK │ ── ticket ──▶ │ MINER │ ── draft ──▶ │ REVIEWER │ │
25
+ │ │ knowledge- │ │ output/ │ │ │
26
+ │ │ requests/ │ │ │ │ │
27
+ │ │ │ │ │ │ │
28
+ │ │ ◀── resolution ── │ │ │ │ │
29
+ │ │ knowledge- │ │ │ │ │
30
+ │ │ requests/ │ │ │ │ │
31
+ │ │ │ │ │ │ │
32
+ │ │ ◀─────────────── review ──────────────────────── │ │ │
33
+ │ │ review-output/ │ │ │
34
+ └────────┘ └─────────┘ └──────────┘ │
35
+ │ │
36
+ └────────────────────────── answer posted ──────────────────────────────────┘
37
+ ```
38
+
39
+ Clerk reads both `output/` (mined drafts) and `review-output/` (reviewed material) as its "library"; writes tickets to `knowledge-requests/`. Miner reads `knowledge-requests/`, writes drafts to `output/`. Reviewer reads `output/`, writes to `review-output/`. Every pair is a one-way wake loop: the producer materializes a file, the consumer's chokidar watcher fires, the consumer's event gate matches a wake policy, the consumer infers.
40
+
41
+ ## Why run it this way
42
+
43
+ - **Separation of concerns.** Mining is deep and slow (large forks, heavy context). Reviewing is skeptical and linear (goes document by document). Fronting a chat channel must be fast and sourced. Collapsing these into one agent makes every one of them worse.
44
+ - **Loose coupling.** Filesystem-mediated handoffs mean each agent runs its own process, own session, own Chronicle store. You can restart or replace any one without touching the others.
45
+ - **Auditability.** Every handoff is a file on disk. You can read the ticket the Clerk filed, the report the Miner produced, the checklist the Reviewer wrote. Nothing is hidden in agent memory.
46
+
47
+ ## Prerequisites
48
+
49
+ Start by reading [`../recipes/SETUP.md`](../recipes/SETUP.md) — it covers the credentials and MCP servers used by the Miner. The library pipeline needs the same setup plus a few extras.
50
+
51
+ You need all of:
52
+
53
+ - Bun, Node 20+, an Anthropic API key (from SETUP.md).
54
+ - **Zulip**: a bot account with API credentials, subscribed to the channel you want the Clerk to staff (default: `tracker-miner-f`). `.zuliprc` in the project directory.
55
+ - **Zulip MCP server** built and reachable at the path referenced in each recipe (see SETUP.md Step 2).
56
+ - **Miner data sources** (optional but recommended): a Notion MCP server, GitLab, `gitlab-clone-mcp` for code search without Advanced Search.
57
+ - Three free terminal windows (or tmux panes, or `screen` windows) — one per agent.
58
+
59
+ ## Directory layout
60
+
61
+ All three agents must launch with the **same working directory**, because their mounts all resolve relative to `process.cwd()`. They don't need to share their *data* directories — in fact they must not, because each agent's Chronicle store, sessions, and lessons are per-instance state.
62
+
63
+ A working layout:
64
+
65
+ ```
66
+ connectome-host/
67
+ ├── .zuliprc # Zulip bot credentials
68
+ ├── recipes/
69
+ │ ├── clerk.json
70
+ │ ├── knowledge-miner.json
71
+ │ └── knowledge-reviewer.json
72
+ ├── knowledge-requests/ # shared mount: tickets
73
+ ├── output/ # shared mount: mined drafts
74
+ ├── review-output/ # shared mount: reviewed artifacts
75
+ ├── input/ # optional: seed material for miner
76
+ ├── data-frontdesk/ # Clerk's Chronicle, sessions, lessons
77
+ ├── data-miner/ # Miner's Chronicle, sessions, lessons
78
+ └── data-reviewer/ # Reviewer's Chronicle, sessions, lessons
79
+ ```
80
+
81
+ The three `data-*` directories are created on first run; you don't need to precreate them. The three shared-mount directories can be empty — the agents will populate them.
82
+
83
+ ## The wake loop, concretely
84
+
85
+ Two mechanisms work together. If either is missing, the pipeline silently does nothing.
86
+
87
+ ### 1. `autoMaterialize` on the producer side
88
+
89
+ By default, workspace writes in connectome-host stay in Chronicle and never hit disk until the agent calls `workspace--materialize`. That works for single-agent sessions, but it breaks cross-agent pipelines: a chokidar watcher on the consumer can only fire when a real file is written.
90
+
91
+ The producing mount must have `autoMaterialize: true`. Each `workspace--write` / `workspace--edit` / `workspace--delete` immediately reconciles to disk; the local watcher suppresses the self-echo so the producer doesn't wake on its own writes.
92
+
93
+ Verify in the recipe JSON:
94
+
95
+ - Clerk's `knowledge-requests` mount → `autoMaterialize: true` ✓
96
+ - Miner's `products` and `tickets` mounts → `autoMaterialize: true` ✓
97
+ - Reviewer's `products` mount → `autoMaterialize: true` ✓
98
+
99
+ ### 2. `watch: 'always'` + `wakeOnChange` on the consumer side
100
+
101
+ The consumer mount must be watched and must declare which op types trigger a wake:
102
+
103
+ ```json
104
+ {
105
+ "name": "knowledge-requests",
106
+ "path": "./knowledge-requests",
107
+ "mode": "read-only",
108
+ "watch": "always",
109
+ "wakeOnChange": ["created"]
110
+ }
111
+ ```
112
+
113
+ `wakeOnChange` takes an array of `"created" | "modified" | "deleted"`, or `true` for all three. The WorkspaceModule emits `workspace:created` / `workspace:modified` / `workspace:deleted` events carrying mount-prefixed paths.
114
+
115
+ ### 3. A matching gate policy
116
+
117
+ An event arriving at the agent still has to pass the EventGate to cause an inference. Each recipe's `modules.wake.policies` contains the match rules. The important ones for the library pipeline:
118
+
119
+ ```json
120
+ {
121
+ "name": "ticket-resolutions",
122
+ "match": {
123
+ "scope": ["workspace:modified"],
124
+ "mount": "knowledge-requests",
125
+ "pathGlob": "knowledge-requests/*.md"
126
+ },
127
+ "behavior": "always"
128
+ }
129
+ ```
130
+
131
+ The `mount` field matches the mount name; `pathGlob` matches any of the event's paths. Both are optional but recommended — without them the policy fires on *every* file event for its scope.
132
+
133
+ The gate file (`_config/gate.json` inside the data dir) is seeded from the recipe on first start and then reconciled additively on every subsequent start: new policies from the recipe are appended by name, but policies the user added or edited via `workspace--edit _config/gate.json` are preserved. Hot-reloads land in ~1 second.
134
+
135
+ ### 4. Who wakes whom
136
+
137
+ | Trigger | Fires in | Policy name |
138
+ |----------------------------------------------------|--------------------|-------------------------|
139
+ | Someone posts in Zulip `#tracker-miner-f` | Clerk | `tracker-channel` |
140
+ | Clerk creates `knowledge-requests/*.md` | Miner | `new-tickets` |
141
+ | Miner modifies `knowledge-requests/*.md` (resolves)| Clerk | `ticket-resolutions` |
142
+ | Miner creates `output/*.md` (draft report) | Reviewer | `new-reports` |
143
+ | Reviewer creates `review-output/*.md` | Clerk | `reviewed-responses` |
144
+
145
+ Each agent's own wake policies are in its recipe — compare if you need to debug silent failures.
146
+
147
+ ## Running the three
148
+
149
+ Launch each agent in its own terminal, same working directory, distinct `DATA_DIR`:
150
+
151
+ ```bash
152
+ # Terminal 1 — Clerk (the one humans interact with through Zulip)
153
+ cd connectome-host
154
+ DATA_DIR=./data-frontdesk bun src/index.ts recipes/clerk.json
155
+
156
+ # Terminal 2 — Miner
157
+ cd connectome-host
158
+ DATA_DIR=./data-miner bun src/index.ts recipes/knowledge-miner.json
159
+
160
+ # Terminal 3 — Reviewer
161
+ cd connectome-host
162
+ DATA_DIR=./data-reviewer bun src/index.ts recipes/knowledge-reviewer.json
163
+ ```
164
+
165
+ Order doesn't matter. The gate initial-scan will catch any files that were written while an agent was offline: on startup each `watch: 'always'` mount does a one-shot `syncFromFs` diff against its Chronicle tree, firing `workspace:created` for files that are on disk but new to this session. So if the Miner was offline when the Clerk filed three tickets, the Miner will wake on those three tickets the moment it starts.
166
+
167
+ If the Clerk is the only one you expect to interact with, keep the Miner and Reviewer in headless mode — their TUI is still useful for watching progress, but they don't need stdin. You can also pass `--no-tui` if you want to tail logs without OpenTUI taking over the terminal.
168
+
169
+ ## The ticket contract
170
+
171
+ Agents coordinate through a schema, not a protocol. The ticket format is defined in `clerk.json`'s system prompt; the Miner and Reviewer prompts read from it but do not re-define it. Keep them in sync.
172
+
173
+ Filename: `YYYY-MM-DD-short-slug.md`, one ticket per file.
174
+
175
+ Frontmatter:
176
+
177
+ ```yaml
178
+ ---
179
+ filed: 2026-04-20T17:01:45Z
180
+ asker: Anton Kukushkin
181
+ asker_id: 12345
182
+ channel: tracker-miner-f
183
+ topic: general chat
184
+ message_link: <zulip message link or numeric ID>
185
+ status: open # open | in-progress | resolved
186
+ urgency: normal # low | normal | high
187
+ ---
188
+ ```
189
+
190
+ Body sections (required, in order): `## Question`, `## Search Trail`, `## Specific Unknowns`, `## Notes`.
191
+
192
+ **Ownership:** the Clerk writes tickets at `status: open` and never modifies them again. The Miner flips status to `in-progress` while working, then to `resolved` with a `resolution:` block appended. Only the Miner modifies tickets; that's what makes `workspace:modified` a reliable "resolution-ready" signal for the Clerk.
193
+
194
+ **Resolutions reach the asker via Zulip.** The Clerk, on waking from a ticket modification, reads the resolved ticket, reads any newly-created `review-output/` file that covers the topic, and posts back to the channel — citing `library-mined:` / `library-reviewed:` paths inline. The asker gets notified by Zulip's normal mention/reply mechanics.
195
+
196
+ ## Confidence markers — end-to-end
197
+
198
+ Every non-trivial claim in mined or reviewed material carries a marker:
199
+
200
+ | Marker | Meaning |
201
+ |--------|---------|
202
+ | `[SRC: source]` | Directly sourced. Quote verbatim when citing. |
203
+ | `[INF]` | Inferred across sources. |
204
+ | `[GEN]` | General domain knowledge — no specific source. |
205
+ | `❓` | Knowledge gap — admission of "we don't know." |
206
+
207
+ Markers are written by the Miner, audited by the Reviewer (who looks especially for unmarked claims that *should* have been `[GEN]`), and preserved by the Clerk when citing in chat. **Never launder markers**: a `[GEN]` claim quoted without its tag becomes a confident assertion the Clerk didn't intend to make.
208
+
209
+ ## Operational notes
210
+
211
+ ### Adding a channel the Clerk listens to
212
+
213
+ Two layers, both required:
214
+
215
+ 1. `zulip--listen { channels: ["new-stream"] }` — subscribes the bot to the Zulip stream (server-side state, persists across restarts).
216
+ 2. Append a gate policy to `_config/gate.json` via `workspace--edit`:
217
+
218
+ ```json
219
+ {
220
+ "name": "new-stream",
221
+ "match": { "scope": ["mcpl:channel-incoming"], "channel": "zulip:new-stream" },
222
+ "behavior": "always"
223
+ }
224
+ ```
225
+
226
+ Subscription without a policy means events arrive but don't wake. Policy without subscription means nothing arrives at all. The EventGate hot-reloads the file in ~1s — no restart.
227
+
228
+ ### Removing a channel
229
+
230
+ Reverse order: remove the gate policy first (so the context doesn't fill with messages you'll never react to), then `zulip--unlisten`.
231
+
232
+ **Do not** unsubscribe the Clerk from `tracker-miner-f` or remove the `tracker-channel` policy without explicit confirmation — it silences the only channel the Clerk is supposed to staff.
233
+
234
+ ### Channel subscription blast radius
235
+
236
+ The Zulip MCPL server registers every visible public stream it can see. In a large org that can be 100+ streams, and `ChannelRegistry` will open all of them by default. Quiet channels still accumulate messages in context. Symptoms: the Miner's next wake includes a 100K+ token burst of unrelated chat.
237
+
238
+ Fix: set `channelSubscription` on the zulip server in recipes that aren't meant to listen passively:
239
+
240
+ ```json
241
+ "zulip": {
242
+ "command": "node",
243
+ "args": ["../zulip-mcp/build/index.js"],
244
+ "env": { "...": "..." },
245
+ "channelSubscription": "manual"
246
+ }
247
+ ```
248
+
249
+ Values: `"auto"` (default, everything opens), `"manual"` (nothing opens; agent opens channels explicitly), or `string[]` (allow-list). Clerk is intentionally `"auto"` (passive listening). Miner and Reviewer, if they connect to Zulip at all, should be `"manual"` or allow-listed.
250
+
251
+ ### Lessons don't cross agents
252
+
253
+ Each data dir has its own `lessons.json`. The Miner's extracted lessons are not visible to the Clerk. This is intentional — the library (files on disk) is the shared knowledge, not the lesson store. Use lessons for meta-observations about each role ("tickets about X usually need Y"), not facts that belong in the library.
254
+
255
+ ## Troubleshooting
256
+
257
+ | Symptom | Likely cause | Check |
258
+ |---|---|---|
259
+ | Clerk files tickets but Miner never wakes | Producer missing `autoMaterialize`, or consumer missing `watch: 'always'` + `wakeOnChange` | `ls knowledge-requests/` — files on disk? If yes, check Miner's recipe for those two flags. |
260
+ | Miner wakes but never runs | Event reaching the gate but no policy matching it | In the Miner, run `gate:status`. If `defaultDecisions.byEventType["workspace:created"].skipped > 0` and no policy's `matchCount` went up, the policy's `mount` or `pathGlob` doesn't match. |
261
+ | Fresh session sees empty directories | Gate initial-scan didn't run, or the mount isn't `watch: 'always'` | Check `workspace--status` — `initialSyncDone: false` means watchers haven't started. |
262
+ | Clerk was silent through a known question | Zulip subscription or `tracker-channel` policy was removed | `zulip--listen` with no args shows subscribed streams; `workspace--read _config/gate.json` shows active policies. |
263
+ | Miner's context is flooded with Zulip chat it doesn't care about | `channelSubscription` defaulted to `"auto"` on a large Zulip | Set `"manual"` or an allow-list on the zulip server in `knowledge-miner.json`. |
264
+ | Three-agent pipeline works on one machine, breaks on another | Agents launched from different working directories | All three must share `cwd`. `./knowledge-requests` resolves to three different paths otherwise. |
265
+ | Tickets pile up, Miner is "busy" but never writes reports | Miner is context-saturated, or wedged on a long fork | `/status` in the Miner's TUI; `Tab` for fleet view to see if forks are actually progressing. |
266
+ | Clerk posts answers but cites `[GEN]` claims as facts | Prompt drift; retrain or re-read the clerk prompt | The clerk prompt explicitly forbids this — if it happens, regenerate the session with `/session new` and re-verify. |
267
+
268
+ ### Diagnosing a silent wake failure
269
+
270
+ Three specific things to check, in order:
271
+
272
+ 1. **Is the file on disk?** `ls` the producer's output directory. If the producer's mount lacks `autoMaterialize`, the file exists only in Chronicle and no event will ever fire.
273
+ 2. **Is the watcher running?** In the consumer, `workspace--status` should show `initialSyncDone: true` for the watched mount. If it's `false`, watcher setup didn't complete.
274
+ 3. **Is the gate dropping the event?** In the consumer, `gate:status` returns per-policy `matchCount` and an aggregate `defaultDecisions.byEventType`. If `workspace:created`'s `skipped` count is non-zero but the target policy's `matchCount` didn't change, the policy is there but the match fields don't cover the actual event.
275
+
276
+ A non-zero `skipped` without a matching policy is the fingerprint of a mount-name or pathGlob mismatch — the event arrived, the gate looked at it, no policy claimed it.
277
+
278
+ ## Extending the pipeline
279
+
280
+ The schema-through-files pattern generalizes. A few natural extensions:
281
+
282
+ - **Miner-manager** — polls `knowledge-requests/` for `status: open`, spawns a bounded number of miner sessions, marks tickets `in-progress`, and writes resolutions. Currently the Miner fills this role itself; splitting it out makes dispatch policy (priority, concurrency, deduplication) explicit.
283
+ - **Specialist miners** — one miner per source (Zulip-only, GitLab-only, Notion-only) with distinct recipes. The manager routes tickets by `topic` or by heuristics in the request body. Each specialist's lesson store accumulates source-specific expertise.
284
+ - **Synthesis reviewer** — a second reviewer that specifically checks cross-document consistency (the current reviewer is intra-document). Would watch `review-output/` and write to `review-output/meta/`.
285
+
286
+ The constraint for any new member: one mount points at the producer's output with `watch: 'always'` + `wakeOnChange`, and one gate policy matches that scope. That's the whole protocol.