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.
- package/AGENTS.md +362 -0
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +91 -0
- package/DESIGN.md +402 -0
- package/Dockerfile +61 -0
- package/LICENSE +656 -17
- package/README.md +174 -2
- package/SECURITY.md +205 -0
- package/dist/absorb/brane-id.js +11 -0
- package/dist/absorb/chatgpt.js +161 -0
- package/dist/absorb/claude-code.js +504 -0
- package/dist/absorb/index.js +543 -0
- package/dist/absorb/organelle.js +345 -0
- package/dist/absorb/run.js +520 -0
- package/dist/absorb/sources.js +168 -0
- package/dist/absorb/types.js +3 -0
- package/dist/agent-env.js +45 -0
- package/dist/agents.js +1168 -0
- package/dist/claude-live-session.js +124 -0
- package/dist/cli.js +50 -0
- package/dist/codex-tool-log.js +144 -0
- package/dist/commands/absorb.js +291 -0
- package/dist/commands/attach.js +31 -0
- package/dist/commands/certify.js +62 -0
- package/dist/commands/config.js +116 -0
- package/dist/commands/cost.js +126 -0
- package/dist/commands/daemon.js +350 -0
- package/dist/commands/doctor.js +262 -0
- package/dist/commands/fold.js +95 -0
- package/dist/commands/list.js +89 -0
- package/dist/commands/mcp.js +84 -0
- package/dist/commands/meld.js +332 -0
- package/dist/commands/memory.js +215 -0
- package/dist/commands/revive.js +74 -0
- package/dist/commands/run.js +85 -0
- package/dist/commands/spawn.js +1094 -0
- package/dist/commands/token.js +153 -0
- package/dist/copilot-tool-log.js +145 -0
- package/dist/cost/pricing.js +116 -0
- package/dist/cost/reprice.js +136 -0
- package/dist/crush-tool-log.js +293 -0
- package/dist/daemon/agent-scope.js +116 -0
- package/dist/daemon/awaiting.js +54 -0
- package/dist/daemon/cert-manager.js +349 -0
- package/dist/daemon/client.js +262 -0
- package/dist/daemon/gated-routes.js +112 -0
- package/dist/daemon/keep-awake.js +103 -0
- package/dist/daemon/local-socket.js +145 -0
- package/dist/daemon/presence-gate.js +206 -0
- package/dist/daemon/server.js +6349 -0
- package/dist/daemon/signal-source.js +35 -0
- package/dist/daemon/tailscale-rebind.js +58 -0
- package/dist/daemon/token-ring.js +263 -0
- package/dist/daemon/token-routes.js +190 -0
- package/dist/db/events.js +152 -0
- package/dist/db/mentions.js +179 -0
- package/dist/db/schema.js +633 -0
- package/dist/db/tokens.js +583 -0
- package/dist/db/tool-uses.js +647 -0
- package/dist/diagram/index.js +85 -0
- package/dist/diagram/label.js +455 -0
- package/dist/diagram/layout.js +476 -0
- package/dist/diagram/links.js +50 -0
- package/dist/diagram/metrics.js +152 -0
- package/dist/diagram/parse.js +487 -0
- package/dist/diagram/svg.js +389 -0
- package/dist/diagram/types.js +9 -0
- package/dist/export.js +629 -0
- package/dist/files/archive-types.js +2 -0
- package/dist/files/archive.js +182 -0
- package/dist/files/browse.js +327 -0
- package/dist/files/bytes.js +162 -0
- package/dist/files/codecs.js +33 -0
- package/dist/files/find.js +212 -0
- package/dist/files/tar.js +223 -0
- package/dist/files/zip.js +209 -0
- package/dist/folds.js +72 -0
- package/dist/grammars/ebnf.js +92 -0
- package/dist/highlight.js +250 -0
- package/dist/ids.js +24 -0
- package/dist/markdown-diagnostics.js +233 -0
- package/dist/math.js +168 -0
- package/dist/mcp/server.js +886 -0
- package/dist/memory/embedder.js +145 -0
- package/dist/memory/embedding-worker.js +71 -0
- package/dist/memory/embeddings.js +91 -0
- package/dist/memory/index.js +232 -0
- package/dist/memory/storage.js +587 -0
- package/dist/memory/wiki.js +688 -0
- package/dist/memory/wordpiece.js +247 -0
- package/dist/mentions-scan.js +40 -0
- package/dist/presence.js +259 -0
- package/dist/session-affinity.js +185 -0
- package/dist/shared/api-request.js +124 -0
- package/dist/shared/block-glyphs.js +114 -0
- package/dist/shared/diagram-css.js +17 -0
- package/dist/shared/export-selection.js +101 -0
- package/dist/shared/find-in-text.js +110 -0
- package/dist/shared/glow-hues.js +225 -0
- package/dist/shared/hex-dump.js +62 -0
- package/dist/shared/hex-pages.js +52 -0
- package/dist/shared/hit-drum.js +79 -0
- package/dist/shared/known-nids.js +58 -0
- package/dist/shared/list-scroll-keys.js +56 -0
- package/dist/shared/markdown-lists.js +72 -0
- package/dist/shared/markdown-math.js +485 -0
- package/dist/shared/markdown-mentions.js +79 -0
- package/dist/shared/markdown-nids.js +41 -0
- package/dist/shared/markdown-protocols.js +53 -0
- package/dist/shared/markdown-refs.js +123 -0
- package/dist/shared/membrane-invasion.js +342 -0
- package/dist/shared/membrane-motion.js +83 -0
- package/dist/shared/membrane-palette.js +253 -0
- package/dist/shared/membrane-terrain.js +212 -0
- package/dist/shared/message-selection.js +123 -0
- package/dist/shared/mo-glyph.js +57 -0
- package/dist/shared/narrative-format.js +237 -0
- package/dist/shared/narrative-menu.js +82 -0
- package/dist/shared/narrative-sort.js +47 -0
- package/dist/shared/narrative-status.js +52 -0
- package/dist/shared/no-pan.js +85 -0
- package/dist/shared/pane-layout.js +50 -0
- package/dist/shared/payload-inline.js +83 -0
- package/dist/shared/speaker-color.js +61 -0
- package/dist/shared/token-rotation.js +14 -0
- package/dist/shared/view-url.js +197 -0
- package/dist/shared/wiki-links.js +70 -0
- package/dist/shared/wiki-window.js +4 -0
- package/dist/tailscale.js +55 -0
- package/dist/test/absorb-claude.test.js +892 -0
- package/dist/test/agent-scope.test.js +193 -0
- package/dist/test/agents.test.js +55 -0
- package/dist/test/api-request.test.js +122 -0
- package/dist/test/awaiting-glow.test.js +54 -0
- package/dist/test/awaiting.test.js +122 -0
- package/dist/test/bind-token.test.js +88 -0
- package/dist/test/block-glyphs.test.js +68 -0
- package/dist/test/cert-manager.test.js +262 -0
- package/dist/test/claude-live-session.test.js +110 -0
- package/dist/test/codex-tool-log.test.js +568 -0
- package/dist/test/config.test.js +71 -0
- package/dist/test/copilot-tool-log.test.js +93 -0
- package/dist/test/cors-preflight.test.js +49 -0
- package/dist/test/cost-linger.test.js +31 -0
- package/dist/test/cost-pricing.test.js +72 -0
- package/dist/test/cost-reprice.test.js +139 -0
- package/dist/test/cost-sessions.test.js +301 -0
- package/dist/test/crush-tool-log.test.js +289 -0
- package/dist/test/daemon-client.test.js +109 -0
- package/dist/test/daemon-socket.test.js +293 -0
- package/dist/test/diagram-arrows.test.js +205 -0
- package/dist/test/diagram-export.test.js +210 -0
- package/dist/test/diagram-labels.test.js +313 -0
- package/dist/test/diagram-parse.test.js +191 -0
- package/dist/test/diagram-render.test.js +175 -0
- package/dist/test/diagram-review.test.js +288 -0
- package/dist/test/diagram-self-loop-labels.test.js +303 -0
- package/dist/test/embedder.test.js +84 -0
- package/dist/test/events.test.js +123 -0
- package/dist/test/export-selection.test.js +102 -0
- package/dist/test/export.test.js +505 -0
- package/dist/test/files-browse.test.js +393 -0
- package/dist/test/files-find.test.js +272 -0
- package/dist/test/find-in-text.test.js +86 -0
- package/dist/test/first-paint-viewport.test.js +59 -0
- package/dist/test/fixtures/embedding-golden.js +23 -0
- package/dist/test/fixtures/wordpiece-golden.js +167 -0
- package/dist/test/glow-hues.test.js +202 -0
- package/dist/test/harness-cost.test.js +423 -0
- package/dist/test/helpers.js +21 -0
- package/dist/test/hex-pages.test.js +68 -0
- package/dist/test/highlight.test.js +69 -0
- package/dist/test/hit-drum.test.js +71 -0
- package/dist/test/hook-config.test.js +262 -0
- package/dist/test/json-gzip.test.js +70 -0
- package/dist/test/key-routing.test.js +58 -0
- package/dist/test/known-nids.test.js +106 -0
- package/dist/test/link-preview.test.js +67 -0
- package/dist/test/list-scroll-keys.test.js +66 -0
- package/dist/test/listen-own-words.test.js +147 -0
- package/dist/test/listen-timeout.test.js +33 -0
- package/dist/test/markdown-diagnostics.test.js +121 -0
- package/dist/test/markdown-lists.test.js +182 -0
- package/dist/test/markdown-math.test.js +256 -0
- package/dist/test/markdown-nids.test.js +146 -0
- package/dist/test/markdown-protocols.test.js +139 -0
- package/dist/test/mcp-bridge-cancel.test.js +98 -0
- package/dist/test/mcp-bridge-reconnect.test.js +121 -0
- package/dist/test/mcp-narrative.test.js +193 -0
- package/dist/test/mcp.test.js +548 -0
- package/dist/test/meld.test.js +85 -0
- package/dist/test/membrane-invasion.test.js +482 -0
- package/dist/test/membrane-motion.test.js +101 -0
- package/dist/test/membrane-palette.test.js +166 -0
- package/dist/test/membrane-terrain.test.js +170 -0
- package/dist/test/memory-log.test.js +202 -0
- package/dist/test/mentions-e2e.test.js +261 -0
- package/dist/test/mentions-ledger.test.js +265 -0
- package/dist/test/mentions-listen.test.js +265 -0
- package/dist/test/mentions.test.js +238 -0
- package/dist/test/message-selection.test.js +114 -0
- package/dist/test/mo-glyph.test.js +22 -0
- package/dist/test/model-resolution.test.js +166 -0
- package/dist/test/narrate-validate.test.js +77 -0
- package/dist/test/narrate-wait.test.js +160 -0
- package/dist/test/narrative-format.test.js +219 -0
- package/dist/test/narrative-menu.test.js +137 -0
- package/dist/test/narrative-sort.test.js +75 -0
- package/dist/test/no-pan.test.js +60 -0
- package/dist/test/pane-layout.test.js +153 -0
- package/dist/test/paths.test.js +37 -0
- package/dist/test/payload-inline.test.js +91 -0
- package/dist/test/precache-fonts.test.js +72 -0
- package/dist/test/presence-gate.test.js +440 -0
- package/dist/test/presence.test.js +160 -0
- package/dist/test/read-events.test.js +220 -0
- package/dist/test/remote-meld.test.js +115 -0
- package/dist/test/revive-cli.test.js +93 -0
- package/dist/test/search-floor.test.js +206 -0
- package/dist/test/search-fusion.test.js +168 -0
- package/dist/test/search.test.js +205 -0
- package/dist/test/security.test.js +48 -0
- package/dist/test/selection-wiring.test.js +61 -0
- package/dist/test/session-affinity.test.js +295 -0
- package/dist/test/signal-source.test.js +32 -0
- package/dist/test/socket-account.test.js +268 -0
- package/dist/test/spawn-toml.test.js +72 -0
- package/dist/test/speaker-color.test.js +90 -0
- package/dist/test/tailscale-rebind.test.js +79 -0
- package/dist/test/terminal-cursor.test.js +61 -0
- package/dist/test/terminal-resync.test.js +23 -0
- package/dist/test/thin-cli.test.js +382 -0
- package/dist/test/token-bootstrap.test.js +272 -0
- package/dist/test/token-devices.test.js +587 -0
- package/dist/test/token-lapse.test.js +94 -0
- package/dist/test/token-ring.test.js +188 -0
- package/dist/test/token-rotate-cli.test.js +58 -0
- package/dist/test/token-routes.test.js +95 -0
- package/dist/test/tool-uses.test.js +1006 -0
- package/dist/test/usage-ledger.test.js +126 -0
- package/dist/test/view-url.test.js +581 -0
- package/dist/test/wiki-links.test.js +57 -0
- package/dist/test/wiki-sources.test.js +85 -0
- package/dist/test/wiki-window.test.js +31 -0
- package/dist/test/wiki.test.js +399 -0
- package/dist/test/wordpiece.test.js +68 -0
- package/dist/tls.js +148 -0
- package/dist/tmux.js +202 -0
- package/dist/user-agent.js +46 -0
- package/docker-entrypoint.sh +141 -0
- package/docs/README.md +3 -0
- package/docs/absorb.md +231 -0
- package/docs/cost-ledger.md +125 -0
- package/docs/daemon-api.md +189 -0
- package/docs/diagrams.md +163 -0
- package/docs/doctor.md +36 -0
- package/docs/export-scope.md +69 -0
- package/docs/files-view.md +133 -0
- package/docs/link-previews.md +38 -0
- package/docs/math.md +32 -0
- package/docs/mcp.md +69 -0
- package/docs/memory-search.md +75 -0
- package/docs/mentions.md +101 -0
- package/docs/multi-brane.md +14 -0
- package/docs/passkeys.md +165 -0
- package/docs/presence.md +202 -0
- package/docs/releasing.md +53 -0
- package/docs/rendering-warnings.md +22 -0
- package/docs/request-layer.md +72 -0
- package/docs/selection-store.md +50 -0
- package/docs/session-affinity.md +61 -0
- package/docs/terminal-mirroring.md +211 -0
- package/docs/token-rotation.md +413 -0
- package/docs/view-urls.md +114 -0
- package/docs/web-app.md +23 -0
- package/docs/wiki.md +43 -0
- package/expo/dist/_expo/static/js/web/entry-e56efc1291dca7258ea00b8108b033a4.js +1097 -0
- package/expo/dist/apple-touch-icon.png +0 -0
- package/expo/dist/favicon.svg +3 -0
- package/expo/dist/index.html +177 -0
- package/expo/dist/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/expo/dist/katex/katex.min.css +1 -0
- package/expo/dist/manifest.json +16 -0
- package/expo/dist/metadata.json +1 -0
- package/expo/dist/og/membrane.v1.png +0 -0
- package/expo/dist/pdf.min.mjs +28 -0
- package/expo/dist/pdf.worker.min.mjs +28 -0
- package/expo/dist/sw.js +154 -0
- package/package.json +81 -23
- package/protocols/LIVE_loop.md +407 -0
- package/protocols/bebrief.md +41 -0
- package/protocols/digest.md +231 -0
- package/protocols/fly.md +266 -0
- package/protocols/idle.md +81 -0
- package/protocols/keeper.md +829 -0
- package/protocols/markdown.md +80 -0
- package/protocols/memory.md +419 -0
- package/protocols/mentions.md +93 -0
- package/protocols/payloads.md +53 -0
- package/protocols/prelude.md +91 -0
- package/protocols/ramify.md +105 -0
- package/protocols/review.md +356 -0
- package/protocols/rrr.md +232 -0
- package/protocols/scribe.md +429 -0
- package/protocols/summarize.md +203 -0
- package/protocols/twin.md +174 -0
- package/protocols/wiki.md +438 -0
- package/protocols/worker.md +38 -0
- package/wiki/shared/append-only-event-log.md +148 -0
- package/wiki/shared/index.md +48 -0
- package/wiki/shared/owning-your-narratives.md +161 -0
- package/wiki/shared/premature-ontologies.md +435 -0
- package/wiki/shared/sedimentary-superintelligence.md +128 -0
- package/wiki/shared/sequestering-the-narrative.md +143 -0
- package/wiki/shared/why-wikis-work.md +216 -0
- package/.npmignore +0 -2
- package/.travis.yml +0 -5
- package/index.js +0 -8
- package/index.js~ +0 -12
package/DESIGN.md
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# Brane Design
|
|
2
|
+
|
|
3
|
+
Brane is a TypeScript npm package providing a CLI tool (`npx brane`) and
|
|
4
|
+
userspace daemon for managing narrative agent sessions, with a clean
|
|
5
|
+
SQLite-first architecture.
|
|
6
|
+
|
|
7
|
+
"Brane" is short for membrane — the biological metaphor carries over from
|
|
8
|
+
organelle, enabling reuse of terms like nucleus (daemon), narrative, and
|
|
9
|
+
organelle (agent).
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm i --save-dev brane
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## CLI Subcommands
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
brane fold # Register cwd as a fold (spawn point)
|
|
21
|
+
brane fold --list # List all registered folds
|
|
22
|
+
brane daemon start|stop|status # Manage the daemon
|
|
23
|
+
brane daemon restart # Restart, preserving port
|
|
24
|
+
brane daemon logs [-f] [-n N] # View daemon logs
|
|
25
|
+
brane spawn <agent> [-- <args>] # Spawn agent in tmux session
|
|
26
|
+
--role <role> # worker | coordinator | image-analyst
|
|
27
|
+
--attach # Attach to session after spawning
|
|
28
|
+
--model <model> # Model hint (e.g., haiku, sonnet)
|
|
29
|
+
brane list # List active sessions
|
|
30
|
+
brane attach <id> # Attach to tmux session
|
|
31
|
+
brane revive <id> # Restart a dead session
|
|
32
|
+
brane meld # Show QR + tappable URL for mobile access
|
|
33
|
+
brane config <key> [<value>] # Get/set configuration (git-config style)
|
|
34
|
+
brane memory search <query> # Semantic search across narratives
|
|
35
|
+
brane memory index # Re-index narrative content
|
|
36
|
+
brane mcp # Stdio MCP server (for agents)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Folds
|
|
40
|
+
|
|
41
|
+
A "fold" is a registered directory where agents can be spawned. The term
|
|
42
|
+
draws from brain folds (gyri/sulci), membrane folding, and folder as
|
|
43
|
+
prefix. `brane fold` registers the current directory in the central
|
|
44
|
+
SQLite database.
|
|
45
|
+
|
|
46
|
+
### Agent flexibility
|
|
47
|
+
|
|
48
|
+
`brane spawn` handles:
|
|
49
|
+
- Scrolling TUI apps (Claude, aider) that use the LIVE loop via MCP
|
|
50
|
+
- Static/paned TUI apps (lazygit) that need tmux + terminal streaming
|
|
51
|
+
- Plain commands (caffeinate) with no UI at all
|
|
52
|
+
|
|
53
|
+
MCP injection only happens for agents that support it. The tmux-loop
|
|
54
|
+
supervisor treats all spawned processes uniformly.
|
|
55
|
+
|
|
56
|
+
### Meld
|
|
57
|
+
|
|
58
|
+
`brane meld` auto-detects Tailscale hostname (if available) and prints
|
|
59
|
+
a tappable URL + QR code for mobile access. The URL includes the daemon
|
|
60
|
+
auth token as a query parameter. Defaults to `brane.organelle.dev` with
|
|
61
|
+
the daemon as backend; falls back to local mode (`--local`) automatically
|
|
62
|
+
when the daemon lacks TLS certificates.
|
|
63
|
+
|
|
64
|
+
## Storage
|
|
65
|
+
|
|
66
|
+
All state lives in `~/.brane/brane.db` (SQLite, WAL mode). No scattered
|
|
67
|
+
JSON files. The only files written to fold directories are agent-agnostic
|
|
68
|
+
MCP config stubs (`.mcp.json`) pointing back to `brane mcp`.
|
|
69
|
+
|
|
70
|
+
### Identity
|
|
71
|
+
|
|
72
|
+
Each brane instance has a stable ID in the format `brane-<slug>` (short
|
|
73
|
+
random hex/base36), generated once and stored in SQLite.
|
|
74
|
+
|
|
75
|
+
### Schema
|
|
76
|
+
|
|
77
|
+
```sql
|
|
78
|
+
CREATE TABLE meta (
|
|
79
|
+
key TEXT PRIMARY KEY,
|
|
80
|
+
value TEXT NOT NULL
|
|
81
|
+
);
|
|
82
|
+
-- Keys: brane_id, daemon_port, daemon_pid, daemon_version,
|
|
83
|
+
-- daemon_tls, daemon_http_port
|
|
84
|
+
-- (No token: tokens are stored as hashes in `tokens`.)
|
|
85
|
+
|
|
86
|
+
CREATE TABLE tokens (
|
|
87
|
+
id TEXT PRIMARY KEY,
|
|
88
|
+
hash TEXT NOT NULL UNIQUE, -- SHA-256 of the token; the token is never stored
|
|
89
|
+
kind TEXT NOT NULL, -- 'local' (this machine's CLI) | 'device'
|
|
90
|
+
label TEXT NOT NULL,
|
|
91
|
+
origin TEXT NOT NULL, -- 'minted' | 'adopted' (plaintext from before hashing)
|
|
92
|
+
created_at INTEGER NOT NULL,
|
|
93
|
+
last_used_at INTEGER,
|
|
94
|
+
revoked_at INTEGER -- revoked, never deleted
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
CREATE TABLE folds (
|
|
98
|
+
path TEXT PRIMARY KEY,
|
|
99
|
+
-- ~/relative for portability
|
|
100
|
+
brane_id TEXT,
|
|
101
|
+
created_at INTEGER NOT NULL,
|
|
102
|
+
last_spawned INTEGER
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
CREATE TABLE narratives (
|
|
106
|
+
id TEXT PRIMARY KEY,
|
|
107
|
+
-- e.g. claude-4c7289d1
|
|
108
|
+
brane_id TEXT NOT NULL,
|
|
109
|
+
fold_path TEXT,
|
|
110
|
+
agent_type TEXT,
|
|
111
|
+
-- claude, lazygit, caffeinate
|
|
112
|
+
role TEXT,
|
|
113
|
+
-- worker, coordinator
|
|
114
|
+
created_at INTEGER NOT NULL,
|
|
115
|
+
status TEXT DEFAULT 'active',
|
|
116
|
+
agent_session_id TEXT,
|
|
117
|
+
title TEXT,
|
|
118
|
+
summary TEXT,
|
|
119
|
+
archived INTEGER DEFAULT 0
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
-- Append-only event log
|
|
123
|
+
CREATE TABLE events (
|
|
124
|
+
narrative_id TEXT NOT NULL,
|
|
125
|
+
seq INTEGER NOT NULL,
|
|
126
|
+
time INTEGER NOT NULL,
|
|
127
|
+
-- unix ms
|
|
128
|
+
type TEXT NOT NULL,
|
|
129
|
+
-- narration, human_signal, agent_signal
|
|
130
|
+
content TEXT,
|
|
131
|
+
content_type TEXT,
|
|
132
|
+
source TEXT,
|
|
133
|
+
-- who produced this event (narrative ID, device name, etc.)
|
|
134
|
+
re TEXT,
|
|
135
|
+
-- JSON array of back-references
|
|
136
|
+
payloads TEXT,
|
|
137
|
+
-- JSON array of attachment paths
|
|
138
|
+
meta TEXT,
|
|
139
|
+
-- JSON object for per-event metadata (e.g. {"model": "gpt-4o"})
|
|
140
|
+
PRIMARY KEY (narrative_id, seq)
|
|
141
|
+
);
|
|
142
|
+
CREATE INDEX idx_events_narrative_seq ON events(narrative_id, seq);
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## ID Scheme
|
|
147
|
+
|
|
148
|
+
Globally unique event addressing: `<brane-id>:<narrative-id>:<seq>`
|
|
149
|
+
|
|
150
|
+
- `brane-a7f3c2:claude-4c7289d1:42` — full global address
|
|
151
|
+
- `claude-4c7289d1:42` — local address (within a brane)
|
|
152
|
+
- `claude-4c7289d1` — narrative address (no specific event)
|
|
153
|
+
- `42` — seq within current narrative
|
|
154
|
+
|
|
155
|
+
## MCP Architecture
|
|
156
|
+
|
|
157
|
+
The MCP server (`brane mcp`, defined in `src/mcp/server.ts`) is a **thin
|
|
158
|
+
stdio↔WebSocket bridge** (~230 lines). It has zero SQLite dependency.
|
|
159
|
+
All tool logic lives in the daemon (`src/daemon/server.ts`), which the
|
|
160
|
+
MCP bridge connects to via `/ws/mcp?nid=<id>`.
|
|
161
|
+
|
|
162
|
+
When `brane spawn` creates an agent session, it generates `.mcp.json`
|
|
163
|
+
in the fold directory with `BRANE_NARRATIVE_ID` as an environment
|
|
164
|
+
variable.
|
|
165
|
+
|
|
166
|
+
### MCP Tools
|
|
167
|
+
|
|
168
|
+
#### narrate
|
|
169
|
+
|
|
170
|
+
Post a narration and receive pending signals in one call.
|
|
171
|
+
|
|
172
|
+
```jsonc
|
|
173
|
+
{
|
|
174
|
+
"say": "Working on the auth module",
|
|
175
|
+
// Cursor: last seq you've processed
|
|
176
|
+
// Number for own narrative, or map for multi-narrative
|
|
177
|
+
"heard": 42,
|
|
178
|
+
// OR: "heard": { "self": 42, "claude-def": 10 }
|
|
179
|
+
|
|
180
|
+
// Optional: target narrative (default: self)
|
|
181
|
+
"narrative": "claude-def",
|
|
182
|
+
// Optional: reply-to refs
|
|
183
|
+
"re": [42, "claude-def:10"]
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
- Writes to one narrative at a time: self (default) or explicit
|
|
188
|
+
`narrative` target
|
|
189
|
+
- Foreign narration (narrative != self) is always explicit
|
|
190
|
+
- `re` is metadata only — does not affect routing
|
|
191
|
+
- `heard` polls for signals newer than the cursor
|
|
192
|
+
|
|
193
|
+
#### listen
|
|
194
|
+
|
|
195
|
+
Block and wait for signals. Stateless cursor-based long-polling.
|
|
196
|
+
|
|
197
|
+
```jsonc
|
|
198
|
+
{
|
|
199
|
+
// Number for own narrative
|
|
200
|
+
"heard": 42
|
|
201
|
+
// OR map for multi-narrative polling:
|
|
202
|
+
// "heard": { "self": 42, "claude-def": 10 }
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
#### whoami
|
|
207
|
+
|
|
208
|
+
Returns the agent's narrative ID and brane ID.
|
|
209
|
+
|
|
210
|
+
#### revive
|
|
211
|
+
|
|
212
|
+
Restart a dead tmux session. Idempotent. Optional prompt injection.
|
|
213
|
+
|
|
214
|
+
#### narrative_search
|
|
215
|
+
|
|
216
|
+
Semantic search across narrative history. Auto-indexes unindexed events.
|
|
217
|
+
Takes `query` (required) and `limit` (optional). Returns ranked results
|
|
218
|
+
with narrative_id, seq, content, and score.
|
|
219
|
+
|
|
220
|
+
#### narrative_info
|
|
221
|
+
|
|
222
|
+
Get info about narratives. Without `nid`: browse recently active
|
|
223
|
+
narratives (takes optional `since`, `search`, `status`, `fold`, `limit`).
|
|
224
|
+
With `nid` (string or array): get detailed info on specific narratives
|
|
225
|
+
including event count, metadata, timestamps, mcp_connected, and archived
|
|
226
|
+
status.
|
|
227
|
+
|
|
228
|
+
#### list_protocols / read_protocol
|
|
229
|
+
|
|
230
|
+
List and read protocol documents (LIVE_loop, memory, worker, etc.).
|
|
231
|
+
|
|
232
|
+
## Daemon
|
|
233
|
+
|
|
234
|
+
The daemon (`src/daemon/server.ts`) serves:
|
|
235
|
+
|
|
236
|
+
- **Static files**: the built Expo web app from `expo/dist/`
|
|
237
|
+
- **HTTP API**: narratives, events, signals
|
|
238
|
+
- **WebSocket endpoints**:
|
|
239
|
+
- `/ws/control` — event hints for live UI updates
|
|
240
|
+
- `/ws/terminal/:nid` — tmux terminal streaming (capture-pane at 10fps)
|
|
241
|
+
- `/ws/mcp/:nid` — MCP tool bridge (daemon owns all tool logic)
|
|
242
|
+
|
|
243
|
+
All endpoints require `Authorization: Bearer <token>`.
|
|
244
|
+
|
|
245
|
+
Port reuse: the daemon automatically reuses its previous port across
|
|
246
|
+
restarts. The restart command captures the port before sending SIGTERM
|
|
247
|
+
(since the shutdown handler deletes it from SQLite).
|
|
248
|
+
|
|
249
|
+
### TLS
|
|
250
|
+
|
|
251
|
+
The daemon owns its TLS cert lifecycle end-to-end via
|
|
252
|
+
`src/daemon/cert-manager.ts`:
|
|
253
|
+
|
|
254
|
+
- **Auto-provision on first start.** If `tailscale` is available and no
|
|
255
|
+
cert exists at `~/.brane/certs/tls.{crt,key}`, the daemon shells out
|
|
256
|
+
to `tailscale cert <host>` (same call `brane certify` makes) and
|
|
257
|
+
binds HTTPS with the result. First-run users never need to run
|
|
258
|
+
`brane certify`.
|
|
259
|
+
- **Auto-renewal.** An hourly tick re-checks the cert's `notAfter`. When
|
|
260
|
+
fewer than 30 days remain (Tailscale issues 90-day Let's Encrypt
|
|
261
|
+
certs), the daemon re-provisions in the background. Failures back
|
|
262
|
+
off `1h → 2h → 4h → 8h → 24h`.
|
|
263
|
+
- **Hot-reload.** `fs.watch` on the certs directory catches both
|
|
264
|
+
internal renewals and operator-driven `brane certify` runs, then
|
|
265
|
+
calls `https.Server#setSecureContext` with the new bytes — no
|
|
266
|
+
daemon restart required.
|
|
267
|
+
- **Subject-CN check.** A cert whose CN doesn't match the current
|
|
268
|
+
Tailscale host is treated as invalid and forces re-provisioning,
|
|
269
|
+
catching tailnet/node renames.
|
|
270
|
+
|
|
271
|
+
When TLS is active the daemon also starts an HTTP fallback on a
|
|
272
|
+
separate ephemeral port (restricted to private/local IPs only). This
|
|
273
|
+
allows local and LAN clients to connect without TLS certificate
|
|
274
|
+
hostname mismatches. The HTTP fallback port is stored as
|
|
275
|
+
`daemon_http_port` in the meta table and cleaned up on shutdown.
|
|
276
|
+
|
|
277
|
+
The current state (`expires_at`, `state`, `last_error_*`,
|
|
278
|
+
`next_attempt_at`, `host`) is exposed on `GET /api/status` and
|
|
279
|
+
broadcast over `/ws/control` as `{ type: "tls", ... }` on every
|
|
280
|
+
transition for the UI to surface a banner. `brane certify` remains as
|
|
281
|
+
a manual escape hatch for forcing an immediate re-provision.
|
|
282
|
+
|
|
283
|
+
### CORS
|
|
284
|
+
|
|
285
|
+
The daemon allows cross-origin requests from:
|
|
286
|
+
- `http://localhost:*` — any port, for local dev
|
|
287
|
+
- `https://*.organelle.dev` — production domains
|
|
288
|
+
- Configurable `app_domain` via config
|
|
289
|
+
|
|
290
|
+
## Web App (Expo)
|
|
291
|
+
|
|
292
|
+
The Expo app at `expo/` is a React Native Web PWA served by the daemon.
|
|
293
|
+
|
|
294
|
+
### Views
|
|
295
|
+
|
|
296
|
+
- **Narrative list** (`app/index.tsx`) — shows all active narratives
|
|
297
|
+
- **Narrative detail** (`app/[nid].tsx`) — chat + terminal tabs
|
|
298
|
+
|
|
299
|
+
### Chat Tab
|
|
300
|
+
|
|
301
|
+
- Operator messages: blue bubbles (#1a4a7a), right-aligned
|
|
302
|
+
- Agent narrations: dark gray (#2a2a2a), left-aligned
|
|
303
|
+
- Foreign narrations: centered, hash-based pastel backgrounds
|
|
304
|
+
- Source labels on agent/foreign messages
|
|
305
|
+
|
|
306
|
+
### Terminal Tab
|
|
307
|
+
|
|
308
|
+
- Live tmux mirroring via `ansi_up` ANSI→HTML conversion
|
|
309
|
+
- ResizeObserver calculates cols/rows from monospace metrics
|
|
310
|
+
- Physical keyboard capture on desktop (tabIndex + keydown)
|
|
311
|
+
- Quick bar: macros (live loop, go) + quick keys (1, 2, 3, ↑, ↓)
|
|
312
|
+
- Full software keyboard with multitouch modifier support
|
|
313
|
+
- Swipe gestures for tab switching
|
|
314
|
+
|
|
315
|
+
### Building
|
|
316
|
+
|
|
317
|
+
```sh
|
|
318
|
+
cd expo && npx expo export --platform web
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Output goes to `expo/dist/`, served by the daemon as static files.
|
|
322
|
+
|
|
323
|
+
## Threading Model
|
|
324
|
+
|
|
325
|
+
Invariants:
|
|
326
|
+
|
|
327
|
+
1. Every event belongs to exactly one narrative (stored in `narrative_id`)
|
|
328
|
+
2. Every event has a monotonic `seq` within its narrative
|
|
329
|
+
3. Every event has a `re` field — an array of back-references (can be
|
|
330
|
+
empty)
|
|
331
|
+
4. Long-polling a narrative returns all new events with `seq > cursor`,
|
|
332
|
+
regardless of their `re` values
|
|
333
|
+
5. Threading is metadata, not routing — `re` affects display, not
|
|
334
|
+
delivery
|
|
335
|
+
|
|
336
|
+
### re: references
|
|
337
|
+
|
|
338
|
+
The `re` field is a JSON array. Each element is either:
|
|
339
|
+
- A bare seq number — refers to that seq in the same narrative
|
|
340
|
+
- A qualified string `"narrative-id:seq"` — cross-narrative reference
|
|
341
|
+
- A narrative ID without seq `"narrative-id"` — refers to the narrative
|
|
342
|
+
as a whole
|
|
343
|
+
|
|
344
|
+
Examples:
|
|
345
|
+
- `re` omitted or `[]` → new topic / continuation by adjacency
|
|
346
|
+
- `[42]` → replying to one specific event in the same narrative
|
|
347
|
+
- `[42, "claude-def:10"]` → synthesizing multiple sources
|
|
348
|
+
|
|
349
|
+
### UI threading
|
|
350
|
+
|
|
351
|
+
The web UI displays a linear timeline (all events in seq order). `re:`
|
|
352
|
+
links render as clickable back-references. Click to scroll/highlight
|
|
353
|
+
the referenced event. No visual nesting — threading is navigational.
|
|
354
|
+
|
|
355
|
+
Foreign agent messages are stored in the timeline but collapsed by
|
|
356
|
+
default to avoid clutter from side conversations. Expandable on demand.
|
|
357
|
+
|
|
358
|
+
### Prefix memory (copy-on-write)
|
|
359
|
+
|
|
360
|
+
When spawning with `--re <narrative-id>`, the new narrative logically
|
|
361
|
+
shares prefix memory with the parent. Events are not duplicated — the
|
|
362
|
+
parent's history is resolved on demand by walking the ref chain.
|
|
363
|
+
|
|
364
|
+
## Payloads (Attachments)
|
|
365
|
+
|
|
366
|
+
Payloads are copied into a stable location on receipt:
|
|
367
|
+
`~/.brane/attachments/<narrative_id>/<seq>_<index>.<ext>`
|
|
368
|
+
|
|
369
|
+
Referenced in the events table as a JSON array in the `payloads` column:
|
|
370
|
+
```json
|
|
371
|
+
["attachments/claude-abc/42_0.png"]
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Paths stored relative to `~/.brane/` for portability. The MCP server
|
|
375
|
+
resolves to absolute paths when returning events to agents.
|
|
376
|
+
|
|
377
|
+
## Identity and Security
|
|
378
|
+
|
|
379
|
+
- All IDs are server-assigned (UUIDs / random slugs) — unspoofable
|
|
380
|
+
- The MCP server fixes a narration's narrative and type server-side: an
|
|
381
|
+
agent's bridge binds only as its own narrative, and narration into
|
|
382
|
+
another arrives as an `agent_signal`
|
|
383
|
+
- A `human_signal`'s `source` is stamped from the authenticated token
|
|
384
|
+
(`web` for a device, `local` for this account), never taken from the
|
|
385
|
+
request, and an agent's token cannot send one
|
|
386
|
+
- **Not built yet:** telling two humans apart. Signals are not tied to
|
|
387
|
+
the device token that sent them. See SECURITY.md, "Who a signal is from"
|
|
388
|
+
|
|
389
|
+
## Dependencies
|
|
390
|
+
|
|
391
|
+
- **citty** — TypeScript-native CLI framework (subcommand definitions)
|
|
392
|
+
- **better-sqlite3** — SQLite driver (synchronous, WAL mode)
|
|
393
|
+
- **ws** — WebSocket library
|
|
394
|
+
- **qrcode-terminal** — QR code generation for `brane meld`
|
|
395
|
+
|
|
396
|
+
Optional (for semantic search):
|
|
397
|
+
- **onnxruntime-node** — runs the local embedding model (`bge-small-en-v1.5`); tokenizer and pooling are in `src/memory/`
|
|
398
|
+
- **sqlite-vec** — vector search extension for SQLite
|
|
399
|
+
|
|
400
|
+
## Code style
|
|
401
|
+
|
|
402
|
+
- Comments above what they annotate, not trailing
|
package/Dockerfile
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
FROM node:24-bookworm
|
|
2
|
+
|
|
3
|
+
# System dependencies (tmux: the daemon fetches a verified static build into
|
|
4
|
+
# ~/.brane/bin when it starts)
|
|
5
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
6
|
+
git \
|
|
7
|
+
curl \
|
|
8
|
+
ca-certificates \
|
|
9
|
+
gnupg \
|
|
10
|
+
gosu \
|
|
11
|
+
sqlite3 \
|
|
12
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
13
|
+
|
|
14
|
+
# Tailscale (sole network access mechanism)
|
|
15
|
+
RUN curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.noarmor.gpg | \
|
|
16
|
+
gpg --dearmor -o /usr/share/keyrings/tailscale-archive-keyring.gpg && \
|
|
17
|
+
curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.tailscale-keyring.list | \
|
|
18
|
+
tee /etc/apt/sources.list.d/tailscale.list && \
|
|
19
|
+
apt-get update && apt-get install -y --no-install-recommends tailscale && \
|
|
20
|
+
rm -rf /var/lib/apt/lists/*
|
|
21
|
+
|
|
22
|
+
# Claude Code CLI. Its postinstall copies the native binary over the
|
|
23
|
+
# `claude` placeholder, and npm 11 skips a global package's install scripts
|
|
24
|
+
# unless they're allowed, so run it explicitly; `claude --version` fails the
|
|
25
|
+
# build if the placeholder is still there.
|
|
26
|
+
RUN npm i -g @anthropic-ai/claude-code && \
|
|
27
|
+
node "$(npm root -g)/@anthropic-ai/claude-code/install.cjs" && \
|
|
28
|
+
claude --version
|
|
29
|
+
|
|
30
|
+
# Brane (from pre-built tarball in the build context)
|
|
31
|
+
COPY brane-*.tgz /tmp/
|
|
32
|
+
RUN npm i -g /tmp/brane-*.tgz
|
|
33
|
+
|
|
34
|
+
# Non-root user for running the daemon and spawned agents
|
|
35
|
+
RUN useradd -m -s /bin/bash brane && \
|
|
36
|
+
mkdir -p /home/brane/.brane/bin /home/brane/workspace && \
|
|
37
|
+
cp /root/.brane/bin/tmux /home/brane/.brane/bin/tmux 2>/dev/null || true && \
|
|
38
|
+
chown -R brane:brane /home/brane/.brane /home/brane/workspace
|
|
39
|
+
|
|
40
|
+
# Pre-download embedding model into image layer (outside ~/.brane volume),
|
|
41
|
+
# with brane's own loader: the files land exactly where the daemon looks
|
|
42
|
+
# (BRANE_MODEL_CACHE), and one embedding is run, so a broken download fails
|
|
43
|
+
# the build rather than the first search.
|
|
44
|
+
ENV BRANE_MODEL_CACHE=/opt/brane/models
|
|
45
|
+
RUN mkdir -p /opt/brane/models && \
|
|
46
|
+
node --input-type=module \
|
|
47
|
+
-e 'const { createEmbedder } = await import(process.argv[1]); const e = await createEmbedder(); const [v] = await e.embed(["warm"]); await e.dispose(); if (v.length !== 384) throw new Error("unexpected vector"); console.log("Model cached.")' \
|
|
48
|
+
"$(npm root -g)/brane/dist/memory/embedder.js" && \
|
|
49
|
+
chown -R brane:brane /opt/brane
|
|
50
|
+
|
|
51
|
+
# Entrypoint (starts as root for tailscaled, drops to brane user)
|
|
52
|
+
COPY docker-entrypoint.sh /usr/local/bin/
|
|
53
|
+
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
|
54
|
+
|
|
55
|
+
# Persist home directory across container restarts (brane data, cargo, etc.)
|
|
56
|
+
VOLUME /home/brane
|
|
57
|
+
|
|
58
|
+
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
|
59
|
+
CMD gosu brane brane daemon status || exit 1
|
|
60
|
+
|
|
61
|
+
ENTRYPOINT ["docker-entrypoint.sh"]
|