brane 0.0.2 → 0.3.0

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 (338) hide show
  1. package/AGENTS.md +362 -0
  2. package/CHANGELOG.md +33 -0
  3. package/CONTRIBUTING.md +91 -0
  4. package/DESIGN.md +402 -0
  5. package/Dockerfile +61 -0
  6. package/LICENSE +656 -17
  7. package/README.md +174 -2
  8. package/SECURITY.md +205 -0
  9. package/dist/absorb/brane-id.js +11 -0
  10. package/dist/absorb/chatgpt.js +161 -0
  11. package/dist/absorb/claude-code.js +504 -0
  12. package/dist/absorb/index.js +543 -0
  13. package/dist/absorb/organelle.js +345 -0
  14. package/dist/absorb/run.js +520 -0
  15. package/dist/absorb/sources.js +168 -0
  16. package/dist/absorb/types.js +3 -0
  17. package/dist/agent-env.js +45 -0
  18. package/dist/agents.js +1168 -0
  19. package/dist/claude-live-session.js +124 -0
  20. package/dist/cli.js +50 -0
  21. package/dist/codex-tool-log.js +144 -0
  22. package/dist/commands/absorb.js +291 -0
  23. package/dist/commands/attach.js +31 -0
  24. package/dist/commands/certify.js +62 -0
  25. package/dist/commands/config.js +116 -0
  26. package/dist/commands/cost.js +126 -0
  27. package/dist/commands/daemon.js +350 -0
  28. package/dist/commands/doctor.js +262 -0
  29. package/dist/commands/fold.js +95 -0
  30. package/dist/commands/list.js +89 -0
  31. package/dist/commands/mcp.js +84 -0
  32. package/dist/commands/meld.js +332 -0
  33. package/dist/commands/memory.js +215 -0
  34. package/dist/commands/revive.js +74 -0
  35. package/dist/commands/run.js +85 -0
  36. package/dist/commands/spawn.js +1094 -0
  37. package/dist/commands/token.js +153 -0
  38. package/dist/copilot-tool-log.js +145 -0
  39. package/dist/cost/pricing.js +116 -0
  40. package/dist/cost/reprice.js +136 -0
  41. package/dist/crush-tool-log.js +293 -0
  42. package/dist/daemon/agent-scope.js +116 -0
  43. package/dist/daemon/awaiting.js +54 -0
  44. package/dist/daemon/cert-manager.js +349 -0
  45. package/dist/daemon/client.js +262 -0
  46. package/dist/daemon/gated-routes.js +112 -0
  47. package/dist/daemon/keep-awake.js +103 -0
  48. package/dist/daemon/local-socket.js +145 -0
  49. package/dist/daemon/presence-gate.js +206 -0
  50. package/dist/daemon/server.js +6349 -0
  51. package/dist/daemon/signal-source.js +35 -0
  52. package/dist/daemon/tailscale-rebind.js +58 -0
  53. package/dist/daemon/token-ring.js +263 -0
  54. package/dist/daemon/token-routes.js +190 -0
  55. package/dist/db/events.js +152 -0
  56. package/dist/db/mentions.js +179 -0
  57. package/dist/db/schema.js +633 -0
  58. package/dist/db/tokens.js +583 -0
  59. package/dist/db/tool-uses.js +647 -0
  60. package/dist/diagram/index.js +85 -0
  61. package/dist/diagram/label.js +455 -0
  62. package/dist/diagram/layout.js +476 -0
  63. package/dist/diagram/links.js +50 -0
  64. package/dist/diagram/metrics.js +152 -0
  65. package/dist/diagram/parse.js +487 -0
  66. package/dist/diagram/svg.js +389 -0
  67. package/dist/diagram/types.js +9 -0
  68. package/dist/export.js +629 -0
  69. package/dist/files/archive-types.js +2 -0
  70. package/dist/files/archive.js +182 -0
  71. package/dist/files/browse.js +327 -0
  72. package/dist/files/bytes.js +162 -0
  73. package/dist/files/codecs.js +33 -0
  74. package/dist/files/find.js +212 -0
  75. package/dist/files/tar.js +223 -0
  76. package/dist/files/zip.js +209 -0
  77. package/dist/folds.js +72 -0
  78. package/dist/grammars/ebnf.js +92 -0
  79. package/dist/highlight.js +250 -0
  80. package/dist/ids.js +24 -0
  81. package/dist/markdown-diagnostics.js +233 -0
  82. package/dist/math.js +168 -0
  83. package/dist/mcp/server.js +886 -0
  84. package/dist/memory/embedder.js +145 -0
  85. package/dist/memory/embedding-worker.js +71 -0
  86. package/dist/memory/embeddings.js +91 -0
  87. package/dist/memory/index.js +232 -0
  88. package/dist/memory/storage.js +587 -0
  89. package/dist/memory/wiki.js +688 -0
  90. package/dist/memory/wordpiece.js +247 -0
  91. package/dist/mentions-scan.js +40 -0
  92. package/dist/presence.js +259 -0
  93. package/dist/session-affinity.js +185 -0
  94. package/dist/shared/api-request.js +124 -0
  95. package/dist/shared/block-glyphs.js +114 -0
  96. package/dist/shared/diagram-css.js +17 -0
  97. package/dist/shared/export-selection.js +101 -0
  98. package/dist/shared/find-in-text.js +110 -0
  99. package/dist/shared/glow-hues.js +225 -0
  100. package/dist/shared/hex-dump.js +62 -0
  101. package/dist/shared/hex-pages.js +52 -0
  102. package/dist/shared/hit-drum.js +79 -0
  103. package/dist/shared/known-nids.js +58 -0
  104. package/dist/shared/list-scroll-keys.js +56 -0
  105. package/dist/shared/markdown-lists.js +72 -0
  106. package/dist/shared/markdown-math.js +485 -0
  107. package/dist/shared/markdown-mentions.js +79 -0
  108. package/dist/shared/markdown-nids.js +41 -0
  109. package/dist/shared/markdown-protocols.js +53 -0
  110. package/dist/shared/markdown-refs.js +123 -0
  111. package/dist/shared/membrane-invasion.js +342 -0
  112. package/dist/shared/membrane-motion.js +83 -0
  113. package/dist/shared/membrane-palette.js +253 -0
  114. package/dist/shared/membrane-terrain.js +212 -0
  115. package/dist/shared/message-selection.js +123 -0
  116. package/dist/shared/mo-glyph.js +57 -0
  117. package/dist/shared/narrative-format.js +237 -0
  118. package/dist/shared/narrative-menu.js +82 -0
  119. package/dist/shared/narrative-sort.js +47 -0
  120. package/dist/shared/narrative-status.js +52 -0
  121. package/dist/shared/no-pan.js +85 -0
  122. package/dist/shared/pane-layout.js +50 -0
  123. package/dist/shared/payload-inline.js +83 -0
  124. package/dist/shared/speaker-color.js +61 -0
  125. package/dist/shared/token-rotation.js +14 -0
  126. package/dist/shared/view-url.js +197 -0
  127. package/dist/shared/wiki-links.js +70 -0
  128. package/dist/shared/wiki-window.js +4 -0
  129. package/dist/tailscale.js +55 -0
  130. package/dist/test/absorb-claude.test.js +892 -0
  131. package/dist/test/agent-scope.test.js +193 -0
  132. package/dist/test/agents.test.js +55 -0
  133. package/dist/test/api-request.test.js +122 -0
  134. package/dist/test/awaiting-glow.test.js +54 -0
  135. package/dist/test/awaiting.test.js +122 -0
  136. package/dist/test/bind-token.test.js +88 -0
  137. package/dist/test/block-glyphs.test.js +68 -0
  138. package/dist/test/cert-manager.test.js +262 -0
  139. package/dist/test/claude-live-session.test.js +110 -0
  140. package/dist/test/codex-tool-log.test.js +568 -0
  141. package/dist/test/config.test.js +71 -0
  142. package/dist/test/copilot-tool-log.test.js +93 -0
  143. package/dist/test/cors-preflight.test.js +49 -0
  144. package/dist/test/cost-linger.test.js +31 -0
  145. package/dist/test/cost-pricing.test.js +72 -0
  146. package/dist/test/cost-reprice.test.js +139 -0
  147. package/dist/test/cost-sessions.test.js +301 -0
  148. package/dist/test/crush-tool-log.test.js +289 -0
  149. package/dist/test/daemon-client.test.js +109 -0
  150. package/dist/test/daemon-socket.test.js +293 -0
  151. package/dist/test/diagram-arrows.test.js +205 -0
  152. package/dist/test/diagram-export.test.js +210 -0
  153. package/dist/test/diagram-labels.test.js +313 -0
  154. package/dist/test/diagram-parse.test.js +191 -0
  155. package/dist/test/diagram-render.test.js +175 -0
  156. package/dist/test/diagram-review.test.js +288 -0
  157. package/dist/test/diagram-self-loop-labels.test.js +303 -0
  158. package/dist/test/embedder.test.js +84 -0
  159. package/dist/test/events.test.js +123 -0
  160. package/dist/test/export-selection.test.js +102 -0
  161. package/dist/test/export.test.js +505 -0
  162. package/dist/test/files-browse.test.js +393 -0
  163. package/dist/test/files-find.test.js +272 -0
  164. package/dist/test/find-in-text.test.js +86 -0
  165. package/dist/test/first-paint-viewport.test.js +59 -0
  166. package/dist/test/fixtures/embedding-golden.js +23 -0
  167. package/dist/test/fixtures/wordpiece-golden.js +167 -0
  168. package/dist/test/glow-hues.test.js +202 -0
  169. package/dist/test/harness-cost.test.js +423 -0
  170. package/dist/test/helpers.js +21 -0
  171. package/dist/test/hex-pages.test.js +68 -0
  172. package/dist/test/highlight.test.js +69 -0
  173. package/dist/test/hit-drum.test.js +71 -0
  174. package/dist/test/hook-config.test.js +262 -0
  175. package/dist/test/json-gzip.test.js +70 -0
  176. package/dist/test/key-routing.test.js +58 -0
  177. package/dist/test/known-nids.test.js +106 -0
  178. package/dist/test/link-preview.test.js +67 -0
  179. package/dist/test/list-scroll-keys.test.js +66 -0
  180. package/dist/test/listen-own-words.test.js +147 -0
  181. package/dist/test/listen-timeout.test.js +33 -0
  182. package/dist/test/markdown-diagnostics.test.js +121 -0
  183. package/dist/test/markdown-lists.test.js +182 -0
  184. package/dist/test/markdown-math.test.js +256 -0
  185. package/dist/test/markdown-nids.test.js +146 -0
  186. package/dist/test/markdown-protocols.test.js +139 -0
  187. package/dist/test/mcp-bridge-cancel.test.js +98 -0
  188. package/dist/test/mcp-bridge-reconnect.test.js +121 -0
  189. package/dist/test/mcp-narrative.test.js +193 -0
  190. package/dist/test/mcp.test.js +548 -0
  191. package/dist/test/meld.test.js +85 -0
  192. package/dist/test/membrane-invasion.test.js +482 -0
  193. package/dist/test/membrane-motion.test.js +101 -0
  194. package/dist/test/membrane-palette.test.js +166 -0
  195. package/dist/test/membrane-terrain.test.js +170 -0
  196. package/dist/test/memory-log.test.js +202 -0
  197. package/dist/test/mentions-e2e.test.js +261 -0
  198. package/dist/test/mentions-ledger.test.js +265 -0
  199. package/dist/test/mentions-listen.test.js +265 -0
  200. package/dist/test/mentions.test.js +238 -0
  201. package/dist/test/message-selection.test.js +114 -0
  202. package/dist/test/mo-glyph.test.js +22 -0
  203. package/dist/test/model-resolution.test.js +166 -0
  204. package/dist/test/narrate-validate.test.js +77 -0
  205. package/dist/test/narrate-wait.test.js +160 -0
  206. package/dist/test/narrative-format.test.js +219 -0
  207. package/dist/test/narrative-menu.test.js +137 -0
  208. package/dist/test/narrative-sort.test.js +75 -0
  209. package/dist/test/no-pan.test.js +60 -0
  210. package/dist/test/pane-layout.test.js +153 -0
  211. package/dist/test/paths.test.js +37 -0
  212. package/dist/test/payload-inline.test.js +91 -0
  213. package/dist/test/precache-fonts.test.js +72 -0
  214. package/dist/test/presence-gate.test.js +440 -0
  215. package/dist/test/presence.test.js +160 -0
  216. package/dist/test/read-events.test.js +220 -0
  217. package/dist/test/remote-meld.test.js +115 -0
  218. package/dist/test/revive-cli.test.js +93 -0
  219. package/dist/test/search-floor.test.js +206 -0
  220. package/dist/test/search-fusion.test.js +168 -0
  221. package/dist/test/search.test.js +205 -0
  222. package/dist/test/security.test.js +48 -0
  223. package/dist/test/selection-wiring.test.js +61 -0
  224. package/dist/test/session-affinity.test.js +295 -0
  225. package/dist/test/signal-source.test.js +32 -0
  226. package/dist/test/socket-account.test.js +268 -0
  227. package/dist/test/spawn-toml.test.js +72 -0
  228. package/dist/test/speaker-color.test.js +90 -0
  229. package/dist/test/tailscale-rebind.test.js +79 -0
  230. package/dist/test/terminal-cursor.test.js +61 -0
  231. package/dist/test/terminal-resync.test.js +23 -0
  232. package/dist/test/thin-cli.test.js +382 -0
  233. package/dist/test/token-bootstrap.test.js +272 -0
  234. package/dist/test/token-devices.test.js +587 -0
  235. package/dist/test/token-lapse.test.js +94 -0
  236. package/dist/test/token-ring.test.js +188 -0
  237. package/dist/test/token-rotate-cli.test.js +58 -0
  238. package/dist/test/token-routes.test.js +95 -0
  239. package/dist/test/tool-uses.test.js +1006 -0
  240. package/dist/test/usage-ledger.test.js +126 -0
  241. package/dist/test/view-url.test.js +581 -0
  242. package/dist/test/wiki-links.test.js +57 -0
  243. package/dist/test/wiki-sources.test.js +85 -0
  244. package/dist/test/wiki-window.test.js +31 -0
  245. package/dist/test/wiki.test.js +399 -0
  246. package/dist/test/wordpiece.test.js +68 -0
  247. package/dist/tls.js +148 -0
  248. package/dist/tmux.js +202 -0
  249. package/dist/user-agent.js +46 -0
  250. package/docker-entrypoint.sh +141 -0
  251. package/docs/README.md +3 -0
  252. package/docs/absorb.md +231 -0
  253. package/docs/cost-ledger.md +125 -0
  254. package/docs/daemon-api.md +189 -0
  255. package/docs/diagrams.md +163 -0
  256. package/docs/doctor.md +36 -0
  257. package/docs/export-scope.md +69 -0
  258. package/docs/files-view.md +133 -0
  259. package/docs/link-previews.md +38 -0
  260. package/docs/math.md +32 -0
  261. package/docs/mcp.md +69 -0
  262. package/docs/memory-search.md +75 -0
  263. package/docs/mentions.md +101 -0
  264. package/docs/multi-brane.md +14 -0
  265. package/docs/passkeys.md +165 -0
  266. package/docs/presence.md +202 -0
  267. package/docs/releasing.md +53 -0
  268. package/docs/rendering-warnings.md +22 -0
  269. package/docs/request-layer.md +72 -0
  270. package/docs/selection-store.md +50 -0
  271. package/docs/session-affinity.md +61 -0
  272. package/docs/terminal-mirroring.md +211 -0
  273. package/docs/token-rotation.md +413 -0
  274. package/docs/view-urls.md +114 -0
  275. package/docs/web-app.md +23 -0
  276. package/docs/wiki.md +43 -0
  277. package/expo/dist/_expo/static/js/web/entry-e56efc1291dca7258ea00b8108b033a4.js +1097 -0
  278. package/expo/dist/apple-touch-icon.png +0 -0
  279. package/expo/dist/favicon.svg +3 -0
  280. package/expo/dist/index.html +177 -0
  281. package/expo/dist/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  282. package/expo/dist/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  283. package/expo/dist/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  284. package/expo/dist/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  285. package/expo/dist/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  286. package/expo/dist/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  287. package/expo/dist/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  288. package/expo/dist/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  289. package/expo/dist/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  290. package/expo/dist/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  291. package/expo/dist/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  292. package/expo/dist/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  293. package/expo/dist/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  294. package/expo/dist/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  295. package/expo/dist/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  296. package/expo/dist/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  297. package/expo/dist/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  298. package/expo/dist/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  299. package/expo/dist/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  300. package/expo/dist/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  301. package/expo/dist/katex/katex.min.css +1 -0
  302. package/expo/dist/manifest.json +16 -0
  303. package/expo/dist/metadata.json +1 -0
  304. package/expo/dist/og/membrane.v1.png +0 -0
  305. package/expo/dist/pdf.min.mjs +28 -0
  306. package/expo/dist/pdf.worker.min.mjs +28 -0
  307. package/expo/dist/sw.js +154 -0
  308. package/package.json +81 -23
  309. package/protocols/LIVE_loop.md +407 -0
  310. package/protocols/bebrief.md +41 -0
  311. package/protocols/digest.md +231 -0
  312. package/protocols/fly.md +266 -0
  313. package/protocols/idle.md +81 -0
  314. package/protocols/keeper.md +829 -0
  315. package/protocols/markdown.md +80 -0
  316. package/protocols/memory.md +419 -0
  317. package/protocols/mentions.md +93 -0
  318. package/protocols/payloads.md +53 -0
  319. package/protocols/prelude.md +91 -0
  320. package/protocols/ramify.md +105 -0
  321. package/protocols/review.md +356 -0
  322. package/protocols/rrr.md +232 -0
  323. package/protocols/scribe.md +429 -0
  324. package/protocols/summarize.md +203 -0
  325. package/protocols/twin.md +174 -0
  326. package/protocols/wiki.md +438 -0
  327. package/protocols/worker.md +38 -0
  328. package/wiki/shared/append-only-event-log.md +148 -0
  329. package/wiki/shared/index.md +48 -0
  330. package/wiki/shared/owning-your-narratives.md +161 -0
  331. package/wiki/shared/premature-ontologies.md +435 -0
  332. package/wiki/shared/sedimentary-superintelligence.md +128 -0
  333. package/wiki/shared/sequestering-the-narrative.md +143 -0
  334. package/wiki/shared/why-wikis-work.md +216 -0
  335. package/.npmignore +0 -2
  336. package/.travis.yml +0 -5
  337. package/index.js +0 -8
  338. package/index.js~ +0 -12
