The Tool Loop That Turns a Chatbot Into an Agent
A tool loop is the difference between a demo chatbot and an agent that can read files, run commands, or modify a codebase. Getting the control flow right — serial execution, parameter validation on untrusted model input, and safe cancellation — prevents the agent from executing garbage arguments or leaving the conversation in an unrecoverable state.
An LLM cannot execute tools itself; it only returns a structured intent. The agent must find the local function, validate the model's untrusted parameters, run it, and construct a tool_result message for the next model request. This creates a loop where the model proposes an action, the agent executes it, and the model observes the outcome before responding.
The implementation separates concerns cleanly: a ToolDefinition (name, description, parameters) is sent to the provider, while the AgentTool adds a local execute() function the provider never sees. Every tool call goes through a strict sequence: emit a start event, check for cancellation, look up the tool by name, validate arguments against the schema, catch execution errors, and unify everything into a ToolResultMessage.
Cancellation is handled at multiple checkpoints. An AbortSignal is checked before execution, after execution, and between serial tool calls in a batch. If cancelled mid-loop, the agent appends a locally-constructed aborted assistant message so the prompt settles cleanly rather than leaving the conversation in a broken state.
The design treats tool name errors and parameter validation failures as recoverable conversation events, not fatal exceptions, because the model can often correct itself when it sees the error result.
Serial tool execution is a deliberate trade-off: determinism and cancellation safety are prioritized over concurrency at this stage of the agent's development.
Copying the tools array at construction and again at each prompt() call prevents external mutation from changing the tool set mid-conversation, a class of bug that is hard to diagnose.
The double AbortSignal check after await is necessary because an AbortSignal is a request, not an enforcement mechanism; a tool might ignore it and still return a result.
An agent loop from agent_start to end refers to the end of the entire conversation; a turn loop refers to the end of a single large model streaming request.
[Like]