fix(goal): kernel correctness, injection consolidation, and hardening - #39
Merged
Conversation
added 2 commits
July 3, 2026 08:32
Two verified correctness bugs fixed: - turns_used: done-state transitions no longer increment; only continuation dispatches consume budget (root cause of agent self-declared completion double-count) - goal.updated(done): single-owner terminal event contract; updateAfterJudge no longer publishes, deleteAndPublishDone owns the updated(done) -> cleared sequence (eliminates double-publish) Prompt injection consolidated from 4 channels to push model: - SystemPrompt.goal(sessionID) now yields live goal state dynamically - continuation prompt + progress message merged into single injection that actually reaches the model (old ignored:true filtered it out) - goal(status) tool reframed from mandatory per-turn to optional Hardening: - judge transport errors now count toward parse-failure budget - zombie-goal freshness guard: stale active goals with zero turns auto-pause instead of orphaning silently Test coverage: 56 tests across judge/loop/goal/prompts (was zero). OpenSpec change: goal-loop-correctness
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes two verified correctness bugs, consolidates the prompt injection design, adds two hardening guards, and establishes kernel test coverage from zero (56 tests). Tracked via OpenSpec change
goal-loop-correctness.Bug fixes (verified against code, TDD-exposed)
turns_useddone-state inflationturns_usedwas incremented on state transitions (active→done), not just on continuation dispatches. Done is a state label, not a turn. This was the root cause of the agent self-declared completion double-count (model callsgoal(complete)mid-loop →markDoneadded a second +1 on top ofupdateAfterJudge's prior +1).Fix: increment ONLY in
updateAfterJudgecontinue branch (immediately before continuation dispatch). Removed +1 from:updateAfterJudgedone branch,markDone.goal.updated(done)double-publishupdateAfterJudgedone branch ANDdeleteAndPublishDoneboth published the same terminalgoal.updated(done)event. Violated the documented bus contract (updated(done) → cleared); risked double-firing hooks keyed on the terminal event.Fix:
updateAfterJudgedone branch no longer publishes.deleteAndPublishDoneis the sole owner of the terminal event sequence.Design: prompt injection consolidation
The model received goal context via 4 fragmented channels (static system prompt, noReply progress message, ignored continuation prompt, tool description commanding per-turn status check). The model was forced into a redundant
goal(status)round-trip every turn.Discovery: the old continuation prompt used
ignored: true, which is filtered from BOTH the transcript AND the model's messages (message-v2.ts:208). The continuation instruction never actually reached the model — the loop continued only becauseprompt()created a new turn.Fix:
SystemPrompt.goal(sessionID)now yields live goal state dynamically (was static text; the Effect shell already existed)ignored) — now actually reaches the model and is visible in transcriptgoal(status)tool reframed from mandatory per-turn to optional steering check-inHardening
parseFailed: false, resetting the failure counter. A flaky provider could alternate bad-JSON and timeout indefinitely without triggering auto-pause. Now returnsparseFailed: true.turns_used: 0andcreated_at > 120swith no assistant message (initial kick failed), auto-pauses instead of orphaning silently.Test coverage
test/goal/judge.test.tstest/goal/loop.test.tstest/goal/goal.test.tstest/goal/prompts.test.tsVerification
bun run typecheck: green (0 errors)bun test test/goal/: 56 pass, 0 failRemaining (live e2e)
tasks.md §11.1-11.3(live /goal lifecycle in tmux, SSE event stream validation, model behavior observation) deferred to reviewer/user validation with an actual provider.OpenSpec
Change:
goal-loop-correctness(proposal + design + specs + tasks all validated--strict)