๐Ÿ“ก #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)

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