-
Notifications
You must be signed in to change notification settings - Fork 6
feat: Add Beads (bd) integration for persistent agent memory via CLI skill and session hooks #157
Description
Problem
copilot-bridge agents lose all task context on session restart. The only persistence mechanism is MEMORY.md — a flat, unstructured markdown file with no query capability, no dependency tracking, no concurrency safety, and unbounded growth.
Proposed Solution
Integrate Beads (bd) — a distributed, Dolt-backed graph issue tracker designed for AI agent memory — via a CLI skill file and session lifecycle hooks.
Journey & Key Decisions
Why CLI over MCP
copilot-bridge agents have shell access via the bash tool. The beads-mcp MCP server costs 10–50k tokens per session in tool schema overhead. Beads itself recommends CLI in shell-capable environments. A .agent.md skill file provides the same discoverability at zero token cost.
Why bd backup export-git as the sessionEnd action
Three sync tiers exist: local Dolt (automatic), git branch JSONL backup, full Dolt remote. The git branch tier requires zero new infrastructure — it reuses the existing workspace repo. Right for most single-bot deployments with no extra accounts or services.
Why bd remember must be called at point of discovery, not session end
Dolt commits immediately on every write. Memories stored mid-session survive any ungraceful shutdown. Batching bd remember to session end means an interrupted session loses everything. The pattern: call it inline, like a logger, not like a report.
Why sessionEnd is awaited but sessionStart is non-blocking
sessionEnd fires before destroySession() — the hook must complete (e.g. git backup) before Dolt shuts down. sessionStart fires after the session is already live — awaiting it would delay the bot's first response. Fire-and-forget is correct there.
Dead code discovery: sessionStart/sessionEnd were never wired
During implementation we found that both hook types were fully defined in hooks-loader.ts, mapped in HOOK_TYPE_MAP, and covered by unit tests — but session-manager.ts never called them. Silent no-op for any user who had configured these hooks.
Event ordering bug (found in code review)
Original wiring unsubscribed event listeners before awaiting sessionEnd. During the await window, a concurrent sendMessage() could reuse the cached session with no listeners wired, silently dropping response/usage events. Fix: fire sessionEnd before unsub().
Stale hook cache on /reload and /new (found in code review)
resolveHooks() caches per working directory. Since /reload is designed to pick up config changes including hooks.json, the cache must be invalidated before resolving hooks at these lifecycle points.
Bugs Found During Live Validation
-
allowWorkspaceHooksbelongs underdefaults, not config top level.session-manager.tsreadsgetConfig().defaults.allowWorkspaceHooks— top-level key is silently ignored. -
/bin/bashnotbash— in nvm-managed Node.js, subprocess PATH may not includebash, causingspawn bash ENOENT. Use absolute path/bin/bashon non-Windows. -
cwdinhooks.jsonis relative tobaseDir(the directory containinghooks.json). Settingcwd: '.github/hooks'when hooks.json is already in.github/hooks/doubles the path → ENOENT. Always usecwd: '.'. -
Hook scripts must output valid JSON —
hooks-loaderexpects JSON on stdout from all hook scripts. Scripts outputting plain text trigger[WARN] Hook command returned invalid JSON. Fix: redirectbdoutput to/dev/nullandecho '{}'as final line.
Validation Results ✅
Tested end-to-end against a live copilot-bridge instance:
sessionEndhook fired on/reload→ new commit onbeads-backupgit branch confirmedsessionStarthook fired cleanly after session reload → no warningsbd primerecovered all 9 stored memories across session boundarybd backup export-gitidempotent — only commits when data changed
Implementation
Phase 1 — Docs and config (PR #159)
docs/beads.md— full integration guide: setup, hooks, 3-tier sync, MCP alternative, troubleshootingtemplates/agents/beads.agent.md— agent skill file with completebdworkflow and point-of-discovery memory disciplinetemplates/agents/AGENTS.md— Beads section added;MEMORY.mdkept as fallbackconfig.sample.json—shell(bd)added to default permissions allow list
Phase 2 — Session hooks wiring (PR #158)
- Wire
sessionStartincreateNewSession()andattachSession()(non-blocking) - Wire
sessionEndinnewSession()andreloadSession()(awaited, before unsub) - Invalidate hooks cache on
/newand/reload - Fix
spawn bash ENOENT— use/bin/bashabsolute path
Acceptance Criteria
- Session hooks (
sessionStart,sessionEnd) fire at correct lifecycle points - Hook scripts execute without errors or warnings in live validation
-
bd backup export-gitruns automatically on session end -
bd primerecovers context automatically on session start - Documentation covers setup, gotchas, and 3-tier sync strategy
- Agent skill file includes point-of-discovery memory discipline
- Merged to
main