# 📡 #1885 — Cross-session state bus

Each `claude agents` session writes its own distilled state to a known file path. Any other session can read it with one tool call. No wrappers around the native CLI; no parsing of CC's internal JSONL.

## ❌ Before — "did the agent ship?"

    $ git -C platform log --oneline -5
    $ git -C platform reflog | head -5
    $ git -C platform log -1 --format='%cI %s'
    $ gh pr list --repo y/p --head feat/x
    $ gh pr checks 3703 --repo y/p
    $ ls ~/.claude/projects/-…/sessions/

    ⏱ 6 tool calls · 200+ lines · context bloated
       Operator hesitates → late detection → bigger blast

## ✅ After

    $ cat ~/.claude/state/orchestkit/platform/*.json
    {
      "repo": "platform",
      "status": "running",
      "last_heartbeat": "2026-05-20T17:42:14Z",
      "last_commit": {
        "sha": "3a99c20",
        "msg": "feat(api): canonical seed-data migration"
      },
      "last_push": { "branch": "feat/x", "ref": "3a99c20" },
      "last_pr":   { "number": 3703, "url": "…", "state": "open" }
    }

    ⏱ 1 tool call · structured JSON · zero grep

## What gets wired (2 hooks, 1 lib)

    src/hooks/src/lib/session-state.ts                       ← schema + atomic I/O
    src/hooks/src/posttool/bash/session-heartbeat-publisher  ← PostToolUse[Bash], async
    src/hooks/src/stop/session-heartbeat-finalizer           ← SessionEnd, sync dispatcher

    PUBLISHER fires on EVERY Bash, no-ops on non-structural commands.
    Matches: git commit | git push | gh pr create|merge|ready
    Writes to: ~/.claude/state/orchestkit/<repo>/<sid>.json (atomic, tmp+rename)

    FINALIZER runs once at SessionEnd to mark status=completed.
    If session is killed unsafely, last_heartbeat goes stale (proxy for stopped).

## Fleet view (no new tool — plain shell)

    $ ls ~/.claude/state/orchestkit/*/*.json | xargs cat | jq -s \
        'map({ repo, status, last_pr: .last_pr.number, last_commit: .last_commit.msg })'
    [
      { "repo": "orchestkit",    "status": "running",   "last_pr": 1900, … },
      { "repo": "portfolio",     "status": "running",   "last_pr": 223,  … },
      { "repo": "platform",      "status": "running",   "last_pr": 3703, … },
      { "repo": "sanity-agency", "status": "completed", "last_pr": 16,   … }
    ]

## Design rules (this is the #1890 takeaway applied)

- ❌ NOT a wrapper around `claude agents` (that was agents-view)
- ❌ NOT a parser of CC's transcript JSONL (would couple to internals)
- ❌ NOT a new skill, CLI, or MCP — file format IS the contract
- ✅ Each session writes ONLY its own file
- ✅ Atomic tmp+rename — readers never see a half-written file
- ✅ Hooks log on debug only — never blocks Claude

## Test coverage (Layer 1 + Layer 5)

    Layer 1 — Unit (real temp fs, no fs mocks):
      ✓ session-state.ts          13 cases (path math + atomic I/O + merge)
      ✓ session-heartbeat-publisher 16 cases (classifier + parsers + integration)
      ✓ session-heartbeat-finalizer  5 cases (no-prior-state, mark complete, …)
      ────────────────────────────
      34 / 34 passing

    Layer 5 — Full hook suite regression:
      ✓ 8385 / 8385 (was 8351 before this PR)
      ✓ Biome lint: clean
      ✓ npm run build: green
      ✓ ./bin/validate-counts.sh: PASSED