Peteosdocs

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 runner argument is auto-injected by PeteOS when declared in a tool's signature.
  • You may want to check if runner is None for 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.