Skip to content

refactor(goal): extract intercept to goalAdvanceResult for FSM output purity#6961

Open
erphenheimer wants to merge 1 commit into
esengine:main-v2from
erphenheimer:refactor/6958-intercept-output-model
Open

refactor(goal): extract intercept to goalAdvanceResult for FSM output purity#6961
erphenheimer wants to merge 1 commit into
esengine:main-v2from
erphenheimer:refactor/6958-intercept-output-model

Conversation

@erphenheimer

Copy link
Copy Markdown
Contributor

Summary

advance() previously wrote interceptMsg directly to the goalMachine struct field as a side effect. Multiple FSM mechanisms (todo incompleteness intercept, strict-mode self-check intercept, idle detection) each wrote to the same field, relying on sequential ordering within the switch statement to avoid contention — if a later mechanism overwrote what an earlier one set, the earlier loss was considered acceptable.

This refactoring extracts intercept as an explicit field in goalAdvanceResult. The caller (advanceGoalAfterTurn) stores it via a new setIntercept() method, which takeIntercept() reads as before. The data flow becomes explicit: advance() produces output, the caller routes it.

Changes

internal/control/goal.go — +20/-11 lines:

  • Add intercept string to goalAdvanceResult struct
  • Replace all g.interceptMsg = X assignments inside advance() with local intercept = X
  • Change the idle-detection guard from g.interceptMsg == "" to intercept == "" (checks the local output, not the struct field)
  • Remove the stale g.interceptMsg = "" on the goal-complete path (already covered by local var)
  • Add setIntercept() method — stores the returned intercept into the struct field for takeIntercept() to consume on the next iteration
  • Keep the g.interceptMsg = "" in the auto-turn-limit path (clears any pending struct-field intercept from a previous iteration)

internal/control/turn_orchestrator.go — +3 lines:

  • After c.goals.advance(...), store res.intercept via c.goals.setIntercept() when non-empty

Behavioral verification

Every FSM path was traced (normal running, todo intercept, self-check intercept, idle detection, auto-turn limit, blocked → idle reset, delivery follow-through, repeated intercept → complete). One theoretical edge case differs:

  • Auto-turn limit fires simultaneously with an intercept: old code cleared g.interceptMsg entirely; new code stores the intercept via setIntercept() before the loop exits on cont=false. The stored intercept is never consumed (loop exits immediately) and gets cleared on the next goal lifecycle event (setGoal / resume / restoreFromState). No practical impact.

Related

… purity

advance() previously wrote interceptMsg directly to the goalMachine
struct field as a side effect. Multiple FSM mechanisms (todo intercept,
self-check, idle detection) could each set interceptMsg, relying on
sequential ordering within the switch statement to avoid contention.

Extract intercept as an explicit field in goalAdvanceResult. The
caller (advanceGoalAfterTurn) stores it via a new setIntercept()
method, which takeIntercept() reads as before. This makes the data
flow explicit: advance() produces output, the caller routes it.

No behavior change. One theoretical edge case (auto-turn limit firing
simultaneously with an intercept) has a different interceptMsg state
after advance() returns, but the loop exits via cont=false immediately
so the intercept is never consumed and gets cleared on the next goal
lifecycle event.

Related to esengine#6958 (FSM output model refinement).
The concrete bug esengine#6956 was fixed separately in PR esengine#6960.
@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development agent Core agent loop (internal/agent, internal/control) and removed v2 Go rewrite (1.x) — main-v2 branch, active development labels Jul 26, 2026
@erphenheimer

Copy link
Copy Markdown
Contributor Author

@SivanCola refactored the FSM output model to make intercept flow explicit. CI passed. Please take a look when you have time. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Core agent loop (internal/agent, internal/control)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant