Skip to main content
This example is a support agent built with Google’s Agent Development Kit (ADK). ADK reaches models other than Gemini through its LiteLlm wrapper, which here points at the AgentOS gateway, so the agent can use any model on your Models page. The model never moves money itself. Its request_refund tool only writes the refund down; when the model is done, the handler asks a person with the core SDK’s inv.ask, and on the resume the code refunds what was approved, with the edited amount if the reviewer changed it.
Tested with google-adk 2.13.0, litellm 1.105.5 and Python 3.13, using the conformance kit: 22 checks passed, including an approved, an edited and a rejected refund.

The bundle

The SDK wheel goes in the bundle’s vendor/ folder and is installed with its starlette extra, which serves the agent as an ASGI app; see SDKs.

How the model is wired

LiteLlm takes LiteLLM’s settings, so the handler gives it the gateway: api_base is inv.llm.base_url, extra_headers carries the run token (inv.llm.headers), and the model name is config.models.main with openai/ in front. The prefix tells LiteLLM that the gateway speaks the OpenAI API; LiteLLM removes it, so the name reaches the gateway exactly as written in agentos.yaml (anthropic/claude-sonnet-4-5). The agent is built inside the handler, on every invocation, because the token belongs to one invocation. Two settings keep LiteLLM working inside an AgentOS container:
  • LITELLM_LOCAL_MODEL_COST_MAP=True (in the Dockerfile): LiteLLM otherwise downloads its model price list when it is imported, and an agent reaches nothing outside AgentOS unless you allow it (see Network access). AgentOS records each call’s cost itself.
  • One event loop. agentos.serve runs every async handler in an event loop of its own, and LiteLLM keeps background tasks on the loop it started on. Serving the handler as an ASGI app with uvicorn gives ADK and LiteLLM one long-lived loop.
When a guardrail blocks a model call, LiteLLM raises BadRequestError with the gateway’s answer, and ADK passes it on. 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 model requests a refund

On the start, the handler gives the agent lookup_order and request_refund. request_refund is defined inside the handler: it adds the refund to a list and tells the model that a person approves it. Nothing is paid.
2

The handler asks a person

When the agent is done and the list isn’t empty, inv.aask creates one hold with an item per requested refund (refund-1, refund-2…), offering approve, edit and reject. The run is held, and nothing in the handler after the ask runs on the start.
3

The code refunds what was approved

Once a person decided, the handler runs again with inv.resume. It doesn’t call the model again: it goes through the decided items, refunds the approved ones (an edit with the reviewer’s data) and returns what happened.
ADK has its own tool confirmation (FunctionTool(require_confirmation=True)), but it continues inside ADK’s session, which InMemoryRunner keeps in the container’s memory. AgentOS may resume the run in another container, so this example keeps the model’s work on the start and the decision on the resume, with nothing ADK needs in between.

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

The run is held: the refund waits in the Review queue. Decide it there, or through the Holds API:

Test it before you deploy

The conformance kit builds the bundle and runs it against a mock platform whose model follows call:<tool> {arguments} in a message, so these cases drive the tools and every decision without a real model:
conformance.yaml

Things to know

  • Pin LiteLLM. litellm 1.106 imports the mcp package whenever a request carries tools and fails without it; 1.105.5 doesn’t. With a newer LiteLLM, add mcp to requirements.txt or check that a call with tools works.
  • The final answer comes from code. After the decision the handler reports what it did instead of asking the model again. Call the model on the resume as well if you want it to word the answer, for example with inv.llm.chat(...).

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.