relaybrain 0.0.0-stage → 0.2.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 (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +290 -2
  3. package/backend/coordinator/client.js +364 -0
  4. package/backend/coordinator/lockTable.js +649 -0
  5. package/backend/coordinator/server.js +179 -0
  6. package/backend/lib/centralAuth.js +62 -0
  7. package/backend/lib/centralStore.js +112 -0
  8. package/backend/lib/centralSync.js +88 -0
  9. package/backend/lib/cloneRepo.js +108 -0
  10. package/backend/lib/collisionMetrics.js +231 -0
  11. package/backend/lib/conflicts.js +35 -0
  12. package/backend/lib/dashboard.js +53 -0
  13. package/backend/lib/depGraph.js +393 -0
  14. package/backend/lib/deps.js +46 -0
  15. package/backend/lib/dirtyFiles.js +33 -0
  16. package/backend/lib/editMatchers.js +55 -0
  17. package/backend/lib/falkor/analytics.js +228 -0
  18. package/backend/lib/falkor/bus.js +16 -0
  19. package/backend/lib/falkor/central.js +281 -0
  20. package/backend/lib/falkor/client.js +165 -0
  21. package/backend/lib/falkor/config.js +73 -0
  22. package/backend/lib/falkor/coord.js +299 -0
  23. package/backend/lib/falkor/cursor.js +58 -0
  24. package/backend/lib/falkor/fixtures.js +85 -0
  25. package/backend/lib/falkor/index.js +146 -0
  26. package/backend/lib/falkor/ingest.js +233 -0
  27. package/backend/lib/falkor/intents.js +32 -0
  28. package/backend/lib/falkor/keys.js +102 -0
  29. package/backend/lib/falkor/migrations.js +56 -0
  30. package/backend/lib/falkor/project.js +307 -0
  31. package/backend/lib/falkor/query.js +344 -0
  32. package/backend/lib/falkor/redact.js +30 -0
  33. package/backend/lib/falkor/routes.js +81 -0
  34. package/backend/lib/falkor/schema.js +108 -0
  35. package/backend/lib/falkor/write.js +281 -0
  36. package/backend/lib/githubLogin.js +108 -0
  37. package/backend/lib/githubOAuth.js +42 -0
  38. package/backend/lib/githubRepo.js +117 -0
  39. package/backend/lib/harvest.js +85 -0
  40. package/backend/lib/httpServer.js +27 -0
  41. package/backend/lib/installAgentRelay.js +235 -0
  42. package/backend/lib/installHooks.js +309 -0
  43. package/backend/lib/lamport.js +14 -0
  44. package/backend/lib/localBrowse.js +109 -0
  45. package/backend/lib/memorySync.js +63 -0
  46. package/backend/lib/notices.js +143 -0
  47. package/backend/lib/ot.js +74 -0
  48. package/backend/lib/patches.js +192 -0
  49. package/backend/lib/paths.js +85 -0
  50. package/backend/lib/peerClash.js +64 -0
  51. package/backend/lib/presence.js +35 -0
  52. package/backend/lib/relayOsBlock.js +84 -0
  53. package/backend/lib/repoSnapshot.js +125 -0
  54. package/backend/lib/room.js +498 -0
  55. package/backend/lib/roomAsk.js +148 -0
  56. package/backend/lib/roomAuth.js +215 -0
  57. package/backend/lib/roomControl.js +226 -0
  58. package/backend/lib/roomLocks.js +201 -0
  59. package/backend/lib/roomSignal.js +265 -0
  60. package/backend/lib/roomSync.js +194 -0
  61. package/backend/lib/seed.js +107 -0
  62. package/backend/lib/sseHub.js +40 -0
  63. package/backend/lib/store.js +79 -0
  64. package/backend/lib/supergraph.js +46 -0
  65. package/backend/lib/transcripts/discover.js +338 -0
  66. package/backend/lib/transcripts/parse.js +558 -0
  67. package/backend/lib/transcripts/sessionId.js +36 -0
  68. package/backend/lib/transcripts/sync.js +291 -0
  69. package/backend/lib/transcripts/util.js +299 -0
  70. package/backend/lib/treeSitterImports.js +219 -0
  71. package/backend/lib/watchWorkspace.js +42 -0
  72. package/backend/mcp/README.md +193 -0
  73. package/backend/mcp/server.js +42 -0
  74. package/backend/mcp/tools.js +484 -0
  75. package/backend/server.js +3839 -0
  76. package/cli/graph.js +278 -0
  77. package/cli/relay.js +419 -0
  78. package/docker-compose.falkor.yml +17 -0
  79. package/docs/MEMORY-GRAPH.md +269 -0
  80. package/hooks/relay-antigravity-post-tool.js +2 -0
  81. package/hooks/relay-antigravity-pre-read.js +2 -0
  82. package/hooks/relay-antigravity-pre-tool.js +2 -0
  83. package/hooks/relay-antigravity-stop.js +2 -0
  84. package/hooks/relay-claude-post-tool.js +2 -0
  85. package/hooks/relay-claude-pre-read.js +2 -0
  86. package/hooks/relay-claude-pre-tool.js +2 -0
  87. package/hooks/relay-claude-stop.js +2 -0
  88. package/hooks/relay-codex-post-tool.js +2 -0
  89. package/hooks/relay-codex-pre-read.js +2 -0
  90. package/hooks/relay-codex-pre-tool.js +2 -0
  91. package/hooks/relay-codex-stop.js +2 -0
  92. package/hooks/relay-copilot-post-tool.js +2 -0
  93. package/hooks/relay-copilot-pre-read.js +2 -0
  94. package/hooks/relay-copilot-pre-tool.js +2 -0
  95. package/hooks/relay-copilot-stop.js +2 -0
  96. package/hooks/relay-cursor-post-tool.js +2 -0
  97. package/hooks/relay-cursor-pre-read.js +2 -0
  98. package/hooks/relay-cursor-pre-tool.js +2 -0
  99. package/hooks/relay-cursor-stop.js +2 -0
  100. package/hooks/relay-hook-lib.js +621 -0
  101. package/mission-control/out/404.html +1 -0
  102. package/mission-control/out/__next.__PAGE__.txt +26 -0
  103. package/mission-control/out/__next._full.txt +21 -0
  104. package/mission-control/out/__next._tree.txt +4 -0
  105. package/mission-control/out/_next/static/chunks/0-3l04fnym95-.js +22 -0
  106. package/mission-control/out/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
  107. package/mission-control/out/_next/static/chunks/0ehjiuuxbbhq9.js +1 -0
  108. package/mission-control/out/_next/static/chunks/1q0cmt0kqufow.js +2 -0
  109. package/mission-control/out/_next/static/chunks/1ya1u0izct4f6.js +1 -0
  110. package/mission-control/out/_next/static/chunks/25rvg4bnew99f.css +4 -0
  111. package/mission-control/out/_next/static/chunks/2n66mlnb34vjq.js +1 -0
  112. package/mission-control/out/_next/static/chunks/2xar70jzx8f1y.css +1 -0
  113. package/mission-control/out/_next/static/chunks/31cgqj_-l15yt.js +31 -0
  114. package/mission-control/out/_next/static/chunks/3_5x4qn1_b4al.js +1 -0
  115. package/mission-control/out/_next/static/chunks/3o1tiwncjss_k.js +1 -0
  116. package/mission-control/out/_next/static/chunks/3vq9c4hivvgn_.js +1 -0
  117. package/mission-control/out/_next/static/chunks/turbopack-3hsllzpnzz12p.js +1 -0
  118. package/mission-control/out/_next/static/dglM39XRu0cmneClUkGYd/_buildManifest.js +11 -0
  119. package/mission-control/out/_next/static/dglM39XRu0cmneClUkGYd/_clientMiddlewareManifest.js +1 -0
  120. package/mission-control/out/_next/static/dglM39XRu0cmneClUkGYd/_ssgManifest.js +1 -0
  121. package/mission-control/out/_next/static/media/icon.27otmh_fv3buh.svg +4 -0
  122. package/mission-control/out/_not-found/__next._full.txt +18 -0
  123. package/mission-control/out/_not-found/__next._not-found/__PAGE__.txt +24 -0
  124. package/mission-control/out/_not-found/__next._tree.txt +3 -0
  125. package/mission-control/out/_not-found.html +1 -0
  126. package/mission-control/out/_not-found.txt +18 -0
  127. package/mission-control/out/agents/antigravity.png +0 -0
  128. package/mission-control/out/agents/claude.png +0 -0
  129. package/mission-control/out/agents/codex.png +0 -0
  130. package/mission-control/out/agents/copilot.png +0 -0
  131. package/mission-control/out/agents/cursor.png +0 -0
  132. package/mission-control/out/icon.svg +4 -0
  133. package/mission-control/out/index.html +1 -0
  134. package/mission-control/out/index.txt +21 -0
  135. package/mission-control/out/logo.png +0 -0
  136. package/mission-control/out/logo_transparent.png +0 -0
  137. package/package.json +74 -4
  138. package/scripts/falkor-rebuild.js +57 -0
  139. package/scripts/falkor-seed-demo.js +157 -0
@@ -0,0 +1,17 @@
1
+ services:
2
+ falkordb:
3
+ image: falkordb/falkordb:latest
4
+ container_name: relay-falkordb
5
+ ports:
6
+ - "6379:6379"
7
+ - "3010:3000"
8
+ environment:
9
+ # RDB snapshots, not AOF: replaying FalkorDB's AOF effect stream can segfault
10
+ # on restart (see #21). Relay re-sends anything a lost snapshot missed.
11
+ REDIS_ARGS: "--save 30 1 --save 300 10 --appendonly no"
12
+ volumes:
13
+ # The image writes here; /data only holds a symlink, so mounting /data persisted nothing.
14
+ - falkordb_data:/var/lib/falkordb/data
15
+ restart: unless-stopped
16
+ volumes:
17
+ falkordb_data: {}
@@ -0,0 +1,269 @@
1
+ # Memory graph (FalkorDB)
2
+
3
+ Relay's coordination plane decides **who may edit a file right now**. The memory graph answers what that alone can't:
4
+ **who did what to which file, during which session, because of which prompt, and who got in whose way.** It also hands that
5
+ context to agents *before* they act.
6
+
7
+ It is built on [FalkorDB](https://www.falkordb.com/), with one graph per project. Every write is a parameterized `MERGE`, so the
8
+ graph is idempotent, and every read is a named, read-only Cypher query.
9
+
10
+ > Built during **Graph Hacks** (WeMakeDevs × FalkorDB, Oct 15–18 2026), Track 2: Agent Memory & Coordination.
11
+ > Everything below lives in `backend/lib/falkor/`, `mission-control/components/memorygraph/`, `scripts/falkor-*.js`
12
+ > and `docker-compose.falkor.yml`, plus one-line taps in existing modules.
13
+
14
+ ---
15
+
16
+ ## Why a graph
17
+
18
+ Before this, relay's project memory was `memory.json`: flat arrays of chat events, edits and timeline rows, capped at
19
+ 2,000 events and 400 edits and rebuilt from transcripts on every sync. Decisions lived in a separate JSONL log. Collisions were
20
+ counters. None of it was connected. "Why does `auth.ts` look like this, who touched it, and who was blocked on it?" meant
21
+ three manual scans across three stores, and anything past the caps was gone.
22
+
23
+ In the graph, that question is a single traversal, and nothing ages out.
24
+
25
+ ## What's in it
26
+
27
+ ```
28
+ (Person)-[:RUNS]->(Agent)-[:RAN]->(Session)-[:HAS_TURN]->(Turn)
29
+ │ └─[:MADE]->(Edit)-[:MODIFIED]->(File)
30
+ └─[:TOUCHED {count,lastAt}]──────────────────────▲
31
+ (Edit)-[:PROMPTED_BY {via}]->(Turn {role:user}) inferred "why"
32
+ (Edit)-[:EXPLAINED_BY]->(Turn {role:assistant})
33
+ (Edit {source:patch})-[:SAME_CHANGE]->(Edit {source:transcript})
34
+ (Session)-[:HELD {count}]->(File) aggregated lock claims
35
+ (Contention)-[:ON]->(File), -[:HELD_BY]->(Session), -[:WANTED_BY]->(Session)
36
+ (Overlap)-[:ON]->(File), -[:BETWEEN]->(Edit)×2 two agents, same file, within 5 min
37
+ (Decision|Task)-[:ABOUT]->(File), -[:DURING]->(Session); (Agent)-[:DECIDED]->(Decision)
38
+ (File)-[:CO_EDITED {weight}]->(File) projection → PageRank, label propagation
39
+ (Agent)-[:OVERLAPS_WITH {weight}]->(Agent) projection → PageRank
40
+ ```
41
+
42
+ Where each part comes from:
43
+
44
+ | Source | Becomes |
45
+ |---|---|
46
+ | Agent transcripts (Claude Code, Cursor, Codex, Copilot, Antigravity) via `transcripts/sync.js` | Person, Agent, Session, Turn, Edit, `PROMPTED_BY`/`EXPLAINED_BY` |
47
+ | Watcher patches (`patches.js`) | `Edit {source:"patch"}`, attributed to the lock holder (`originAgentId`) |
48
+ | Room peers' snapshots (`roomSync.js`) | Teammates' sessions and edits, re-attached to their sessions |
49
+ | Every lock claim (`lockTable.claim`, wrapped once) | `HELD` counts, and a `Contention` for each block |
50
+ | Collision counters (`recordCollision`) | `Contention` for patch blocks and merge flags |
51
+ | `relay_report_decision` / `relay_update_task` (Central JSONL) | Decision, Task, `ABOUT`, `DURING` |
52
+ | Analytics job (debounced) | `Overlap`, `CO_EDITED`, `OVERLAPS_WITH`, `File.rank`, `File.community`, `Agent.entanglement` |
53
+
54
+ ### How the "why" is inferred
55
+
56
+ Agents rarely record decisions on their own, so the graph doesn't depend on them doing it. Every code edit in a transcript is
57
+ linked to the **last meaningful user request in the same session**. Filler like "continue", "yes" and "hi" is skipped, and the
58
+ filler is kept on the edge as `via`. So every change has an explanation without any agent cooperation. On real multi-agent
59
+ history, 100% of transcript edits get a prompt link.
60
+
61
+ ### Context at the moment of conflict
62
+
63
+ When an agent's pre-tool hook is denied a lock, the deny reason now says what the holder is doing:
64
+
65
+ ```
66
+ File `src/auth.ts` is locked by Claude Code:DESK:52b2…. Pick a different task.
67
+ They're working on: "switch the login flow to JWT tokens" (2 min ago).
68
+ ```
69
+
70
+ The holder's intent comes from an in-process cache (filled on ingest, and refilled from the graph after a restart). The lock
71
+ path never waits on the database.
72
+
73
+ #### How a lock finds its session, per agent
74
+
75
+ A lock id is `<label>:<host>:<session>` (`hooks/relay-hook-lib.js` → `lockAgentIdFor`). The session must be the same id the
76
+ transcript parser gives that conversation, or the graph can't connect the lock to what the holder was doing. Both sides use one
77
+ function, `backend/lib/transcripts/sessionId.js` → `sessionIdFromTranscript`. When a hook payload includes a transcript path,
78
+ the session is derived from that file, so the two sides can't disagree.
79
+
80
+ | Agent | Hook payload field used | Transcript session id | How it's verified |
81
+ |---|---|---|---|
82
+ | Claude Code | `transcript_path` (falls back to `session_id`) | `<session>.jsonl` file name | Live run; both equal the Claude session UUID |
83
+ | Cursor | `transcript_path` (`conversation_id` is ignored when a path exists; Cursor's docs don't say it equals the transcript id) | `agent-transcripts/<id>/<id>.jsonl` | Same function on both sides |
84
+ | Codex | `transcript_path`, else `session_id` (the thread id) | `session_meta.payload.id`, else the UUID at the end of `rollout-<ts>-<uuid>.jsonl` | Real rollout files: `payload.id` = filename UUID |
85
+ | Antigravity | `conversationId` | `brain/<uuid>/` directory | On disk, `conversations/<uuid>.pb` and `brain/<uuid>` share ids |
86
+ | Copilot | `sessionId` | chat session `sessionId`, else `chatSessions/<id>` file name | Documented field; not yet checked live |
87
+
88
+ `backend/lib/falkor/sessionjoin.test.js` parses a real-format transcript for each agent with the actual parser, builds the lock
89
+ id from that agent's documented hook payload, and asserts the two session keys are equal.
90
+
91
+ #### Agent support
92
+
93
+ | | Claude Code | Cursor | Codex | Copilot (VS Code agent) | Antigravity IDE |
94
+ |---|---|---|---|---|---|
95
+ | Turns (user + assistant) | ✅ | ✅ | ✅ | ✅ (transcript + chatSessions, deduped) | ✅ |
96
+ | Edits from the transcript | ✅ Edit/Write/MultiEdit | ✅ Write/StrReplace/… | ✅ `apply_patch`, one edit per file in the patch | ✅ create_file, replace_string_in_file, multi_replace…, apply_patch | ✅ CODE_ACTION + planner TargetFile |
97
+ | Edit → prompt (`PROMPTED_BY`) | ✅ | ✅ | ✅ | ✅ | ✅ |
98
+ | Real timestamps | ✅ | ⚠️ minute precision, from `<timestamp>` in newer Cursor builds; older turns flagged synthetic | ✅ | ✅ | ✅ |
99
+ | Lock ↔ session join | ✅ live-verified | ✅ via `transcript_path` | ✅ | ✅ (documented `sessionId`) | ✅ |
100
+ | Hook claims every edit tool | ✅ | ✅ | ✅ incl. files inside `apply_patch` | ✅ incl. `replace_string_in_file`, `apply_patch` | ✅ |
101
+ | MCP calls attributed (`RELAY_AGENT_LABEL`) | ✅ | ✅ | ✅ | ✅ | ✅ |
102
+
103
+ Measured on one developer machine's real history: Copilot 223/223 edits prompt-linked (from 0 parsed), Codex 144/144 (from 0
104
+ parsed), Cursor 54/120 edits with real times (from 0).
105
+
106
+ Not supported: the **older Antigravity desktop app** (`~/.gemini/antigravity/`) keeps conversations as binary protobuf
107
+ (`conversations/<uuid>.pb`), with no JSONL transcript to read. The Antigravity IDE (`~/.gemini/antigravity-ide/`) is supported.
108
+
109
+ Whether each agent actually *follows* the instruction to call `relay_session_brief` before editing unfamiliar files depends on
110
+ the agent. Check it during a live run (#15).
111
+
112
+ ## Agent tools (MCP)
113
+
114
+ | Tool | What it does |
115
+ |---|---|
116
+ | `relay_session_brief { paths }` | Call before touching files: who is on them now and why, past blocks, decisions, the prompts behind recent changes, who to ask, and files in the same work area. Returns markdown and data. |
117
+ | `relay_graph_query { query, … }` | Named queries: `file_story`, `who_to_ask`, `contention_trace`, `search_memory` (full-text over every agent conversation), `agent_activity`, `hotspots` (PageRank), `overlaps`, `decision_trace`, `session_brief`, `overview` |
118
+ | `relay_report_decision { decision, files }` | Now takes `files`, and is attributed to the calling agent (`RELAY_AGENT_LABEL`) and its session |
119
+ | `relay_room_brief` (`/relay ask`) | Now ends with a short "Project memory graph" section |
120
+
121
+ All graph tools are read-only and available to room guests over the tunnel. A guest without FalkorDB gets the host's graph.
122
+
123
+ Every response includes the Cypher that produced it.
124
+
125
+ ## Mission Control
126
+
127
+ **Memory Graph** tab (`/?tab=graph`): live counts, recent blocks with the holder's intent, PageRank hotspots, full-text search
128
+ across every agent conversation, and a canvas around one file (agents → sessions and their prompts → file → blocks, decisions,
129
+ co-edited files). Click a co-edited file to walk the graph. Each file has five explain tabs, including **What agents are told**:
130
+ the exact brief an agent gets.
131
+
132
+ ## Setup
133
+
134
+ The same commands work on Windows, macOS and Linux. No environment variables are needed.
135
+
136
+ ### 1. Prerequisites
137
+
138
+ - Relay installed and a repo attached ([Quickstart](./QUICKSTART.md)): Node 20+, `relay init` / `relay add .` in the repo.
139
+ - **Docker** (Docker Desktop on Windows/macOS, the Docker engine on Linux) to run FalkorDB locally. If you use a hosted
140
+ FalkorDB (for example FalkorDB Cloud), skip Docker and use its URL in step 3.
141
+
142
+ ### 2. Start FalkorDB
143
+
144
+ ```bash
145
+ relay graph up
146
+ ```
147
+
148
+ This starts the `falkordb/falkordb` container from `docker-compose.falkor.yml`: the database on `localhost:6379`, FalkorDB's own
149
+ browser UI on <http://localhost:3010>, and data in a named Docker volume that survives restarts.
150
+
151
+ ### 3. Turn the graph on
152
+
153
+ ```bash
154
+ relay graph enable # local Docker: falkor://localhost:6379
155
+ relay graph enable falkor://default:PASSWORD@HOST:PORT # hosted FalkorDB
156
+ ```
157
+
158
+ `enable` checks that the database answers, saves the URL to `~/.relay/falkor.json` (readable only by you), and, if `relay serve`
159
+ is already running, switches it on immediately. You can also do this from Mission Control: **Memory Graph** tab → **Enable**.
160
+
161
+ ### 4. Refresh agent configs (existing workspaces only)
162
+
163
+ Workspaces set up before the memory graph need their agent wiring refreshed once, so each agent's MCP entry learns its name
164
+ (`RELAY_AGENT_LABEL`), the new instructions, and the updated Copilot edit matcher:
165
+
166
+ ```bash
167
+ relay init # in each repo, then restart your agents
168
+ ```
169
+
170
+ ### 5. Start relay and check it
171
+
172
+ ```bash
173
+ relay serve
174
+ relay graph status
175
+ ```
176
+
177
+ `relay graph status` should report `graph running, connected` and the database as reachable. `relay doctor` shows the same thing
178
+ as one line.
179
+
180
+ ### 6. Bring in history you already have (optional)
181
+
182
+ ```bash
183
+ relay graph rebuild # every project; or: relay graph rebuild <projectId>
184
+ ```
185
+
186
+ Project ids are listed by `curl http://127.0.0.1:3001/api/projects`. New agent activity is ingested automatically when each
187
+ agent turn ends.
188
+
189
+ ### 7. Use it
190
+
191
+ - **Mission Control** → **Memory Graph** tab (`http://localhost:3002/?tab=graph`)
192
+ - **In any agent:** "call relay_session_brief for src/auth.ts", or `relay_graph_query` with `query: "file_story"`
193
+ - **Room guests:** nothing to install. Their agents' graph queries go to the host's graph over the room tunnel.
194
+
195
+ ### Environment variables (optional)
196
+
197
+ Everything above works without these. Set them only to override the saved config for one run.
198
+
199
+ | Env | Default | Meaning |
200
+ |---|---|---|
201
+ | `RELAY_FALKOR_URL` | from `~/.relay/falkor.json` | `falkor://[user:pass@]host:port`; overrides the saved URL |
202
+ | `RELAY_FALKOR_DISABLED` | `0` | `1` = off, whatever is saved |
203
+ | `RELAY_FALKOR_TEXT` | `1` | `0` = metadata-only graph (no chat text or diffs) |
204
+ | `RELAY_FALKOR_TEXT_DAYS` | `90` | Retention window in days: `Turn.text` and `Edit.diffHead` older than this are nulled while keeping all nodes and edges |
205
+ | `RELAY_FALKOR_GRAPH_PREFIX` | `relay_` | graph name prefix (`<prefix><projectId>`) |
206
+ | `RELAY_PUBLIC_READONLY` | `0` | `1` = hosted demo mode: non-loopback callers may only run read queries |
207
+
208
+ Setting one for a single run:
209
+
210
+ | Shell | Command |
211
+ |---|---|
212
+ | bash / zsh | `RELAY_FALKOR_TEXT=0 relay serve` |
213
+ | PowerShell | `$env:RELAY_FALKOR_TEXT = "0"; relay serve` |
214
+ | cmd | `set RELAY_FALKOR_TEXT=0 && relay serve` |
215
+
216
+ With no URL saved or set, relay behaves exactly as it did before the memory graph. If FalkorDB goes down mid-session, relay keeps
217
+ coordinating; history is re-sent when FalkorDB comes back, and non-replayable events (patches, blocks) are buffered with a bound.
218
+
219
+ ### Troubleshooting
220
+
221
+ | Symptom | Cause and fix |
222
+ |---|---|
223
+ | `relay graph up`: *Docker is not running* (Windows: `failed to connect to the docker API at npipe:////./pipe/dockerDesktopLinuxEngine`) | Start Docker Desktop, wait until it says it's running, run `relay graph up` again |
224
+ | `relay graph up` fails with a port in use | Something else (often a local Redis) holds 6379. Change the host port in `docker-compose.falkor.yml` (`"6380:6379"`) and `relay graph enable falkor://localhost:6380` |
225
+ | `relay graph status`: *FalkorDB unreachable (retrying)* | The container stopped (`docker ps`). `relay graph up`. Relay reconnects by itself within ~30 s |
226
+ | Memory Graph tab: *not enabled on this relay* | Click **Enable**, or `relay graph enable` |
227
+ | Graph is empty | Ingest runs when an agent turn ends (Stop hook) or on `relay_sync`. For history from before, `relay graph rebuild`. Check `relay doctor` that hooks reach relay |
228
+ | `relay graph status` shows **NEEDS REBUILD** | An update changed how history is stored: `relay graph rebuild` |
229
+ | `RELAY_FALKOR_URL` seems ignored, or wins over `enable` | The env var beats the saved file. `relay graph status` shows which one is in use (`from env` / `from file`) |
230
+ | Container crash-loops after a hard shutdown | See [Persistence and recovery](#persistence-and-recovery) |
231
+ | Running a second relay for testing without touching your real setup | Point `HOME` (and `USERPROFILE` on Windows) at a scratch directory and set `RELAY_SKIP_GLOBAL_HOOKS=1`, so the test relay doesn't rewrite `~/.claude` / `~/.cursor` hooks |
232
+
233
+ ## Persistence and recovery
234
+
235
+ - `docker-compose.falkor.yml` mounts the volume at **`/var/lib/falkordb/data`**, where the image actually writes. (`/data` in the
236
+ image is only a symlink, so a volume mounted there persists nothing.)
237
+ - Persistence uses **RDB snapshots** (`--save 30 1 --save 300 10`), **not AOF**: replaying FalkorDB's AOF effect stream crashed
238
+ 4.22.1 on restart (#21).
239
+ - A snapshot can miss the last ≤30 s. Every ingest stamps `Meta.lastIngestAt` in the graph before saving its local cursor. On
240
+ the first ingest after relay starts, a cursor newer than the graph's stamp means data was lost (or the database is new or
241
+ wiped), so relay re-sends the history. `MERGE` makes the re-send idempotent.
242
+ - The full rebuild is still available: `npm run falkor:rebuild -- <projectId|all>`. It only knows what `memory.json` still holds,
243
+ so the re-send above is the normal path.
244
+ - **In-place migrations and retention (#18).** Schema and ingest version updates run in-place migrations (`backend/lib/falkor/migrations.js`, recorded on `Meta.migrations`) at startup so history older than `memory.json`'s 2,000-event cap is never wiped on version bumps. Analytics also runs a retention pass (`RELAY_FALKOR_TEXT_DAYS`, default 90 days) that nulls `Turn.text` and `Edit.diffHead` on expired nodes while keeping every node, edge, and timestamp intact, and updates `CO_EDITED` incrementally (`Meta.lastAnalyticsAt`).
245
+ - **Inspecting and backing up (`relay graph stats`).** Run `relay graph stats` to inspect node/edge counts by label, turn time spans, applied migrations, and memory footprint. To back up a running container, run `docker exec relay-falkordb redis-cli BGSAVE` and copy `/var/lib/falkordb/data/dump.rdb`.
246
+
247
+ If FalkorDB ever crash-loops on start: `docker logs relay-falkordb`, copy the data out
248
+ (`docker cp relay-falkordb:/var/lib/falkordb/data ./falkordb-backup`), then `docker compose -f docker-compose.falkor.yml down`
249
+ and `up -d`. Relay re-sends history on its next sync.
250
+
251
+ ## Design notes
252
+
253
+ - **Single writer.** Only `relay serve` writes. Per-agent MCP stdio processes read through `relay serve`'s HTTP API, and their
254
+ decisions reach the graph through a JSONL tailer. No cross-process cursor races.
255
+ - **Idempotent.** Keys are content-addressed. Turn keys hash owner, agent, session, timestamp, role and the first 200 chars,
256
+ so a local turn and the same turn clipped in a peer snapshot converge. Counters only bump for nodes a batch actually created
257
+ (batch tokens), so replaying a batch is a no-op.
258
+ - **Noise control.** Lock claims are aggregated into `HELD` counts, retry storms collapse into one `Contention` per 10 s bucket,
259
+ relay's own config writes aren't treated as edits, and a change seen both as a transcript edit and a watcher patch is counted
260
+ once.
261
+ - **Privacy.** Text is redacted for common secret formats before it's written. `RELAY_FALKOR_TEXT=0` stores none at all.
262
+
263
+ ## Tests
264
+
265
+ ```bash
266
+ npm test # unit tests; live tests skip
267
+ RELAY_FALKOR_TEST_URL=falkor://localhost:6379 npm test # + integration tests against FalkorDB
268
+ npm run falkor:spike # re-verify every Cypher feature the graph relies on
269
+ ```
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPostTool("antigravity");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPreRead("antigravity");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPreTool("antigravity");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runStop("antigravity");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPostTool("claude");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPreRead("claude");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPreTool("claude");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runStop("claude");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPostTool("codex");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPreRead("codex");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPreTool("codex");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runStop("codex");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPostTool("copilot");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPreRead("copilot");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPreTool("copilot");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runStop("copilot");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPostTool("cursor");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPreRead("cursor");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runPreTool("cursor");
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("./relay-hook-lib").runStop("cursor");