FunctionAgent. It looks orders up with a tool, and its refund tool waits for a person with the workflow’s own wait_for_event. The handler keeps the workflow’s context in the run’s state, asks through AgentOS, and on the resume sends the decision back in as a HumanResponseEvent.
Tested with
llama-index-core 0.14.25 (workflows 2.26.1) and llama-index-llms-openai-like 0.8.1 on 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.
How the model is wired
OpenAILike gets the gateway’s base URL, the run token in default_headers (x-agentos-run-token) and inv.llm.httpx_client(asynchronous=True), an HTTP client that never follows redirects and never sends the token to another host. is_chat_model and is_function_calling_model must be on for FunctionAgent to use tools, and context_window tells LlamaIndex how much it can send. The model name from config.models reaches the gateway exactly as written (anthropic/claude-sonnet-4-5). FunctionAgent streams its model calls, which the gateway supports.
The agent is built inside the handler, on every invocation, because the token belongs to one invocation. With max_retries=0 a guardrail block isn’t retried; agentos.guardrail_block(error) finds it inside the OpenAI client’s BadRequestError, and the run fails with blocked_by_guardrail. See Guardrails.
How the approval works
1
The tool waits
issue_refund calls ctx.wait_for_event(HumanResponseEvent, waiter_event=InputRequiredEvent(...)). The handler sees the InputRequiredEvent in the event stream, keeps handler.ctx.to_dict(...) in the run’s state, stops the workflow and asks one AgentOS hold. The invocation answers suspended.2
A person decides
The run is held until someone decides in the Review queue or through the Holds API.
3
The workflow continues
On the resume the handler rebuilds the context with
Context.from_dict, runs the agent on it and sends the decision as a HumanResponseEvent. The tool’s step runs again from its start, wait_for_event returns the decision, and the tool refunds as approved or edited, or reports the rejection.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
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 context travels in run state. A run’s state value can be at most 1 MiB, so a very long conversation needs trimming. The pickled context is written and read only by your agent, with its own run token.
- Everything before
wait_for_eventruns again on the resume: keep it free of side effects. - One refund per model answer. A run has one open hold at a time, and this example asks for the first
InputRequiredEventit sees. It was tested with one refund call per answer; if your model calls the tool several times at once, collect the events before you ask, as one hold with an item per call.
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.

