Part of the v0.58.7 → HEAD TinyAgents-migration audit (see parent issue).
Problem
Mid-turn steer/collect messages corrupt persisted history on both the parent and subagent paths. forward_steers/forward_collects (src/openhuman/tinyagents/mod.rs:108-130) inject steers as Message::user(...); the crate appends them to the working transcript. Post-run persistence slices the returned conversation with messages_since_last_user (src/openhuman/tinyagents/convert.rs:311-318, rposition(Message::User)) — so an injected steer moves the boundary:
- Parent:
self.history.extend(...) at src/openhuman/agent/harness/session/turn/core.rs:940 persists only the post-steer suffix — every assistant/tool round executed before the steer, plus the steer text itself, vanish from history, the persisted transcript, and the next turn's KV-cache prefix (which now diverges from what the provider saw, busting the cache).
- Subagent: identical pattern at
subagent_runner/ops/graph.rs:286-294; the ask_user_clarification checkpoint (ops/runner.rs:1061-1074), the cap digest (graph.rs:554-582), and the worker-thread mirror (graph.rs:375-390) all consume the same truncated conversation, so resume replays corrupted state.
The legacy engine appended drained steers and all rounds to history incrementally in place, so nothing was lost.
Failure scenario
User steers a running turn ("also check the logs") while tools execute → turn completes → the transcript and next-turn context are missing both the pre-steer work and the user's steer. Checkpoint/resume of a steered subagent replays a hole in the middle of the run.
Fix plan
- Replace the "suffix after last user message" convention with an explicit boundary: record the index (or a sentinel id) of the request's final user message before the run, and persist everything after that index from
outcome.conversation — injected steers are then included, in order, tagged as user messages.
- Concretely: capture
base_len = request_messages.len() before run_loop; persist outcome.conversation[base_len..] (after verifying the crate returns the full transcript, which it does — the compression middleware only rewrites the per-call clone).
- Apply the same fix to the subagent path (
ops/graph.rs) so checkpoints, cap digests, and worker mirrors see the full post-request transcript.
- Persist steer messages with their existing steer framing so they're distinguishable from the original prompt in transcripts.
Acceptance criteria
- A steered turn's persisted history contains: all pre-steer assistant/tool rounds, the steer message, and all post-steer rounds, in execution order.
- The next turn's provider request prefix matches what the provider actually saw during the steered turn (KV-cache preserved).
- e2e: scripted run with a steer injected between two tool rounds; assert full history on both chat and subagent paths, plus checkpoint/resume round-trip.
Part of the v0.58.7 → HEAD TinyAgents-migration audit (see parent issue).
Problem
Mid-turn steer/collect messages corrupt persisted history on both the parent and subagent paths.
forward_steers/forward_collects(src/openhuman/tinyagents/mod.rs:108-130) inject steers asMessage::user(...); the crate appends them to the working transcript. Post-run persistence slices the returned conversation withmessages_since_last_user(src/openhuman/tinyagents/convert.rs:311-318,rposition(Message::User)) — so an injected steer moves the boundary:self.history.extend(...)atsrc/openhuman/agent/harness/session/turn/core.rs:940persists only the post-steer suffix — every assistant/tool round executed before the steer, plus the steer text itself, vanish from history, the persisted transcript, and the next turn's KV-cache prefix (which now diverges from what the provider saw, busting the cache).subagent_runner/ops/graph.rs:286-294; theask_user_clarificationcheckpoint (ops/runner.rs:1061-1074), the cap digest (graph.rs:554-582), and the worker-thread mirror (graph.rs:375-390) all consume the same truncated conversation, so resume replays corrupted state.The legacy engine appended drained steers and all rounds to history incrementally in place, so nothing was lost.
Failure scenario
User steers a running turn ("also check the logs") while tools execute → turn completes → the transcript and next-turn context are missing both the pre-steer work and the user's steer. Checkpoint/resume of a steered subagent replays a hole in the middle of the run.
Fix plan
outcome.conversation— injected steers are then included, in order, tagged as user messages.base_len = request_messages.len()beforerun_loop; persistoutcome.conversation[base_len..](after verifying the crate returns the full transcript, which it does — the compression middleware only rewrites the per-call clone).ops/graph.rs) so checkpoints, cap digests, and worker mirrors see the full post-request transcript.Acceptance criteria