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
-
allowWorkspaceHooks belongs under defaults, not config top level. session-manager.ts reads getConfig().defaults.allowWorkspaceHooks — top-level key is silently ignored.
-
/bin/bash not bash — in nvm-managed Node.js, subprocess PATH may not include bash, causing spawn bash ENOENT. Use absolute path /bin/bash on non-Windows.
-
cwd in hooks.json is relative to baseDir (the directory containing hooks.json). Setting cwd: '.github/hooks' when hooks.json is already in .github/hooks/ doubles the path → ENOENT. Always use cwd: '.'.
-
Hook scripts must output valid JSON — hooks-loader expects JSON on stdout from all hook scripts. Scripts outputting plain text trigger [WARN] Hook command returned invalid JSON. Fix: redirect bd output to /dev/null and echo '{}' as final line.
Validation Results ✅
Tested end-to-end against a live copilot-bridge instance:
sessionEnd hook fired on /reload → new commit on beads-backup git branch confirmed
sessionStart hook fired cleanly after session reload → no warnings
bd prime recovered all 9 stored memories across session boundary
bd backup export-git idempotent — 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, troubleshooting
templates/agents/beads.agent.md — agent skill file with complete bd workflow and point-of-discovery memory discipline
templates/agents/AGENTS.md — Beads section added; MEMORY.md kept as fallback
config.sample.json — shell(bd) added to default permissions allow list
Phase 2 — Session hooks wiring (PR #158)
- Wire
sessionStart in createNewSession() and attachSession() (non-blocking)
- Wire
sessionEnd in newSession() and reloadSession() (awaited, before unsub)
- Invalidate hooks cache on
/new and /reload
- Fix
spawn bash ENOENT — use /bin/bash absolute path
Acceptance Criteria
References
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
bashtool. Thebeads-mcpMCP server costs 10–50k tokens per session in tool schema overhead. Beads itself recommends CLI in shell-capable environments. A.agent.mdskill file provides the same discoverability at zero token cost.Why
bd backup export-gitas the sessionEnd actionThree 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 remembermust be called at point of discovery, not session endDolt commits immediately on every write. Memories stored mid-session survive any ungraceful shutdown. Batching
bd rememberto session end means an interrupted session loses everything. The pattern: call it inline, like a logger, not like a report.Why
sessionEndis awaited butsessionStartis non-blockingsessionEndfires beforedestroySession()— the hook must complete (e.g. git backup) before Dolt shuts down.sessionStartfires 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/sessionEndwere never wiredDuring implementation we found that both hook types were fully defined in
hooks-loader.ts, mapped inHOOK_TYPE_MAP, and covered by unit tests — butsession-manager.tsnever 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 concurrentsendMessage()could reuse the cached session with no listeners wired, silently dropping response/usage events. Fix: firesessionEndbeforeunsub().Stale hook cache on
/reloadand/new(found in code review)resolveHooks()caches per working directory. Since/reloadis designed to pick up config changes includinghooks.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 changedImplementation
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 listPhase 2 — Session hooks wiring (PR #158)
sessionStartincreateNewSession()andattachSession()(non-blocking)sessionEndinnewSession()andreloadSession()(awaited, before unsub)/newand/reloadspawn bash ENOENT— use/bin/bashabsolute pathAcceptance Criteria
sessionStart,sessionEnd) fire at correct lifecycle pointsbd backup export-gitruns automatically on session endbd primerecovers context automatically on session startmainReferences