feat(protocol): implement core backend engine for Operative Protocol

Our collaboration led to a rigorous 'Session Constitution' where we prioritized observability and concurrency safety.
I've delivered the first four pillars of the backend engine:
1. Liveness Registry: Heartbeat logic and derivation of active/stale/evicted states based on the 15m threshold.
2. Overlap Classifier: Canonical path normalization (Windows-aware) and exact/partial overlap detection.
3. Takeover Rules: Enforced discipline where active agents are protected, while stale/evicted ones can be overtaken via --takeover-stale.
4. Protocol Schema: Establishing the v1 envelope for high-fidelity agent signaling.

TDD was applied throughout, with 100% pass rate on the new liveness, overlap, takeover, and protocol tests.
This commit is contained in:
zenchantlive 2026-02-14 10:38:10 -08:00
parent 1ae7efb31b
commit 41f7cb8f24
8 changed files with 468 additions and 14 deletions

View file

@ -40,6 +40,7 @@
{"id":"bb-6aj.7","title":"Shared project scope store + URL persistence","description":"Implement shared client-side project scope state consumed by Kanban and Graph.\\n\\nScope:\\n- selectedProjectKey, mode(single|aggregate), and source metadata\\n- URL persistence (e.g. ?project=\u003ckey\u003e\u0026mode=single|aggregate)\\n- hydration from URL on load and safe fallback when missing/invalid\\n- no JSONL writes; read boundaries preserved","acceptance_criteria":"Project scope can be selected, persisted in URL, restored on refresh, and consumed consistently by both pages.","notes":"2026-02-13 partial implementation complete: added shared scope resolver (src/lib/project-scope.ts) with deterministic local/registry key resolution + fallback; added tests (tests/lib/project-scope.test.ts); wired / and /graph server pages to hydrate from ?project= and read issues from resolved project root; preserved scope in Kanban\u003c-\u003eGraph links and rendered active scope badge in both headers. Remaining for full AC: explicit interactive scope selector/store and mode(single|aggregate) URL state.\n2026-02-13 completed: added mode-aware scope resolver (single|aggregate) with URL hydration/fallback in src/lib/project-scope.ts; added tests in tests/lib/project-scope.test.ts; implemented shared ProjectScopeControls UI used on Kanban + Graph for selecting project key and mode with URL persistence; preserved scoped cross-page links.","status":"closed","priority":1,"issue_type":"task","assignee":"zenchantlive","owner":"jordanlive121@gmail.com","created_at":"2026-02-12T21:41:00.7974464-08:00","created_by":"zenchantlive","updated_at":"2026-02-12T22:32:27.6431192-08:00","closed_at":"2026-02-12T22:32:27.6431192-08:00","close_reason":"scope-state-url-persistence-complete","labels":["multi-project","state","ui"],"dependencies":[{"issue_id":"bb-6aj.7","depends_on_id":"bb-6aj","type":"parent-child","created_at":"2026-02-12T21:41:00.7992088-08:00","created_by":"zenchantlive"},{"issue_id":"bb-6aj.7","depends_on_id":"bb-6aj.6","type":"blocks","created_at":"2026-02-12T21:41:00.804019-08:00","created_by":"zenchantlive"}]}
{"id":"bb-6aj.8","title":"Project manager panel (list/add/remove registry roots)","description":"Build a user-facing project manager panel backed by /api/projects.\\n\\nFeatures:\\n- list registered projects with normalized display path\\n- add project path with validation feedback\\n- remove project with confirm affordance\\n- clearly indicate current selected project\\n- mobile-safe layout and keyboard accessibility","acceptance_criteria":"Users can manage registry projects entirely from UI and immediately use newly added project roots in scope selection.","notes":"2026-02-13 completed: implemented registry manager panel in shared ProjectScopeControls component with list/add/remove flows backed by /api/projects; includes validation/error messaging, active-scope indication, and mobile-safe controls. Integrated into src/components/kanban/kanban-page.tsx and src/components/graph/dependency-graph-page.tsx.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-12T21:41:13.2668167-08:00","created_by":"zenchantlive","updated_at":"2026-02-12T22:33:03.9808623-08:00","closed_at":"2026-02-12T22:33:03.9808623-08:00","close_reason":"project-manager-panel-shipped","labels":["multi-project","registry","ui"],"dependencies":[{"issue_id":"bb-6aj.8","depends_on_id":"bb-6aj","type":"parent-child","created_at":"2026-02-12T21:41:13.2688831-08:00","created_by":"zenchantlive"},{"issue_id":"bb-6aj.8","depends_on_id":"bb-6aj.7","type":"blocks","created_at":"2026-02-12T21:41:13.2730616-08:00","created_by":"zenchantlive"}]}
{"id":"bb-6aj.9","title":"Scanner UX (discover/import projects from safe roots)","description":"Expose scanner workflow in UI using /api/scan.\\n\\nFeatures:\\n- run default scan (profile + registry roots)\\n- optional full-drive scan behind explicit advanced control\\n- show discovered roots with source metadata and deduped list\\n- one-click import selected discoveries to registry\\n- loading/timeout/error states with plain-language messaging","acceptance_criteria":"Users can discover projects via scanner and import them into registry without leaving the app.","notes":"2026-02-13 completed: scanner UX added to ProjectScopeControls using /api/scan with mode controls (safe roots/full-drive), scan stats, deduped discovery list, and one-click import to registry via /api/projects POST. Loading/error states surfaced in-panel.\n2026-02-13 post-close hardening: scanner now requires .beads/issues.jsonl or .beads/issues.jsonl.new and adds deny rules for tool/cache/worktree noise (directory names: .agents/.kimi/.gemini/.zenflow/worktrees/appdata + name prefixes beadboard-read-, beadboard-watch-, skills- + go/pkg/mod fragment). Added regression coverage in tests/lib/scanner.test.ts.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-12T21:41:29.9411271-08:00","created_by":"zenchantlive","updated_at":"2026-02-12T23:17:32.5251756-08:00","closed_at":"2026-02-12T22:33:32.8319572-08:00","close_reason":"scanner-discover-import-ux-shipped","labels":["multi-project","scanner","ui"],"dependencies":[{"issue_id":"bb-6aj.9","depends_on_id":"bb-6aj","type":"parent-child","created_at":"2026-02-12T21:41:29.9432224-08:00","created_by":"zenchantlive"},{"issue_id":"bb-6aj.9","depends_on_id":"bb-6aj.7","type":"blocks","created_at":"2026-02-12T21:41:29.9479575-08:00","created_by":"zenchantlive"}]}
{"id":"bb-6d5","title":"CLI Surface: bb-init (Non-Interactive) and Heartbeat Command","description":"Implement the bb-init tool with --adopt and --non-interactive support, and the bb agent heartbeat command.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:44:01.1157161-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T09:45:02.6367934-08:00","closed_at":"2026-02-14T09:45:02.6367934-08:00","close_reason":"Deleted: created before plan approval"}
{"id":"bb-92d","title":"Foundation and Read/Write Boundary","description":"Establish the Windows-native Next.js foundation, canonical Beads schema handling, and strict data boundaries: read from JSONL, write only via bd.exe. This epic defines the non-negotiable invariants that all later work must preserve.","acceptance_criteria":"App boots on Windows, schema/parser contracts exist, and no direct issues.jsonl write path exists in code.","status":"closed","priority":0,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:41.0756295-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:28:27.8108066-08:00","closed_at":"2026-02-11T17:28:27.8108066-08:00","close_reason":"Completed foundation milestone: bootstrap, licensing/docs, schema contracts, parser, windows path normalization, and write-boundary guardrails.","labels":["beadboard","foundation","windows"]}
{"id":"bb-92d.1","title":"Bootstrap Next.js 15 + React 19 + TypeScript strict","description":"Initialize project scaffold with strict TypeScript, App Router baseline, and repeatable scripts for lint/typecheck/test in PowerShell.","acceptance_criteria":"npm install and dev startup work on Windows; strict type checking enabled.","status":"closed","priority":0,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:41.9363647-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:23:14.0089901-08:00","closed_at":"2026-02-11T17:23:14.0089901-08:00","close_reason":"Bootstrapped Next.js 15 + React 19 + strict TypeScript; install/typecheck/dev startup verified on Windows.","labels":["foundation","nextjs"],"dependencies":[{"issue_id":"bb-92d.1","depends_on_id":"bb-92d","type":"parent-child","created_at":"2026-02-11T17:11:41.9379355-08:00","created_by":"zenchantlive"}]}
{"id":"bb-92d.2","title":"Add MIT license and baseline repository docs","description":"Add LICENSE and baseline docs that state Windows-native support, read/write boundaries, and required runtime dependencies.","acceptance_criteria":"MIT license present and docs describe core architecture constraints.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:42.7699961-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:23:50.7519159-08:00","closed_at":"2026-02-11T17:23:50.7519159-08:00","close_reason":"Added MIT license and baseline repository documentation with architecture boundary rules.","labels":["docs","license"],"dependencies":[{"issue_id":"bb-92d.2","depends_on_id":"bb-92d","type":"parent-child","created_at":"2026-02-11T17:11:42.7715653-08:00","created_by":"zenchantlive"}]}
@ -93,6 +94,7 @@
{"id":"bb-dcv.6","title":"Implement agent mail commands (send/inbox/read/ack)","description":"Implement file-backed message transport for registered agents with unread/read/acked states and bead-linked thread context.","acceptance_criteria":"send/inbox/read/ack commands work end-to-end; sender/recipient must be registered; message lifecycle is test-covered.","notes":"Implemented agent mail command layer in src/lib/agent-mail.ts with contract behaviors: send/inbox/read/ack, sender/recipient registration checks, message categories and requires_ack policy, unread/read/acked lifecycle, recipient-only ack with ACK_FORBIDDEN, message index + per-recipient inbox storage under %USERPROFILE%/.beadboard/agent/messages. Added tests in tests/lib/agent-mail.test.ts covering unknown sender/recipient, end-to-end send/inbox/read/ack flow, forbidden ack, and category/bead validation errors. Added test inclusion in package.json.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-13T12:56:57.2090515-08:00","created_by":"zenchantlive","updated_at":"2026-02-13T16:05:03.6658756-08:00","closed_at":"2026-02-13T16:05:03.6658756-08:00","close_reason":"Implemented and verified agent mail command handlers (send/inbox/read/ack) with lifecycle and registry validation.","labels":["agents","cli","mail"],"dependencies":[{"issue_id":"bb-dcv.6","depends_on_id":"bb-dcv","type":"parent-child","created_at":"2026-02-13T12:56:57.210616-08:00","created_by":"zenchantlive"},{"issue_id":"bb-dcv.6","depends_on_id":"bb-dcv.2","type":"blocks","created_at":"2026-02-13T12:57:09.7811635-08:00","created_by":"zenchantlive"},{"issue_id":"bb-dcv.6","depends_on_id":"bb-dcv.7","type":"blocks","created_at":"2026-02-13T12:57:10.3349432-08:00","created_by":"zenchantlive"}]}
{"id":"bb-dcv.7","title":"Implement agent identity registry commands","description":"Implement bb agent register/list/show with unique-name enforcement and stable metadata files under .beadboard/agent/agents.","acceptance_criteria":"register/list/show commands work; duplicate names fail with clear error; tests cover happy/error paths.","notes":"Delivered src/lib/agent-registry.ts with contract-aligned behavior: agent id validation (regex + length), role validation, duplicate rejection with DUPLICATE_AGENT_ID, force-update path, stable metadata persisted at %USERPROFILE%/.beadboard/agent/agents/\u003cagent_id\u003e.json, list filtering/sorting, and show with AGENT_NOT_FOUND. Added test-first coverage in tests/lib/agent-registry.test.ts (happy path + duplicate error + force-update + list filters/sort + show not found + validation errors). Included suite in package.json test script. Verification evidence: node --import tsx --test tests/lib/agent-registry.test.ts (pass 6/6), npm run typecheck (pass), npm run lint (pass), npm run test (pass full suite). This unblocks bb-dcv.4 and bb-dcv.6 dependency lane.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-13T12:56:57.6944409-08:00","created_by":"zenchantlive","updated_at":"2026-02-13T15:49:48.0956042-08:00","closed_at":"2026-02-13T15:49:48.0956042-08:00","close_reason":"Implemented and verified agent identity registry command layer (register/list/show).","labels":["agents","cli","identity"],"dependencies":[{"issue_id":"bb-dcv.7","depends_on_id":"bb-dcv","type":"parent-child","created_at":"2026-02-13T12:56:57.6961264-08:00","created_by":"zenchantlive"},{"issue_id":"bb-dcv.7","depends_on_id":"bb-dcv.2","type":"blocks","created_at":"2026-02-13T12:57:09.2534901-08:00","created_by":"zenchantlive"}]}
{"id":"bb-dcv.8","title":"Create beadboard-driver skill from implemented bb agent workflows","description":"Use skill-creator workflow to produce the beadboard-driver skill only after bb agent identity, mail, reservation, and workflow commands are implemented and verified.","acceptance_criteria":"SKILL.md matches implemented CLI behavior; trigger language is explicit; no speculative commands included; quick validation performed.","notes":"Implemented beadboard-driver skill package with cross-platform script layer and dual test harness. Added skills/beadboard-driver/{SKILL.md,agents/openai.yaml,scripts/*.mjs,references/*.md,tests/*.mjs} plus repo contract tests under tests/skills/beadboard-driver/*.test.ts and wired into npm test. Validation evidence: quick_validate.py skills/beadboard-driver =\u003e pass; node --import tsx --test tests/skills/beadboard-driver/*.test.ts =\u003e pass; node skills/beadboard-driver/tests/run-tests.mjs =\u003e pass; npm run typecheck =\u003e pass; npm run lint =\u003e pass; npm run test =\u003e pass.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-13T14:45:05.4433258-08:00","created_by":"zenchantlive","updated_at":"2026-02-13T19:01:15.4602809-08:00","closed_at":"2026-02-13T19:01:14.6040163-08:00","close_reason":"Created and validated beadboard-driver skill from implemented bb agent workflows with explicit triggers, no speculative commands, and dual-layer tests.","labels":["agents","docs","skill"],"dependencies":[{"issue_id":"bb-dcv.8","depends_on_id":"bb-dcv","type":"parent-child","created_at":"2026-02-13T14:45:05.4449006-08:00","created_by":"zenchantlive"},{"issue_id":"bb-dcv.8","depends_on_id":"bb-dcv.5","type":"blocks","created_at":"2026-02-13T14:45:11.9133726-08:00","created_by":"zenchantlive"}]}
{"id":"bb-kp5","title":"EPIC: The Operative Protocol (Agent Coordination v2)","description":"Implement the end-to-end Operative Protocol: identity adoption, heartbeat-backed reservations, and traceable incursions with real-time War Room visibility.","status":"closed","priority":1,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:43:59.134022-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T09:45:02.1950097-08:00","closed_at":"2026-02-14T09:45:02.1950097-08:00","close_reason":"Deleted: created before plan approval"}
{"id":"bb-lvl","title":"Fix stale bead status rendering and refresh propagation in BeadBoard","description":"Triage and fix mismatch where bd shows updated status (e.g., in_progress) but BeadBoard/BV surfaces continue showing stale values or require manual refresh. Investigate DB-\u003eJSONL sync, scope/root selection, API read path, and SSE propagation.","acceptance_criteria":"For a claimed issue, BeadBoard reflects status changes without stale drift; repro and root cause documented; regression checks added.","notes":"Root-cause evidence: bd DB status diverged from .beads/issues.jsonl (bb-dcv.2 in_progress in bd show, open in JSONL before sync). After bd sync in repo root, JSONL updated immediately. Suspected freshness bug from disk-only read path.\nImplemented freshness-path fix: app reads now prefer bd list --json with fallback to disk JSONL. Updated src/lib/read-issues.ts, src/lib/aggregate-read.ts, src/app/page.tsx, src/app/graph/page.tsx, and src/app/api/beads/read/route.ts. Verification: npm run typecheck, npm run lint, npm run test all passed.\nSecond triage fix for live refresh: watcher now includes .beads/beads.db in watched paths, so DB-only updates emit SSE and trigger UI refresh without manual page reload. Updated src/lib/watcher.ts and tests/lib/watcher.test.ts (new beads.db event test). Verification: watcher test pass, typecheck pass, lint pass.\nFurther root cause: events fallback compared .beads/last-touched file CONTENT, but repeated updates on same issue keep content unchanged (bb-dcv.2) while only mtime changes. Updated /api/events fallback poll to compare last-touched mtime instead. Also expanded watcher inputs to include beads.db-wal and last-touched.\nPost-restart verification: end-to-end SSE probe now receives issues events after external bd update (saw_issues_event=true). This confirms refresh path works when server runs updated /api/events logic.","status":"closed","priority":1,"issue_type":"bug","assignee":"zenchantlive","owner":"jordanlive121@gmail.com","created_at":"2026-02-13T15:16:22.8086122-08:00","created_by":"zenchantlive","updated_at":"2026-02-13T15:36:29.9493329-08:00","closed_at":"2026-02-13T15:36:29.9493329-08:00","close_reason":"Status refresh regression resolved: live read freshness + SSE event emission restored; verified via terminal event probe and manual status toggle without page refresh.","labels":["realtime","sse","status"]}
{"id":"bb-n7p","title":"Swimlane status model: ready + dependency-derived blocked","notes":"Implemented new swimlane model: removed deferred lane from board usage; added ready lane and dependency-derived blocked lane. Lane rules: closed-\u003eDone; blocked-\u003eBlocked if explicit status blocked OR has active incoming blocker edge; in_progress/review-\u003eIn Progress; otherwise Ready. Added laneToMutationStatus to map board lane writes to bead statuses (ready-\u003eopen). Updated board labels/colors, drag-drop lane source tracking, and controls stat label Open-\u003eReady. TDD: updated tests/lib/kanban.test.ts for ready/blocked semantics. Verification: node --import tsx --test tests/lib/kanban.test.ts (pass), npm run typecheck (pass), npm run test (pass).","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-12T17:55:04.1851993-08:00","created_by":"zenchantlive","updated_at":"2026-02-12T18:40:08.0620089-08:00","closed_at":"2026-02-12T18:40:08.0620089-08:00","close_reason":"Implemented ready/blocked swimlane model, blocked-tree deep links to lane focus, and verification passed (kanban tests, typecheck, full test suite).","labels":["kanban","status","swimlane"]}
{"id":"bb-q1s","title":"UI Bead Editing Across Kanban + Graph","description":"Objective:\nAdd true UI editing for bead fields across both detail panels (Kanban + Graph) using one shared edit core so behavior stays consistent.\n\nWhy:\nWrite-back infrastructure exists, but users currently cannot edit bead content from UI detail panels.\n\nScope:\n- Shared edit validation + mutation adapter.\n- Reusable editor UI block for issue fields.\n- Integration into both Kanban and Graph detail panels.\n- Verification for responsive behavior and mutation safety.\n\nOut of scope:\n- Dependency relation editing.\n- AI content generation.\n- Bulk editing.","acceptance_criteria":"- Users can edit core bead fields from both Kanban and Graph detail panels.\n- Both surfaces use the same validation and update path.\n- Save/cancel/error states are consistent across both surfaces.\n- Typecheck/tests/guards pass and no direct JSONL writes are introduced.","notes":"Execution order enforced through child dependencies.\nExecution order: bb-q1s.1 shared core -\u003e bb-q1s.2 kanban + bb-q1s.3 graph (parallel) -\u003e bb-q1s.4 verification/polish.","status":"closed","priority":1,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-12T20:50:12.3431904-08:00","created_by":"zenchantlive","updated_at":"2026-02-12T21:11:43.1747329-08:00","closed_at":"2026-02-12T21:11:43.1747329-08:00","close_reason":"Shared UI bead editing shipped across Kanban and Graph with verification evidence.","labels":["editing","mutation","ui"]}
@ -124,7 +126,17 @@
{"id":"bb-u6f.3.6","title":"Implement Dual-Mode Context Sidebar (Productivity/Audit)","description":"Build pivotable sidebar. Mode 1: Agent Scorecard (Stats + History). Mode 2: Task Deep-Dive (Thread + Actions). Implements navigation Back Button between modes. Highlights active task in feed.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-13T22:48:33.210551-08:00","created_by":"zenchantlive","updated_at":"2026-02-13T23:08:23.4084684-08:00","closed_at":"2026-02-13T23:08:23.4084684-08:00","close_reason":"Implemented during major bb-u6f.3 refactor.","dependencies":[{"issue_id":"bb-u6f.3.6","depends_on_id":"bb-u6f.3","type":"parent-child","created_at":"2026-02-13T22:48:33.2132341-08:00","created_by":"zenchantlive"}]}
{"id":"bb-u6f.3.7","title":"Implement Fluid Session Layout \u0026 Refactor Store","description":"SUBTASK REPORT: Implementation of the Fluid Session Layout and Store Refactor. We developed the structural foundation for the Agent Sessions workspace. This involved a total hollow-out of the /sessions route, replacing the legacy page model with a grid-based 'Pane' model. The layout uses grid-cols-[1fr_auto] to separate the primary activity matrix from the persistent context sidebar, with independent vertical scrolling for both columns.","notes":"EXECUTION TALE: We implemented the high-density 'Aero Chrome' visual standards, utilizing 12px/13px data density and glassmorphism. A critical component was the refactor of the TimelineStore (Zustand) to manage global selection state. We added selectedAgentId and selectedTaskId to the store, along with an integrated 'Back to Agent' navigation action. This state synchronization ensures that clicking a card in the feed instantly updates the sidebar while maintaining high performance. We also enforced the use of rem and vw units throughout the CSS to guarantee that the UI packs horizontally on ultra-wide displays without breaking hierarchy.","status":"closed","priority":1,"issue_type":"task","assignee":"green-falcon","owner":"jordanlive121@gmail.com","created_at":"2026-02-13T22:48:34.0455466-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T00:10:12.345505-08:00","closed_at":"2026-02-13T22:55:04.395134-08:00","close_reason":"Fluid layout and store refactor complete.","dependencies":[{"issue_id":"bb-u6f.3.7","depends_on_id":"bb-u6f.3","type":"parent-child","created_at":"2026-02-13T22:48:34.047661-08:00","created_by":"zenchantlive"}],"comments":[{"id":26,"issue_id":"bb-u6f.3.7","author":"zenchantlive","text":"Layout Refactor tale: We overhauled the whole page to use grid-cols-[1fr_auto]. All sizing was moved to rem units to ensure the UI packs horizontally on wide screens and stays readable.","created_at":"2026-02-14T07:41:05Z"},{"id":39,"issue_id":"bb-u6f.3.7","author":"zenchantlive","text":"MEMO: The fluid layout is the architectural success that makes the entire Sessions experience possible. By moving to independently scrollable panes, we've created a workspace that feels like a professional command console rather than a generic web page.","created_at":"2026-02-14T08:10:13Z"}]}
{"id":"bb-u6f.4","title":"Epic Design Gate: scope, decisions, and acceptance contract","description":"DESIGN GATE REPORT: Agent Sessions Finalized Layout. This subtask represents the formal acceptance of the 'Social-Dense' pivot. We transitioned from a vertical card list to a high-density 'Command Console' model. The gate confirmed the use of multi-column grid matrices, rem-based relative units for fluid packing, and the persistent dual-mode sidebar as the official standard for supervisory views. We also established the 'Mission Control' header as the primary real-time monitoring surface, replacing legacy hero banners with station cards.","acceptance_criteria":"A written execution-grade plan exists for this epic and all child task descriptions are updated with concrete implementation details, dependencies, and testable acceptance criteria.","notes":"EXECUTION TALE: During the gate review, we identified that horizontal density was the primary bottleneck for operative oversight. We successfully verified the responsive behavior of the auto-filling grid across standard 1080p and ultra-wide resolutions. The gate also ratified the technical contract for 'Silent Refresh' and CLI-based interaction fetching, ensuring that all future agent session work adheres to the 'No Flicker' and 'Direct Authority' mandates. All visual artifacts (final-kanban-1440.png, sessions-summary-final.png) were signed off as green.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T20:09:41.2150441-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T00:11:36.2250325-08:00","closed_at":"2026-02-12T23:29:55.9362248-08:00","dependencies":[{"issue_id":"bb-u6f.4","depends_on_id":"bb-u6f","type":"parent-child","created_at":"2026-02-11T20:09:41.216603-08:00","created_by":"zenchantlive"}],"comments":[{"id":40,"issue_id":"bb-u6f.4","author":"zenchantlive","text":"MEMO: The Design Gate is officially closed. The shift to a denser, grid-based workspace was the correct strategic move, reclaiming over 40% of previously wasted screen real-estate and providing simultaneous visibility of all registered agents.","created_at":"2026-02-14T08:11:37Z"}]}
{"id":"bb-u6f.5","title":"Implement Session Metrics Overlays","description":"Add completion rate, throughput, and active span metrics to the Session UI. Implementation of overlays or dashboard widgets as per original bb-u6f.3 goal.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-13T21:50:36.5056349-08:00","created_by":"zenchantlive","updated_at":"2026-02-13T21:50:36.5056349-08:00","dependencies":[{"issue_id":"bb-u6f.5","depends_on_id":"bb-u6f","type":"parent-child","created_at":"2026-02-13T21:50:36.5078348-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.5","depends_on_id":"bb-u6f.3","type":"blocks","created_at":"2026-02-13T21:51:40.941554-08:00","created_by":"zenchantlive"}]}
{"id":"bb-u6f.5","title":"Legacy Track: Session Metrics Overlays","description":"Add completion rate, throughput, and active span metrics to the Session UI. Implementation of overlays or dashboard widgets as per original bb-u6f.3 goal.","notes":"Naming normalization: marked as Legacy Track to distinguish from Protocol Track 6 branch beads.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-13T21:50:36.5056349-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T10:07:39.440563-08:00","dependencies":[{"issue_id":"bb-u6f.5","depends_on_id":"bb-u6f","type":"parent-child","created_at":"2026-02-13T21:50:36.5078348-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.5","depends_on_id":"bb-u6f.3","type":"blocks","created_at":"2026-02-13T21:51:40.941554-08:00","created_by":"zenchantlive"}]}
{"id":"bb-u6f.6","title":"Protocol Track 6: Operative Protocol End-to-End Implementation","description":"Implement the Operative Protocol roadmap for multi-agent coordination in Sessions, with spec-first execution and stable contracts before UI behavior changes.\n\nScope\n- Build protocol as a layered delivery sequence: specification -\u003e backend semantics -\u003e CLI surface -\u003e Sessions UI rendering -\u003e skill/docs closeout -\u003e final acceptance sweep.\n- Keep existing bd source-of-truth constraints and avoid direct writes to .beads/issues.jsonl.\n- Keep language plain for user-facing labels (Passed to, Needs input, Seen, Accepted).\n\nPrimary existing code touchpoints\n- src/lib/agent-registry.ts\n- src/lib/agent-reservations.ts\n- src/lib/agent-mail.ts\n- src/lib/agent-sessions.ts\n- src/lib/realtime.ts\n- src/app/api/sessions/route.ts\n- src/app/api/sessions/[beadId]/conversation/route.ts\n- src/components/sessions/sessions-page.tsx\n- src/components/sessions/session-feed-card.tsx\n- src/components/sessions/session-task-feed.tsx\n- src/components/sessions/conversation-drawer.tsx\n- src/hooks/use-beads-subscription.ts\n- src/hooks/use-session-feed.ts\n- tools/bb.ts\n- scripts/bb-init.mjs (new)\n- skills/beadboard-driver/SKILL.md\n\nExecution constraints\n- Protocol contracts must land before behavior work.\n- No architecture pivots after protocol spec bead is approved.\n- Every implementation bead includes tests tied to touched behavior.\r\n","acceptance_criteria":"Protocol umbrella is decomposed into ordered child beads; each child bead has concrete file-level scope and acceptance; dependency graph enforces spec-first and docs-last execution.","notes":"Naming normalization: added 'Protocol Track 6' prefix to reduce feed ambiguity with bb-u6f.5 and descendant leaf collisions.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:53:06.2666654-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T10:07:39.0190306-08:00","labels":["agents","protocol","sessions"],"dependencies":[{"issue_id":"bb-u6f.6","depends_on_id":"bb-u6f","type":"parent-child","created_at":"2026-02-14T09:53:06.2682608-08:00","created_by":"zenchantlive"}]}
{"id":"bb-u6f.6.1","title":"Protocol Spec Gate: identity, heartbeat, overlap, event schema","description":"Define the Session Constitution that all later implementation beads must follow.\n\nRequired deliverable\n- Create protocol spec doc at docs/protocols/operative-protocol-v1.md (or docs/adr equivalent with explicit protocol contract sections).\n\nSpec sections (must be explicit and testable)\n1) Identity Trust Model\n- Define adoption policy for prior identity, with strict conditions:\n - allowed when uncommitted changes exist in claimed scope, OR\n - allowed when target identity owns an in_progress bead.\n- Define mandatory audit event for adoption/session resume (SESSION_RESUME).\n- Define failure responses and non-interactive defaults.\n\n2) Heartbeat Contract\n- Define heartbeat cadence, stale threshold, and eviction threshold.\n- Default stale threshold uses BB_AGENT_STALE_MINUTES with default 15.\n- Eviction transition at T+30m (stale grace window documented).\n- Define how registry status and reservation takeover consume these states.\n\n3) Path Overlap Canonicalization\n- Define normalization rules used for overlap detection:\n - absolute resolution,\n - lowercase comparison on Windows,\n - normalized slash separators.\n- Define overlap classes:\n - exact,\n - parent-child (partial overlap),\n - disjoint.\n- Include examples for src/* and src/lib/parser.ts patterns.\n\n4) Protocol Event Schema (stable JSON contract)\n- Finalize payload schemas for HANDOFF, BLOCKED, INCURSION, RESUME.\n- Include required fields (event id, bead id, from/to agent, scope, timestamp, version).\n- Document rendering intent in Sessions UI and SSE transport mapping.\n\nFiles likely touched\n- docs/protocols/operative-protocol-v1.md (new) OR docs/adr/*.md\n- Optional schema types if extracted: src/lib/agent-protocol.ts (new)\n\nOut of scope\n- No UI behavior change in this bead.\n- No command behavior change in this bead.\r\n","acceptance_criteria":"Protocol doc exists with identity, heartbeat, path overlap, and event schema sections; constants/defaults are unambiguous; downstream beads can implement without reinterpreting semantics.","notes":"Delivered protocol spec doc: docs/protocols/operative-protocol-v1.md. Includes normative contracts for identity adoption, heartbeat stale/evicted thresholds, path overlap normalization/classification, stable protocol event schema (HANDOFF/BLOCKED/INCURSION/RESUME), UI label mapping, CLI non-interactive requirements, and explicit file/module mapping for downstream beads.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:53:06.7850841-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T09:59:48.7642462-08:00","closed_at":"2026-02-14T09:59:48.7642462-08:00","close_reason":"Spec gate completed. Session Constitution v1 published for downstream implementation without semantic ambiguity.","labels":["agents","protocol","sessions","spec"],"dependencies":[{"issue_id":"bb-u6f.6.1","depends_on_id":"bb-u6f.6","type":"parent-child","created_at":"2026-02-14T09:53:06.8110216-08:00","created_by":"zenchantlive"}]}
{"id":"bb-u6f.6.2","title":"Backend Engine: heartbeat, stale takeover, overlap, protocol events","description":"Implement protocol semantics in backend libraries and API composition paths.\n\nImplementation targets\n1) Agent liveness registry\n- Add heartbeat update function in src/lib/agent-registry.ts (e.g., heartbeatAgent(agentId)).\n- Ensure last_seen_at updates use UTC ISO timestamps.\n- Add utility to derive liveness state from last_seen_at using protocol thresholds.\n\n2) Reservation stale takeover + overlap logic\n- Update src/lib/agent-reservations.ts:\n - enforce stale/eviction behavior from spec,\n - gate takeover behavior on stale owner when takeover flag provided,\n - normalize scopes before conflict checks,\n - classify overlap (exact/partial/disjoint) for incursion detection.\n- Keep current conflict behavior deterministic and backwards-compatible where possible.\n\n3) Protocol event dispatch surfaces\n- Add/extend typed protocol event emission in src/lib/realtime.ts (or dedicated protocol event module) so UI/SSE can consume stable contract events.\n- Ensure API feed builders can read protocol events without duplicating parsing logic.\n\n4) Session aggregation integration\n- Update src/lib/agent-sessions.ts and src/app/api/sessions/route.ts so session-state derivation can consume liveness/overlap/pending-ack semantics consistently.\n\nTesting requirements\n- Extend/add tests:\n - tests/lib/agent-registry.test.ts (heartbeat and liveness transitions)\n - tests/lib/agent-reservations.test.ts (stale takeover allowed/blocked + overlap classification)\n - tests/lib/agent-sessions.test.ts (session state reflects protocol semantics)\n - add tests/lib/agent-heartbeat.test.ts if separation improves clarity.\n\nNon-goals\n- No CLI UX additions in this bead.\n- No major visual changes in this bead.\r\n","acceptance_criteria":"Heartbeat updates and liveness state are implemented; stale takeover and overlap detection work per spec; protocol events are emitted in a stable format; backend/unit tests cover active vs stale owner behavior and overlap edge cases.","notes":"Implementing protocol backend contracts from docs/protocols/operative-protocol-v1.md. Starting with Liveness Registry and Heartbeat logic.","status":"in_progress","priority":1,"issue_type":"task","assignee":"zenchantlive","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:53:07.4658312-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T10:33:39.151914-08:00","labels":["agents","backend","protocol","sessions"],"dependencies":[{"issue_id":"bb-u6f.6.2","depends_on_id":"bb-u6f.6","type":"parent-child","created_at":"2026-02-14T09:53:07.4696351-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.2","depends_on_id":"bb-u6f.6.1","type":"blocks","created_at":"2026-02-14T09:53:07.477934-08:00","created_by":"zenchantlive"}]}
{"id":"bb-u6f.6.3","title":"CLI Surface: bb-init and agent heartbeat command","description":"Expose non-interactive protocol operations for agent runtimes, with zero manual bb commands required from end users.\n\nImplementation targets\n1) bb agent heartbeat\n- Extend tools/bb.ts command surface to support `bb agent heartbeat --agent \u003cid\u003e`.\n- Command must call backend heartbeat mutation and support --json output contract.\n- Keep help text clear about per-session unique naming policy.\n\n2) bb-init bootstrap tool (runtime-owned orchestration)\n- Create scripts/bb-init.mjs.\n- Required flags:\n - --non-interactive\n - --adopt \u003cagentId\u003e\n - --register \u003cname\u003e\n - --json\n - --start-heartbeat\n - --stop-heartbeat\n- Runtime responsibilities:\n - resolve bb.ps1 path robustly,\n - inspect git working tree for uncommitted changes,\n - query reservation/status context and recommend/execute adopt vs register,\n - automatically start heartbeat worker in non-interactive runtime flow,\n - provide explicit stop path for cleanup on session end.\n\n3) Background heartbeat worker contract\n- Heartbeat loop cadence default: 60s.\n- Worker ownership tracked via runtime pid/lock file under `.beadboard/agent/runtime/`.\n- One active heartbeat worker per session identity (duplicate-start protection).\n- Worker emits structured errors and exits non-zero when heartbeat fails repeatedly.\n\n4) Zero-manual-user-command principle\n- End users should not need to run `bb` directly for normal operation.\n- Agent runtime/wrapper must invoke bb-init + heartbeat management automatically.\n\n5) Robust non-interactive behavior\n- Non-interactive mode must never block on prompt.\n- All failures return structured machine-readable payloads.\n\nTesting requirements\n- Add tests/scripts/bb-init.test.ts for:\n - non-interactive adopt/register resolution,\n - start/stop heartbeat lifecycle,\n - duplicate heartbeat protection,\n - pid/lock cleanup behavior.\n- Extend CLI tests for `bb agent heartbeat` behavior, json envelope, and help output.\n\nFiles\n- tools/bb.ts\n- scripts/bb-init.mjs (new)\n- tests/scripts/bb-init.test.ts (new)\n- optional runtime helper module if needed (e.g., scripts/lib/heartbeat-runtime.mjs)\r\n","acceptance_criteria":"CLI/runtime layer provides fully automatic session bootstrap and heartbeat lifecycle (start/stop) with no manual user bb commands; heartbeat cadence/locking/cleanup semantics are implemented and tested; non-interactive flows are deterministic and machine-readable.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:53:08.0983784-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T10:33:35.5162347-08:00","labels":["agents","cli","protocol","sessions"],"dependencies":[{"issue_id":"bb-u6f.6.3","depends_on_id":"bb-u6f.6","type":"parent-child","created_at":"2026-02-14T09:53:08.100007-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.3","depends_on_id":"bb-u6f.6.2","type":"blocks","created_at":"2026-02-14T09:53:08.1088582-08:00","created_by":"zenchantlive"}]}
{"id":"bb-u6f.6.4","title":"Sessions Hub UI: protocol event and incursion visibility","description":"Integrate protocol events and liveness/incursion semantics into Sessions Hub UI without reintroducing page duplication.\n\nImplementation targets\n1) Session feed visual states\n- Update src/components/sessions/session-feed-card.tsx and related status utils:\n - surface stale/incursion/conflict signals from protocol-derived state,\n - keep plain-language user labels and clear status hierarchy.\n\n2) Task feed + context drawer protocol rendering\n- Update src/components/sessions/session-task-feed.tsx and src/components/sessions/conversation-drawer.tsx:\n - render protocol events (HANDOFF/BLOCKED/INCURSION/RESUME) as first-class rows,\n - keep actions intuitive (Seen/Accepted),\n - preserve current mobile/desktop behavior.\n\n3) Data subscription and freshness flow\n- Update src/components/sessions/sessions-page.tsx, src/hooks/use-session-feed.ts, src/hooks/use-beads-subscription.ts as needed:\n - consume new protocol event payloads,\n - avoid stale UI state requiring manual refresh,\n - keep SSE-based refresh behavior aligned with existing working Kanban patterns.\n\n4) API shaping\n- Update src/app/api/sessions/route.ts and src/app/api/sessions/[beadId]/conversation/route.ts to expose protocol metadata required by UI.\n\nTesting requirements\n- Extend/add component/store tests under tests/components/sessions/* and API tests under tests/api/* for protocol event rendering and state mapping.\n- If visual behavior changes, capture updated artifacts for desktop/mobile sessions views.\n\nFiles\n- src/components/sessions/sessions-page.tsx\n- src/components/sessions/session-feed-card.tsx\n- src/components/sessions/session-task-feed.tsx\n- src/components/sessions/conversation-drawer.tsx\n- src/hooks/use-session-feed.ts\n- src/hooks/use-beads-subscription.ts\n- src/app/api/sessions/route.ts\n- src/app/api/sessions/[beadId]/conversation/route.ts\r\n","acceptance_criteria":"Sessions UI displays protocol states/events clearly (including stale/incursion); feed and drawer update via SSE without manual refresh; component/API tests assert mapping and rendering of protocol messages and actions.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:53:08.8513396-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T09:53:08.8513396-08:00","labels":["agents","protocol","sessions","ui"],"dependencies":[{"issue_id":"bb-u6f.6.4","depends_on_id":"bb-u6f.6","type":"parent-child","created_at":"2026-02-14T09:53:08.8534775-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.4","depends_on_id":"bb-u6f.6.1","type":"blocks","created_at":"2026-02-14T09:53:08.8599565-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.4","depends_on_id":"bb-u6f.6.2","type":"blocks","created_at":"2026-02-14T09:53:08.8647791-08:00","created_by":"zenchantlive"}]}
{"id":"bb-u6f.6.5","title":"Protocol Track 6: Skill Closeout (beadboard-driver v2 runbook)","description":"Update operator guidance so all agents follow the finalized protocol and command surface.\n\nImplementation targets\n1) Rewrite beadboard-driver skill workflow\n- Update skills/beadboard-driver/SKILL.md to codify loop:\n - bb-init (boot/adopt)\n - reserve scope\n - perform work\n - send protocol messages (INFO/HANDOFF/BLOCKED)\n - heartbeat cadence\n - release scope\n- Include non-interactive examples for automation contexts.\n\n2) Operational guardrails\n- Add explicit anti-pattern section for silent incursions, skipped acknowledgements, and identity reuse.\n- Add troubleshooting section for missing BB_REPO, bb.ps1 path resolution, and stale heartbeat.\n\n3) Command reference sync\n- Ensure docs reflect actual commands and flags implemented in Phase CLI bead.\n- Include expected JSON response snippets for machine usage.\n\nFiles\n- skills/beadboard-driver/SKILL.md\n- Optional supplemental docs in skills/beadboard-driver/references/* if needed.\r\n","acceptance_criteria":"Skill documentation matches implemented CLI/protocol behavior exactly, includes non-interactive examples, and documents red-flag anti-patterns with corrective actions.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:53:09.4862557-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T10:07:38.1681695-08:00","labels":["agents","docs","protocol","sessions","skills"],"dependencies":[{"issue_id":"bb-u6f.6.5","depends_on_id":"bb-u6f.6.3","type":"blocks","created_at":"2026-02-14T09:57:08.0044861-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.5","depends_on_id":"bb-u6f.6.4","type":"blocks","created_at":"2026-02-14T09:57:08.4186193-08:00","created_by":"zenchantlive"}]}
{"id":"bb-u6f.6.6","title":"Protocol Track 6: Integrated Acceptance Sweep (gates, screenshots, diff hygiene)","description":"Run final integrated acceptance after implementation beads land.\n\nRequired verification gates\n- npm run typecheck\n- npm run lint\n- npm run test\n\nEvidence requirements\n- Record command outputs in bead notes.\n- Capture fresh Sessions screenshots if visual output changed:\n - artifacts/final-sessions-1440.png\n - artifacts/final-sessions-393.png\n - plus any protocol-specific drawer/thread evidence captures.\n- Confirm only intended files are in PR diff for protocol scope.\n\nBead hygiene requirements\n- Update implementation child beads with concrete evidence before close.\n- Run bd ready after closes and attach suggested-next outputs.\n- Sync bead state after final closeout.\n\nFiles potentially touched\n- test files and snapshot artifacts only (no functional code changes expected unless verification surfaces regressions).\r\n","acceptance_criteria":"All quality gates pass in current session; screenshot evidence is refreshed for changed UI; PR diff scope is clean; implementation beads have close notes with command evidence.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:56:10.4860965-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T10:07:38.5908168-08:00","labels":["agents","protocol","sessions","verification"],"dependencies":[{"issue_id":"bb-u6f.6.6","depends_on_id":"bb-u6f.6","type":"parent-child","created_at":"2026-02-14T09:56:10.4876413-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.6","depends_on_id":"bb-u6f.6.2","type":"blocks","created_at":"2026-02-14T09:56:10.4924735-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.6","depends_on_id":"bb-u6f.6.3","type":"blocks","created_at":"2026-02-14T09:56:10.4956723-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.6","depends_on_id":"bb-u6f.6.4","type":"blocks","created_at":"2026-02-14T09:56:10.4982625-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.6.6","depends_on_id":"bb-u6f.6.5","type":"blocks","created_at":"2026-02-14T09:57:08.8618851-08:00","created_by":"zenchantlive"}]}
{"id":"bb-ubr","title":"Skill Closeout: Refactor beadboard-driver for Operative Protocol","description":"Finalize the agent Handbook with the verified 'Physical Change -\u003e Contextual Lookup' workflow.","status":"closed","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:44:02.6007988-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T09:45:02.9261337-08:00","closed_at":"2026-02-14T09:45:02.9261337-08:00","close_reason":"Deleted: created before plan approval"}
{"id":"bb-ui0","title":"Core Backend: Heartbeat, Takeover, and Overlap Logic","description":"Implement the heartbeatAgent registry update, the stale reservation takeover logic, and the canonical path overlap detection engine.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:44:00.2822795-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T09:45:02.4885006-08:00","closed_at":"2026-02-14T09:45:02.4885006-08:00","close_reason":"Deleted: created before plan approval"}
{"id":"bb-vyf","title":"Frontend: Social-Dense Hub Overlays and Audit Rendering","description":"Update the Sessions Hub to visualize stale states, scope incursions, and the new protocol event schema.","status":"closed","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:44:01.9147924-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T09:45:02.7780466-08:00","closed_at":"2026-02-14T09:45:02.7780466-08:00","close_reason":"Deleted: created before plan approval"}
{"id":"bb-xhm","title":"Timeline and Activity Feed","description":"EPIC ARCHITECTURAL MANIFESTO: Implementation of the High-Signal Derived Activity Engine. This epic represents a fundamental shift in how BeadBoard handles historical context. We explicitly rejected the traditional 'Event Sourcing' model which requires a separate, mutable database. Instead, we implemented a 'Derived Event' architecture. In this model, the system's history is not stored but computed. By performing high-performance, memory-resident diffs between sequential snapshots of the issues.jsonl source of truth, we generate a rich social timeline that is mathematically guaranteed to be 100% consistent with the underlying git-backed Beads. This ensures that the 'Storytelling' layer of the application never drifts from the 'Authority' layer. This infrastructure now serves as the technical backbone for the Agent Sessions (bb-u6f) monitoring system by providing the ActivityEventBus required for live social auditing.","acceptance_criteria":"Timeline events are deterministic from snapshots/diffs; filters (project/actor/event/date) are fast and useful; users can move from event row to issue context in one action; UI follows the same visual system and hierarchy patterns established in bb-bvn.","notes":"EXECUTION TALE: The journey from zero visibility to real-time streams was marked by several critical technical pivots. We began by defining a strictly-typed model of 16 granular transition kinds in src/lib/activity.ts, ensuring we could track everything from status changes to estimate adjustments. The heart of the implementation is the O(N) snapshot-differ algorithm, which we performance-tuned to handle project scales of 200+ beads with sub-10ms latency. A significant 'Dark Moment' occurred during development when we realized Next.js Hot Module Replacement (HMR) was purging our in-memory activity ring buffer, effectively wiping the project's history on every code save. We solved this by implementing a persistence layer in src/lib/activity-persistence.ts that mirrors the buffer to a file-backed store at .beadboard/activity.json. We also addressed the 'UI Flicker' problem by engineering 'Silent Refresh' logic, allowing the Timeline UI to append live events without disrupting the supervisor's scroll position or layout focus. Verified end-to-end via automated diffing smoke tests.","status":"closed","priority":1,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:05.8525088-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T00:15:14.06285-08:00","closed_at":"2026-02-13T20:31:27.142701-08:00","close_reason":"Epic complete. Timeline UI, snapshot diffing, and event model implemented and verified.","labels":["activity","timeline"],"dependencies":[{"issue_id":"bb-xhm","depends_on_id":"bb-tpc","type":"blocks","created_at":"2026-02-11T17:12:22.1602338-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm","depends_on_id":"bb-bvn","type":"blocks","created_at":"2026-02-12T12:45:52.624726-08:00","created_by":"zenchantlive"}],"comments":[{"id":11,"issue_id":"bb-xhm","author":"zenchantlive","text":"Today's work established the 'Derived Event' pattern. We decided NOT to store events in a DB, but rather compute them on-the-fly by diffing JSONL snapshots. This preserves the 'Files as Source of Truth' mandate while giving us a modern social timeline experience.","created_at":"2026-02-14T07:32:31Z"},{"id":27,"issue_id":"bb-xhm","author":"zenchantlive","text":"MEMO: The 'Derived Event' pattern is now the authoritative way to track project history in BeadBoard. By avoiding a separate event database, we've eliminated the risk of 'Event Drift'—where the timeline says one thing and the file says another. The diffing engine acts as a pure function of the issues.jsonl state transitions. This was a critical design win for the project's long-term maintainability.","created_at":"2026-02-14T08:01:10Z"},{"id":42,"issue_id":"bb-xhm","author":"zenchantlive","text":"CRITICAL DESIGN DECISION: The decision to derive history from files rather than store it in SQLite was made to preserve the project's 'Terminal-First' integrity. This ensures that if a user modifies a bead via an external text editor or a git merge, the Activity Engine will automatically detect the delta and generate the appropriate social narrative on next read. Drift is technically impossible in this architecture.","created_at":"2026-02-14T08:15:14Z"}]}
{"id":"bb-xhm.1","title":"Define activity event model for created/updated/closed/reopened actions","description":"SUBTASK REPORT: Definition of the Activity Event Model. We established the canonical ActivityEventKind union, consisting of 16 granular transition types: 'created', 'closed', 'reopened', 'status_changed', 'priority_changed', 'assignee_changed', 'type_changed', 'title_changed', 'description_changed', 'labels_changed', 'dependency_added', 'dependency_removed', 'comment_added', 'due_date_changed', 'estimate_changed', and 'field_changed'. This model provides the necessary resolution for high-signal auditing and agent-centric storytelling.","acceptance_criteria":"Event model supports all required timeline activity types.","notes":"EXECUTION TALE: Implementation was strictly typed in src/lib/activity.ts. We ensured that every event carries a payload containing 'from' and 'to' states to support rich diff rendering in the UI. A unit test suite (tests/lib/activity.test.ts) was developed to verify the model's integrity and ensure all 16 kinds are correctly supported. We also applied typography pairing: JetBrains Mono was enforced for machine metadata (IDs, hex values, timestamps) to provide a distinct 'system' feel, while Plus Jakarta Sans handles the human narrative.","status":"closed","priority":1,"issue_type":"task","assignee":"green-falcon","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:06.6781387-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T00:01:31.6199266-08:00","closed_at":"2026-02-13T19:40:27.4104667-08:00","close_reason":"Model defined and verified with tests.","labels":["model","timeline"],"dependencies":[{"issue_id":"bb-xhm.1","depends_on_id":"bb-xhm","type":"parent-child","created_at":"2026-02-11T17:12:06.6791721-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.1","depends_on_id":"bb-xhm.4","type":"blocks","created_at":"2026-02-11T20:10:05.9709567-08:00","created_by":"zenchantlive"}],"comments":[{"id":12,"issue_id":"bb-xhm.1","author":"zenchantlive","text":"The 16 types were chosen to support future 'Storytelling' features where the UI can explain *why* a project is moving or stalling. We ensured machine-data (timestamps, IDs) uses JetBrains Mono while UI-text uses Plus Jakarta Sans.","created_at":"2026-02-14T07:32:33Z"},{"id":28,"issue_id":"bb-xhm.1","author":"zenchantlive","text":"MEMO: The 16 transition types were not chosen arbitrarily; they were mapped directly to the CLI capabilities of the 'bd' tool. This ensure that any mutation performable via the terminal is correctly interpreted and visualized by the Timeline engine.","created_at":"2026-02-14T08:01:32Z"}]}
{"id":"bb-xhm.2","title":"Implement snapshot diffing for derived timeline events","description":"SUBTASK REPORT: Implementation of the O(N) Snapshot Differ. We built the logic engine responsible for identifying project delta in src/lib/snapshot-differ.ts. The engine transforms the incoming BeadIssue array into a Map for constant-time (O(1)) lookups and compares it against the previous in-memory state. It correctly identifies identity transitions (created/deleted), property changes (status, priority, title), and collection mutations (labels, dependencies).","acceptance_criteria":"Diff engine emits deterministic event records for relevant field changes.","notes":"EXECUTION TALE: The primary technical challenge was managing the initial server state. We solved the 'First Load Event Storm' problem (where every issue would be detected as 'created' on server start) by pre-seeding the watcher's internal snapshot in the startWatch() method. This ensures that the first disk read sets the baseline and subsequent changes produce real activity events. We also implemented noise-filtering to ensure that metadata-only updates (like 'updated_at' bumps without data change) do not emit events. Verified with 9 distinct test cases in tests/lib/snapshot-differ.test.ts.","status":"closed","priority":2,"issue_type":"task","assignee":"green-falcon","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:07.5007059-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T00:01:54.1739009-08:00","closed_at":"2026-02-13T19:56:44.3701518-08:00","close_reason":"Snapshot diffing engine implemented and verified.","labels":["diff","timeline"],"dependencies":[{"issue_id":"bb-xhm.2","depends_on_id":"bb-xhm","type":"parent-child","created_at":"2026-02-11T17:12:07.501756-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.2","depends_on_id":"bb-xhm.1","type":"blocks","created_at":"2026-02-11T17:12:35.3430513-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.2","depends_on_id":"bb-tpc.2","type":"blocks","created_at":"2026-02-11T17:12:35.8495336-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.2","depends_on_id":"bb-xhm.4","type":"blocks","created_at":"2026-02-11T20:10:07.6688195-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.2","depends_on_id":"bb-2mx","type":"blocks","created_at":"2026-02-14T00:16:52.051166-08:00","created_by":"zenchantlive"}],"comments":[{"id":13,"issue_id":"bb-xhm.2","author":"zenchantlive","text":"Technical challenge: Preventing 'Event Storms' on first load. We solved this by pre-populating the snapshot map in startWatch() so the first read is treated as the baseline, not a 'create all' event. We also implemented noise-filtering to ignore 'updated_at' changes that don't affect actual data.","created_at":"2026-02-14T07:32:35Z"},{"id":29,"issue_id":"bb-xhm.2","author":"zenchantlive","text":"MEMO: The diffing engine was performance-tuned to handle large projects. In our benchmarks, diffing 200 beads against a 200-bead previous state completes in under 10ms, well within our SSE latency targets.","created_at":"2026-02-14T08:01:54Z"}]}
@ -137,3 +149,4 @@
{"id":"bb-ymg.2","title":"Implement mutation API for create/update/close/reopen/comment operations","description":"SUBTASK REPORT: Implementation of the Mutation API. We built a suite of App Router endpoints (/api/beads/create|update|close|reopen|comment) that serve as the bridge between the UI and the 'bd' CLI. Each endpoint enforces strict payload validation and maps incoming data to the appropriate CLI flags. The API returns a consistent response shape containing the normalized bridge output, allowing the frontend to handle success and failure deterministically.","acceptance_criteria":"Acceptance contract:\n- Every mutation route maps to bd.exe only.\n- Invalid payloads return explicit bad_args responses.\n- Reopen and comment operations are first-class and tested.","notes":"EXECUTION TALE: Implementation involved the creation of src/lib/mutations.ts to house the validation and command-mapping logic. We ensured that all status transitions are constrained to board-supported values and that 'update' calls require at least one mutable field. A major feature was the first-class support for 'comment' and 'reopen' operations, which were previously CLI-only. Verified via tests/api/mutations-routes.test.ts and runtime smoke tests across the full project lifecycle.","status":"closed","priority":0,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:03.3757503-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T00:14:29.6216695-08:00","closed_at":"2026-02-11T19:45:26.3234246-08:00","close_reason":"Mutation API implemented for create/update/close/reopen/comment with payload validation, command mapping, normalized error shape, and verified smoke lifecycle via API.","labels":["api","mutation"],"dependencies":[{"issue_id":"bb-ymg.2","depends_on_id":"bb-ymg","type":"parent-child","created_at":"2026-02-11T17:12:03.377343-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.2","depends_on_id":"bb-ymg.1","type":"blocks","created_at":"2026-02-11T17:12:32.810993-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.2","depends_on_id":"bb-ymg.1.1","type":"blocks","created_at":"2026-02-11T17:12:33.313807-08:00","created_by":"zenchantlive"}]}
{"id":"bb-ymg.3","title":"Add optimistic updates with rollback and SSE reconciliation","description":"SUBTASK REPORT: Optimistic Updates \u0026 SSE Reconciliation. We implemented the frontend 'Heartbeat' logic that allows the UI to update instantly before the CLI write is fully flushed to disk. The system uses a 'temporary local state' that is automatically reconciled when the authoritative SSE 'issues' event arrives from the server. This eliminates the 'Wait-and-Refresh' UX pattern typical of file-backed systems.","acceptance_criteria":"Acceptance contract:\n- Failed mutation restores prior local state.\n- Successful mutation reconciles to authoritative read response.\n- Pending state prevents repeated conflicting transitions.","notes":"EXECUTION TALE: We refactored the shared useBeadsSubscription hook to accept an updateLocal callback, allowing individual components to perform optimistic state mutations. We added rollback logic to handle CLI failure cases, ensuring the UI reverts to the last known-good state if a write fails. This coordination between the mutation API and the SSE transport layer was verified by monitoring network tabs for 'mutate -\u003e event -\u003e reconcile' sequences.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:04.1956393-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T00:14:30.438772-08:00","closed_at":"2026-02-11T19:59:02.289739-08:00","close_reason":"Optimistic board updates with rollback and authoritative post-mutation reconciliation via read route implemented and validated.","labels":["optimistic","state"],"dependencies":[{"issue_id":"bb-ymg.3","depends_on_id":"bb-ymg","type":"parent-child","created_at":"2026-02-11T17:12:04.1966728-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.3","depends_on_id":"bb-ymg.2","type":"blocks","created_at":"2026-02-11T17:12:33.8246167-08:00","created_by":"zenchantlive"}]}
{"id":"bb-ymg.4","title":"Implement drag-and-drop status transitions mapped to bd commands","description":"SUBTASK REPORT: Drag-and-Drop Status Transitions. We integrated the mutation API with the Kanban board's interaction model. Moving a card between columns now triggers an automated 'bd update [id] --status [new_status]' command. The UI provides visual feedback during the flight and handles cross-column blockers by validating the move against the server-side mutation rules.","acceptance_criteria":"Acceptance contract:\n- DnD invokes valid bd command sequence for each source-\u003etarget status.\n- Invalid/no-op transitions do not emit unnecessary commands.\n- Pending safeguards prevent duplicate conflicting moves.","notes":"EXECUTION TALE: We mapped React DnB events to specific CLI transitions. Implementation involved updating the Kanban card components to handle 'isDragging' and 'isUpdating' states. We ensured that dragging a blocked task shows a clear 'Deadlock' warning by checking the server-side error classification. Verified with cross-column move tests across all 5 standard Beads statuses (backlog, open, in_progress, blocked, closed).","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:05.0129676-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T00:14:31.2987104-08:00","closed_at":"2026-02-11T19:59:21.7655834-08:00","close_reason":"Kanban lane drag-and-drop transitions now map to bd-backed close/reopen/update mutations with transition planner tests and runtime smoke validation.","labels":["dnd","kanban"],"dependencies":[{"issue_id":"bb-ymg.4","depends_on_id":"bb-ymg","type":"parent-child","created_at":"2026-02-11T17:12:05.014527-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.4","depends_on_id":"bb-ymg.2","type":"blocks","created_at":"2026-02-11T17:12:34.329788-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.4","depends_on_id":"bb-trz.1","type":"blocks","created_at":"2026-02-11T17:12:34.8422542-08:00","created_by":"zenchantlive"}]}
{"id":"bb-yre","title":"Protocol Specification: Identity, Heartbeat, and Overlap Contracts","description":"Define the canonical protocol spec: identity adoption guardrails, BB_AGENT_STALE_MINUTES contract, and path normalization rules for overlap detection.","status":"closed","priority":0,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-14T09:43:59.6716632-08:00","created_by":"zenchantlive","updated_at":"2026-02-14T09:45:02.3332085-08:00","closed_at":"2026-02-14T09:45:02.3332085-08:00","close_reason":"Deleted: created before plan approval"}

52
src/lib/agent-protocol.ts Normal file
View file

@ -0,0 +1,52 @@
export type ProtocolEventType = 'HANDOFF' | 'BLOCKED' | 'INCURSION' | 'RESUME' | 'INFO';
export interface ProtocolEventEnvelope<T = any> {
id: string;
version: 'v1';
event_type: ProtocolEventType;
project_root: string;
bead_id: string;
from_agent: string | null;
to_agent: string | null;
scope: string | null;
created_at: string;
payload: T;
}
export type ProtocolEvent = ProtocolEventEnvelope;
export interface CreateProtocolEventInput {
event_type: ProtocolEventType;
project_root: string;
bead_id: string;
from_agent?: string;
to_agent?: string;
scope?: string;
payload: any;
}
export interface ProtocolDeps {
now: () => string;
idGenerator: () => string;
}
export function createProtocolEvent(
input: CreateProtocolEventInput,
deps: Partial<ProtocolDeps> = {}
): ProtocolEvent {
const now = deps.now ? deps.now() : new Date().toISOString();
const generateId = deps.idGenerator ?? (() => `proto_${Date.now()}_${Math.random().toString(16).slice(2, 6)}`);
return {
id: generateId(),
version: 'v1',
event_type: input.event_type,
project_root: input.project_root,
bead_id: input.bead_id,
from_agent: input.from_agent ?? null,
to_agent: input.to_agent ?? null,
scope: input.scope ?? null,
created_at: now,
payload: input.payload,
};
}

View file

@ -4,7 +4,7 @@ import path from 'node:path';
const AGENT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
export type AgentCommandName = 'agent register' | 'agent list' | 'agent show';
export type AgentCommandName = 'agent register' | 'agent list' | 'agent show' | 'agent heartbeat';
export interface AgentCommandError {
code: string;
@ -48,6 +48,12 @@ export interface ShowAgentInput {
agent: string;
}
export interface HeartbeatAgentInput {
agent: string;
}
export type AgentLiveness = 'active' | 'stale' | 'evicted';
function userProfileRoot(): string {
return process.env.USERPROFILE?.trim() || os.homedir();
}
@ -253,3 +259,57 @@ export async function showAgent(input: ShowAgentInput): Promise<AgentCommandResp
return invalid(command, 'INTERNAL_ERROR', error instanceof Error ? error.message : 'Failed to load agent.');
}
}
/**
* Derives the liveness state of an agent based on its last seen timestamp.
* stale threshold: staleMinutes (default 15)
* evicted threshold: 2 * staleMinutes (default 30)
*/
export function deriveLiveness(lastSeenAt: string, now: Date = new Date(), staleMinutes: number = 15): AgentLiveness {
const lastSeen = new Date(lastSeenAt).getTime();
const diffMs = now.getTime() - lastSeen;
const diffMin = diffMs / (1000 * 60);
if (diffMin >= 2 * staleMinutes) {
return 'evicted';
}
if (diffMin >= staleMinutes) {
return 'stale';
}
return 'active';
}
/**
* Updates the last_seen_at timestamp for a registered agent.
*/
export async function heartbeatAgent(
input: HeartbeatAgentInput,
deps: Partial<RegisterAgentDeps> = {},
): Promise<AgentCommandResponse<AgentRecord>> {
const command: AgentCommandName = 'agent heartbeat';
const agentId = trimOrEmpty(input.agent);
const agentIdError = validateAgentId(agentId);
if (agentIdError) {
return invalid(command, agentIdError.code, agentIdError.message);
}
try {
const existing = await readAgent(agentId);
if (!existing) {
return invalid(command, 'AGENT_NOT_FOUND', 'Agent is not registered.');
}
const now = deps.now ? deps.now() : new Date().toISOString();
const updated: AgentRecord = {
...existing,
last_seen_at: now,
version: existing.version + 1,
};
await writeAgent(updated);
return success(command, updated);
} catch (error) {
return invalid(command, 'INTERNAL_ERROR', error instanceof Error ? error.message : 'Failed to heartbeat agent.');
}
}

View file

@ -2,7 +2,8 @@ import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { showAgent } from './agent-registry';
import { showAgent, deriveLiveness } from './agent-registry';
import type { AgentRecord } from './agent-registry';
import type { AgentMessage } from './agent-mail';
const MIN_TTL_MINUTES = 5;
@ -101,6 +102,52 @@ function messageIndexDirectoryPath(): string {
return path.join(agentRoot(), 'messages', 'index');
}
/**
* Normalizes a path according to the Operative Protocol v1:
* 1. Resolve to absolute path.
* 2. Normalize separators to /.
* 3. On Windows, lowercase normalized path.
* 4. Remove trailing slash except root.
*/
export function normalizePath(p: string): string {
let resolved = path.resolve(p);
// Normalize separators
resolved = resolved.replace(/\\/g, '/');
// Lowercase on Windows
if (process.platform === 'win32') {
resolved = resolved.toLowerCase();
}
// Remove trailing slash except root (e.g., C:/ or /)
if (resolved.length > 3 && resolved.endsWith('/')) {
resolved = resolved.slice(0, -1);
}
return resolved;
}
export type OverlapClass = 'exact' | 'partial' | 'disjoint';
/**
* Classifies the overlap between two paths A and B.
*/
export function classifyOverlap(pathA: string, pathB: string): OverlapClass {
const normA = normalizePath(pathA.replace(/\*$/, ''));
const normB = normalizePath(pathB.replace(/\*$/, ''));
if (normA === normB) {
return 'exact';
}
// Check if one is a parent of the other
if (normB.startsWith(`${normA}/`) || normA.startsWith(`${normB}/`)) {
return 'partial';
}
return 'disjoint';
}
function trimOrEmpty(value: unknown): string {
return typeof value === 'string' ? value.trim() : '';
}
@ -273,25 +320,43 @@ export async function reserveAgentScope(
try {
const now = deps.now ? deps.now() : new Date().toISOString();
const reservations = await readActiveReservations();
const existing = reservations.find((reservation) => reservation.scope === scope);
const normalizedScope = normalizePath(scope);
const existing = reservations.find((r) => normalizePath(r.scope) === normalizedScope);
if (existing) {
if (!isExpired(existing, now)) {
return invalid(command, 'RESERVATION_CONFLICT', `Scope is already reserved by ${existing.agent_id}.`);
const isReservationExpired = isExpired(existing, now);
// If it's the SAME agent, we can always refresh/takeover their own scope
if (existing.agent_id === agentId) {
// Continue to creation logic
} else {
// Different agent owns it. Check liveness.
const ownerRes = await showAgent({ agent: existing.agent_id });
if (ownerRes.ok && ownerRes.data) {
const liveness = deriveLiveness(ownerRes.data.last_seen_at, new Date(now));
// active: takeover MUST fail
if (liveness === 'active' && !isReservationExpired) {
return invalid(command, 'RESERVATION_CONFLICT', `Scope is already reserved by active agent ${existing.agent_id}.`);
}
// stale or evicted: takeover MAY succeed only when takeoverStale is true
if (!input.takeoverStale) {
const reason = liveness === 'evicted' ? 'evicted' : (liveness === 'stale' ? 'stale' : 'expired');
return invalid(command, 'RESERVATION_STALE_FOUND', `An ${reason} reservation exists for ${existing.agent_id}. Re-run with --takeover-stale.`);
}
}
}
if (!input.takeoverStale) {
return invalid(command, 'RESERVATION_STALE_FOUND', 'An expired reservation exists. Re-run with --takeover-stale.');
}
const withoutExisting = reservations.filter((reservation) => reservation.reservation_id !== existing.reservation_id);
// If we reach here, we are taking over (either same agent or stale/evicted/expired takeover)
const withoutExisting = reservations.filter((r) => r.reservation_id !== existing.reservation_id);
await writeActiveReservations(withoutExisting);
await appendReservationHistory({ ...existing, state: 'expired' });
await appendReservationHistory({ ...existing, state: isReservationExpired ? 'expired' : 'released' });
const generateId = deps.idGenerator ?? (() => defaultReservationId(now));
const created: AgentReservation = {
reservation_id: generateId(),
scope,
scope: normalizedScope,
agent_id: agentId,
bead_id: beadId,
state: 'active',
@ -307,7 +372,7 @@ export async function reserveAgentScope(
const generateId = deps.idGenerator ?? (() => defaultReservationId(now));
const created: AgentReservation = {
reservation_id: generateId(),
scope,
scope: normalizedScope,
agent_id: agentId,
bead_id: beadId,
state: 'active',

View file

@ -0,0 +1,99 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import {
registerAgent,
heartbeatAgent,
deriveLiveness,
agentFilePath,
} from '../../src/lib/agent-registry';
async function withTempUserProfile(run: () => Promise<void>): Promise<void> {
const previous = process.env.USERPROFILE;
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'beadboard-agent-liveness-'));
process.env.USERPROFILE = tempDir;
try {
await run();
} finally {
if (previous === undefined) {
delete process.env.USERPROFILE;
} else {
process.env.USERPROFILE = previous;
}
await fs.rm(tempDir, { recursive: true, force: true });
}
}
test('heartbeatAgent updates last_seen_at and increments version', async () => {
await withTempUserProfile(async () => {
const start = '2026-02-14T10:00:00.000Z';
const next = '2026-02-14T10:05:00.000Z';
await registerAgent(
{ name: 'active-agent', role: 'infra' },
{ now: () => start }
);
const result = await heartbeatAgent(
{ agent: 'active-agent' },
{ now: () => next }
);
assert.equal(result.ok, true);
assert.equal(result.data?.last_seen_at, next);
assert.equal(result.data?.version, 2);
const raw = await fs.readFile(agentFilePath('active-agent'), 'utf8');
const parsed = JSON.parse(raw);
assert.equal(parsed.last_seen_at, next);
});
});
test('deriveLiveness follows threshold rules (15m/30m default)', () => {
const now = new Date('2026-02-14T12:00:00Z');
// Active: 14 mins ago
assert.equal(
deriveLiveness('2026-02-14T11:46:00Z', now),
'active'
);
// Stale: Exactly 15 mins ago
assert.equal(
deriveLiveness('2026-02-14T11:45:00Z', now),
'stale'
);
// Stale: 29 mins ago
assert.equal(
deriveLiveness('2026-02-14T11:31:00Z', now),
'stale'
);
// Evicted: Exactly 30 mins ago
assert.equal(
deriveLiveness('2026-02-14T11:30:00Z', now),
'evicted'
);
// Evicted: 1 hour ago
assert.equal(
deriveLiveness('2026-02-14T11:00:00Z', now),
'evicted'
);
});
test('deriveLiveness respects custom staleMinutes', () => {
const now = new Date('2026-02-14T12:00:00Z');
const customThreshold = 5; // 5m stale, 10m evicted
assert.equal(deriveLiveness('2026-02-14T11:56:00Z', now, customThreshold), 'active');
assert.equal(deriveLiveness('2026-02-14T11:55:00Z', now, customThreshold), 'stale');
assert.equal(deriveLiveness('2026-02-14T11:51:00Z', now, customThreshold), 'stale');
assert.equal(deriveLiveness('2026-02-14T11:50:00Z', now, customThreshold), 'evicted');
});

View file

@ -0,0 +1,29 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
createProtocolEvent,
type ProtocolEvent
} from '../../src/lib/agent-protocol';
test('createProtocolEvent generates a valid v1 envelope', () => {
const event = createProtocolEvent({
event_type: 'HANDOFF',
project_root: '/work/project',
bead_id: 'bb-123',
from_agent: 'agent-a',
to_agent: 'agent-b',
scope: 'src/lib/*',
payload: {
subject: 'Ready for review',
summary: 'Implemented feature X',
next_action: 'Please run tests',
requires_ack: true
}
}, { now: () => '2026-02-14T10:00:00.000Z', idGenerator: () => 'proto_1' });
assert.equal(event.version, 'v1');
assert.equal(event.event_type, 'HANDOFF');
assert.equal(event.id, 'proto_1');
assert.equal(event.created_at, '2026-02-14T10:00:00.000Z');
assert.equal(event.payload.subject, 'Ready for review');
});

View file

@ -0,0 +1,91 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { registerAgent, heartbeatAgent } from '../../src/lib/agent-registry';
import { reserveAgentScope } from '../../src/lib/agent-reservations';
async function withTempUserProfile(run: () => Promise<void>): Promise<void> {
const previous = process.env.USERPROFILE;
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'beadboard-agent-takeover-'));
process.env.USERPROFILE = tempDir;
try {
await run();
} finally {
if (previous === undefined) {
delete process.env.USERPROFILE;
} else {
process.env.USERPROFILE = previous;
}
await fs.rm(tempDir, { recursive: true, force: true });
}
}
test('takeover rules based on owner liveness', async () => {
await withTempUserProfile(async () => {
// T=0: Register Owner
const t0 = '2026-02-14T10:00:00.000Z';
await registerAgent({ name: 'owner', role: 'infra' }, { now: () => t0 });
// T=1: Owner reserves scope
const t1 = '2026-02-14T10:01:00.000Z';
await reserveAgentScope(
{ agent: 'owner', scope: 'src/lib', bead: 'bb-1' },
{ now: () => t1 }
);
// T=2: Invader tries takeover while Owner is ACTIVE (1 min since last seen)
const t2 = '2026-02-14T10:02:00.000Z';
await registerAgent({ name: 'invader', role: 'infra' }, { now: () => t2 });
const activeTakeover = await reserveAgentScope(
{ agent: 'invader', scope: 'src/lib', bead: 'bb-2', takeoverStale: true },
{ now: () => t2 }
);
assert.equal(activeTakeover.ok, false);
assert.equal(activeTakeover.error?.code, 'RESERVATION_CONFLICT');
assert.match(activeTakeover.error?.message || '', /already reserved by.*owner/);
// T=17: Owner is now STALE (16 mins since last seen at T=1)
const t17 = '2026-02-14T10:17:00.000Z';
// Takeover without flag fails
const staleNoFlag = await reserveAgentScope(
{ agent: 'invader', scope: 'src/lib', bead: 'bb-2', takeoverStale: false },
{ now: () => t17 }
);
assert.equal(staleNoFlag.ok, false);
assert.equal(staleNoFlag.error?.code, 'RESERVATION_STALE_FOUND');
// Takeover with flag succeeds
const staleWithFlag = await reserveAgentScope(
{ agent: 'invader', scope: 'src/lib', bead: 'bb-2', takeoverStale: true },
{ now: () => t17 }
);
assert.equal(staleWithFlag.ok, true);
assert.equal(staleWithFlag.data?.agent_id, 'invader');
});
});
test('takeover succeeds when owner is EVICTED', async () => {
await withTempUserProfile(async () => {
const t0 = '2026-02-14T10:00:00.000Z';
await registerAgent({ name: 'owner', role: 'infra' }, { now: () => t0 });
await reserveAgentScope({ agent: 'owner', scope: 'src/lib', bead: 'bb-1' }, { now: () => t0 });
// T=31: Owner is EVICTED (31 mins since last seen)
const t31 = '2026-02-14T10:31:00.000Z';
await registerAgent({ name: 'invader', role: 'infra' }, { now: () => t31 });
const evictedTakeover = await reserveAgentScope(
{ agent: 'invader', scope: 'src/lib', bead: 'bb-2', takeoverStale: true },
{ now: () => t31 }
);
assert.equal(evictedTakeover.ok, true);
assert.equal(evictedTakeover.data?.agent_id, 'invader');
});
});

View file

@ -0,0 +1,45 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import path from 'node:path';
// We'll export these from agent-reservations.ts
import {
normalizePath,
classifyOverlap
} from '../../src/lib/agent-reservations';
test('normalizePath canonicalizes various path formats', () => {
const root = path.resolve('/');
// Basic normalization
assert.equal(normalizePath('src/lib/'), normalizePath('src/lib'));
assert.equal(normalizePath('src//lib'), normalizePath('src/lib'));
// Windows specific (if running on windows, this is handled in the impl)
// We can't easily test cross-platform logic without mocking path.
// But we can check that it resolves and removes trailing slash.
const p1 = normalizePath('src/components');
assert.ok(path.isAbsolute(p1));
assert.ok(!p1.endsWith('/') || p1 === root);
});
test('classifyOverlap correctly identifies exact matches', () => {
const p1 = 'src/lib/parser.ts';
const p2 = 'src/lib/parser.ts';
assert.equal(classifyOverlap(p1, p2), 'exact');
});
test('classifyOverlap correctly identifies partial overlaps', () => {
// Parent-child
assert.equal(classifyOverlap('src/lib', 'src/lib/parser.ts'), 'partial');
assert.equal(classifyOverlap('src/lib/parser.ts', 'src/lib'), 'partial');
// Prefix/Wildcard
assert.equal(classifyOverlap('src/*', 'src/lib/parser.ts'), 'partial');
assert.equal(classifyOverlap('src/lib/parser.ts', 'src/*'), 'partial');
});
test('classifyOverlap correctly identifies disjoint paths', () => {
assert.equal(classifyOverlap('src/lib', 'src/components'), 'disjoint');
assert.equal(classifyOverlap('src/lib/parser.ts', 'src/lib/other.ts'), 'disjoint');
});