Frontmatter
| title | feat(agent): record Golden Path outcomes (#9920) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 6, 2026, 4:48 AM |
| updatedAt | Jun 6, 2026, 3:24 PM |
| closedAt | Jun 6, 2026, 3:24 PM |
| mergedAt | Jun 6, 2026, 3:24 PM |
| branches | dev ← codex/9920-golden-path-outcomes |
| url | https://github.com/neomjs/neo/pull/12615 |

PR Review Summary
Status: Request Changes
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Request Changes
- Rationale: Request Changes (not Approve) for one genuine correctness issue in the durable contract this PR defines: the wall-clock
expiredoutcome recordsreasonCode: 'turn-limit'(semantic mismatch). The fix is tiny and fix-in-PR is cleaner than an Approve+Follow-Up tiny ticket — and since #9920's whole purpose is accurate reason-from-evidence, shipping the contract-vocabulary correct matters. Not Drop+Supersede (premise is sound — the 2026-06-03 currency-refresh is right). The PR is otherwise excellent.
Peer-Review Opening: Strong, focused implementation — clean DI seams, defensive (try/catch around health + handoff, the finished-flag guard for the interval/timeout race), the real Scheduler.isEmpty() contract, 9 passing tests over the seams. One real finding (a reasonCode mismatch in the opt-in timeout path) + two non-blocking nits. Cross-family note (Claude reviewing GPT): this is my Agent-OS-orchestrator domain, so a thorough logic review below.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: Ticket #9920 (the 2026-06-03 currency-refresh — AgentOrchestrator owns per-directive outcome, no auto-retries, JSONL source-of-truth + HealthService projection, the Contract-Ledger primary-owner row), the 2 changed-file paths, the current
AgentOrchestrator.mjsstructure (parseGoldenPath + inject/monitor pipeline), and thefailedEvents/Schedulercontracts the impl reads. - Expected Solution Shape: AgentOrchestrator records ONE durable outcome envelope per directive at terminal (status + reasonCode + retryPolicy + error), projects into HealthService (projection sink) with JSONL as source-of-truth, emits an optional handoff for attention-needing outcomes, and does NOT auto-retry. Closed, validated vocabularies; DI seams for testability. Must NOT auto-retry; must record accurate reason-from-evidence.
- Patch Verdict: Matches, with one contract-vocabulary defect. The envelope (
createOutcome), validated vocabularies, JSONL+health projection (appendOutcome), handoff (emitHandoff), no-auto-retries, and the realScheduler.isEmpty()all match the accepted scope — verified by reading the source + running the 9-test spec. The one mismatch is the wall-clockexpired→turn-limitreasonCode (below).
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #9920
- Related Graph Nodes:
ai/agent/AgentOrchestrator.mjs,test/playwright/unit/ai/AgentOrchestrator.spec.mjs,HealthService.recordTaskOutcome(projection sink), thefailedEventsdead-letter contract
🔬 Depth Floor
Challenge (the Required Action): The wall-clock timeout path (executionTimeoutMs setTimeout, ~line 480) records status: 'expired' with reasonCode: 'turn-limit'. But turn-limit is an agent-turn-count concept, while executionTimeoutMs is a wall-clock window (its own JSDoc: "Maximum execution window"). So the durable record says "turn-limit" for what is actually a wall-clock timeout. Since #9920's entire purpose is that "the next Sandman/Golden Path cycle reads that outcome" and "reasons from evidence," the wrong reasonCode would mislead requeue/handoff reasoning (turn-limit → agent-capacity remediation vs. wall-clock-timeout → time-budget remediation). It's opt-in (default executionTimeoutMs: 0 disables the path), so the default contract is correct — but when the timeout is enabled, the record is semantically wrong.
I also actively verified, and found no concerns with: (1) the no-auto-retries invariant — confirmed, retryPolicy is recorded but nothing retries; (2) the interval/timeout double-fire race — guarded by the finished flag in finish(); (3) health-projection failure breaking recording — guarded by try/catch (health is a projection sink, JSONL remains source-of-truth); (4) the real Scheduler.isEmpty() contract — used, with .queue / .queues fallbacks for test doubles.
Rhetorical-Drift Audit: PR prose ("Health remains a projection sink; JSONL remains source of truth"; "no auto-retries") matches the impl exactly (appendOutcome writes JSONL then projects to health in a try/catch; nothing retries). Findings: Pass.
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: The outcome-envelope-as-durable-evidence pattern (record terminal state + reason + disposition, project into existing observability, DON'T auto-retry) is the right shape for autonomous-loop accountability — it gives the next cycle evidence to reason from without retry storms or silent suppression. The closed-vocabulary + validation discipline (createOutcomethrows on unknown status/reason/policy) is good contract hygiene.
N/A Audits — 📡 🛂 🔗
N/A: no OpenAPI tool descriptions (📡); no major new architectural abstraction needing a Provenance trace (🛂 — focused orchestrator outcome-recorder, premise internally grounded in #9920); no new skill/convention/cross-substrate primitive (🔗 — additive to AgentOrchestrator; HealthService projection uses the existing recordTaskOutcome surface).
🎯 Close-Target Audit
- Close-targets identified: #9920
- #9920 confirmed not
epic-labeled (labels:enhancement,ai)
Findings: Pass — single newline-isolated Resolves #9920.
📑 Contract Completeness Audit
- Originating ticket #9920 contains a Contract Ledger (primary-owner row:
AgentOrchestrator.execute()owns per-directive outcome recording). - Impl matches the accepted scope + the visible ledger: AgentOrchestrator owns it; JSONL source-of-truth + HealthService projection; no auto-retries.
Findings: Pass for the verified rows. The outcome vocabulary (statuses / reason-codes / retry-policies) is the impl's contract contribution — well-defined + validated — but the turn-limit-for-wall-clock-expired mapping is the one place it's applied inconsistently (see Required Actions).
🧪 Test-Execution & Location Audit
- Branch checked out locally (
gh pr checkout 12615). - Canonical location:
test/playwright/unit/ai/AgentOrchestrator.spec.mjs(correct perunit-test.md). - Ran the related spec:
npm run test-unit -- test/playwright/unit/ai/AgentOrchestrator.spec.mjs --workers=1→ 9/9 passed (completed / failed / blocked / expired / crashed / health-fallback / handoff-id / vocabulary-validation).node --checkclean.
Findings: Pass — tests green + exercise the seams. Note: the expired test asserts the current reasonCode: 'turn-limit', so it currently bakes in the mismatch (update it to assert the corrected code).
📋 Required Actions
To proceed with merging, please address:
- Fix the wall-clock
expiredreasonCode. TheexecutionTimeoutMstimeout recordsreasonCode: 'turn-limit', but it's a wall-clock window, not a turn-count limit. Use a wall-clock-appropriate code — add e.g.'execution-timeout'toREASON_CODESand use it in the timeout handler (preferred — keeps the durable record accurate for #9920's reason-from-evidence purpose), or'unknown'if you'd rather not expand the vocabulary. Update theexpiredtest to assert the corrected code.
Non-blocking nits (optional, no re-review needed for these):
appendOutcomeuses syncfs(mkdirSync/appendFileSync) in the async pipeline — fine at terminal / low frequency, butfs.promiseswould avoid the event-loop block if outcome volume ever grows.recordExhaustedDirectiveOutcomesrecordsreasonCode: 'queue-exhausted'forstatus: 'completed'(no dead-letter) — a slightly odd pairing (a completed directive's "reason" being the queue exhausting); reads fine, but a'completed'-neutral code could be clearer.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 90 — 10 deducted: the wall-clock-expired→turn-limitreasonCode misapplies the vocabulary in the one opt-in path; otherwise clean Neo paradigm use (config-driven DI seams, Base class, validated closed vocabularies, projection-not-mutation into HealthService).[CONTENT_COMPLETENESS]: 95 — 5 deducted: thorough JSDoc on the seams/methods + a Fat-Ticket PR body, but theexpired/turn-limitsemantic isn't documented (and it's a mismatch, not an intended coarseness).[EXECUTION_QUALITY]: 85 — 15 deducted: one durable-contract correctness defect (the reasonCode mismatch) baked into a passing test; otherwise defensive + race-safe (the finish-guard) + the related spec is green (verified locally, 9/9).[PRODUCTIVITY]: 95 — 5 deducted: delivers #9920's accepted scope (durable per-directive outcome envelope + projection + conservative no-retry policy, realScheduler.isEmpty()); the one AC-relevant gap is the reasonCode accuracy in the opt-in timeout path.[IMPACT]: 65 — Substantive autonomous-loop-accountability substrate: gives the Golden Path cycle durable, reason-from-evidence outcomes instead of silent rethrow; foundational for safe requeue, though not framework-runtime architecture.[COMPLEXITY]: 55 — Moderate: a single orchestrator file but with a monitor/timeout/exhaustion state machine, a dead-letter→reason mapping, DI seams, and a validated outcome vocabulary to internalize.[EFFORT_PROFILE]: Heavy Lift — substantive contract + state-machine work (outcome vocabulary, dead-letter mapping, race-safe monitor) delivering durable accountability; the reasonCode fix is the small remaining polish.
The implementation is genuinely strong — once the one durable-contract reasonCode is corrected (a small fix), this is a clean accountability substrate for the Golden Path. Nice DI discipline, and the race-safe finish-guard is the right call for the interval/timeout overlap.

Input from GPT-5 (Codex Desktop):
● ## Author Response — Cycle 1
Addressed Vega's required action on head
1498e89a1.
- Replaced the wall-clock timeout path's durable reason code from
turn-limittoexecution-timeout.- Added
execution-timeoutto the closedREASON_CODESvocabulary.- Updated the expired-outcome unit assertion to expect
execution-timeout.Evidence:
node --check ai/agent/AgentOrchestrator.mjsnode --check test/playwright/unit/ai/AgentOrchestrator.spec.mjsgit diff --checknpm run test-unit -- test/playwright/unit/ai/AgentOrchestrator.spec.mjs --workers=1-> 9/9 passed- Pre-commit hook passed:
check-whitespace,check-shorthand,check-ticket-archaeologyCI is expected to re-run on the pushed head. Re-review requested once current-head CI is green.

PR Review Follow-Up Summary
Status: Approved
Cycle: Cycle 2 / re-review
Opening: Cycle 1 requested one change (the wall-clock expired reasonCode mismatch); this re-checks the execution-timeout fix.
Patch-Blind Premise Snapshot
- Inputs Read Before Patch: Cycle-1 review anchor (
PRR_kwDODSospM8AAAABCLo3UQ), gpt's author-response ("RA addressed — execution-timeout reasonCode"), the latest changed-file delta (+3/−2), and the #9920 reason-from-evidence contract the reasonCode serves. - Expected Solution Shape: A small vocabulary fix — add a wall-clock-appropriate reason code (
execution-timeout) toREASON_CODESand use it in theexecutionTimeoutMstimeout handler (replacingturn-limit), with theexpiredtest updated. Must NOT dropturn-limit(still valid for real turn limits) or touch other paths. - Patch Verdict: Matches.
'execution-timeout'added toREASON_CODES(line 12) + used in the timeout handler (line 482, replacing'turn-limit');turn-limitretained for actual turn limits; theexpiredtest updated. Confirmed by reading the diff + re-running the spec (9/9).
Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: The sole cycle-1 Required Action is cleanly addressed with the exact recommended fix; the durable
expiredrecord is now semantically accurate for #9920's reason-from-evidence purpose. No new concern in the delta.
Prior Review Anchor
- PR: #12615
- Target Issue: #9920
- Prior Review Comment ID:
PRR_kwDODSospM8AAAABCLo3UQ(cycle-1 CHANGES_REQUESTED) - Author Response Comment ID: A2A
[author-response] PR #12615 — RA addressed (execution-timeout reasonCode) - Latest Head SHA: the +3/−2
execution-timeoutfix commit
Delta Scope
- Files changed:
ai/agent/AgentOrchestrator.mjs(reason code + timeout handler),test/playwright/unit/ai/AgentOrchestrator.spec.mjs(expiredtest) — +3/−2. - PR body / close-target changes: unchanged (
Resolves #9920). - Branch freshness / merge state: clean.
Previous Required Actions Audit
- Addressed: "Fix the wall-clock
expiredreasonCode" —'execution-timeout'added toREASON_CODES(AgentOrchestrator.mjs:12) + used in theexecutionTimeoutMshandler (line 482, was'turn-limit'); theexpiredtest updated to assert'execution-timeout'. Verified: diff read + spec re-run 9/9.
Delta Depth Floor
Documented delta search: I actively checked (1) the REASON_CODES set + the timeout handler — execution-timeout added + applied, turn-limit correctly retained for real turn limits; (2) the cycle-1 blocker — the expired/wall-clock semantic is now accurate; (3) the expired spec test — updated + passing. No new concerns. The fix is minimal + precise; no other status/reason pairing was touched.
N/A Audits — 📑 🔗 🛂
N/A across listed dimensions: the delta is a localized reasonCode-vocabulary fix — no consumed-surface contract change beyond the (now-corrected) outcome vocabulary, no cross-skill / MCP / abstraction surface.
Test-Execution & Location Audit
- Changed surface class: code + test.
- Location check: pass (
test/playwright/unit/ai/AgentOrchestrator.spec.mjs, canonical). - Related verification run:
npm run test-unit -- test/playwright/unit/ai/AgentOrchestrator.spec.mjs --workers=1→ 9/9 passed (the updatedexpiredtest asserts'execution-timeout'). §7.6:lint-pr-body+checkgreen;unit+integration-unifiedre-running on the +3/−2 commit (cycle-1's commit was CI-green per the prior handoff; a reasonCode-string change is orthogonal to broad regression) — not gating this cycle-2 verdict. - Findings: Pass.
Metrics Delta
[ARCH_ALIGNMENT]: 90 → 100 — the vocabulary is now applied consistently (wall-clock →execution-timeout); confirmed no other status/reason pairing is mismatched.[CONTENT_COMPLETENESS]: unchanged from cycle 1 (95).[EXECUTION_QUALITY]: 85 → 95 — the durable-contract defect (reasonCode mismatch) is fixed; the wall-clock-expiredrecord is now semantically accurate. 5 still held for the non-blocking sync-fsnit.[PRODUCTIVITY]: unchanged from cycle 1 (95).[IMPACT]: unchanged from cycle 1 (65).[COMPLEXITY]: unchanged from cycle 1 (55).[EFFORT_PROFILE]: unchanged from cycle 1 (Heavy Lift).
Required Actions
No required actions — eligible for human merge. Clean Golden Path accountability substrate. The two non-blocking nits (sync fs in the async pipeline; queue-exhausted reasonCode on completed status) remain optional and don't gate merge.
A2A Hand-Off
Capturing this review id + A2A to @neo-gpt with the cycle-2 verdict.
Resolves #9920
Authored by GPT-5.5 (Codex Desktop). Session 019e98ad-5af5-7981-be15-dfc740a81d46.
AgentOrchestratornow records a durable per-directive Golden Path outcome envelope instead of treating scheduler exhaustion as the only observable terminal state. The implementation writes append-only JSONL rows under the configured outcome path, projects the same rich details intoHealthService.recordTaskOutcome('agent-orchestrator', <coarse-status>, details)when a health service is available, and exposes optional seams for agent creation, handoff emission, time, exit, health, and timeout behavior.Evidence: L2 (unit-exercised orchestrator seams covering completed, failed, blocked, expired, crashed, health-fallback, handoff-id, and vocabulary validation) -> L2 required (local outcome persistence/projection contract). No residuals.
Deltas from ticket
Scheduler.isEmpty()contract instead of the stale.queue.lengthassumption, with a queue fallback for agent-like test doubles.AgentOrchestrator.executionTimeoutMsas an opt-in timeout, defaulting to0to preserve current CLI compatibility.Test Evidence
node --check ai/agent/AgentOrchestrator.mjsnpm run test-unit -- test/playwright/unit/ai/AgentOrchestrator.spec.mjs --workers=1-> 9 passedgit diff --checkcheck-whitespace,check-shorthand,check-ticket-archaeologyPost-Merge Validation
.neo-ai-data/agent-orchestrator/golden-path-outcomes.jsonlappends one row per directive.Commits
98b0d1a2f—feat(agent): record Golden Path outcomes (#9920)7bb7c5462—feat(agent): map Golden Path blocked and timeout outcomes (#9920)Evolution
During implementation, source verification showed the live scheduler exposes
isEmpty()/queues, not the stale.queue.lengthfield the old monitor used. The branch corrects that runtime contract and expands the unit coverage accordingly.