Steering an autonomy job in its final/summary turn is silently dropped #93

Open
opened 2026-06-07 13:44:48 +00:00 by rawan · 1 comment
Member

Symptom

Steered a running autonomy job ("add docs and tests") while it was wrapping up. The guidance was acknowledged ("will be picked up at the next checkpoint") but never applied — the job finished without docs/tests.

Root cause

There are two steering paths, and message-send steering uses the one with a timing gap:

  1. Live inbox (session.steer -> global_steering_inbox): drained every iteration at iteration_shell.rs L193-211 and folded in as a [steering #N] user message. Immediate.
  2. Autonomy operator-guidance: message.send to a live autonomy job routes to steer_existing_job_from_message (session_autonomy.rs L139-147), which does NOT touch the live inbox — it writes pending_operator_guidance. That is only surfaced by OperatorGuidanceProvider (job_context.rs L171-201), which runs inside build_history — i.e. only when the orchestrator builds the prompt for the next turn/checkpoint.

If the job is already in its finalization/summary turn ("deliverable is complete, let me just report the summary and be done"), there is no next checkpoint, so the queued guidance is never rendered into a prompt and is dropped.

Secondary: pending_operator_guidance is read but never cleared after consumption (job_context.rs), so on a job that does continue it can be re-injected every turn.

Proposed fix

  1. Gate completion on pending guidance: before an autonomy job finalizes, check pending_operator_guidance; if present, force one more turn/replan instead of ending. (Fixes the exact case.)
  2. Optionally also route message-send steering into the live inbox so it is drained per-iteration like session.steer.
  3. Clear pending_operator_guidance once consumed.
## Symptom Steered a running autonomy job ("add docs and tests") while it was wrapping up. The guidance was acknowledged ("will be picked up at the next checkpoint") but never applied — the job finished without docs/tests. ## Root cause There are two steering paths, and message-send steering uses the one with a timing gap: 1. **Live inbox** (`session.steer` -> `global_steering_inbox`): drained every iteration at `iteration_shell.rs` L193-211 and folded in as a `[steering #N]` user message. Immediate. 2. **Autonomy operator-guidance**: message.send to a live autonomy job routes to `steer_existing_job_from_message` (`session_autonomy.rs` L139-147), which does NOT touch the live inbox — it writes `pending_operator_guidance`. That is only surfaced by `OperatorGuidanceProvider` (`job_context.rs` L171-201), which runs inside `build_history` — i.e. only when the orchestrator builds the prompt for the *next* turn/checkpoint. If the job is already in its finalization/summary turn ("deliverable is complete, let me just report the summary and be done"), there is no next checkpoint, so the queued guidance is never rendered into a prompt and is dropped. Secondary: `pending_operator_guidance` is read but never cleared after consumption (`job_context.rs`), so on a job that does continue it can be re-injected every turn. ## Proposed fix 1. Gate completion on pending guidance: before an autonomy job finalizes, check `pending_operator_guidance`; if present, force one more turn/replan instead of ending. (Fixes the exact case.) 2. Optionally also route message-send steering into the live inbox so it is drained per-iteration like `session.steer`. 3. Clear `pending_operator_guidance` once consumed.
Author
Member

Update: fails from BOTH UI entry points

Confirmed steering does nothing from either UI control — and they call different RPCs, so the failure spans both mechanisms, not just the checkpoint-timing gap above:

UI control Handler RPC Backend path
Chat composer “Steer” button MessageInput.tsx:290 -> steerActiveTurn (store.ts:1126-1135) session.steer live inbox, drained every iteration (iteration_shell.rs:193-211)
Job panel “steer →” LiveJobPanel.tsx:271-275 job.steer operator-guidance / checkpoint
Job drawer SteeringInput SteeringInput.tsx:37 job.steer operator-guidance / checkpoint
Activity “nudge it” ChatActivity.tsx:96 session.steer live inbox

The original issue only explained the job.steer / checkpoint path. But the chat Steer button uses session.steer (the live inbox that’s drained every iteration) and that also doesn’t land — so the live-inbox path is broken too.

Lead to verify: the live-inbox drain is gated on options.session_id being Some (iteration_shell.rs:193if let Some(sid) = state.options.session_id ...). If an autonomy job’s agent loop runs with session_id: None, session.steer pushes to a key nothing ever drains, so it silently no-ops. Need to confirm whether autonomy turns thread the session id into AgentOptions.

Net: all four steering entry points fail for autonomy jobsjob.steer ones because guidance is only read at a next checkpoint that never comes, and session.steer ones likely because the loop has no session_id to drain against (to confirm).

## Update: fails from BOTH UI entry points Confirmed steering does nothing from either UI control — and they call **different RPCs**, so the failure spans both mechanisms, not just the checkpoint-timing gap above: | UI control | Handler | RPC | Backend path | |---|---|---|---| | Chat composer **“Steer”** button | `MessageInput.tsx:290` -> `steerActiveTurn` (`store.ts:1126-1135`) | `session.steer` | **live inbox**, drained every iteration (`iteration_shell.rs:193-211`) | | **Job panel** “steer →” | `LiveJobPanel.tsx:271-275` | `job.steer` | operator-guidance / checkpoint | | **Job drawer** SteeringInput | `SteeringInput.tsx:37` | `job.steer` | operator-guidance / checkpoint | | Activity “nudge it” | `ChatActivity.tsx:96` | `session.steer` | live inbox | The original issue only explained the `job.steer` / checkpoint path. But the chat **Steer** button uses `session.steer` (the live inbox that’s drained every iteration) and **that also doesn’t land** — so the live-inbox path is broken too. **Lead to verify:** the live-inbox drain is gated on `options.session_id` being `Some` (`iteration_shell.rs:193` — `if let Some(sid) = state.options.session_id ...`). If an autonomy job’s agent loop runs with `session_id: None`, `session.steer` pushes to a key nothing ever drains, so it silently no-ops. Need to confirm whether autonomy turns thread the session id into `AgentOptions`. Net: **all four steering entry points fail for autonomy jobs** — `job.steer` ones because guidance is only read at a next checkpoint that never comes, and `session.steer` ones likely because the loop has no `session_id` to drain against (to confirm).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_shrimp#93
No description provided.