LearnNewsExamplesServices
Frontmatter
id14473
titleResolve lifecycle-state path contract for hook live boards
stateClosed
labels
enhancementaiarchitecturemodel-experience
assigneesneo-gpt
createdAtJul 2, 2026, 4:26 PM
updatedAtJul 3, 2026, 12:21 AM
githubUrlhttps://github.com/neomjs/neo/issues/14473
authorneo-gpt
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 3, 2026, 12:21 AM

Resolve lifecycle-state path contract 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, 4:26 PM

Context

PR #14469 implemented the writer requested by #14466, but team review found that the ticket described the wrong consumer path. The implementation is useful salvage, yet the default write target is dead-on-arrival against current hook readers: it writes the stale .claude/logs/lifecycle-state.json contract from #14466 while the live readers resolve state under .neo-ai-data.

This ticket supersedes #14466 instead of trying to patch PR #14469 in place. Tobias challenged the PR to drop/supersede, @neo-gpt accepted the author-side disposition, @neo-opus-vega and @neo-fable-clio posted peer-role concurrence with falsifying evidence, and @neo-opus-grace released her redundant peer-role claim after Vega resolved the constant evidence.

Live latest-open sweep: checked latest 20 open issues at 2026-07-02T14:24:34Z; #14466 is the stale superseded source, and no separate replacement-path contract ticket was present. A2A in-flight sweep: checked latest 30 all-status messages at 2026-07-02T14:24:34Z; saw Vega and Clio peer-role posts, Grace claim release, and Vega topology correction, but no competing ticket-creation claim. KB ticket sweep for lifecycle-state hook live board .neo-ai-data harness-state duplicate returned unrelated historical board material and no equivalent ticket.

The Problem

#14466 transcribed the hook consumer as .claude/logs/lifecycle-state.json. That was stale against the current hook source. Implementing it faithfully creates a writer and consumer that do not meet:

  • .claude/hooks/laneStateStopHook.mjs:64 sets LOG_DIR to process.env.NEO_AI_DAEMON_DIR || path.join(os.homedir(), .neo-ai-data, lane-state-hook); line 158 reads lifecycle-state.json under that directory.
  • .codex/hooks/codex-lane-state-stop.mjs:30 mirrors the neutral .neo-ai-data daemon-dir pattern.
  • #14466 still says the writer should produce .claude/logs/lifecycle-state.json.

The old PR body, tests, ticket close target, and default constant all encode the stale path. A rename patch would still leave the larger contract implicit: which .neo-ai-data home, whether the file is per-agent or shared, how hooks discover it without network calls, and which module owns the path resolver.

The Architectural Reality

Source anchors checked before filing:

  • .claude/hooks/laneStateStopHook.mjs:154-158 says the hook reads daemon-written current-agent lane state, cheaply and fail-open, from path.join(LOG_DIR, lifecycle-state.json).
  • .codex/hooks/codex-lane-state-stop.mjs:27-31 establishes the Codex family adapter uses the same neutral daemon-dir family, not .claude.
  • .gitignore:100-103 makes .neo-ai-data the ignored Agent OS local data home while preserving the tracked .neo-ai-data/concepts/ exception.
  • package.json:72 uses ./.neo-ai-data/chroma/unified for the local Chroma server.
  • ai/scripts/migrations/bootstrapWorktree.mjs:48-67 documents why the .neo-ai-data boundary is load-bearing: the parent directory must not be symlinked wholesale, while gitignored child substrate dirs can be linked to unify Memory Core, A2A, wake, and logs without clobbering tracked concepts.
  • ai/services/graph/GoldenPathSynthesizer.mjs already owns Golden Path synthesis, Active PR Cycle sections, and handoff writing; ai/daemons/orchestrator/scheduling/pipeline.mjs:557-561 invokes it from the golden-path cadence.

Pre-Flight (structural fast-path): a shared lifecycle-state path resolver under ai/scripts/lifecycle/ matches the sibling pattern of ai/scripts/lifecycle/stopHookDecision.mjs and ai/scripts/lifecycle/validateLaneStateTerminal.mjs; family hook files remain thin adapters, writer code imports the same resolver, and no novel directory choice is needed. Map-maintenance: not needed because this is another lifecycle helper under an existing directory role.

The Fix

Replace #14466 with a resolver-first implementation:

  1. Define one shared lifecycle-state path resolver used by every reader and writer. It must honor NEO_AI_DAEMON_DIR and default to a neutral .neo-ai-data hook-state home, never a family-local .claude path.
  2. Update the Claude and Codex hook readers to share the same resolver contract instead of rebuilding separate default paths.
  3. Port the useful PR #14469 writer pieces: payload builder, generated timestamp, own-PR board, unread-count honesty, Golden Path direction projection, same-directory temp + rename write, and degrade-by-omission behavior.
  4. Decide and implement the topology explicitly. Vega follow-up probe falsified the earlier unkeyed per-clone lean because harness-state is symlinked to canonical. The preferred shape is now a central/canonical home with per-agent-keyed files, for example lifecycle-state/.json, unless implementation V-B-A proves a better equivalent. A single unkeyed file is not acceptable for multi-peer operation.
  5. Keep hook relocation to ai/agent/hooks out of this ticket. The replacement may prepare shared logic under ai/scripts/lifecycle/, but the v13.2 hook-home move belongs to the broader harness/Agent OS architecture lane.
  6. Close PR #14469 unmerged after the replacement ticket exists; the code should be ported, not merged from that branch as-is.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
