LearnNewsExamplesServices
Frontmatter
id14466
titleEmit shared lifecycle-state.json for hook live boards
stateClosed
labels
enhancementaiarchitecturemodel-experience
assigneesneo-gpt
createdAtJul 2, 2026, 12:41 PM
updatedAtJul 2, 2026, 4:27 PM
githubUrlhttps://github.com/neomjs/neo/issues/14466
authorneo-gpt
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 2, 2026, 4:27 PM

Emit shared lifecycle-state.json for hook live boards

Closed v13.1.0/archive-v13-1-0-chunk-8 enhancementaiarchitecturemodel-experience
neo-gpt
neo-gpt commented on Jul 2, 2026, 12:41 PM

Context

Grace surfaced the missing producer while reviewing the #14463 / #13751 hook-read consumer: lifecycle-state.json now has multiple readers or near-term consumers, but no writer exists. The current #13623 parent remains not-code-ready for AC4 ratio/re-tread/operator-scope observability because those fields still lack source/window semantics. Phase 1 does not need to wait for that: the sources for {openPRs, unreadCount, goldenPathDirection} are already live and can unblock the existing hook board consumers.

Live latest-open sweep: checked the latest 20 open issues on 2026-07-02T10:39Z; no equivalent standalone shared-writer ticket found. A2A in-flight sweep: checked latest 30 all-status messages on 2026-07-02T10:39Z; Grace's #13623 co-scope ask exists, but no competing ticket-creation claim exists.

The Problem

.claude/hooks/laneStateStopHook.mjs already reads .claude/logs/lifecycle-state.json as a fail-open enrichment for the no-hold block directive. The comment says the orchestrator/wake daemon writes the file, but the repository currently only has the read side. That leaves three surfaces dormant or partially dormant:

  • #13678's live-board fields: openPRs and unreadCount.
  • #14463 / #13751's hook-read consumer: goldenPathDirection.
  • #13623 AC4's future observability fields, which should be additive later but are not source-ready today.

A single shared writer prevents multiple hook-adjacent lanes from inventing separate files, races, or duplicated GitHub/mailbox reads.

The Architectural Reality

Source anchors checked before filing:

  • .claude/hooks/laneStateStopHook.mjs:154 names the daemon-write side and .claude/logs/lifecycle-state.json; readLifecycleState() is fail-open and formatLifecycleBoard() currently renders only openPRs and unreadCount.
  • ai/services/graph/GoldenPathSynthesizer.mjs:501 already fetches live open PR data for the Active PR Cycle State section.
  • ai/services/graph/GoldenPathSynthesizer.mjs:1021 renders Active PR Cycle State during the same synthesis pass that writes sandman_handoff.md at ai/services/graph/GoldenPathSynthesizer.mjs:1085.
  • ai/daemons/orchestrator/scheduling/pipeline.mjs:557 runs the Golden Path task via services.goldenPathSynthesizer.synthesizeGoldenPath(); the orchestrator already treats successful handoff writing as a task outcome.
  • ai/services/memory-core/MailboxService.mjs:2095 has a direct countMessages({box:'inbox', status:'unread'}) path for unreadCount.

The home call: do not put this in ai/scripts/runners/runSandman.mjs; that file is a thin CLI wrapper over DreamService.executeRemCycle(). The writer should live with the synthesized state owner, preferably as a small helper/service invoked from the Golden Path synthesis/orchestrator golden-path task after the data is computed. The hook remains a fail-open reader only.

The Fix

Create one lifecycle-state writer for Phase 1 fields:

  • generatedAt: ISO timestamp for freshness display.
  • openPRs: bounded array of the current agent's open PR board entries suitable for the hook formatter.
  • unreadCount: current bound-agent unread A2A count.
  • goldenPathDirection: compact top-route / degraded-route directive for #14463's consumer, sourced from the same Golden Path pass that writes sandman_handoff.md.

The writer should write exactly the file the hook reads: .claude/logs/lifecycle-state.json. The write must be atomic enough for hook-read safety: write temp + rename or equivalent. Schema/read failures stay fail-open in the hook.

Recommended substrate: a dedicated lifecycle-state helper under the graph/orchestrator service boundary, called from the Golden Path synthesis/orchestrator golden-path task. Keep the CLI runner passive.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
.claude/logs/lifecycle-state.json .claude/hooks/laneStateStopHook.mjs read side + #13623 co-scope comment IC-4864771245 Single writer emits {generatedAt, openPRs, unreadCount, goldenPathDirection}. Missing/malformed file remains fail-open; hook injects bare reminder. JSDoc on writer helper and hook schema comment. Unit test for valid write shape and malformed/fail-open read behavior.
openPRs GoldenPathSynthesizer live gh pr list enrichment Reuse/derive from the existing open PR fetch path; bound to the active agent where needed by the hook board. If GitHub fetch fails, omit openPRs and preserve other fields. JSDoc field contract. Unit fixture for populated, empty, and degraded PR fetch.
unreadCount MailboxService countMessages unread path Emit integer unread count for the current bound agent identity. If identity/mailbox unavailable, omit or set 0 only when that state is verified; do not fabricate from errors. JSDoc field contract. Unit fixture for count success and mailbox failure.
goldenPathDirection Golden Path synthesis result / #13751 consumer contract Emit compact directional data from the routed top node or degraded-route state. If Golden Path route is degraded, emit an explicit degraded/null state rather than a fake recommendation. JSDoc field contract; no new ADR unless direction semantics change. Unit fixture for routed, empty, contradiction, and degraded route states.

Decision Record impact

Aligned with the existing Golden Path / Dream Pipeline architecture and ADR-0019-style SSOT discipline: one writer, one file, fail-open reader. No ADR required if this stays a hook-board enrichment and does not change Stop-hook admission semantics.

Acceptance Criteria

  • A single writer produces .claude/logs/lifecycle-state.json with generatedAt, openPRs, unreadCount, and goldenPathDirection.
  • The writer is invoked from the Golden Path/orchestrator cadence, not from the thin Sandman CLI wrapper.
  • File writes are atomic/bounded so the Stop hook never observes a partially written JSON payload under normal operation.
  • Hook reader behavior remains fail-open for missing, malformed, or partially unavailable fields.
  • Unit coverage proves valid shape, missing-field tolerance, malformed JSON/read failure tolerance, and degraded source handling.
  • The implementation does not add #13623 AC4 ratio/re-tread/operator-scope fields yet; it only leaves an additive schema path for them.
  • The PR body explicitly names post-merge validation for a real hook block reading the generated file.

Out of Scope

  • #13623 AC4 ratio-over-window, re-tread, or forcedContinuationNoCompliantLane observability fields.
  • COP rendering under #13444.
  • Stop-hook admission, lane-state validator semantics, or any new permission-to-stop path.
  • Multiple writers or per-harness divergent files.
  • Changing health/check economics or Golden Path ranking semantics.

Avoided Traps

  • Do not bury this in runSandman.mjs. That runner is intentionally a thin CLI wrapper; the writer belongs with the synthesized state owner.
  • Do not keep this inside #13623 AC4. Phase 1 has resolved sources and unblocks existing consumers; AC4 Phase 2 remains source-unresolved and should not hold the writer hostage.
  • Do not create a second hook-state file for goldenPathDirection. The hook needs one fail-open board file, not competing substrates.

Related

Related: #13623 Related: #13678 Related: #13751 Related: #14463 Related: #13652

Origin Session ID: 8facbc96-c346-4633-9141-79a968ca1c5d

Retrieval Hint: lifecycle-state.json writer GoldenPathSynthesizer openPRs unreadCount goldenPathDirection #13623 IC-4864771245