-
-
Notifications
You must be signed in to change notification settings - Fork 7
Architecture
bastra-recall is a TypeScript monorepo (packages/):
| Package | Role |
|---|---|
@bastra-recall/core |
The engine. Vault loader (vault.ts), BM25 index + hybrid recall pipeline (search.ts), embeddings (embeddings.ts), save path + frontmatter schema (save.ts, schema.ts). No transport, no I/O beyond the vault. |
@bastra-recall/daemon |
The process. Wraps core in two transports — MCP over stdio (index.ts) and HTTP REST (http.ts) — plus the tool handlers (tool-handlers.ts), the reflex hooks (hook.ts, session-hook.ts), the MCP forwarder (mcp-forwarder.ts), and telemetry. |
skill |
The Claude Code Skill (SKILL.md) — the trigger discipline that teaches the assistant when to save and recall. |
@bastra-recall/statusline |
A powerline statusline segment showing live recall stats + vault size, fed out-of-band (Claude Code does not render MCP progress in the TUI). |
The core idea: one local daemon holds the in-memory index and serves every AI tool at once, instead of each client spawning its own vault.
Vault (Markdown + YAML frontmatter, Obsidian-compatible)
│ chokidar file-watcher (auto-polls on cloud mounts)
▼
┌──────────────────────────────────────────────┐
│ bastra-recall daemon (single local process) │
│ • core: BM25 + embeddings + RRF │
│ • MCP stdio server (index.ts) │
│ • HTTP server :6723 (http.ts) │
│ • idle self-shutdown (respawns on demand) │
└──────────────────────────────────────────────┘
▲ stdio (per session) ▲ HTTP /api/v1/*
│ │
mcp-forwarder ChatGPT Custom GPT,
(Claude Code / Desktop / web apps, scripts
Cursor — stdio → HTTP)
-
MCP clients (Claude Code, Desktop, Cursor) connect to a thin
mcp-forwarder: it speaks MCP over stdio to the client and forwards to the shared daemon over loopback HTTP. The forwarder is a per-session stdio child; the daemon is shared. -
Non-MCP clients (ChatGPT, scripts) call the HTTP REST surface directly:
POST /api/v1/recall,/api/v1/load_memory, etc. -
Reflex hooks (
PreToolUse,SessionStart) are separate short-lived CLI processes that POST to the daemon's loopback endpoints.
| Route | Auth | Purpose |
|---|---|---|
GET /health |
open | liveness + version + vault size |
POST /hook/recall |
open | recall for the reflex hooks; supports SSE streaming of pipeline stages |
POST /api/v1/recall |
token* | the recall tool for REST clients (→ recallHandler) |
POST /api/v1/load_memory |
token* |
load_memory (→ loadMemoryHandler) |
POST /api/v1/save_memory |
token* | save_memory |
POST /api/v1/{find,read,save,...}_document |
token* | document tools |
* loopback requests skip auth by default (BASTRA_AUTH_LOOPBACK_SKIP=1).
This trips people up. The mcp-forwarder does not send the recall tool call to /api/v1/recall. It routes it through /hook/recall (SSE) via callRecallStreaming, so it can stream pipeline stages for live progress UX. Consequences:
- The forwarder reassembles the tool result from the SSE stream and historically attached a
stagesblock itself (now removed). - The
/hook/recallpath defaultsexpand_hopsdifferently than the explicit tool — the forwarder setsexpand_hops=0for the MCP path (exactlykhits), while thePreToolUsehook CLI usesexpand_hops=1(1-hop neighbors help proactive hints).
Takeaway: to change what Claude Code sees from recall, edit mcp-forwarder.ts (callRecallStreaming + reassembly), not only recallHandler. recallHandler / /api/v1/recall is the path for REST clients and the Mac-App. Forwarder code changes only take effect in a new session (the forwarder is loaded once at session start).
- The forwarder dies with its client session (stdin EOF / SIGTERM). If the shared daemon is down when a tool call arrives, the forwarder auto-spawns it.
- The daemon self-terminates after
BASTRA_DAEMON_IDLE_SHUTDOWN_MS(default 30 min) of no activity —/healthpings don't count as activity. The next recall respawns it. This keeps the process table clean (no orphaned daemons after sessions end). -
Restart rule: to pick up new daemon code, just
pkill -f "packages/daemon/dist/index.js"and let the forwarder respawn it. Do not manuallynohup node dist/index.js— that creates an orphan that fights the auto-spawn for the port.
bastra-recall ist ein TypeScript-Monorepo (packages/):
| Paket | Rolle |
|---|---|
@bastra-recall/core |
Die Engine. Vault-Loader (vault.ts), BM25-Index + Hybrid-Recall-Pipeline (search.ts), Embeddings (embeddings.ts), Save-Pfad + Frontmatter-Schema (save.ts, schema.ts). Kein Transport, keine I/O außer dem Vault. |
@bastra-recall/daemon |
Der Prozess. Verpackt core in zwei Transports — MCP über stdio (index.ts) und HTTP REST (http.ts) — plus Tool-Handler (tool-handlers.ts), Reflex-Hooks (hook.ts, session-hook.ts), MCP-Forwarder (mcp-forwarder.ts) und Telemetry. |
skill |
Der Claude-Code-Skill (SKILL.md) — die Trigger-Disziplin, die dem Assistenten beibringt, wann gespeichert und abgerufen wird. |
@bastra-recall/statusline |
Ein Powerline-Statusline-Segment mit Live-Recall-Stats + Vault-Größe, out-of-band gefüttert (Claude Code rendert MCP-Progress nicht im TUI). |
Die Kernidee: ein lokaler Daemon hält den In-Memory-Index und bedient alle AI-Tools gleichzeitig — statt dass jeder Client seinen eigenen Vault hochfährt.
Vault (Markdown + YAML-Frontmatter, Obsidian-kompatibel)
│ chokidar File-Watcher (auto-polling auf Cloud-Mounts)
▼
┌──────────────────────────────────────────────┐
│ bastra-recall daemon (ein lokaler Prozess) │
│ • core: BM25 + Embeddings + RRF │
│ • MCP-stdio-Server (index.ts) │
│ • HTTP-Server :6723 (http.ts) │
│ • Idle-Self-Shutdown (respawnt on demand) │
└──────────────────────────────────────────────┘
▲ stdio (pro Session) ▲ HTTP /api/v1/*
│ │
mcp-forwarder ChatGPT Custom GPT,
(Claude Code / Desktop / Web-Apps, Skripte
Cursor — stdio → HTTP)
-
MCP-Clients (Claude Code, Desktop, Cursor) verbinden sich mit einem dünnen
mcp-forwarder: er spricht MCP über stdio mit dem Client und leitet per Loopback-HTTP an den geteilten Daemon weiter. Der Forwarder ist ein stdio-Kind pro Session; der Daemon ist geteilt. -
Nicht-MCP-Clients (ChatGPT, Skripte) rufen die HTTP-REST-Fläche direkt:
POST /api/v1/recall,/api/v1/load_memory, usw. -
Reflex-Hooks (
PreToolUse,SessionStart) sind separate kurzlebige CLI-Prozesse, die an die Loopback-Endpunkte des Daemons POSTen.
| Route | Auth | Zweck |
|---|---|---|
GET /health |
offen | Liveness + Version + Vault-Größe |
POST /hook/recall |
offen | Recall für die Reflex-Hooks; unterstützt SSE-Streaming der Pipeline-Stages |
POST /api/v1/recall |
Token* | das recall-Tool für REST-Clients (→ recallHandler) |
POST /api/v1/load_memory |
Token* |
load_memory (→ loadMemoryHandler) |
POST /api/v1/save_memory |
Token* | save_memory |
POST /api/v1/{find,read,save,...}_document |
Token* | Dokument-Tools |
* Loopback-Requests überspringen Auth standardmäßig (BASTRA_AUTH_LOOPBACK_SKIP=1).
Hierüber stolpert man. Der mcp-forwarder schickt den recall-Tool-Call nicht an /api/v1/recall. Er routet ihn über /hook/recall (SSE) via callRecallStreaming, um die Pipeline-Stages für die Live-Progress-UX zu streamen. Folgen:
- Der Forwarder baut das Tool-Result aus dem SSE-Stream selbst zusammen und hängte historisch einen
stages-Block an (jetzt entfernt). - Der
/hook/recall-Pfad hat einen anderenexpand_hops-Default als das explizite Tool — der Forwarder setzt für den MCP-Pfadexpand_hops=0(genaukHits), während diePreToolUse-Hook-CLIexpand_hops=1nutzt (1-Hop-Nachbarn helfen den proaktiven Hints).
Merke: Um zu ändern, was Claude Code von recall sieht, editiere mcp-forwarder.ts (callRecallStreaming + Reassembly), nicht nur recallHandler. recallHandler / /api/v1/recall ist der Pfad für REST-Clients und die Mac-App. Forwarder-Code-Änderungen wirken erst in einer neuen Session (der Forwarder wird einmal beim Session-Start geladen).
- Der Forwarder stirbt mit seiner Client-Session (stdin-EOF / SIGTERM). Ist der geteilte Daemon beim Eintreffen eines Tool-Calls unten, auto-spawnt der Forwarder ihn.
- Der Daemon beendet sich nach
BASTRA_DAEMON_IDLE_SHUTDOWN_MS(Default 30 min) ohne Aktivität —/health-Pings zählen nicht als Aktivität. Der nächste Recall respawnt ihn. Das hält die Prozessliste sauber (keine verwaisten Daemons nach Session-Ende). -
Restart-Regel: Um neuen Daemon-Code zu laden, einfach
pkill -f "packages/daemon/dist/index.js"und den Forwarder respawnen lassen. Nicht manuellnohup node dist/index.js— das erzeugt einen Orphan, der mit dem Auto-Spawn um den Port kämpft.