Lifecycle-state path resolver laneStateStopHook.mjs:64/:158, codex-lane-state-stop.mjs:30, structural fast-path to ai/scripts/lifecycle/ One resolver returns the exact path for both readers and writers, honoring NEO_AI_DAEMON_DIR and a neutral .neo-ai-data default. Missing env var uses documented default; invalid identity/path inputs fail closed at write time and fail open at hook read time. JSDoc on resolver naming locality, identity keying, and env override semantics. Unit test proving Claude reader, Codex reader, and writer resolve the same path for default and env override cases.
Per-agent lifecycle-state payload Hook read-side docblock and peer-role topology correction on PR #14469 Store agent-specific state under a keyed path, not one clobber-prone shared JSON file. Missing keyed file returns null; hook injects bare reminder. Writer/helper JSDoc names the key source and scope. Unit test with two agent identities proving no overwrite or clobber.
Writer cadence GoldenPathSynthesizer plus orchestrator golden-path task Golden Path/orchestrator cadence writes the resolved file after source data is computed. Writer failure warns and does not break handoff synthesis. JSDoc on writer owner contract. Focused unit coverage for success, partial-source omission, and write failure isolation.
Hook readers Claude and Codex family hook adapters Hooks remain cheap fail-open readers; they do not fetch network data or make admission decisions from the live board. Missing/malformed/unreadable state yields no board enrichment. Existing hook schema comments updated to point at shared resolver. Existing hook tests plus new resolver-agreement regression.
Migration posture PR #14469 peer-role convergence .claude/logs/lifecycle-state.json is legacy at most; no canonical write targets it. Optional best-effort read of legacy file may be allowed only if it cannot mask the resolved canonical file. PR body must state migration behavior explicitly. Test or code comment proving canonical path wins.

Decision Record impact

Aligned with ADR-0019-style SSOT discipline: path/default semantics should live in one resolver rather than being re-derived in each reader/writer. No new ADR is required if this stays a lifecycle hook enrichment and does not change Stop-hook admission semantics. If implementation discovers that cloud/local state-home discovery needs a broader config-plane precedent, route that delta to the existing Agent OS/config-plane discussion before expanding this ticket.

Acceptance Criteria

  • Shared resolver exists for lifecycle-state path calculation and is used by every lifecycle-state reader and writer touched by the PR.
  • Resolver honors NEO_AI_DAEMON_DIR and defaults to a neutral .neo-ai-data hook-state location, never .claude/logs.
  • State is per-agent keyed or equivalently collision-proof; tests prove two agent identities cannot clobber each live board.
  • The writer ports the useful #14469 behavior but writes only through the shared resolver.
  • Reader/writer path agreement is pinned by a regression test that would have failed PR #14469 .claude/logs default.
  • Hook behavior remains fail-open for missing, malformed, unreadable, or partially unavailable state.
  • The implementation keeps v13.2 hook relocation out of scope, except for sharing pure lifecycle utilities from ai/scripts/lifecycle/.
  • PR body explicitly states how PR #14469 code was ported or discarded and why #14466 was superseded.

Out of Scope

  • Moving family hook definitions into ai/agent/hooks.
  • Changing Stop-hook admission, lane-state validation, or no-hold semantics.
  • Adding #13623 AC4 ratio/re-tread/operator-scope fields.
  • Broad cloud deployment state discovery beyond the minimum local/cloud-safe path contract needed here.
  • Reusing .claude/logs as a canonical state home.

Avoided Traps

  • Resolved constants beat ticket transcription. Future review must compare reader and writer resolved defaults, not only field shapes or issue prose.
  • Do not merge a functionally dead writer. Green tests that inject paths do not prove production reader/writer rendezvous.
  • Do not use one unkeyed file for many peers. The board is current-agent state; clobbering across agent identities violates the consumer contract.
  • Do not symlink parent .neo-ai-data. Follow the child-link discipline from bootstrapWorktree.mjs so tracked concepts remains safe.

Related

Supersedes #14466 Related: #14469 Related: #13623 Related: #13678 Related: #13751 Related: #14463 Related: #13012

Origin Session ID: c5938a7c-42e6-4f94-ac19-1a874529dfb4

Retrieval Hint: PR 14469 lifecycle-state writer drop supersede .neo-ai-data resolver per-agent keyed live board