For pseudocode conventions, see the README.
execute_tool_calls (src/agent_loop/)
Purpose: Dispatch a list of tool calls using the configured execution strategy.
Preconditions: tool_calls is non-empty.
Postconditions: Returns one ToolResult message per input tool call (in order); skipped tools produce error results.
FUNCTION execute_tool_calls(
tools: Vec<AgentTool>,
tool_calls: [(id, name, args)],
tx: EventChannel<AgentEvent>,
cancel: CancellationToken,
get_steering: optional function,
strategy: ToolExecutionStrategy
) -> ToolExecutionResult { tool_results, steering_messages }
MATCH strategy
CASE Sequential →
RETURN execute_sequential(tools, tool_calls, tx, cancel, get_steering)
CASE Parallel →
RETURN execute_batch(tools, tool_calls, tx, cancel, get_steering)
CASE Batched { size } →
results ← []
steering_messages ← null
FOR EACH batch IN chunks(tool_calls, size)
batch_result ← AWAIT execute_batch(tools, batch, tx, cancel, steering=null)
results.extend(batch_result.tool_results)
// Check steering between batches
IF get_steering defined THEN
steering ← get_steering()
IF steering is non-empty THEN
steering_messages ← steering
// Skip remaining tool calls
remaining_idx ← (batch_index + 1) * size
FOR EACH (skip_id, skip_name) IN tool_calls[remaining_idx..]
results.append(skip_tool_call(skip_id, skip_name, tx))
END FOR
BREAK
END IF
END IF
END FOR
RETURN { tool_results: results, steering_messages }
END MATCH
END FUNCTION
execute_sequential (src/agent_loop/)
Purpose: Execute tool calls one at a time, checking for steering between each.
FUNCTION execute_sequential(
tools, tool_calls, tx, cancel, get_steering
) -> ToolExecutionResult
results ← []
steering_messages ← null
FOR EACH (index, (id, name, args)) IN enumerate(tool_calls)
(result_msg, _) ← AWAIT execute_single_tool(tools, id, name, args, tx, cancel)
results.append(result_msg)
IF get_steering defined THEN
steering ← get_steering()
IF steering is non-empty THEN
steering_messages ← steering
// Skip remaining tool calls
FOR EACH (skip_id, skip_name) IN tool_calls[index+1..]
results.append(skip_tool_call(skip_id, skip_name, tx))
END FOR
BREAK
END IF
END IF
END FOR
RETURN { tool_results: results, steering_messages }
END FUNCTION
execute_batch (src/agent_loop/)
Purpose: Execute all tool calls in a batch concurrently, then check for steering.
FUNCTION execute_batch(
tools, tool_calls, tx, cancel, get_steering
) -> ToolExecutionResult
// Launch all tools concurrently
futures ← [execute_single_tool(tools, id, name, args, tx, cancel)
FOR EACH (id, name, args) IN tool_calls]
batch_results ← AWAIT_ALL(futures) // wait for all to complete
results ← [msg FOR (msg, _) IN batch_results]
// Check steering after all complete
steering_messages ← null
IF get_steering defined THEN
steering ← get_steering()
IF steering is non-empty THEN
steering_messages ← steering
END IF
END IF
RETURN { tool_results: results, steering_messages }
END FUNCTION
execute_single_tool (src/agent_loop/)
Purpose: Execute one tool call, emitting progress events and returning the result as a ToolResult message.
FUNCTION execute_single_tool(
tools: Vec<AgentTool>,
id: String, name: String, args: JSON,
tx: EventChannel<AgentEvent>,
cancel: CancellationToken,
config: AgentLoopConfig // for before/after_tool_execution* hooks
) -> (Message::ToolResult, is_error: bool)
tool ← find tool WHERE tool.name() == name // may be None
// ── before_tool_execution hook ───────────────────────────────────────────
// Return false to skip this tool call entirely.
IF config.before_tool_execution defined THEN
IF NOT before_tool_execution(name, id, args) THEN
// Emit a skipped error result so the LLM knows the call did not run
skip_result ← ToolResult{ content: [Text("Tool call skipped by before_tool_execution hook")], is_error: true }
EMIT ToolExecutionEnd(id, name, skip_result, is_error=true, child_loop_id=None)
msg ← Message::ToolResult{ ..., is_error: true }
EMIT MessageStart(msg); EMIT MessageEnd(msg)
RETURN (msg, true)
END IF
END IF
EMIT ToolExecutionStart(tool_call_id=id, tool_name=name, args)
// Build callbacks for streaming partial results.
// Each on_update call runs through the before/after_tool_execution_update hooks.
on_update ← callback(partial: ToolResult):
// Extract text content for hooks
text_content ← JOIN text blocks from partial.content
// before_tool_execution_update — false suppresses the event
emit ← IF config.before_tool_execution_update defined
THEN before_tool_execution_update(name, id, text_content)
ELSE true
IF emit THEN
EMIT ToolExecutionUpdate(id, name, partial_result=partial)
// after_tool_execution_update — fires only when event was not suppressed
IF config.after_tool_execution_update defined THEN
after_tool_execution_update(name, id, text_content)
END IF
END IF
on_progress ← callback that EMITS ProgressMessage(id, name, text)
ctx ← ToolContext {
tool_call_id: id,
tool_name: name,
cancel: cancel.child_token(), // new child token, same lineage
on_update: on_update,
on_progress: on_progress
}
(result, is_error) ←
IF tool found THEN
MATCH AWAIT tool.execute(args, ctx)
CASE Ok(r) → (r, false)
CASE Err(e) → (ToolResult{ content: [Text(e.to_string())] }, true)
END MATCH
ELSE
(ToolResult{ content: [Text("Tool {name} not found")] }, true)
END IF
// child_loop_id is set by SubAgentTool; None for all other tools
EMIT ToolExecutionEnd(id, name, result, is_error, child_loop_id: result.child_loop_id)
// ── after_tool_execution hook ────────────────────────────────────────────
IF config.after_tool_execution defined THEN
after_tool_execution(name, id, is_error)
END IF
msg ← Message::ToolResult {
tool_call_id: id, tool_name: name,
content: result.content, is_error, timestamp: now_ms()
}
EMIT MessageStart(msg)
EMIT MessageEnd(msg)
RETURN (msg, is_error)
END FUNCTION