Tested with Upsonic 0.77.3 and Python 3.13, using the conformance kit: 25 checks passed, including an approved, an edited and a rejected refund.
The bundle
vendor/ folder; see SDKs. The sqlite-storage extra is what Upsonic needs for SqliteDatabase.
How the model is wired
Upsonic’sOpenAIChatModel takes any OpenAI client, so the agent hands it one made from inv.llm.openai_kwargs(asynchronous=True): the gateway’s base URL, the run token in the x-agentos-run-token header, and an HTTP client that never sends the token anywhere else. The model name from config.models reaches the gateway exactly as written (anthropic/claude-sonnet-4-5 here); Upsonic doesn’t read a provider out of it.
The agent is built inside the handler, on every invocation, because the token belongs to one invocation. max_retries=0 stops the OpenAI client from retrying a call a guardrail blocked; if one does, agentos.guardrail_block(error) finds the block inside Upsonic’s ModelHTTPError and the run fails with blocked_by_guardrail and the guardrail’s message. See Guardrails.
How the approval works
issue_refund is an Upsonic external-execution tool: when the model calls it, Upsonic pauses the run instead of running the function.
1
The run pauses
do_async returns a paused output. The agent keeps what Upsonic needs to continue in the run’s state (Upsonic’s SQLite database, the run id and the requirements), then asks one AgentOS hold with an item per refund call: its arguments are the item’s data, so a reviewer can edit the amount. The invocation answers suspended.2
A person decides
The run is held until someone decides in the Review queue or through the Holds API. The container may stop meanwhile.
3
The run continues
AgentOS invokes the agent again with the decision. It writes the database back, issues each refund as decided (or explains why it didn’t), hands the results to Upsonic with
set_external_execution_result, and calls continue_run_async. The model reads the results and answers.Upsonic’s
@tool(requires_confirmation=True) pauses too, but on a resume in a new process it runs the tool with the arguments it stored, so a reviewer’s edit wouldn’t reach the refund. An external-execution tool lets the agent run the refund with the decided arguments.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 a question
3
Ask for a refund
GET …/holds/h_12).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
- The database travels in run state. A run’s state value can be at most 1 MiB. Upsonic’s SQLite file for a short conversation was about 320 KB (430 KB as base64). For long conversations, give
Agenta database outside the container instead, such as Upsonic’s Postgres database, and allow its address innetwork.allow(see Network access). - Keep the default
retry=1. A higherretryonAgentruns the whole task again after an error, model calls and tools included. - Upsonic’s own memory and storage live wherever its database lives. For memory that outlasts a run, use AgentOS memory.
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.

