Repository navigation
Conversation
…ad of steering A mention that arrived while the lane already had an active Codex turn was always sent as `turn/steer`, which never read the delivery's model or effort, so a pick made for a busy agent was silently dropped. The Codex app-server's `turn/steer` request carries only input and context; model and effort exist solely on `turn/start`, so the pick cannot be forwarded on the steer path. Record the resolved model and effort on the active turn, and when a mid-turn pick differs from either, queue the message on the lane's existing tail (as the "turn already finished" fallback does) so it runs as the next turn with the picked settings. Matching or absent picks still steer. Claude Code applies picks mid-turn already and is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
wesbillman
left a comment
There was a problem hiding this comment.
No changes requested; I found no actionable defect in this change.
Star Lord’s automated source review via Wes’s account: head 22d2047ce57b9cc9d3977e98b384db360b229ddc, base ecfbb8a5835dd0fb04e48111ef406a975f2c7715.
The change reuses the existing lane queue and per-turn resolution without making picks sticky; matching or absent picks retain steering. Source-only: I did not run tests or the app, and the author-reported protocol-fixture checks do not establish live Codex/native acceptance.
| (request.model && request.model !== active.model) || | ||
| (request.effort && request.effort !== active.effort) | ||
| ) { | ||
| this.start(request, text, key, lane, entry); |
There was a problem hiding this comment.
🤖 [P2] Keep later follow-ups behind a queued model/effort pick
While turn 1 is active, send A with a differing model/effort, then B with no pick (or a pick matching turn 1). A takes this early return and queues behind lane.tail, but B still steers turn 1 and updates its reply handle. B therefore executes before the earlier A. Fixture probes confirmed a turn/steer targeting turn 1 while A remained queued; the selected settings are preserved for A, but message order is not.
Once a differing pick is queued, route subsequent follow-ups behind that queue boundary rather than continuing to steer the old turn. Add a regression test for differing-pick → plain-follow-up ordering.
Bug
A model or effort picked for a Codex agent that is already mid-turn was silently dropped. When a mention arrives for a conversation lane with an active Codex turn,
src/bundled/codex/runtime.tssends it asturn/steer, and that branch never read the delivery'smodel/effort. Because an unpicked later turn deliberately returns to the agent's defaults (picks are per-turn by design), the pick never applied anywhere — not to the running turn and not to any later one. The window also covers a turn's startup, sincelane.activeis set beforethread/start/turn/start.Claude Code is unaffected: its runtime applies picks mid-turn via
set_model/ effort control requests, so the remainder of a running turn switches immediately. No change is made there.Why queue a fresh turn (and not the alternatives)
turn/steerrequest carries only input and context: upstreamTurnSteerParamshas no model or effort fields; onlyTurnStartParamsdoes. So the only ways to honour a pick on a busy agent are to queue a fresh turn or to interrupt.STOPGAP3-second typing pulse from feat(agents2): show agents working: 👀💬 reactions, local typing, Codex settles #860 (flicker, startup gap). It also removes a legitimate action that Claude Code doesn't need removed.<turn_aborted>noise in the thread.service.tscontract, runtime comment, composer clearing picks after each send).Queueing is ~10 LOC. It reuses the lane's existing
tailqueue and the existing "turn already finished → start instead of steer" fallback in the steer branch, and it makes the picker's existing "next run" copy true as written. Matching or absent picks still steer exactly as before.Change
src/bundled/codex/runtime.ts—Activerecords the resolvedmodel/effort(set inexecutebeforethread/start, so they are available onceactive.readyresolves). In the steer branch, theactive.finishedfallback is widened: if the request carries a model or effort that differs from the running turn's, route tothis.start(...)so the message runs as the next turn with the picked settings.src/bundled/codex/runtime.test.ts— a differing pick test (model+effort pick, then effort-only pick, both arriving mid-turn) asserts zeroturn/steercalls and three sequentialturn/starts on the same thread (low→deep-model/xhigh→test-model/high) with replies in order. A matching pick / no pick test confirms both still steer with a singleturn/start.src/bundled/codex/README.md— one sentence in the steering paragraph describing the new behaviour.Verification
vitest run src/bundled/codex/runtime.test.ts: 51/51 pass.runtime.tschange reverted, the differing-pick test fails (pick is steered and dropped); restored, it passes.tsc --noEmit,biome check,git diff --checkclean; pre-commit hooks passed.Known tradeoffs
start()sets the lane status to "Starting" while the old turn is still finishing — cosmetic.active.modeland therefore queues, even thoughexecutefalls back to the agent's own model. Harmless; the message simply runs as a fresh turn.Origin
Addresses review comment #856 (comment) on #856, which identified this behaviour (correctly, for Codex only).
Reply posted on the original thread: #856 (comment)
🤖 Generated with Claude Code