Steering the Agent
When invoke_agent() runs, the agent reasons in a loop until it returns its result via one of the built-in tools produce_output or produce_error.
During this loop, the agent can interact with its environment through @tool or @sandbox decorated member functions.
When called directly by the agent, these member functions steer the agent by either returning a value that becomes a tool result or by adding a new message to the runner's queue — from the agent's perspective, these are two very different signals.
Why
A tool result is the agent's answer to a question it just asked itself. It continues reasoning and considers the result as the completion of that call. A queued message provides further information that is not tied to any tool call. It shapes the agent's next reasoning step without interrupting anything — the agent simply continues its loop and factors the new information into its next LLM response. Which you use depends on whether the information completes a tool call or adds independent context.
Tool Return Values
Every @tool decorated method returns a value that the agent sees as a tool result.
The return value is injected directly as the answer to the tool call the agent just made.
This is the primary steering channel and should be used whenever the information completes the tool's purpose.
@tool
def check_inventory(self, item: str) -> str:
"""Check stock levels for an item."""
qty = self._stock.get(item, 0)
if qty == 0:
return f"No stock available for '{item}'."
return f"Stock for '{item}': {qty} units."
The agent sees this as the response to its check_inventory call and adjusts its reasoning accordingly.
Queuing New Messages
You can also push new messages into the runner's event queue from inside a tool method.
To get access to the runner, simply add a runner argument to your tool's signature — when provided, PeteOS will fill it with the runner instance.
@tool
async def analyze_image(self, image_path: str, runner: Runner) -> str:
"""Analyze an image and return the description. If the image is unclear, request additional photos from the user."""
description = await self._process_image(image_path)
if not description:
from peteos.conversation import Message, ContentPart
await runner.queue_message(
Message.create(
role="user",
content_parts=[ContentPart.create_text(
"The image was unclear. Please provide a better photo."
)],
)
)
return "Requested a better photo."
return f"Image analysis: {description}"
When to Use Which
| Scenario | Mechanism | Why |
|---|---|---|
| Tool completes normally | Tool return value | Agent expects this as the tool's answer |
| Tool completes with an error or hint to retry | Tool return value (error message) | Agent already expects this tool's result |
| Provide context not tied to a tool call | runner.queue_message() |
Independent information, shapes next reasoning step |
Key Points
- The
runnerargument is auto-injected by PeteOS when declared in a tool's signature. - You may want to check
if runner is Nonefor non-invocation contexts. - Tool return values are always available and require no extra code.
- Queued messages go into the runner's event queue and are drained before the next LLM call.
- Queued messages always cause the agent to continue reasoning — the agent will loop to the next step and factor the new message into its LLM response, independent of any other pending tool results.