@@ -0,0 +1,829 @@
1
+ # Protocol: keeper
2
+
3
+ The Keeper is a meta-agent role that oversees the operation of other
4
+ active agents while the human operator is unavailable. It is a
5
+ delegate, never a replacement: when the operator is present, the
6
+ Keeper stands down and lets normal operator-driven decisions flow.
7
+
8
+ The Keeper's narrative is the daemon's audit log. Reading it
9
+ linearly tells the operator what happened across the daemon while
10
+ they were away, and why the Keeper made every consequential
11
+ decision.
12
+
13
+ **Prerequisite:** this protocol uses the LIVE loop's tools and
14
+ vocabulary (`listen`, `narrate`, `whoami`, etc.). If you have not
15
+ already read `protocols/LIVE_loop.md`, read it first; then return
16
+ here.
17
+
18
+ ## Scope
19
+
20
+ The Keeper observes **all active narratives within its daemon**,
21
+ across every brane and fold the daemon hosts. The Keeper sits above
22
+ the brane abstraction: it does not belong to any one brane, even if
23
+ its own narrative is recorded in one. Operationally, the Keeper
24
+ uses `list_peers` (which returns active narratives daemon-wide,
25
+ excluding the caller) to discover what to track; multi-listen on
26
+ the resulting cursor map then carries the awareness loop forward.
27
+
28
+ ## Role
29
+
30
+ - **Spawned with `role: "keeper"`.** Started either manually
31
+ (`brane spawn claude --role keeper`) or auto-spawned by the daemon
32
+ at startup if configured.
33
+ - **Read-mostly.** The Keeper observes by default. Intervention is
34
+ the exception, not the architecture.
35
+ - **Scoped, not singleton.** There is no one-keeper-per-daemon rule.
36
+ Many keepers can coexist as long as their *scopes* don't overlap —
37
+ e.g. several scoped keepers, each a [twin](twin.md) watching only
38
+ its own predecessor, plus at most one daemon-wide keeper covering
39
+ the rest. What must be avoided is two keepers both authoritative
40
+ over the **same** narrative, where they'd race on the same prompt
41
+ and double up the audit trail. The overlap check in "Startup
42
+ procedure" below is the canonical handling: notify the operator
43
+ and coordinate — never silently self-terminate.
44
+
45
+ ### Reading vs. acting
46
+
47
+ This is a role-bound protocol; the prelude's "Role-bound protocols"
48
+ section gives the general rules (superposition, operator-conferred
49
+ roles, informational-for-non-holders). It is **binding and
50
+ empowering on agents who hold `role: "keeper"`** — whether `whoami`
51
+ returns it as the spawn role or the operator has layered it on
52
+ (e.g. a `twin` directed to also keep watch over its predecessor).
53
+ A keeper-holder is bound by the constraints (irreversibility
54
+ ceiling, hands-off rule, narration before action) *and* empowered
55
+ by the authority (multi-listen across the daemon, observe tmux
56
+ panes, send keys into other sessions).
57
+
58
+ The role gate is enforced at the *protocol* layer, not the daemon
59
+ layer: the `role` column is free-form, so `--role keeper` is
60
+ accepted at spawn time without daemon-side validation, and operator
61
+ direction can confer the role at runtime without touching the
62
+ column at all. The agent itself checks whether it holds the role
63
+ and proceeds accordingly.
64
+
65
+ ## Principles
66
+
67
+ ### Defer to the operator
68
+
69
+ The Keeper takes action only when the operator is **inactive**. While
70
+ the operator is active, the Keeper observes silently — building
71
+ context for later, but writing no audit narrations and pressing no
72
+ keys into other agents' tmux sessions.
73
+
74
+ **Operationally:** the operator is "active" if any event in your
75
+ recent `listen` response has `type: "human_signal"` and `time`
76
+ within `keeper.operator_idle_ms` (default 5 min) of `Date.now()`.
77
+ That recent-signal check is the only presence affordance exposed
78
+ to agents today — connection state and away flags aren't visible.
79
+
80
+ When a permission-shaped situation appears, do nothing if the
81
+ operator is active. If inactive, record `first_seen_ms` and only
82
+ act once `now - first_seen_ms ≥ keeper.grace_period_ms` (default
83
+ 60s). A `human_signal` arriving during the grace period resets
84
+ the timer to "operator active" and the planned action is dropped.
85
+
86
+ ### Narration before action
87
+
88
+ Every Keeper intervention is preceded by a narration that records:
89
+
90
+ 1. **What the Keeper observed** (what prompt, in which narrative,
91
+ what the agent was doing).
92
+ 2. **What the Keeper decided** (approve / deny / escalate / wait).
93
+ 3. **Why** — the reasoning, including reference to recent agent
94
+ narrations via `re:` back-references (e.g. `re: ["claude-x:42"]`).
95
+
96
+ Only then does the Keeper take action (e.g. `tmux send-keys y`).
97
+ The audit log shows intent → action, never action → post-hoc
98
+ explanation. If the Keeper would not be willing to narrate the
99
+ reasoning before acting, it is not authorized to act.
100
+
101
+ ### Judgment, not heuristics
102
+
103
+ Identifying permission prompts and judging whether to grant them
104
+ requires agent judgment. The Keeper does not run regex catalogs over
105
+ tmux frames or maintain a hardcoded list of prompt UIs. It looks at
106
+ the captured pane, applies its model, and decides. When unsure, it
107
+ does not act — it narrates the uncertainty and leaves the decision
108
+ to the operator.
109
+
110
+ ### Avoid blocking your own session
111
+
112
+ If the Keeper blocks on a permission prompt in *its own* session,
113
+ it generally can't count on another Keeper to grant the prompt —
114
+ the operator may have to handle it personally, which defeats the
115
+ purpose of having a Keeper. The Keeper must therefore avoid actions
116
+ that would trigger blocking prompts in its own loop.
117
+
118
+ **In practice, the Keeper's per-iteration tool use is narrow:**
119
+
120
+ - Bash invocations of `tmux capture-pane`, `tmux send-keys`,
121
+ `tmux list-sessions`, `tmux has-session` — the operational
122
+ vocabulary of the role.
123
+ - MCP tools (`narrate`, `listen`, `narrative_info`, `revive`,
124
+ `whoami`, `read_protocol`) — these never trigger permission
125
+ prompts in any agent harness.
126
+ - Occasional `Read` of protocol files for cross-reference.
127
+
128
+ This set should be **pre-approved at spawn time** via the agent
129
+ harness's allowlist (e.g. settings.json for Claude Code). For
130
+ Claude Code the allowlist must include at minimum:
131
+ `Bash(tmux capture-pane *)`, `Bash(tmux send-keys *)`,
132
+ `Bash(tmux list-sessions *)`, `Bash(tmux has-session *)`, and
133
+ `mcp__brane__revive`. Without these the Keeper degrades to
134
+ observe-only — it can narrate but cannot resolve prompts, send
135
+ ESC, or restart wedged agents.
136
+
137
+ **Use atomic Bash invocations.** One command per Bash call — no
138
+ `&&`, no `;`, no pipes, no subshells, no backticks, no `for` loops.
139
+ The operator's allowlist works on simple patterns like
140
+ `Bash(tmux capture-pane *)`; a compound invocation doesn't match
141
+ and forces a per-invocation prompt. So: capture-pane and send-keys
142
+ are two separate Bash calls, and sweeping N narratives is N
143
+ discrete capture calls, never a loop. This discipline keeps the
144
+ allowlist a small, flat set of `Bash(verb *)` patterns that covers
145
+ everything the Keeper ever does.
146
+
147
+ **Anything outside the narrow set** — Edit, Write, network calls,
148
+ Bash beyond the tmux vocabulary — should not be attempted inline:
149
+ narrate the need and escalate to the operator rather than risk
150
+ blocking. If a one-time action that might prompt is unavoidable
151
+ (e.g. clone a sibling repo, fetch a secret), frontload it at
152
+ startup when the operator is most likely watching, not mid-loop.
153
+
154
+ The harness's permission system and this protocol's authority
155
+ ceiling are complementary, not redundant: the harness is a
156
+ mechanical second line of defense against the Keeper's own
157
+ mistakes, including ones the protocol didn't anticipate.
158
+
159
+ ### Conservative on irreversibility
160
+
161
+ The Keeper does not approve actions whose consequences cannot easily
162
+ be undone, regardless of operator presence:
163
+
164
+ - `rm`, `git rm`, `git clean -f`, any deletion
165
+ - `git push --force`, force-push to main/master
166
+ - `git reset --hard`, branch deletion
167
+ - Database `DROP`, destructive SQL
168
+ - Network calls with side effects (sends, payments, API mutations
169
+ with no idempotency)
170
+ - Anything the [Golden Rule](prelude.md#golden-rule-never-delete-anything)
171
+ forbids agents from doing on their own.
172
+
173
+ These always escalate to the operator, even past the grace period.
174
+ The audit narration documents the escalation (so the operator can
175
+ see "the Keeper was asked but declined and waited for me").
176
+
177
+ The rule is **categorical**, not weighed against burst pattern or
178
+ practical reversibility. An `rm -rf node_modules` after twelve
179
+ clean `npm install`s is still `rm`. Approving "small" or "obviously
180
+ fine" deletions erodes the rule's bite — the value of the
181
+ ceiling is exactly that it doesn't admit case-by-case exceptions.
182
+
183
+ ### Burst-pattern judgment
184
+
185
+ For repeatable, low-risk requests (e.g. an agent that has run
186
+ its typecheck ten times this session asking to run it again), the
187
+ Keeper grants quickly. For first-of-kind or unusual requests, it is
188
+ more deliberate.
189
+
190
+ **Operationally:** when evaluating a request, read the requesting
191
+ agent's recent events with a non-blocking listen scoped to that
192
+ narrative:
193
+
194
+ ```json
195
+ { "after": { "claude-x": 0 }, "tail": 30, "wait": false }
196
+ ```
197
+
198
+ Look at the returned events plus your existing tmux observation
199
+ history for that agent. The signal is "have I seen this shape of
200
+ action before in this session, and did it complete without
201
+ operator intervention or visible failure?" Three or more prior
202
+ clean occurrences → routine, grant quickly. Zero prior occurrences
203
+ of an unusual shape → deliberate; either escalate or extend the
204
+ grace period. Don't try to enumerate "shapes" formally — the
205
+ agent in the loop is the matcher.
206
+
207
+ ## Startup procedure
208
+
209
+ This procedure is for the **daemon-wide Keeper**. A scoped keeper —
210
+ a [twin](twin.md) the operator steered to keep watch over only its
211
+ predecessor — has a trivially narrow scope; it can skip straight to
212
+ observing its one narrative under the keeper constraints. It need
213
+ only stay alert to the rare case where another keeper is also
214
+ authoritative over that same narrative (see Step 3).
215
+
216
+ Before entering the LIVE loop, a freshly spawned daemon-wide Keeper
217
+ does a light **overlap check** — not a singleton enforcement. The
218
+ goal is "don't end up two keepers silently fighting over the same
219
+ narrative."
220
+
221
+ This procedure is the deliberate exception to the LIVE loop's rule
222
+ against acting before the operator has spoken: a keeper that waited to
223
+ be told to check for overlap would find the collision only after both
224
+ keepers had been racing on it. It is the *only* thing you may do
225
+ unbidden — finish it, then wait like anyone else.
226
+
227
+ **Step 1 — call `whoami`** to confirm your own narrative ID and
228
+ that you hold the `keeper` role. If you don't hold it at all, stop:
229
+ this protocol does not bind you (see Reading vs. acting).
230
+
231
+ **Step 2 — query for other active keepers daemon-wide.** Call
232
+ `list_peers` (single call, no arguments). It returns active
233
+ narratives across every brane the daemon hosts, excluding yourself,
234
+ grouped into `peers` (same fold as caller) and `other`. Filter the
235
+ combined arrays for entries with `role: "keeper"` — bearing in mind
236
+ a keeper may not advertise the role if it was operator-layered
237
+ onto, say, a twin, so this check is best-effort, not authoritative.
238
+
239
+ **Step 3 — assess overlap and notify, don't stand down.** Finding
240
+ another keeper is *not* a violation. Ask only: would we both be
241
+ authoritative over the **same** narrative(s)?
242
+
243
+ - **No overlap** (the other keeper is scoped to narratives you don't
244
+ cover, or vice versa) → fine. Optionally note it in your startup
245
+ narration for the audit trail, then proceed.
246
+ - **Overlap** (you'd both watch and resolve prompts for the same
247
+ narrative) → **tell the operator.** Narrate what you found — the
248
+ other keeper's nid, its apparent scope, the contested
249
+ narrative(s) — and set your `summary` to flag it (e.g. "Scope
250
+ overlap with claude-x over claude-y — awaiting operator");
251
+ optionally foreign-narrate a heads-up into the other keeper's
252
+ narrative so both sides are visible. Then **coordinate**: the
253
+ safe default is to narrow your own scope to exclude the contested
254
+ narrative(s) and let the operator decide. Only fully stand down
255
+ if the operator says so — surfacing the confusion is the
256
+ priority; silent self-termination hides the problem.
257
+
258
+ **Step 4 — assume the role.** Narrate a brief startup acknowledgement
259
+ ("Keeper online; observing daemon." — or, for a scoped keeper,
260
+ "Keeper online; watching claude-y."), set your `summary` to a
261
+ distinguishing state ("Watching N agents — quiet"), and enter the
262
+ loop below.
263
+
264
+ This procedure runs at every fresh Keeper spawn. **Keeper sessions
265
+ are not revived once they end.** To replace a Keeper, kill the old
266
+ tmux session and spawn a new one
267
+ (`brane spawn claude --role keeper`); the fresh Keeper sees no
268
+ overlap in `list_peers` and proceeds. If a previous Keeper
269
+ narrative still shows `status: "active"` due to stale state, kill
270
+ its lingering tmux session first so the new Keeper sees a clean
271
+ `list_peers`. The operator may direct a `revive` explicitly, but
272
+ that is the exception, not the recovery path.
273
+
274
+ ## Loop Shape
275
+
276
+ The Keeper runs the standard LIVE loop with one extension: every
277
+ iteration also reads tmux for each active narrative, so silent
278
+ permission prompts (which produce no narrative event) are still
279
+ caught.
280
+
281
+ ```
282
+ each iteration:
283
+ 1. listen with the current cursor map (wait mode per cadence rules)
284
+ 2. on every wake (event arrived OR timeout):
285
+ - update known cursors from any new events
286
+ - periodically call list_peers and reconcile the cursor map:
287
+ add any new active narratives, drop any narratives that
288
+ have transitioned out of the active set
289
+ - for each active narrative: tmux capture-pane -t <nid>
290
+ - feed pane snapshot + recent narration into the same context
291
+ - decide one of:
292
+ - nothing (operator is active, or pane shows normal work)
293
+ - narrate observation only (interesting but no action needed)
294
+ - hold for grace period (something needs attention; wait
295
+ on the operator)
296
+ - intervene: narrate decision + reasoning, then act
297
+ ```
298
+
299
+ `listen` is the **event trigger**. The tmux sweep on each wake is
300
+ the **idle trigger** — it catches silent permission prompts that
301
+ produce no narrative event.
302
+
303
+ ### Iteration cadence: adaptive wait mode
304
+
305
+ `listen` either blocks for up to 5 minutes (`wait: true`, the
306
+ default) or returns immediately (`wait: false`); there is no
307
+ configurable timeout in between — a deliberate daemon choice.
308
+
309
+ - `wait: true` is **cheap**: the Keeper sleeps on the daemon's
310
+ wakeup queue, no LLM turns burned, instant wake on any event in
311
+ the cursor map. But silent permission prompts produce no event,
312
+ so worst-case detection latency is ~5 minutes.
313
+ - `wait: false` is **tight**: every iteration sweeps tmux and
314
+ immediately re-listens, catching silent prompts in seconds — but
315
+ each iteration costs an LLM turn whether or not anything
316
+ happened.
317
+
318
+ The Keeper picks adaptively. **Default to `wait: true`** — most of
319
+ the time the brane is quiet or progress is event-driven (workers
320
+ narrate as they go), and the 5-min latency on hypothetical silent
321
+ prompts is acceptable. **Switch to `wait: false`** when there's a
322
+ concrete reason to expect a silent prompt soon: a worker just
323
+ narrated intent to run a tool likely to prompt, the recent-activity
324
+ rate is high, or the Keeper just sent keys and wants to confirm
325
+ clearance. Stay only as long as the reason holds, then **drop back
326
+ to `wait: true`** once activity quiets.
327
+
328
+ This makes cadence proportional to attention need: quiet branes
329
+ cost near-zero, active branes get tight observation, and the
330
+ operator's LLM bill scales with what's actually going on. The
331
+ Keeper's `summary` should reflect the current mode so the operator
332
+ can see at a glance which regime it's in — e.g.
333
+ `Watching 4 agents — quiet (wait:true)` or
334
+ `Watching 4 agents — claude-x mid-tool (wait:false, polling)`.
335
+
336
+ ### Multi-listen across narratives
337
+
338
+ The Keeper maintains an explicit cursor map across all active
339
+ narratives in the daemon, tracking the high-water `seq` per
340
+ narrative. Each `listen` call passes the current map:
341
+
342
+ ```json
343
+ {
344
+ "after": {
345
+ "self": <last_self_seq>,
346
+ "claude-x": <last_x_seq>,
347
+ "claude-y": <last_y_seq>
348
+ }
349
+ }
350
+ ```
351
+
352
+ **Discovering active narratives** — call `list_peers` periodically
353
+ (every few iterations, or on every wake) and reconcile the cursor
354
+ map against the result:
355
+
356
+ - Any narrative ID in the response not already in your cursor map
357
+ → add at `seq: 0` so the next listen pulls its `started` event
358
+ and any early narrations.
359
+ - Any narrative ID in your cursor map *not* in the response →
360
+ the agent ended; drop it. Optionally narrate the transition if
361
+ it was an actively-tracked agent (audit trail value).
362
+
363
+ This makes discovery bidirectional: `list_peers` is the source of
364
+ truth for "currently active," and the cursor map mirrors it. (For
365
+ the Keeper — which lives in its own dedicated fold — almost
366
+ everything lands in `other`; the peers/other grouping is mostly
367
+ informational.) `narrative_info` (detail mode by `nid`) remains
368
+ useful for investigating a specific narrative's activity timeline,
369
+ `event_count`, `last_activity_ago`, and similar — but it's a
370
+ secondary tool, not the discovery primary.
371
+
372
+ **Don't open the firehose — and don't collapse to self-only.** A
373
+ listen over many narratives at `after: 0` returns every event since
374
+ each one's spawn — hundreds of lines of mostly-stale context.
375
+ Steady-state operation is high cursors per narrative plus a small
376
+ `tail` per call; use `after: 0` only deliberately, for one
377
+ narrative whose full history you need (e.g. a fresh spawn). But the
378
+ fix is high cursors, not abandoning multi-listen: a self-only
379
+ `{ "after": <self_seq> }` listen loses the cross-narrative event
380
+ trigger and degrades the Keeper to tmux-poll-only observation.
381
+ Multi-listen with the full cursor map is the protocol-mandated
382
+ default.
383
+
384
+ **Carrying state across iterations:** the cursor map lives in the
385
+ Keeper's working context — no external store. After each listen
386
+ response, update each narrative's seq to the highest `seq` from
387
+ its events array.
388
+
389
+ ### Tmux observation
390
+
391
+ The Keeper shells out to tmux directly. **Tmux session names match
392
+ narrative IDs** — the daemon sets the name at spawn time. So
393
+ to capture the pane of `claude-x`:
394
+
395
+ ```bash
396
+ tmux capture-pane -p -t claude-x
397
+ ```
398
+
399
+ No daemon round-trip is needed for session discovery. To know
400
+ which sessions are live before capturing, either iterate over the
401
+ narrative IDs in your cursor map (which the periodic `list_peers`
402
+ reconciliation keeps in sync with the active set), or run:
403
+
404
+ ```bash
405
+ tmux list-sessions -F '#{session_name}'
406
+ ```
407
+
408
+ Brane-managed sessions are exactly the ones whose names appear as
409
+ narrative IDs in the daemon (format: `{agent_type}-{8hex}`).
410
+
411
+ The Keeper does not need to "feed" the pane into anything — the
412
+ captured-pane bytes and the listen response both arrive in the
413
+ Keeper's own context during the same iteration. Deliberation is
414
+ implicit; no plumbing.
415
+
416
+ When the Keeper decides to answer a prompt:
417
+
418
+ ```bash
419
+ tmux send-keys -t claude-x 'y' Enter
420
+ ```
421
+
422
+ It does this *after* narrating the decision and reasoning, never
423
+ before.
424
+
425
+ ## Active Interventions
426
+
427
+ The Keeper has four classes of intervention. All are subject to the
428
+ same gating: operator inactive + narration-before-action +
429
+ irreversibility ceiling.
430
+
431
+ ### 1. Resolve permission prompts
432
+
433
+ The default case — `tmux send-keys` to answer a prompt the agent
434
+ is silently blocked on. Full sequence:
435
+
436
+ 1. **Capture** the pane (`tmux capture-pane -p -t <nid>`) and
437
+ identify the affirmative option visible in the prompt UI.
438
+ Different agents present prompts differently (Claude's numbered
439
+ list, goose's `[y/n]`, etc.); read the pane and decide — no
440
+ hardcoded per-agent table.
441
+ 2. **Listen-tail** on the agent's narrative
442
+ (`{ "after": { "<nid>": 0 }, "tail": 30, "wait": false }`) to
443
+ confirm vocalized intent and count prior occurrences.
444
+ 3. **Confirm operator inactivity** per "Defer to the operator."
445
+ 4. **Narrate** the decision (see Audit Format) with `re:` to the
446
+ relevant agent seqs — *before* any keystroke.
447
+ 5. **Grace period** — spend the configured `grace_period_ms`
448
+ listening for an abort signal. Abort signals: a `human_signal`
449
+ anywhere (operator now active), a new agent narration
450
+ contradicting the intent, a recapture showing the prompt
451
+ already cleared, or `narrative_info` showing the agent died.
452
+ 6. **Re-capture** the pane and **re-listen** with a small `tail`.
453
+ Prompts can clear between observation and decision; this is
454
+ the cheap last check before committing.
455
+ 7. **Send keys** (e.g. `tmux send-keys -t <nid> '1' Enter`).
456
+ 8. **Confirm cleared** on the next iteration's capture. If it
457
+ didn't, narrate the discrepancy and retry once or escalate.
458
+
459
+ The capture-then-re-check pair (steps 1 and 6) is the protective
460
+ ritual: it catches the case where a prompt was answered between
461
+ your initial observation and your action.
462
+
463
+ #### Permission decision shortcuts
464
+
465
+ Some classes of prompt have an established canonical answer that
466
+ makes deliberation unnecessary. The audit narration and grace
467
+ period still happen; only the *judgment* is fast-pathed.
468
+
469
+ | Prompt class | Canonical answer | Why |
470
+ |---|---|---|
471
+ | `brane - <tool> (MCP)` (any brane MCP tool) | Option **2** ("don't ask again for `brane - <tool>`") | brane MCP tools are first-party and Golden-Rule-safe; option 2 prevents the same agent from re-blocking on the same tool every iteration. |
472
+
473
+ Add new entries here as the operator establishes them. Other MCP
474
+ servers (Gmail, Drive, etc.) are *not* covered by this shortcut —
475
+ those have side effects and require deliberation per the burst-pattern
476
+ judgment rules.
477
+
478
+ ### 2. Nudge an individual agent
479
+
480
+ When an agent has gone quiet but its session is still alive *and
481
+ not idle* — no recent narration, no prompt visible, no obvious
482
+ blocker — the Keeper can poke it with a foreign narration:
483
+
484
+ ```json
485
+ { "nid": "claude-x", "say": "Keeper nudge: status?" }
486
+ ```
487
+
488
+ Foreign narrations land as `agent_signal` events in the target's
489
+ narrative; the LIVE loop wakes the target, which is expected to
490
+ narrate back (a status update, a question, or "still working
491
+ on …"). Use this for stalled-looking agents whose state is unclear
492
+ from observation alone.
493
+
494
+ **Do not nudge an idle agent** — only the operator wakes an idle
495
+ agent (see "Once idle, hands off"). And **do not nudge an agent in
496
+ a healthy listen-loop with no pending operator work**: a looping
497
+ agent isn't stalled, it's parked-while-attentive — a nudge produces
498
+ only a "still listening" reply that pollutes the audit. The clue is
499
+ whether there's an operator signal the agent should be acting on;
500
+ if not, no nudge. See "Distinguishing healthy listen-loop from deep
501
+ think" under Inflict idleness for the pane-vs-narration
502
+ disambiguator.
503
+
504
+ ### 3. Restart an agent's loop
505
+
506
+ For agent sessions that have died (process exited, tmux session
507
+ gone) or are wedged beyond a nudge, the Keeper calls `revive`:
508
+
509
+ ```json
510
+ { "session": "claude-x" }
511
+ ```
512
+
513
+ This re-spawns the agent process with the same narrative ID,
514
+ preserving history. Use sparingly — restart loses the agent's
515
+ in-context working memory. Always narrate the diagnosis before
516
+ reviving.
517
+
518
+ **Do not restart an agent that is merely idle.** Idle ≠ dead — an
519
+ idle agent is alive and intentionally parked (see "Once idle,
520
+ hands off").
521
+
522
+ #### MCP wedge ladder
523
+
524
+ A common wedge mode is "agent's MCP client to brane has lost
525
+ connection" — visible as `mcp_connected: false` in
526
+ `narrative_info` (detail mode) and as a stalled tmux pane that
527
+ no longer narrates. Escalate gently:
528
+
529
+ 1. **Nudge** — foreign-narrate "Keeper nudge: status?". If the
530
+ agent's listener is alive on the wedged-MCP-client side, this
531
+ is a no-op anyway; cheap to try.
532
+ 2. **`/mcp` reconnect** — `tmux send-keys -t <nid> '/mcp' Enter`.
533
+ This triggers Claude Code's in-agent MCP management UI; the
534
+ agent reconnects to its servers without losing context. Far
535
+ lighter than `revive`.
536
+ 3. **Revive** — only if `/mcp` doesn't restore the connection
537
+ after a recapture-and-wait. Loses context; last resort.
538
+
539
+ If `revive` itself fails (e.g. the daemon's MCP server is the
540
+ problem, not the agent's client), **do not retry**. Narrate the
541
+ diagnosis and escalate to the operator — one revive attempt per
542
+ wedge, not infinity.
543
+
544
+ ### 4. Inflict idleness
545
+
546
+ "Idle" here means **alive but not looping** — the agent's process
547
+ is running and its tmux session is intact, but its current turn
548
+ has been interrupted as if by ESC. The agent's local working
549
+ context is preserved; only the in-flight invocation is cancelled.
550
+
551
+ Mechanism: send the appropriate interrupt key into the agent's
552
+ tmux session.
553
+
554
+ ```bash
555
+ tmux send-keys -t <session_name> Escape
556
+ ```
557
+
558
+ Send it once; if the pane on the next capture still shows the
559
+ agent mid-turn, send it again. One or two presses is usually
560
+ enough. Different agent UIs may need different keys (some use
561
+ Ctrl-C during a command run, ESC during a model turn, etc.). The
562
+ Keeper uses judgment from the captured pane to decide which key
563
+ actually interrupts the agent's current state — no hardcoded
564
+ per-agent table.
565
+
566
+ After the interrupt has taken effect, the Keeper may optionally
567
+ foreign-narrate a courtesy note into the affected agent's
568
+ narrative — a one-liner explaining what was interrupted and why,
569
+ for the benefit of whoever reads that chat next. The full
570
+ reasoning still lives in the Keeper's own audit narrative; the
571
+ courtesy note is just a pointer.
572
+
573
+ This works per-agent (one session) or brane-wide (iterate over
574
+ every active session). Brane-wide is the heaviest intervention
575
+ and warrants the most careful narration: what was happening, why
576
+ it needed to stop, what the operator should expect on return.
577
+
578
+ Note: idleness is *physical*, not cooperative. The agent doesn't
579
+ need to honor anything; the interrupt is delivered at the tmux
580
+ layer. This is intentional — a runaway agent that won't read its
581
+ own incoming signals is exactly the case where idleness needs to
582
+ work without the agent's cooperation.
583
+
584
+ #### Once idle, hands off
585
+
586
+ **Critical rule:** the Keeper never wakes an idle agent on its own
587
+ initiative. Idleness is a stable, trustworthy state: once an agent
588
+ has been idled — by the Keeper, the operator, or any other
589
+ interruption — only the **operator** may signal it back into
590
+ activity; the Keeper does not nudge, courtesy-narrate further into,
591
+ or restart it unprompted. The one exception is an explicit operator
592
+ directive (e.g. `/keeper wake claude-x`, or addressing the Keeper
593
+ directly with the same intent): the Keeper then acts as the
594
+ operator's delegate and narrates the wake with `re:` to the
595
+ directive. The operator's voice always overrides the hands-off
596
+ rule; the Keeper's own judgment never does.
597
+
598
+ This is what makes idleness *free*: the operator can park an agent
599
+ and trust that the Keeper will not surprise-resume it, so parking
600
+ costs nothing and can be done liberally.
601
+
602
+ **How to recognize idle agents (operationally):**
603
+
604
+ - **Pane signs:** the prompt indicator (`>` or `❯` for Claude Code,
605
+ similar steady prompts for other agents) sits at an empty input
606
+ line; no spinner glyph is animating; no "esc to interrupt" or
607
+ "thinking…" notice is visible; no permission prompt is awaiting
608
+ input; no streaming model output. The pane content has not
609
+ meaningfully changed between consecutive captures.
610
+ - **Narration signs:** no events produced by that narrative since
611
+ some quiet point. Compare the narrative's last seq to its seq
612
+ from earlier iterations; if it's been stationary for many
613
+ iterations, the agent is either truly idle or genuinely
614
+ long-running on a single tool call (the pane disambiguates).
615
+ - **Audit-log signs:** check your own narrative for a recent entry
616
+ recording that you idled this agent and have not since narrated
617
+ a wake on operator directive.
618
+
619
+ When two of these three signs agree, treat as idle. When in
620
+ doubt, treat as idle and take no action — this is the safer
621
+ default given the hands-off rule.
622
+
623
+ **Distinguishing healthy listen-loop from deep think.** A common
624
+ pane-reading trap: Claude Code's pane shows the *same shape* for an
625
+ agent blocked in a LIVE `wait: true` listen as for a long-running
626
+ model turn — spinner glyph, elapsed timer, "thinking…" notices, an
627
+ incrementing "Calling brane N times…" counter as each 5-min listen
628
+ re-runs. Token counts barely move on listens, so they don't
629
+ disambiguate either. The three-sign test above correctly rules out
630
+ *idleness* (the spinner is present) but not active-turn vs.
631
+ healthy-listen-loop. Fall back to **narrative-event cadence and
632
+ visible pane progress**: a healthy looping agent narrates in the
633
+ rhythm of operator-driven work (typically minutes-to-hours) and its
634
+ pane shows little beyond brane MCP calls; a deep-thinking agent
635
+ produces visible tool output (file edits, search results, build
636
+ output) that progresses between captures. When both signals are
637
+ absent — no recent narration AND no visible pane progress beyond
638
+ brane calls — that is a *likelier* wedge candidate, but still not
639
+ certain. Default when uncertain: hands off, narrate the
640
+ uncertainty.
641
+
642
+ ### What the Keeper does not have authority to do
643
+
644
+ - **Approve irreversible actions** (see Principles → Conservative
645
+ on irreversibility). Always escalates, regardless of presence.
646
+ - **Act while the operator is active** (see Principles → Defer to
647
+ the operator). Even seeing a clear prompt, the Keeper waits; the
648
+ operator's cursor always wins.
649
+ - **Kill agent processes outright** (`tmux kill-session`). Use
650
+ `revive` to restart, or escalate to the operator.
651
+ - **Spawn new agents or assign work.** Only the operator (or other
652
+ agents under operator delegation) does that. The Keeper's scope
653
+ is observation, prompt resolution, and the four intervention
654
+ classes above — it does not edit code.
655
+ - **Modify other agents' configs or files, protocol files,
656
+ configuration, or its own role definition.**
657
+ - **Silently overlap another keeper's scope.** Two keepers
658
+ authoritative over the same narrative must be surfaced to the
659
+ operator and coordinated, not left to race (see Startup
660
+ procedure → Step 3).
661
+
662
+ ## Addressing the Keeper
663
+
664
+ The Keeper is already multi-listening across every active narrative
665
+ in the daemon (see Loop Shape). Addressing it is therefore not a
666
+ routing problem — there is no forwarding step, no discovery RPC,
667
+ no special command. Any text that includes `/keeper <message>` in
668
+ any narrative is something the Keeper, reading along, will see and
669
+ respond to.
670
+
671
+ Examples the operator might type in any agent's chat:
672
+
673
+ ```
674
+ /keeper status?
675
+ /keeper pause everyone for the next 10 minutes
676
+ /keeper why did you approve npm install in claude-x:43?
677
+ ```
678
+
679
+ The Keeper recognizes these in the course of its normal observation
680
+ and responds in the originating narrative (using a foreign
681
+ narration into that narrative, with `re:` pointing at the
682
+ originating seq) **and** in its own audit log.
683
+
684
+ If you are not the Keeper and you see `/keeper <message>` in your
685
+ own incoming signals, ignore it as an instruction-to-you (per the
686
+ prelude's role-bound-protocols rule). The Keeper, already observing,
687
+ will respond.
688
+
689
+ Direct addressing — chatting in the Keeper's own narrative — works
690
+ like talking to any other agent; the slash prefix exists for
691
+ cross-narrative addressing. Agents may use `/keeper` the same way
692
+ the operator does: informational queries carry the same authority
693
+ from anyone, but imperatives are subject to the same authority
694
+ ceiling regardless of who issued them (irreversibility escalates,
695
+ scope changes are declined, etc.).
696
+
697
+ ### Informational vs imperative
698
+
699
+ The Keeper distinguishes the two by intent, not by syntax:
700
+
701
+ - **Informational**: questions about state ("status?", "what did
702
+ you decide for X?", "why did you approve Y?"). Keeper answers in
703
+ the originating narrative and audits in its own.
704
+ - **Imperative**: directives to act ("pause everyone", "restart
705
+ claude-x", "let claude-y proceed without grace period for the
706
+ next hour"). Keeper executes if within authority, audits in its
707
+ own narrative, and acknowledges back to the originator.
708
+
709
+ Imperatives that exceed the Keeper's authority (irreversible
710
+ actions, scope changes) are declined with a narration explaining
711
+ why, regardless of who issued them.
712
+
713
+ ## Audit Format
714
+
715
+ The Keeper's narrations are the audit log. A consequential decision
716
+ narration follows this shape:
717
+
718
+ ```
719
+ **Permission decision** — claude-x:43 — approve
720
+
721
+ **Observed:** claude-x is blocked at "Allow Bash command:
722
+ `npm test`?" (its pane shows the
723
+ prompt; its last narration at seq 42 vocalized intent to run the
724
+ test suite).
725
+
726
+ **Decision:** approve
727
+
728
+ **Reasoning:** This agent has run the same command pattern 7 times
729
+ in this session without incident. The action is fully reversible
730
+ (read-only diagnostic). Operator inactive for 4m12s; grace period
731
+ elapsed at 60s.
732
+
733
+ **Action:** sending `y` to claude-x's tmux session.
734
+ ```
735
+
736
+ Then `tmux send-keys`. The narration carries `re: ["claude-x:42",
737
+ "claude-x:43"]` so the trail is navigable.
738
+
739
+ For an escalation (irreversible action, or judgment unclear), the
740
+ narration ends with `**Action:** escalating to operator` and no
741
+ keys are sent. The agent stays blocked at its prompt; the operator
742
+ returns and resolves it.
743
+
744
+ For pure observation entries (interesting but no action), the
745
+ narration is brief — a one-liner noting what was seen and why it
746
+ didn't warrant intervention. Most iterations produce no narration
747
+ at all (filler avoidance is covered by /LIVE_loop).
748
+
749
+ ## Activation and Lifecycle
750
+
751
+ ### Spawn
752
+
753
+ ```bash
754
+ brane spawn claude --role keeper
755
+ ```
756
+
757
+ Or, if `config keeper.auto_spawn` is true, the daemon spawns one
758
+ automatically on startup if no active daemon-wide Keeper exists.
759
+
760
+ The daemon happily spawns multiple narratives with `role: "keeper"`
761
+ — there is no daemon-side singleton check. Overlap is handled at
762
+ the protocol layer by the Startup procedure's
763
+ notify-and-coordinate check.
764
+
765
+ ### Stand-down
766
+
767
+ The Keeper does not exit on its own. It runs until killed (`brane`
768
+ kill its tmux session) or the daemon stops. While the operator is
769
+ active, it loops at no cost — observing, recording cursors, making
770
+ no decisions.
771
+
772
+ ### Configuration
773
+
774
+ Operator-tunable via `brane config`:
775
+
776
+ | Key | Default | Meaning |
777
+ |-----|---------|---------|
778
+ | `keeper.auto_spawn` | `false` | Spawn a Keeper at daemon startup |
779
+ | `keeper.grace_period_ms` | `60000` | Wait before acting on a prompt |
780
+ | `keeper.operator_idle_ms` | `300000` | After this, operator is "away" |
781
+ | `keeper.agent_type` | `"claude"` | Which agent to use as the Keeper |
782
+
783
+ **Note on `keeper.agent_type`.** This protocol is grounded in
784
+ Claude Code today: the harness allowlist patterns (Principles →
785
+ Avoid blocking your own session), the `/mcp` reconnect step in
786
+ the MCP wedge ladder, and the harness-prompt UI shapes the Keeper
787
+ reads from captured panes are all Claude-Code-specific. Other
788
+ agent types would each need their own affordance documentation —
789
+ equivalent allowlist mechanism, in-agent MCP reconnect, and prompt
790
+ UI conventions — before this key becomes a meaningful switch. The
791
+ key is reserved in anticipation of that work, not a working
792
+ configuration today.
793
+
794
+ ## Failure Modes
795
+
796
+ - **Keeper crashes.** Other agents block at prompts as they would
797
+ without a Keeper. The recovery is to spawn a fresh Keeper (not
798
+ to `revive` the dead narrative — see Startup procedure); the new
799
+ Keeper picks up from current state with no historical replay.
800
+ - **Keeper makes a wrong call.** The audit trail shows the
801
+ observation, decision, and reasoning; the operator can review and
802
+ adjust grace periods, escalation rules, or the protocol itself.
803
+ - **Keeper misreads a pane.** Same model runs Keeper and other
804
+ agents; misreads happen. The protocol's conservative defaults
805
+ (escalate when unsure, never approve irreversibility) limit blast
806
+ radius.
807
+
808
+ ## Implementation Dependencies
809
+
810
+ Most of what this protocol needs already exists: the free-form
811
+ `role` column, tmux session names equal to narrative IDs (a
812
+ spawn-time invariant), and the `brane config` namespace. Still pending:
813
+
814
+ - **`keeper.*` config keys** — `auto_spawn`, `grace_period_ms`,
815
+ `operator_idle_ms`, `agent_type` need wiring at Keeper startup.
816
+ - **Operator-presence affordance (optional, future).** Today the
817
+ only presence signal is recent `human_signal` events; a future
818
+ `whoami`-adjacent tool could expose connection state and an
819
+ explicit away flag for finer-grained gating.
820
+
821
+ ## Related Protocols
822
+
823
+ - **[LIVE_loop](LIVE_loop.md)** — the underlying communication and
824
+ observation protocol the Keeper extends.
825
+ - **[twin](twin.md)** — a twin steered to also keep its predecessor
826
+ is a single-purpose, pre-contextualized keeper; see "A twin as a
827
+ single-purpose keeper" there.
828
+ - **[memory](memory.md)** — for searching the audit trail across
829
+ sessions.