Execution Flow
This document traces what happens when you call run() — from user code through Orbiter’s internals and back.
This document traces what happens when you call run() — from user code through Orbiter’s internals and back.
Entry Points
Orbiter provides three entry points, all defined in orbiter.runner:
| Entry Point | Signature | Description |
|---|---|---|
run() | async def run(agent, input, ...) -> RunResult | Primary async API |
run.sync() | def run.sync(agent, input, ...) -> RunResult | Blocking wrapper via asyncio.run() |
run.stream() | async def run.stream(agent, input, ...) -> AsyncIterator[StreamEvent] | Streaming via async generator |
Full Execution Trace
User code orbiter internals
--------- -----------------
run(agent, input)
|
+---> resolve provider from agent.model string
| (via orbiter.models.provider.get_provider)
|
+---> detect Swarm vs Agent
| if Swarm: delegate to swarm.run()
| if Agent: continue below
|
+---> call_runner(agent, input, state)
| |
| +---> create RunState (tracks messages, nodes, usage)
| +---> state.start()
| +---> state.new_node(agent_name)
| +---> node.start()
| |
| +---> agent.run(input, messages, provider)
| | |
| | +---> resolve instructions (str or callable)
| | +---> build initial message list:
| | | message_builder.build_messages(instructions, history)
| | | [SystemMessage, ...history, UserMessage(input)]
| | |
| | +---> get tool schemas (OpenAI function-calling format)
| | |
| | +---> TOOL LOOP (up to max_steps):
| | | |
| | | +---> _call_llm(msg_list, tool_schemas, provider)
| | | | |
| | | | +---> hooks: PRE_LLM_CALL
| | | | +---> provider.complete(messages, tools)
| | | | +---> hooks: POST_LLM_CALL
| | | | +---> output_parser.parse_response()
| | | | \---> return AgentOutput
| | | |
| | | +---> if no tool_calls: return output (done)
| | | |
| | | +---> if tool_calls:
| | | | +---> parse_tool_arguments(tool_calls)
| | | | | (JSON string -> dict[str, Any])
| | | | |
| | | | +---> _execute_tools(actions)
| | | | | |
| | | | | +---> for each tool IN PARALLEL:
| | | | | | +---> hooks: PRE_TOOL_CALL
| | | | | | +---> tool.execute(**args)
| | | | | | +---> hooks: POST_TOOL_CALL
| | | | | | \---> ToolResult (or error)
| | | | | |
| | | | | \---> return [ToolResult, ...]
| | | | |
| | | | +---> append AssistantMessage + ToolResults
| | | | \---> loop back to _call_llm
| | | |
| | | \---> max_steps exhausted: return last output
| | |
| | \---> return AgentOutput
| |
| +---> record usage on state
| +---> _check_loop (detect repeated tool-call patterns)
| +---> build final messages
| +---> state.succeed()
| |
| \---> return RunResult(output, messages, usage, steps)
|
\---> return RunResultKey Components in the Loop
RunState (orbiter._internal.state)
RunState is the mutable execution tracker for a single run. It holds:
- messages — Full conversation history accumulated during the run
- nodes — List of
RunNodeobjects, one per LLM call step - iterations — Step counter
- total_usage — Aggregated
Usage(input_tokens, output_tokens, total_tokens)
Each RunNode tracks:
- Status transitions:
INIT -> RUNNING -> SUCCESS | FAILED | TIMEOUT - Timing:
created_at,started_at,ended_at,duration - Token usage for that step
- Metadata (e.g.,
tool_signaturefor loop detection)
Message Builder (orbiter._internal.message_builder)
build_messages() constructs the correctly ordered message list:
[SystemMessage(instructions), ...history, UserMessage(input)]It also provides:
validate_message_order()— Detects dangling tool calls (tool call without matching result)extract_last_assistant_tool_calls()— Checks if conversation is mid-tool-executionmerge_usage()— Accumulates token counts across multiple LLM calls
Output Parser (orbiter._internal.output_parser)
Bridges the model layer to the agent layer:
parse_response()— Maps raw model fields toAgentOutputparse_tool_arguments()— Decodes JSON-encodedToolCall.argumentsintoActionModelobjects withdict[str, Any]argumentsparse_structured_output()— Validates LLM text against a Pydantic model whenoutput_typeis set
Loop Detection (orbiter._internal.call_runner)
The call runner detects endless loops where an agent repeatedly produces identical tool calls. It works by:
- Computing a deterministic signature from tool call names and arguments (sorted, order-independent)
- Storing the signature in the
RunNode.metadata - Counting consecutive nodes with the same signature
- Raising
CallRunnerErrorwhen the count reachesloop_threshold(default: 3)
Swarm Execution Modes
When run() receives a Swarm instead of a bare Agent, it delegates to the Swarm’s own run() method, which dispatches based on mode:
Workflow Mode (mode="workflow")
Agents execute sequentially in topological order (from flow DSL). Each agent’s output becomes the next agent’s input:
researcher.run("topic") -> output_1
writer.run(output_1) -> output_2
editor.run(output_2) -> final_resultHandoff Mode (mode="handoff")
The first agent runs. If its output matches a declared handoff target name, control transfers to that agent with the full conversation history. This continues until an agent produces output that is not a handoff, or max_handoffs is exceeded.
Team Mode (mode="team")
The first agent is the lead. Auto-generated delegate_to_{worker_name} tools are added to the lead. When the lead calls a delegate tool, the worker agent runs and returns its output as the tool result. The lead synthesizes the final answer.
Streaming Flow
run.stream() follows a similar pattern but uses the provider’s stream() method:
- Build message list and tool schemas
- Stream from provider, yielding
TextEventfor each text delta - Accumulate
ToolCallDeltachunks into completeToolCallobjects - When tool calls are detected, yield
ToolCallEventfor each, execute tools, and re-stream with results - Loop until a text-only response or
max_stepsis reached
Retry Logic
Agent._call_llm() implements exponential backoff retry:
- Retries up to
max_retries(default: 3) on transient errors - Delay between retries:
2^attemptseconds - Context-length errors are detected and raised immediately (no retry)
- After all retries exhausted, raises
AgentErrorwith the last exception chained viafrom