fix(agent): restore the hook-free AgentRun integration surface - #2284
Open
gold-silver-copper wants to merge 3 commits into
Open
fix(agent): restore the hook-free AgentRun integration surface#2284gold-silver-copper wants to merge 3 commits into
gold-silver-copper wants to merge 3 commits into
Conversation
Restore the focused low-level capabilities lost when Agent fields and raw completion construction became private: callers manually driving AgentRun can seed a run from durable agent policy, prepare one configured hook-free request, pair its response and tool calls with the exact turn metadata/implementation snapshot, and clone the agent's live ToolServerHandle when rebuilding after a resume. AgentRunner remains the only batteries-included runtime; this PR adds no second coordinator.
…rface - build_prepared_completion_request takes a named-field PreparedRequestInputs struct instead of 17 positional arguments, so same-typed parameters cannot be silently transposed at either call site. - PreparedAgentTurn::execute_call dispatches through a new ToolRegistrySnapshot::execute that mirrors ToolServerHandle::execute, instead of hand-rolling a third copy of the clear/dispatch/accept sequence. - Agent::new_run calls build_agent_run directly with defaults shared with AgentRunner::from_agent as named constants, instead of deep-cloning a throwaway runner. - The example's resume loop holds one ToolContext for the whole run, matching Part 1's stated convention and the classic runner.
…view findings - prepare_completion_request now advertises the run's tool choice (via a new pub(crate) AgentRun::tool_choice accessor), so an AgentRun::with_tool_choice override after new_run reaches the provider and the run never classifies calls under a policy the request didn't carry; covered by a new test. - The #1928 output-tool read/commit pairing now lives once, in build_prepared_completion_request_for_run, shared by the classic driver and the manual surface; the committed name is borrowed, not cloned. - PendingToolCall::result_content is a new public helper correlating an executed tool's output with its call (id, provider call id, tool name), so the cross-process-resume recipe no longer hand-copies correlation fields; the example, tests, and execute_call use it. - ToolDispatch::publish owns the shared accept/return half of the dispatch context-hygiene contract for all three execute surfaces; the clear stays before dispatch, as the cancelled-dispatch hygiene tests pin. - PreparedRequestInputs.chat_history is Cow, so the manual surface moves the owned history from CallModel instead of re-cloning it each turn; the snapshot dispatch takes owned args, dropping a per-call copy of the argument JSON.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Restore the focused low-level capabilities lost when Agent fields and raw
completion construction became private: callers manually driving AgentRun can
seed a run from durable agent policy, prepare one configured hook-free
request, pair its response and tool calls with the exact turn
metadata/implementation snapshot, and clone the agent's live ToolServerHandle
when rebuilding after a resume. AgentRunner remains the only
batteries-included runtime; this PR adds no second coordinator.
What's restored
In v0.40 a manual
AgentRundriver could use an already-builtAgentas theconfiguration and tool source for its loop; v0.41 removed that when
Agentfields went private and the raw
Completion/StreamingCompletionimpls weredeleted. This PR restores exactly that capability, additively:
Agent::tool_server_handle()— owned clone of the live registry handle(the capability to use after a cross-process resume; observes later registry
changes).
Agent::new_run(prompt)— a plainAgentRunseeded with the agent'sdurable run policy, constructed through the same
build_agent_runpath therunner uses, so the two cannot drift.
Agent::prepare_completion_request(prompt, history, &mut run)— one fullyconfigured, hook-free provider request, fed directly from
AgentRunStep::CallModel's fields. Reads the run's committed output-toolname and commits the freshly resolved name back (exactly as the classic
driver does), so multi-turn Tool-mode pinning and run/request output policy
stay paired. Impossible tool choices fail before any provider IO.
PreparedAgentRequest::into_parts()→ the sendable request builder plus anopaque
PreparedAgentTurnowning exactly two operations:model_turn(response)supplies the exact executable/allowed tool-namesets captured at preparation (no more hand-ordering two same-typed sets);
execute_call(call, &mut context)honorspreresolved_result, appliesthe standard clear→dispatch→accept context hygiene, and executes only
through the registry snapshot pinned at preparation — a tool
re-registered afterwards is unreachable, a tool registered afterwards is
rejected.
The prepared pair is in-process state for one issued request — not
serializable, no durability claims. Durable state remains the
AgentRun.Hook-free means hook-free: no hooks, memory, telemetry spans, concurrency
policy, or passive dynamic context run on this surface, and the docs say so.
Docs and tests
examples/agent_run_steppingrewritten around the new surface, including across-process resume that finishes pending calls via the rebuilt agent's
tool_server_handle().runmodule docs gained the Agent ⇄ AgentRun ⇄ PreparedAgentRequest/Turnrelationship diagram and the list of what manual driving makes the caller
own; MIGRATING.md gained a matching "0.41 → next" section (
DynamicToolremains the replacement for
Box<dyn ToolDyn>).exact name-set handoff, snapshot fidelity, seeded-run policy agreement
(output mode/tool name, output validation, turn and retry budgets, pre-IO
tool-choice validation), the hook-free boundary (plus a guard that the same
hook still fires under
AgentRunner), a full manual round trip,pre-resolved hygiene, and a cross-process resume guard.
Verified:
cargo fmt, fullrig-agentsuite,clippy --all-targets --all-features -D warnings(rig-agent + example), rustdoc with warningsdenied,
git diff --check.Supersedes the direction of #2278 for fixing this regression (analysis:
https://gist.github.com/gold-silver-copper/5bdcbc6afbab072cdb93fb614b719fc5);
no
AgentDriver/DriveStep/TurnToolsand no second coordinator.(
on_reasoning_deltawas already fixed by #2282.)