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.
- package/LICENSE +21 -0
- package/README.md +290 -2
- package/backend/coordinator/client.js +364 -0
- package/backend/coordinator/lockTable.js +649 -0
- package/backend/coordinator/server.js +179 -0
- package/backend/lib/centralAuth.js +62 -0
- package/backend/lib/centralStore.js +112 -0
- package/backend/lib/centralSync.js +88 -0
- package/backend/lib/cloneRepo.js +108 -0
- package/backend/lib/collisionMetrics.js +231 -0
- package/backend/lib/conflicts.js +35 -0
- package/backend/lib/dashboard.js +53 -0
- package/backend/lib/depGraph.js +393 -0
- package/backend/lib/deps.js +46 -0
- package/backend/lib/dirtyFiles.js +33 -0
- package/backend/lib/editMatchers.js +55 -0
- package/backend/lib/falkor/analytics.js +228 -0
- package/backend/lib/falkor/bus.js +16 -0
- package/backend/lib/falkor/central.js +281 -0
- package/backend/lib/falkor/client.js +165 -0
- package/backend/lib/falkor/config.js +73 -0
- package/backend/lib/falkor/coord.js +299 -0
- package/backend/lib/falkor/cursor.js +58 -0
- package/backend/lib/falkor/fixtures.js +85 -0
- package/backend/lib/falkor/index.js +146 -0
- package/backend/lib/falkor/ingest.js +233 -0
- package/backend/lib/falkor/intents.js +32 -0
- package/backend/lib/falkor/keys.js +102 -0
- package/backend/lib/falkor/migrations.js +56 -0
- package/backend/lib/falkor/project.js +307 -0
- package/backend/lib/falkor/query.js +344 -0
- package/backend/lib/falkor/redact.js +30 -0
- package/backend/lib/falkor/routes.js +81 -0
- package/backend/lib/falkor/schema.js +108 -0
- package/backend/lib/falkor/write.js +281 -0
- package/backend/lib/githubLogin.js +108 -0
- package/backend/lib/githubOAuth.js +42 -0
- package/backend/lib/githubRepo.js +117 -0
- package/backend/lib/harvest.js +85 -0
- package/backend/lib/httpServer.js +27 -0
- package/backend/lib/installAgentRelay.js +235 -0
- package/backend/lib/installHooks.js +309 -0
- package/backend/lib/lamport.js +14 -0
- package/backend/lib/localBrowse.js +109 -0
- package/backend/lib/memorySync.js +63 -0
- package/backend/lib/notices.js +143 -0
- package/backend/lib/ot.js +74 -0
- package/backend/lib/patches.js +192 -0
- package/backend/lib/paths.js +85 -0
- package/backend/lib/peerClash.js +64 -0
- package/backend/lib/presence.js +35 -0
- package/backend/lib/relayOsBlock.js +84 -0
- package/backend/lib/repoSnapshot.js +125 -0
- package/backend/lib/room.js +498 -0
- package/backend/lib/roomAsk.js +148 -0
- package/backend/lib/roomAuth.js +215 -0
- package/backend/lib/roomControl.js +226 -0
- package/backend/lib/roomLocks.js +201 -0
- package/backend/lib/roomSignal.js +265 -0
- package/backend/lib/roomSync.js +194 -0
- package/backend/lib/seed.js +107 -0
- package/backend/lib/sseHub.js +40 -0
- package/backend/lib/store.js +79 -0
- package/backend/lib/supergraph.js +46 -0
- package/backend/lib/transcripts/discover.js +338 -0
- package/backend/lib/transcripts/parse.js +558 -0
- package/backend/lib/transcripts/sessionId.js +36 -0
- package/backend/lib/transcripts/sync.js +291 -0
- package/backend/lib/transcripts/util.js +299 -0
- package/backend/lib/treeSitterImports.js +219 -0
- package/backend/lib/watchWorkspace.js +42 -0
- package/backend/mcp/README.md +193 -0
- package/backend/mcp/server.js +42 -0
- package/backend/mcp/tools.js +484 -0
- package/backend/server.js +3839 -0
- package/cli/graph.js +278 -0
- package/cli/relay.js +419 -0
- package/docker-compose.falkor.yml +17 -0
- package/docs/MEMORY-GRAPH.md +269 -0
- package/hooks/relay-antigravity-post-tool.js +2 -0
- package/hooks/relay-antigravity-pre-read.js +2 -0
- package/hooks/relay-antigravity-pre-tool.js +2 -0
- package/hooks/relay-antigravity-stop.js +2 -0
- package/hooks/relay-claude-post-tool.js +2 -0
- package/hooks/relay-claude-pre-read.js +2 -0
- package/hooks/relay-claude-pre-tool.js +2 -0
- package/hooks/relay-claude-stop.js +2 -0
- package/hooks/relay-codex-post-tool.js +2 -0
- package/hooks/relay-codex-pre-read.js +2 -0
- package/hooks/relay-codex-pre-tool.js +2 -0
- package/hooks/relay-codex-stop.js +2 -0
- package/hooks/relay-copilot-post-tool.js +2 -0
- package/hooks/relay-copilot-pre-read.js +2 -0
- package/hooks/relay-copilot-pre-tool.js +2 -0
- package/hooks/relay-copilot-stop.js +2 -0
- package/hooks/relay-cursor-post-tool.js +2 -0
- package/hooks/relay-cursor-pre-read.js +2 -0
- package/hooks/relay-cursor-pre-tool.js +2 -0
- package/hooks/relay-cursor-stop.js +2 -0
- package/hooks/relay-hook-lib.js +621 -0
- package/mission-control/out/404.html +1 -0
- package/mission-control/out/__next.__PAGE__.txt +26 -0
- package/mission-control/out/__next._full.txt +21 -0
- package/mission-control/out/__next._tree.txt +4 -0
- package/mission-control/out/_next/static/chunks/0-3l04fnym95-.js +22 -0
- package/mission-control/out/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
- package/mission-control/out/_next/static/chunks/0ehjiuuxbbhq9.js +1 -0
- package/mission-control/out/_next/static/chunks/1q0cmt0kqufow.js +2 -0
- package/mission-control/out/_next/static/chunks/1ya1u0izct4f6.js +1 -0
- package/mission-control/out/_next/static/chunks/25rvg4bnew99f.css +4 -0
- package/mission-control/out/_next/static/chunks/2n66mlnb34vjq.js +1 -0
- package/mission-control/out/_next/static/chunks/2xar70jzx8f1y.css +1 -0
- package/mission-control/out/_next/static/chunks/31cgqj_-l15yt.js +31 -0
- package/mission-control/out/_next/static/chunks/3_5x4qn1_b4al.js +1 -0
- package/mission-control/out/_next/static/chunks/3o1tiwncjss_k.js +1 -0
- package/mission-control/out/_next/static/chunks/3vq9c4hivvgn_.js +1 -0
- package/mission-control/out/_next/static/chunks/turbopack-3hsllzpnzz12p.js +1 -0
- package/mission-control/out/_next/static/dglM39XRu0cmneClUkGYd/_buildManifest.js +11 -0
- package/mission-control/out/_next/static/dglM39XRu0cmneClUkGYd/_clientMiddlewareManifest.js +1 -0
- package/mission-control/out/_next/static/dglM39XRu0cmneClUkGYd/_ssgManifest.js +1 -0
- package/mission-control/out/_next/static/media/icon.27otmh_fv3buh.svg +4 -0
- package/mission-control/out/_not-found/__next._full.txt +18 -0
- package/mission-control/out/_not-found/__next._not-found/__PAGE__.txt +24 -0
- package/mission-control/out/_not-found/__next._tree.txt +3 -0
- package/mission-control/out/_not-found.html +1 -0
- package/mission-control/out/_not-found.txt +18 -0
- package/mission-control/out/agents/antigravity.png +0 -0
- package/mission-control/out/agents/claude.png +0 -0
- package/mission-control/out/agents/codex.png +0 -0
- package/mission-control/out/agents/copilot.png +0 -0
- package/mission-control/out/agents/cursor.png +0 -0
- package/mission-control/out/icon.svg +4 -0
- package/mission-control/out/index.html +1 -0
- package/mission-control/out/index.txt +21 -0
- package/mission-control/out/logo.png +0 -0
- package/mission-control/out/logo_transparent.png +0 -0
- package/package.json +74 -4
- package/scripts/falkor-rebuild.js +57 -0
- 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
|
+
```
|