Skip to main content
When an agent needs a person, an external job, or a tool your application runs, the run returns normally (not as an error) with status waiting_for_human and everything needed to continue. Save the whole result, collect the decision in your application, then pass the saved result and the decision to the agent’s continue method.
Phaseo doesn’t store Agent SDK runs. Save resumable state in your own application when it must survive a request or process restart.

Approve or reject tool calls

Approval-gated, manual and human-in-the-loop tools pause the run before they execute. Each pending call has the model’s call (with its id) and a kind: approval, manual or hitl. Answer every call by its exact call ID, not its tool name, so two calls to the same tool stay separate. An approved call runs its tool. A rejected call sends {"error": "<reason>"} to the model as the tool result, with the default reason Tool call rejected.
Where the pending calls are, and what happens when a decision is missing: The pause itself also has a kind. Go and C# set it to tool_approval for every tool pause. PHP, Ruby and Java set tool_approval when any pending call needs approval, otherwise the first call’s kind. Rust uses tool_approval for approval calls, external_output when every pending call needs an external result, and tool_input for a mix of the two.
Rust 0.2 has no rejection type and no rejections field. Leave an unapproved call paused, or apply your application’s rejection policy before you continue.
To require approval for every tool call instead of per tool, set requireApproval on the agent definition (require_approval in Python and Ruby, RequireApproval in Go and C#). In TypeScript it’s a function requireApproval(call, context). Rust only supports approval per tool, with Tool::require_approval(). Tools shows how each kind of tool is defined.

Supply results for manual and human-in-the-loop tools

For manual and hitl calls, pass the result your application produced, keyed by call ID. In Rust, these are tools built with Tool::external(...).
A missing output fails the continuation with Missing output for tool call <id> (lowercase missing in Go, a ValueError in Python, an AgentError in Rust). A human input message is not a substitute for a tool output.

Pause for human review

Set a human review callback on the agent definition to check each model response before the run continues. Return a review request with a reason (and optionally a payload) to pause, or nothing to carry on. The pause kind is human_review. The callback receives the run ID, step index, messages, model response and parsed output (Rust: the run and agent IDs, step index, messages, response and application context).
Continue with human input, which is added to the conversation as a user message:
Continuing a review pause without human input fails with Run <id> is waiting for human input (Go: run <id> is waiting for human input; Java: Run is waiting for human input). Rust requires a non-empty human_input. TypeScript also accepts structured humanMessages.

Save run state between requests

The run result is plain data: store it in your database, object store or job record, and keep application context serializable. A run can only be continued by an agent with the same ID. The durable agent loop cookbook shows a complete save-and-resume flow. To let the SDK save and load runs for you, pass a state accessor with load(runId) and save(result). The SDK then saves after each run and continuation, and you can continue by run ID instead of passing the record through every layer.
In Rust, recreate the same agent definition and tools before continuing: closures and AgentDefinition aren’t part of the serialized run. The effective model and step limit are stored in RunRecord, so per-run overrides stay active after continuation. Whatever you store, your application owns encryption and retention of saved runs, authorization for approvals and human input, and idempotency around side effects that run again after a resume.
Last modified on November 5, 2026