refund call, the tools node calls interrupt(): AgentOS asks a person, and the graph continues with the approval, the edited arguments or the rejection, in whichever container answers the resume.
The SDK’s langgraph adapter does the work: the graph’s checkpoints live in run state instead of a database of your own, an interrupt() becomes one hold, and the resume continues the graph with Command(resume=…). See Agent frameworks.
Tested with
langgraph 1.2.15, langchain-openai 1.7.3 and Python 3.13, using the conformance kit: 22 checks passed, including an approved, an edited and a rejected refund.The bundle
vendor/ folder and is installed with its langgraph extra; see SDKs.
How the model is wired
chat_model() builds a LangChain ChatOpenAI from the invocation that is running (agentos.current()): the gateway’s base URL, the model from config.models.main, the run token in the x-agentos-run-token header, and HTTP clients from inv.llm.httpx_client() that never send the token anywhere else. The model name reaches the gateway exactly as written in agentos.yaml (anthropic/claude-sonnet-4-5).
The graph’s structure is built once, at module level. The model and the compiled graph are made per invocation, because the token belongs to one invocation and the checkpointer belongs to one run: builder.compile(checkpointer=langgraph.checkpointer()) in the handler.
When a guardrail blocks a model call, ChatOpenAI raises an error that carries the gateway’s answer. agentos.guardrail_block(error) finds the block in it, and raising that block fails the run with blocked_by_guardrail and the guardrail’s message. See Guardrails.
How the approval works
1
The graph interrupts
The model asks for
refund. The tools node calls interrupt() with a value shaped like an AgentOS ask (intent, actions, items), so the adapter asks it as it is: one item per refund call, its id the tool call id, its data the call’s arguments, with approve, edit and reject. The graph’s checkpoints are kept in run state.2
A person decides
The run is held until someone decides in the Review queue or through the Holds API. An edit changes the call’s arguments, for example a smaller amount.
3
The graph continues
On the resume the adapter loads the checkpoints and continues the graph.
interrupt() returns the decision (items, each with id, action, data and comment). The node runs approved and edited calls (an edit with the reviewer’s data), answers a rejected one with the reviewer’s comment, and the model answers the customer.LangGraph runs a node again from its start when it resumes, so keep code before
interrupt() free of side effects. Here the node only reads the tool calls before it asks; every tool runs after the decision.Run it
1
Zip and deploy
Put the SDK wheel in
vendor/ (see SDKs), set config.models.main to a model from your Models page, then zip the folder’s contents and upload them:2
Ask for a refund
GET …/holds/h_12). An edit carries the item’s full data:Test it before you deploy
The conformance kit builds the bundle and runs it against a mock platform whose model followscall:<tool> {arguments} in a message, so these cases drive the tools and every decision without a real model:
conformance.yaml
Things to know
- Checkpoints travel in run state. A value there can be at most 1 MiB, and the checkpoints hold the whole message history; keep very long conversations short.
- One interrupt per step, if it is a whole ask. An
interrupt()whose value is shaped like an ask must be the only interrupt of its step. Other values become one item each, and eachinterrupt()then returns its own item’s decision.
Next steps
Human approvals
Everything an ask can carry: several items, details, files, an expiry.
More frameworks
The same agent in other frameworks and languages.

