Skip to content

fix(goal): kernel correctness, injection consolidation, and hardening - #39

Merged
LeXwDeX merged 2 commits into
devfrom
debug/goal-loop
Jul 3, 2026
Merged

fix(goal): kernel correctness, injection consolidation, and hardening#39
LeXwDeX merged 2 commits into
devfrom
debug/goal-loop

Conversation

@LeXwDeX

Copy link
Copy Markdown
Owner

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_used done-state inflation

turns_used was 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 calls goal(complete) mid-loop → markDone added a second +1 on top of updateAfterJudge's prior +1).

Fix: increment ONLY in updateAfterJudge continue branch (immediately before continuation dispatch). Removed +1 from: updateAfterJudge done branch, markDone.

goal.updated(done) double-publish

updateAfterJudge done branch AND deleteAndPublishDone both published the same terminal goal.updated(done) event. Violated the documented bus contract (updated(done) → cleared); risked double-firing hooks keyed on the terminal event.

Fix:updateAfterJudge done branch no longer publishes. deleteAndPublishDone is 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 because prompt() created a new turn.

Fix:

  • SystemPrompt.goal(sessionID) now yields live goal state dynamically (was static text; the Effect shell already existed)
  • continuation prompt + progress message merged into a single injection with plain text part (no ignored) — now actually reaches the model and is visible in transcript
  • goal(status) tool reframed from mandatory per-turn to optional steering check-in

Hardening

  • Transport errors count toward pause budget: judge transport failures (timeout/network) previously returned parseFailed: false, resetting the failure counter. A flaky provider could alternate bad-JSON and timeout indefinitely without triggering auto-pause. Now returns parseFailed: true.
  • Zombie-goal freshness guard: if a goal is active with turns_used: 0 and created_at > 120s with no assistant message (initial kick failed), auto-pauses instead of orphaning silently.

Test coverage

FileTestsCoverage
test/goal/judge.test.ts13parseJudgeResponse 4-step fallback, transport error handling
test/goal/loop.test.ts14shouldPreempt edge cases, isStaleZombie predicate
test/goal/goal.test.ts18state machine (set/pause/resume/clear/markDone), fiber discipline, event contract
test/goal/prompts.test.ts11renderContinuation merged output, renderGoalSystemBlock
Total56was zero

Verification

  • bun run typecheck: green (0 errors)
  • bun test test/goal/: 56 pass, 0 fail
  • Pre-push hook: turbo typecheck 29/29 FULL TURBO

Remaining (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)

Test 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
@LeXwDeX
LeXwDeX merged commit 6dbb61a into devJul 3, 2026
@LeXwDeX
LeXwDeX deleted the debug/goal-loop branch July 3, 2026 00:36
@LeXwDeXLeXwDeX mentioned this pull request Aug 24, 2026
2 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@LeXwDeX