/** * Turn-boundary context cleanup. * * Background. The conversation history sent to the model grows as: * [user, assistant(tool_calls), tool, assistant(tool_calls), tool, …, * assistant(final text), user, assistant(tool_calls), tool, …] * * Once a turn is *complete* (assistant returned text with no tool_calls), * the in-progress scaffolding from that turn — the assistant messages * carrying tool_calls and the tool-result messages — is no longer * actionable. But it still LOOKS actionable to a weaker model: it sees * "tool_calls" entries and treats them as pending TODOs, which is why * owl-alpha kept re-writing the same poem on every new user turn: * * turn 1 user: "write a poem" * ... model writes poem ... * turn 2 user: "find a github repo for X" * model: "I'll handle BOTH requests" (because it still sees the * poem's tool_calls scaffolding as if it hadn't run) * turn 3 user: "research further" * model: "I'll handle all THREE requests" ← infinite re-execution * * Fix. Before each new turn, walk back through history. For every * COMPLETED turn (any turn before the latest user message), collapse the * [user, ...intermediate scaffolding..., final assistant text] sequence * into [user, "final assistant text + [Completed: used X, Y]"]. The model * sees a clean conversational record of what already happened, with no * dangling tool_calls signals to misinterpret. * * The currently-active turn (everything from the latest user message * forward) is left untouched — its tool_calls and tool-result messages * are still in-flight and the API protocol requires them paired. */ import type { Message } from './types.js'; /** * Collapse all completed turns in a message list. Returns a new array; * input is not mutated. * * A completed turn is any turn ENDING before the latest user message. * The latest user message and everything after it is the "active turn" * and is passed through unchanged. */ export declare function collapseCompletedTurns(messages: Message[]): Message[];