# Agent Adapter: guide for AI coding agents Version 1.1.0. Generated from the package's own code, so it matches this install. Print it with `agent-adapter guide`. This is the full guide: the concise one plus the API index. ## What it is A Python SDK: write a tool once as a plain function, then run it from any model API, agent framework, MCP, A2A or HTTP. One executor adds validation, approvals, budgets, cache, retries and tracing. It works with no agent framework; it can be your agent loop. ## Install `agent-adapter` is on PyPI (Apache-2.0). Install the core, or the extras you need: ```bash pip install "agent-adapter[openai]" ``` From a checkout, use `pip install -e ".[openai]"` instead. Check the setup with `agent-adapter doctor`. Python: `>=3.11`; tested on 3.11, 3.12, 3.13. Framework versions: the newest release inside each pinned range is tested. An extra marked "own environment" conflicts with another extra: give it its own venv. | I need | pip command | Supported versions | |---|---|---| | tools, executor, approval, budgets, cache, flows, compression, plain backends, queues, serverless | `pip install agent-adapter` | `pydantic>=2.12,<3` | | OpenAI models (`OpenAIModel`, also Ollama, vLLM, LM Studio) | `pip install "agent-adapter[openai]"` | `openai>=3.41,<4` | | Gemini models (`GeminiModel`, `genai_tool()`) | `pip install "agent-adapter[gemini]"` | `google-genai>=2.30,<3` | | Anthropic models (`AnthropicModel`) | `pip install "agent-adapter[anthropic]"` | `anthropic>=2.3,<3` | | Redis cache and approval store | `pip install "agent-adapter[redis]"` | `redis>=8.1,<9` | | OpenTelemetry spans (`OTelSink`) | `pip install "agent-adapter[otel]"` | `opentelemetry-api>=1.39,<2` | | LangChain tools, export and import | `pip install "agent-adapter[langchain]"` | `langchain-core>=1.4,<2` | | LangGraph tool nodes, approvals, compiled flows | `pip install "agent-adapter[langgraph]"` | `langgraph>=1.2.15,<2`, `langgraph-checkpoint>=4.3,<5` | | Google ADK tools and flows | `pip install "agent-adapter[adk]"` | `google-adk>=2.13,<3`; checked at use: 2.13 to below 3.0; own environment (opentelemetry<=1.44) | | MCP server, client import, Streamable HTTP | `pip install "agent-adapter[mcp]"` | `mcp>=2.3,<3` | | OpenAI Agents SDK tools | `pip install "agent-adapter[openai-agents]"` | `openai-agents>=0.24,<1`; checked at use: 0.24 to below 1.0 | | Pydantic AI tools | `pip install "agent-adapter[pydantic-ai]"` | `pydantic-ai-slim>=2.60,<3` | | LlamaIndex tools | `pip install "agent-adapter[llamaindex]"` | `llama-index-core>=0.14,<0.15` | | CrewAI tools | `pip install "agent-adapter[crewai]"` | `crewai>=1.15,<2`; own environment (mcp<2) | | tools from an OpenAPI document (`import_openapi`) | `pip install "agent-adapter[openapi]"` | `httpx>=0.27,<1`, `pyyaml>=6,<7` | | Microsoft Agent Framework tools | `pip install "agent-adapter[agent-framework]"` | `agent-framework-core>=1.23,<2`; checked at use: 1.23 to below 2.0 | | Agno tools | `pip install "agent-adapter[agno]"` | `agno>=3.1,<4`; checked at use: 3.1 to below 4.0 | | DSPy tools | `pip install "agent-adapter[dspy]"` | `dspy>=3.4,<4`; own environment (LiteLLM) | | Strands Agents tools | `pip install "agent-adapter[strands]"` | `strands-agents>=1.60,<2`; own environment (mcp<2.2) | | Haystack tools | `pip install "agent-adapter[haystack]"` | `haystack-ai>=3.3,<4` | | smolagents tools | `pip install "agent-adapter[smolagents]"` | `smolagents>=1.24,<2` | | Claude Agent SDK tools | `pip install "agent-adapter[claude-agent]"` | `claude-agent-sdk>=0.2.150,<0.3`, `mcp>=2.3,<3`; checked at use: 0.2.150 to below 0.3.0 | | AutoGen tools | `pip install "agent-adapter[autogen]"` | `autogen-core>=0.7.5,<0.8`, `autogen-agentchat>=0.7.5,<0.8` | | Semantic Kernel functions | `pip install "agent-adapter[semantic-kernel]"` | `semantic-kernel>=1.44,<2`; own environment (pydantic<2.14) | | CAMEL-AI tools | `pip install "agent-adapter[camel]"` | `camel-ai>=0.2.90,<0.3`, `mcp<2`; checked at use: 0.2.90 to below 0.3.0; own environment (needs mcp<2; pins pydantic<=2.12.0, while google-genai in `all` needs >=2.12.5) | | AG2 tools | `pip install "agent-adapter[ag2]"` | `ag2>=1.2.2,<2`; checked at use: 1.2.2 to below 2.0 | | A2A server and client | `pip install "agent-adapter[a2a]"` | `a2a-sdk[http-server]>=1.3,<2` | | AG-UI event stream (CopilotKit and other UIs) | `pip install "agent-adapter[ag-ui]"` | `ag-ui-protocol>=1.0,<2`; checked at use: 1.0 to below 2.0 | | Postgres or any SQLAlchemy database as the approval store | `pip install "agent-adapter[sqlalchemy]"` | `sqlalchemy[asyncio]>=2.0.30,<3` | | DynamoDB approval store (AWS serverless) | `pip install "agent-adapter[dynamodb]"` | `aioboto3>=13,<16` | | flows as Temporal workflows (`agent_adapter.integrations.temporal`) | `pip install "agent-adapter[temporal]"` | `temporalio>=1.35,<2` | | flows as DBOS workflows (`DbosFlows`) | `pip install "agent-adapter[dbos]"` | `dbos>=3.4,<4` | | HTTP tool server (ASGI) with MCP mounted | `pip install "agent-adapter[server]"` | `starlette>=1.0,<2`, `uvicorn>=0.30,<1`, `h11>=0.16,<1`, extra `mcp` | | HTTP tool server under WSGI (Flask, Django) | `pip install "agent-adapter[server-wsgi]"` | `a2wsgi>=1.10,<2`, extra `server` | | Presidio PII guard (`PresidioGuard`; also needs a spaCy model) | `pip install "agent-adapter[presidio]"` | `presidio-analyzer>=2.2,<3`, `presidio-anonymizer>=2.2,<3` | | NeMo Guardrails guard (`NemoGuard`) | `pip install "agent-adapter[nemo]"` | `nemoguardrails>=0.24,<1` | | Mem0 memory backend (`Mem0Memory`) | `pip install "agent-adapter[mem0]"` | `mem0ai>=2.2,<3` | | Zep memory backend (`ZepMemory`) | `pip install "agent-adapter[zep]"` | `zep-cloud>=4.1,<5` | | Graphiti knowledge-graph memory (`GraphitiMemory`) | `pip install "agent-adapter[graphiti]"` | `graphiti-core>=0.31,<1` | | every extra that installs together in one environment | `pip install "agent-adapter[all]"` | every extra above except `adk`, `camel`, `crewai`, `dbos`, `dspy`, `graphiti`, `mem0`, `nemo`, `presidio`, `semantic-kernel`, `strands`, `temporal`, `zep` | | running this repository's tests and linters | `pip install -e ".[dev]"` (in a checkout) | `pytest>=8`, `pytest-asyncio>=0.24`, `pytest-cov>=5`, `pytest-xdist>=3.8,<4`, `pytest-testmon>=2.3,<3`, `ruff>=0.6`, `mypy>=1.11`, `nox>=2024.10`, `hatchling>=1.27`, `tzdata>=2024.1`, `fakeredis>=2.26`, `lupa>=2.2`, `sqlalchemy[asyncio]>=2.0.30,<3`, `aiosqlite>=0.20,<1`, `moto[dynamodb]>=5,<6` | | building this repository's docs site | `pip install -e ".[docs]"` (in a checkout) | `mkdocs-material>=9.5`, `mkdocstrings[python]>=0.26` | ## Import rules Import rules: everyday names come from `agent_adapter`; every other error from `agent_adapter.errors`; everything else from its subpackage (`agent_adapter.exec`, `.hooks`, `.approval`, ...); framework exports from `agent_adapter.integrations.`. Never import from `agent_adapter.core` or a `_private` module. ## Safety rules for agents writing code with it Do: - Mark every tool with a side effect (write, pay, send, delete) `confirm=True`, imported ones too: `import_openapi(..., operations=[...], confirm=True)` for the writes, and the same for `import_mcp` and `import_a2a` tools that change data. - Give the executor an approval port: `CallbackApproval`, `DeferredApproval` or `AutoDeny`. With none, a `confirm=True` call is denied (it fails closed). - Approve only through the executor: its approval port, or `executor.resume(approval_id, decision)`. A framework's own "approved" never counts alone; only a one-use decision issued by the adapter does. - Run tools through the executor (`executor.run`, `run_sync`, `aa.call`, or `attach(..., executor=executor)`). Calling the function directly skips validation, approval, limits and the ledger. - Pass `executor=executor` to every server and app (`create_app`, MCP, A2A, `attach`); without it they use the default executor, not your approval port. - Set limits: `RunLimits(max_calls=)`, `BudgetHook(max_usd)`, and `timeout=` on slow tools. - Raise `ToolError("...")` for a message the model should see; other exceptions reach the model only as `tool-failed`. - Require auth on every server: `bearer_auth` (HTTP), `static_bearer` (A2A), `auth=` with `token_verifier=` (MCP). Without auth an app serves loopback only. - Keep `run_id` and the call id with an approval id, so a resume matches its call. Don't: - Never pass an approval to `attach()`; approval belongs to the executor (`attach(..., approval=...)` raises `ToolUsageError`). - Never log, print or return secrets, tokens, passwords or full personal data. `LoggingHook` redacts secret-named keys; `agent_adapter.hooks.redact` does it by hand. - Never make a secret a model-visible argument: bind it with `fixed=` or read it from `ToolContext`. - Never use `cache_scope="global"` for per-user, secret or personal data: global means every caller of the cache gets the same result. - Never set `retries=` on a tool with a side effect unless it is idempotent: a timed-out sync tool keeps running in its thread, so a retry can repeat it. - Never import from `agent_adapter.core` or a `_private` module. - Never use `AutoApprove` for side-effect tools outside tests. - Never add `--extra-index-url` for this package (dependency confusion). - Never serve with `allow_anonymous` on a public host. ## I want to… → use… Every snippet runs after this set-up (a test runs each one, or compiles it and checks it against the API when its framework is not installed). Lines with `await` go in an `async def`; in sync code, wrap them with `aa.run_sync()`. ```python import agent_adapter as aa registry = aa.Registry() @registry.tool def get_weather(city: str) -> str: """Today's weather for a city.""" return f"{city}: sunny, 21 C" @registry.tool(confirm=True) # a side effect: runs only after an approval def cancel_order(order_id: str) -> str: """Cancel an order.""" return f"cancelled {order_id}" def my_llm(request: aa.LLMRequest) -> str: # your model: any SDK, HTTP API or local model return "It is sunny in Oslo." model = aa.CallableModel(my_llm, provider="local", model="my-llm") executor = aa.Executor(registry) ``` ### 1. Make a function a tool → `@registry.tool` (or `@aa.tool` on the default registry) ```python @registry.tool(timeout=10, retries=2) # read-only, so a retry is safe def track_order(order_id: str) -> str: """Where an order is now.""" return f"{order_id}: out for delivery" ``` ### 2. Wrap code you cannot edit → `registry.wrap()` ```python class OrderBook: # a class from a library or legacy code def status(self, order_id: str) -> str: return "shipped" registry.wrap(OrderBook().status, name="order_status", description="Order status.") ``` ### 3. Keep a secret out of the model's view → `fixed=` on `registry.wrap()` (or read it from `ToolContext`) ```python import os def search_orders(query: str, api_key: str) -> str: """Search the order system.""" return f"{query}: 2 orders" secret = {"api_key": os.environ["ORDERS_API_KEY"]} # unset: KeyError at start-up registry.wrap(search_orders, fixed=secret) ``` ### 4. Call a tool from sync or async code → `executor.run_sync()` / `await executor.run()` ```python print(executor.run_sync("get_weather", {"city": "Oslo"}, timeout=30)) weather = await executor.run("get_weather", {"city": "Oslo"}) # in async code ``` ### 5. Use one executor for the whole process → `aa.configure()` then `aa.call()` / `aa.acall()` ```python aa.configure(registry, limits=aa.RunLimits(max_calls=50)) print(aa.call(get_weather, city="Oslo")) ``` ### 6. Run an agent loop with no framework → `aa.run_tool_loop()` ```python ask = [aa.Message(role="user", content="Weather in Oslo?")] result = await aa.run_tool_loop(model, registry, ask, executor=executor) print(result.final.text) ``` ### 7. Make an agent a tool of another agent → `agent_tool(LoopAgent(...), executor=)` (`agent_adapter.exec`) ```python from agent_adapter.exec import LoopAgent, agent_tool desk = LoopAgent(model, registry.subset(names=["get_weather"]), instructions="Weather only.") team = aa.Registry() boss = aa.Executor(team) # the desk's calls share its hooks, budget, approvals and run team.add(agent_tool(desk, name="weather_desk", description="Weather.", executor=boss)) ask = [aa.Message(role="user", content="Oslo?")] result = await aa.run_tool_loop(model, team, ask, executor=boss) # nesting: max_depth=3 ``` ### 8. Hand the conversation to another agent → `handoff(...)` + `HandoffModel(model, registry)` ```python from agent_adapter.exec import HandoffModel, LoopAgent, handoff desk = LoopAgent(model, registry.subset(names=["get_weather"])) front = aa.Registry() front.add(handoff(desk, name="to_weather", description="Weather desk.", executor=executor)) ask = [aa.Message(role="user", content="Oslo?")] front_desk = HandoffModel(model, front) # the desk's answer ends the loop result = await aa.run_tool_loop(front_desk, front, ask, executor=executor) ``` ### 9. Get structured output (JSON matching a schema) → `output_schema=OutputSchema(...)`, then `.final.parsed` ```python from agent_adapter.models import OutputSchema shape = {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]} schema = OutputSchema(name="Answer", json_schema=shape) result = await aa.run_tool_loop(model, registry, ask, executor=executor, output_schema=schema) print(result.final.parsed["city"]) ``` ### 10. Fan out many calls in parallel → `executor.run_many()` ```python cities = ("Oslo", "Rome", "Lima") calls = [aa.ToolCall(name="get_weather", arguments={"city": c}) for c in cities] results = await executor.run_many(calls, concurrency=4) # one ToolResult per call ``` ### 11. Fan in: steps that need earlier results → `executor.run_plan()` with `aa.Step(depends_on=)` ```python first = aa.Step(id="a", tool="get_weather", args={"city": "Oslo"}) then = aa.Step(id="b", tool="get_weather", depends_on=["a"], args=lambda d: {"city": d["a"].value}) by_id = await executor.run_plan([first, then]) ``` ### 12. Run any async work in parallel with a deadline → `executor.parallel()` ```python async def fetch(url: str) -> str: # your own async work return url pages = await executor.parallel(lambda: fetch("a"), lambda: fetch("b"), timeout=30) ``` ### 13. Ask a person before a side effect → `confirm=True` on the tool plus an approval port on the executor ```python def review(request: aa.ApprovalRequest) -> bool: # ask in your UI, chat or CLI return request.arguments["order_id"].startswith("o-") executor = aa.Executor(registry, approval=aa.CallbackApproval(review)) print(executor.run_sync("cancel_order", {"order_id": "o-1"})) ``` ### 14. Park an approval and resume it later (any process) → `DeferredApproval`, `ApprovalPending`, `resume_sync()` ```python executor = aa.Executor(registry, approval=aa.DeferredApproval.sqlite("approvals.db")) try: executor.run_sync("cancel_order", {"order_id": "o-1"}) except aa.ApprovalPending as pending: # store pending.approval_id for a reviewer # later, in any process that opens approvals.db: done = executor.resume_sync(pending.approval_id, True, decided_by="ana") print(done.value) # the call runs once; a second resume is refused ``` ### 15. Share approvals across processes and hosts → `SQLAlchemyApprovalStore` (or `aa.RedisApprovalStore`) ```python from agent_adapter.approval import SQLAlchemyApprovalStore # needs an async driver: pip install aiosqlite (one host), or asyncpg for # "postgresql+asyncpg://..." (every host shares one database) store = SQLAlchemyApprovalStore("sqlite+aiosqlite:///approvals.db") executor = aa.Executor(registry, approval=aa.DeferredApproval(store)) ``` ### 16. Cap spend and tool calls per run → `aa.Ledger`, `aa.Prices`, `aa.BudgetHook`, `aa.RunLimits` ```python mine = {"my-llm": {"provider": "local", "input": 1.0, "output": 4.0}} # USD per 1M ledger = aa.Ledger(prices=aa.Prices.load({"version": "mine", "models": mine})) executor = aa.Executor( registry, ledger=ledger, hooks=[aa.BudgetHook(0.50)], limits=aa.RunLimits(max_calls=20) ) # once_per_run=True too ``` ### 17. Block, redact or approve personal data and secrets → `PIIGuard`, `CallableGuard(fn)` hooks (`agent_adapter.hooks`) ```python from agent_adapter.hooks import CallableGuard, PIIGuard def moderate(stage: str, payload: object) -> bool | str: # or call your guard service return "off topic" if "lottery" in str(payload) else True redact = PIIGuard(on=("result", "llm_response"), action="redact") # emails, cards, keys no_secrets = PIIGuard(kinds=["secret", "card"], on="args") # or action="approve" guards = [no_secrets, redact, CallableGuard(moderate, on=("args", "llm_request"))] executor = aa.Executor(registry, hooks=guards) print(executor.run_sync("get_weather", {"city": "Oslo"})) ``` ### 18. Cache tool results → `cache_ttl=` / `cache_scope=` on the tool, `cache=` on the executor ```python # global: every caller shares one entry; public data only, never per-user data registry.wrap(get_weather, name="cached_weather", cache_ttl=60, cache_scope="global") executor = aa.Executor(registry, cache=aa.MemoryCache()) # many processes: cache=agent_adapter.cache.RedisCache(url) (extra `redis`) ``` ### 19. Cache model replies → `executor.model(model, cache_ttl=)` ```python executor = aa.Executor(registry, cache=aa.MemoryCache()) llm = executor.model(model, cache_ttl=3600) # also hooks, ledger and events ask = aa.LLMRequest(messages=(aa.Message(role="user", content="hi"),)) first, again = await llm.generate(ask), await llm.generate(ask) # again.cached ``` ### 20. Log and trace every call → `aa.LoggingHook` and an event sink ```python from agent_adapter.tracing import EventLog # or OTelSink() (extra `otel`) events = EventLog() executor = aa.Executor(registry, hooks=[aa.LoggingHook()], sink=events) executor.run_sync("get_weather", {"city": "Oslo"}) # secret-named keys are redacted ``` ### 21. Send spans to Langfuse, Phoenix or any OTLP backend → `OTelSink(tracer_provider=)` with an OTLP exporter ```python from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from agent_adapter.tracing import OTelSink endpoint = "http://localhost:6006/v1/traces" # Phoenix; Langfuse: its URL and auth headers provider = TracerProvider() provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint=endpoint))) executor = aa.Executor(registry, sink=OTelSink(tracer_provider=provider)) ``` ### 22. Bring your own model → `aa.CallableModel` (sync or async function) ```python def house_llm(request: aa.LLMRequest) -> dict: # call your SDK or HTTP API here return {"text": "Hello", "usage": {"input_tokens": 12, "output_tokens": 3}} model = aa.CallableModel(house_llm, provider="house", model="house-13b") ``` ### 23. Wrap a vendor SDK (Bedrock, Azure OpenAI, Mistral, Groq, OpenRouter) → `CallableModel` + `build_chat_request` / `parse_chat_completion` ```python from agent_adapter.models.openai import build_chat_request, parse_chat_completion def vendor_llm(request: aa.LLMRequest) -> dict: # any OpenAI-shaped SDK kwargs = build_chat_request(request, model="llama-3.3-70b", strict_tools=False) reply = parse_chat_completion(client.chat.completions.create(**kwargs)) return {"text": reply.text, "tool_calls": reply.tool_calls, "usage": reply.usage} model = aa.CallableModel(vendor_llm, provider="groq", model="llama-3.3-70b") answer = await model.generate(aa.LLMRequest(messages=(aa.Message(role="user", content="hi"),))) ``` ### 24. Use a hosted provider or a local OpenAI-compatible server → `OpenAIModel` / `AnthropicModel` / `GeminiModel` ```python from agent_adapter.models import AnthropicModel, OpenAIModel model = OpenAIModel("gpt-4.1") # reads OPENAI_API_KEY on the first call claude = AnthropicModel("claude-sonnet-4-5") # reads ANTHROPIC_API_KEY ollama = {"base_url": "http://localhost:11434/v1", "api_key": "unused"} # or vLLM llama = OpenAIModel("llama3.1", api="chat", strict_tools=False, client_kwargs=ollama) ``` ### 25. Fall back to a second model → `aa.FallbackModel` ```python backup = aa.CallableModel(lambda request: "Try again later.", model="backup") model = aa.FallbackModel([model, backup]) # on rate limit, outage or timeout ``` ### 26. Bring your own embedder → `CallableEmbedder` ```python from agent_adapter.embeddings import CallableEmbedder embedder = CallableEmbedder(lambda texts: [[1.0, 0.5] for _ in texts], model="mine") vectors = (await embedder.embed(["hello", "world"])).vectors ``` ### 27. Price your own models → `aa.Prices.default().merged()` (or `aa.Prices.empty()`) ```python mine = { "my-llm": {"provider": "local", "free": True}, "gpu-*": {"provider": "rent", "per_second": 0.001}, } # also per_call prices = aa.Prices.default().merged({"version": "mine", "models": mine}) ``` ### 28. Use a known model's context window → `aa.Prices.default().caps(model)` ```python caps = aa.Prices.default().caps("gpt-5.4") print(caps.context_window, caps.prompt_window()) # percent compression needs no setting ``` ### 29. Remember facts per user across chats (recall, forget, export, erase) → `SQLiteMemory` + `memory_tools` (`agent_adapter.memory`); the scope comes from `ctx.user_id` ```python from agent_adapter.memory import MemoryScope, SQLiteMemory, memory_tools memory = SQLiteMemory("memory.db", retention_days=90) # embedder= adds vector search registry.add_many(memory_tools(memory)) # remember, recall, forget: scope from the ctx ctx = executor.context(thread_id="chat-1", user_id="ann") # the signed-in user await executor.run("remember", {"text": "Ann prefers aisle seats"}, ctx) print(await memory.export(MemoryScope(user_id="ann"))) # a data request await memory.delete(scope=MemoryScope(user_id="ann")) # erase the user ``` ### 30. Let the model search your documents and cite sources (RAG) → `retriever_tool` over any `RetrieverPort` (`agent_adapter.retrieval`) ```python from agent_adapter.embeddings import CallableEmbedder from agent_adapter.retrieval import Document, EmbeddingRetriever, retriever_tool pages = [Document(id="hours", text="Open 9 to 17.", metadata={"shop": "north"})] retriever = EmbeddingRetriever(CallableEmbedder(my_embed), pages, scope_key="shop") # the host sets the scope (here from ctx.state), never the model registry.add(retriever_tool(retriever, filters=lambda ctx: {"shop": ctx.state["shop"]})) # a vector DB or search API: CallableRetriever(fn); or LlamaIndexRetriever, LangChainRetriever ``` ### 31. Remember a chat user across chats (recall each turn, save facts or a summary) → `serve(memory=SQLiteMemory(...), user_id=..., memory_save=...)` or `ui --memory sqlite:PATH --user NAME` ```python from agent_adapter.memory import SQLiteMemory from agent_adapter.viewer import serve if __name__ == "__main__": serve(model=model, memory=SQLiteMemory("memory.db"), user_id="ann", memory_save="facts") # session.memories holds the recalled records; tools see ctx.user_id # CLI: agent-adapter ui --model app:model --memory sqlite:memory.db --user ann ``` ### 32. Show reviewers similar past approvals; propose confirm rules a person accepts → `approval_context` / `propose_rules` / `accept` from `agent_adapter.approval`; advice only ```python from agent_adapter.approval import accept, propose_rules store = aa.MemoryApprovalStore() # the store your DeferredApproval uses for rule in propose_rules(await store.list(), min_count=5): # data: changes nothing print(rule.describe()) # accepted = accept(rule, accepted_by="ann"); commit accepted.code (confirm= rule) # approval_context(record.request, records): counts and deciders; the call still waits ``` ### 33. Compress a long context for one call → `CompressionPolicy`, `LLMRequest(compression=)` ```python from agent_adapter.context import CompressionPolicy, RemoveOldMessages policy = CompressionPolicy( start_when_tokens_over=8_000, shrink_to_tokens=6_000, steps=[RemoveOldMessages(keep_last=6)] ) reply = await model.generate(aa.LLMRequest(messages=history, compression=policy)) ``` ### 34. Compress messages in a LangGraph node → `langgraph_compressor` ```python from agent_adapter.integrations.langgraph import langgraph_compressor compressor = langgraph_compressor(policy) # `policy` as in the recipe above messages = await compressor(state["messages"]) # in your node, before the model ``` ### 35. Compress any framework's message list → `compress_for()` / `compress_for_sync()` ```python from agent_adapter.context import compress_for_sync smaller, report = compress_for_sync(chat_dicts, policy, format="openai") print(report.tokens_before, report.tokens_after) # `smaller` is still chat dicts ``` ### 36. Attach tools to any framework → `aa.attach()` (by name or by framework object) ```python tools = aa.attach("openai", registry, executor=executor) # Chat Completions tools # aa.attach(agent_or_graph, registry, executor=executor): LangGraph, Agents SDK... ``` ### 37. Import tools from an OpenAPI document (or an MCP server) → `import_openapi` (or `import_mcp`) ```python from agent_adapter.integrations.openapi import import_openapi api = {"spec": "openapi.json", "base_url": "https://api.example.com"} import_openapi(registry, **api, operations=["getForecast"]) # reads: no approval # writes and deletes run only after an approval (confirm=True): import_openapi(registry, **api, operations=["cancelBooking"], confirm=True) ``` ### 38. Serve tools from FastAPI → `await executor.run()` in an async route ```python from fastapi import FastAPI app = FastAPI() @app.get("/weather/{city}") async def weather(city: str) -> str: return await executor.run("get_weather", {"city": city}) ``` ### 39. Serve tools from Flask → `executor.run_sync()` in a view ```python from flask import Flask app = Flask(__name__) @app.get("/weather/") def weather(city: str) -> str: return executor.run_sync("get_weather", {"city": city}, timeout=30) ``` ### 40. Serve tools from Django → `await executor.run()` in an async view (`run_sync` in a sync one) ```python from django.http import JsonResponse async def weather(request, city: str) -> JsonResponse: return JsonResponse({"text": await executor.run("get_weather", {"city": city})}) ``` ### 41. Run tools in a Celery task → `executor.run_sync()` in the task ```python from celery import Celery app = Celery("tasks") @app.task def weather(city: str) -> str: return executor.run_sync("get_weather", {"city": city}, timeout=30) ``` ### 42. Run tools in AWS Lambda (SQS, EventBridge, direct) → `tool_handler` ```python from agent_adapter.handlers import tool_handler handler = tool_handler(executor, tools={"get_weather"}, timeout=30) # entry point ``` ### 43. Run tools from any queue (RabbitMQ, Kafka, Pub/Sub) → `handle_envelope` ```python from agent_adapter.handlers import handle_envelope body = {"tool": "get_weather", "arguments": {"city": "Oslo"}} # the message body reply = await handle_envelope(executor, body, tools={"get_weather"}, message_id="m1") ``` ### 44. Build a flow (steps, branches, fan-out, pauses) → `aa.Flow`, `aa.FlowRunner`, `Call` / `Route` / `Map` / `Join` ```python from pydantic import BaseModel from agent_adapter.flow import Call class Trip(BaseModel): city: str weather: str = "" look = Call("get_weather", args=lambda trip: {"city": trip.city}, into="weather") flow = aa.Flow("trip", state=Trip).node("look", look).end("look") result = await aa.FlowRunner(executor).run(flow, {"city": "Oslo"}) ``` ### 45. Run a flow inside a flow, or cache a node → `SubFlow(flow, input=, into=)`, `Flow.node(cache=NodeCache(...))` ```python from pydantic import BaseModel from agent_adapter.flow import NodeCache, SubFlow class Trip(BaseModel): city: str weather: str = "" check = aa.Flow("check", state=Trip).node("look", lambda t: {"weather": "sun"}).end("look") cached = NodeCache(ttl=600, reads=["city"]) # needs a cache port on the executor flow = aa.Flow("trip", state=Trip).node("plan", SubFlow(check), cache=cached).end("plan") executor = aa.Executor(registry, cache=aa.MemoryCache()) result = await aa.FlowRunner(executor).run(flow, {"city": "Oslo"}) ``` ### 46. Serve tools over MCP → `streamable_http_app` ```python from agent_adapter.integrations.mcp import streamable_http_app app = streamable_http_app(registry, executor=executor) # ASGI app at /mcp # no auth= + token_verifier=: anonymous, so it serves loopback clients only ``` ### 47. Serve tools over HTTP for other languages → `create_app` ```python from agent_adapter.server import Principal, bearer_auth, create_app auth = bearer_auth({"s3cret": Principal(id="web-app")}) # tokens from a vault app = create_app(registry, executor=executor, authenticate=auth) # uvicorn mod:app ``` ### 48. Serve tools as an A2A agent → `a2a_app` ```python from agent_adapter.integrations.a2a import a2a_app, static_bearer auth = static_bearer({"s3cret": "agent-team"}) # token -> caller id; from a vault app = a2a_app( registry, url="https://tools.example.com/a2a/", executor=executor, authenticate=auth, host="0.0.0.0", allowed_hosts=["tools.example.com"], ) ``` ### 49. Evaluate an agent on eval cases (scores, JUnit for CI, regressions) → `run_eval_sync` with graders (or `agent-adapter eval TARGET CASES`) ```python from agent_adapter.evals import load_cases, run_eval_sync from agent_adapter.evals.graders import contains, max_cost cases, graders = load_cases("cases.jsonl"), [contains(), max_cost(0.05)] report = run_eval_sync(answer, cases, graders, save_to=".agent_adapter/evals") print(report.mean_score, report.n_passed) # then: agent-adapter ui, Evals tab Path("evals.xml").write_text(report.to_junit(), "utf-8") # for CI test reports ``` ### 50. Improve tool descriptions and prompts against eval cases → `CallableHost`, `optimise` (or the optimiser CLI) ```python from agent_adapter.optimiser import CallableHost, OptimiserConfig, load_cases, optimise cases = load_cases("cases.jsonl") # one {'id', 'input', 'expected'} per line host = CallableHost(run_case, surface={"prompt/system": "Be brief."}, cases=cases) result = await optimise(host, OptimiserConfig(), proposer=model, model=model) print(result.patch) # a reviewable diff; nothing is applied ``` ### 51. Make a flow survive a crash (checkpoints, recover) → `FlowRunner(checkpoints=SQLiteCheckpointStore(path), checkpoint_key=KEY)`, `recover_all` ```python from agent_adapter.exec.checkpoints import SQLiteCheckpointStore store = SQLiteCheckpointStore("runs.db") # Redis or SQLAlchemy for many hosts runner = aa.FlowRunner(executor, checkpoints=store, checkpoint_key=KEY) result = await runner.run(flow, state, executor.context(run_id="order-7")) # on a new worker after a crash: finished tool calls replay, never run twice results = await runner.recover_all(flow) ``` ### 52. Run a flow on Temporal or DBOS (durable engine) → `DurableFlows(runner, [flow])`, `flow_worker`, `start_flow`, `notify`; DBOS: `DbosFlows` ```python from agent_adapter.integrations.temporal import DurableFlows, flow_worker, notify, start_flow runner = aa.FlowRunner(executor, checkpoints=store, checkpoint_key=KEY) flows = DurableFlows(runner, [flow]) async with flow_worker(client, flows, task_queue="refunds"): handle = await start_flow(client, "refunds", state, run_id="t-17", task_queue="refunds") # a reviewer decides in the approval store; the notice only wakes the run await approvals.decide(pending_id, aa.ApprovalDecision(approved=True)) await notify(handle, pending_id) ``` ### 53. Let the model write and run code safely (sandbox, always approved) → `code_tool` over a `SandboxPort` (`agent_adapter.sandbox`) ```python from agent_adapter.sandbox import DockerSandbox, code_tool sandbox = DockerSandbox("python:3.12-slim") # no network, read-only, non-root registry.add(code_tool(sandbox, timeout_s=20)) # confirm=True, always # hosted (E2B, Modal): CallableSandbox(fn); dev only: LocalSubprocessSandbox(allow_unsafe_local=True) ``` ### 54. Cap spend per user or team, per day or month, across workers → `BudgetHook(scope=, per=, store=)` (`agent_adapter.hooks.builtin.budgets`) ```python from agent_adapter.hooks.builtin.budgets import SQLiteBudgetStore store = SQLiteBudgetStore("budgets.db") # RedisBudgetStore, SQLAlchemyBudgetStore per_user = aa.BudgetHook(0.50, scope="user", per="day", store=store) # ctx.user_id def team_of(ctx: aa.ToolContext) -> str: # your user -> team lookup return "support" per_team = aa.BudgetHook(0.80, scope="team", per="day", scope_key=team_of, store=store) executor = aa.Executor(registry, hooks=[per_user, per_team]) # reserve, settle, release ``` ### 55. Show tool arguments and partial structured answers while the model streams → `run_tool_loop(on_event=)`; `ToolCallDelta`, `PartialOutput` (`agent_adapter.models`) ```python from agent_adapter.models import PartialOutput, ToolCallDelta def show(event): # every stream event but text; validate only the final reply if isinstance(event, ToolCallDelta | PartialOutput): print(event) messages = [aa.Message(role="user", content="Weather in Oslo?")] result = await aa.run_tool_loop(model, registry, messages, on_event=show) ``` ### 56. Browse OTLP traces from another app in the run viewer; search and tag runs → `agent-adapter ui --import traces.json`, or `import_otlp` (`agent_adapter.viewer`) ```python from pathlib import Path from agent_adapter.viewer import import_otlp Path("traces.json").write_text('{"resourceSpans": []}') # your OTLP JSON export import_otlp("traces.json", ".agent_adapter/runs") # parsed only, never run # then `agent-adapter ui`: search box (tool names, status:failed, tag:vip), tags ``` ### 57. Start a framework project, or move existing framework tools to the registry → `agent-adapter init `, `agent-adapter migrate PATH --diff` ```python # agent-adapter init pydantic-ai my-agent (also adk, agno, strands, crewai) # agent-adapter migrate src/ --diff > migrate.patch (read-only; review, then git apply) ``` ### 58. Set access, approvals, guards and budgets per tool, team and env in one file → `load_policy(path).apply(executor)` (`agent_adapter.policy`) ```python from agent_adapter.policy import load_policy rules = [{"tools": "get_*", "allow": True, "confirm": "city == 'Oslo'"}] policy = load_policy({"default": "deny", "rules": rules}) # or a .yaml/.toml/.json path executor = policy.apply(executor) # the tool loop, frameworks, MCP and HTTP obey it print(policy.explain("cancel_order")) # denied; set AA_ENV for per-env rules ``` ### 59. Keep a signed, tamper-evident audit log of approvals, blocks and erasures → `audit(executor, folder=, key=)`, `verify`, `export` (`agent_adapter.audit`) ```python from agent_adapter.audit import audit, export, verify key = "from-your-secret-manager-16+" # or set AA_AUDIT_KEY log = audit(executor, folder=".agent_adapter/audit", key=key) # never args or content try: executor.run_sync("cancel_order", {"order_id": "o-1"}) # no approval port here except aa.ApprovalDenied as denied: # the denial is in the log print(denied.kind) log.stop() # seals the last segment print(verify(".agent_adapter/audit", key).summary()) # edits, gaps, reordering csv_text = export(".agent_adapter/audit", format="csv", key=key) ``` ### 60. Turn recorded production runs into eval cases → `cases_from_runs` + `write_cases` (`agent_adapter.evals`) ```python from agent_adapter.evals import cases_from_runs, write_cases # record with viewer.record(executor, capture_content=True); runs without content skip found = cases_from_runs(".agent_adapter/runs", status="failed") write_cases("eval_cases.jsonl", found.cases) # fill `expected` for failed runs print(found.skipped) # each skipped run with the reason # CI: agent-adapter eval app:agent eval_cases.jsonl ``` ### 61. Pick a cheaper model per flow node or stage that keeps the eval score → `agent_adapter.evals.advisor.advise_sync`, then `apply_mapping` yourself ```python from agent_adapter.evals import ToolLoopTarget, load_cases from agent_adapter.evals.advisor import advise_sync, apply_mapping from agent_adapter.evals.graders import contains models = {"big": model, "small": small} target, cases = (lambda m: ToolLoopTarget(m, registry)), load_cases("cases.jsonl") report = advise_sync(target, cases, [contains()], candidates=models, baseline="big", prices=prices) print(report.to_markdown()) # per step (flow node or stage): advice, score, cost, latency cheaper = apply_mapping(model, report.mapping, models) # you apply it: FlowRunner(model=) ``` ### 62. See runs in a browser (timeline, flow graph, approvals) → `agent_adapter.viewer.record()`, then `agent-adapter ui` ```python from agent_adapter.viewer import record record(executor) # every run goes to .agent_adapter/runs # then, in a terminal: agent-adapter ui ``` ### 63. Chat with any agent or model in a local page → `viewer.serve(...)` or `agent-adapter ui --agent` ```python from agent_adapter.viewer import ChatMessage, ChatSession, serve async def chat(message: ChatMessage, session: ChatSession) -> str: return f"you said: {message.text}" # your agent; tools: ctx=session.context() if __name__ == "__main__": serve(agent=chat) # or serve(model=model, registry=registry) # CLI: agent-adapter ui --agent app:chat ``` ## Common errors → fixes Every error subclasses `AdapterError` and carries a stable `kind`. | Error | Kind | Retryable | Fix | |---|---|---|---| | `AdapterError` | `internal-error` | no | Catch it to handle any adapter failure; read `kind` to tell them apart | | `ToolDefinitionError` | `tool-definition-invalid` | no | Read the message: a valid name (letters, digits, `_`), a description or docstring, a signature it can model | | `DuplicateTool` | `duplicate-tool` | no | Pick another `name=`, or use a separate `Registry` | | `ToolNotFound` | `tool-not-found` | no | Check the name and the registry; the model may have invented a tool | | `ToolArgsInvalid` | `tool-args-invalid` | no | Send `.errors` back to the model so it can retry with valid arguments | | `ToolUsageError` | `tool-usage-error` | no | Fix the host code the message names: a confirm rule, an approval callback, a reused call id | | `SchemaNotPortable` | `schema-not-portable` | no | Read `.problems`; simplify the type (see [Schemas](https://agent-adapter.veloceai.in/concepts/schemas/)) | | `MissingDependency` | `missing-dependency` | no | Install the extra the message names | | `ToolError` | `tool-error` | no | Raise it from your tool with a message the model can act on | | `ToolTimeout` | `tool-timeout` | yes | Retry, raise the tool's `timeout`, or make the tool faster | | `ToolFailed` | `tool-failed` | no | Read `cause_type` and `__cause__`, fix the tool, or raise `ToolError` for expected failures | | `RunLimitExceeded` | `run-limit-exceeded` | no | Start a new run, or raise the tool's run limit | | `GuardrailBlocked` | `guardrail-blocked` | no | Read `.reason` and `.stage`; change the input or the guardrail check | | `ApprovalDenied` | `approval-denied` | no | Tell the model the call was refused; do not retry it unchanged | | `ApprovalPending` | `approval-pending` | no | Keep `.approval_id`, ask a reviewer, then resume with the decision | | `ApprovalNotFound` | `approval-not-found` | no | Check the id; it may have expired or belong to another store | | `ApprovalAlreadyDecided` | `approval-already-decided` | no | Read the stored decision instead of deciding again | | `ApprovalStoreCorrupt` | `internal-error` | no | Treat it as tampering: inspect the row, then delete it and ask again ([Approval stores](https://agent-adapter.veloceai.in/concepts/approval-stores/)) | | `BudgetExceeded` | `budget-exceeded` | no | Stop the run, or raise the `BudgetHook` limit | | `PlanInvalid` | `plan-invalid` | no | Fix each item in `.problems` | | `FlowError` | `flow-error` | no | Read `.flow` and `.node`; the cause is in `__cause__` | | `FlowDefinitionError` | `flow-definition-invalid` | no | Fix each item in `.problems` | | `FlowCompileError` | `flow-compile-failed` | no | Drop what `.target` does not support, or run the flow with `FlowRunner` | | `FlowLoopExceeded` | `flow-loop-exceeded` | no | Fix the route that loops, or raise the node's loop limit | | `CheckpointNotFound` | `checkpoint-not-found` | no | Check the run id and that the runner uses the store the run was saved in | | `CheckpointConflict` | `checkpoint-conflict` | no | Leave the run to the worker that holds it; a run that ended needs no recovery ([Durable runs](https://agent-adapter.veloceai.in/concepts/durable-runs/)) | | `CheckpointStoreCorrupt` | `checkpoint-store-corrupt` | no | Check `checkpoint_key` is the one the run was saved with; else treat it as tampering and delete the run | | `RecoveryNeedsDecision` | `recovery-needs-decision` | no | Check each call in `.calls` at its system, then recover with `rerun_in_doubt=True`, or mark the tool `idempotent=True` | | `ConfigError` | `config-invalid` | no | Fix the setting the message names | | `IntegrationError` | `integration-error` | no | Check the integration name and the framework version | | `AuthError` | `auth-error` | no | Catch it to handle both subclasses below | | `Unauthorized` | `unauthorized` | no | Send a valid bearer token | | `Forbidden` | `forbidden` | no | Grant the principal the scope it needs, such as the approver scope | | `OptimiserError` | `optimiser-error` | no | Read the message; check the eval cases and the optimiser budget | | `ModelError` | `model-error` | no | Catch it for any provider failure; check `retryable` before retrying | | `ModelRateLimited` | `model-rate-limited` | yes | Wait `.retry_after` seconds, then retry | | `ModelUnavailable` | `model-unavailable` | yes | Retry with backoff, or fall back with `FallbackModel` | | `ModelTimeout` | `model-timeout` | yes | Retry, or raise the client timeout | | `ModelAuthError` | `model-auth-failed` | no | Check the API key and its permissions | | `ModelBadRequest` | `model-bad-request` | no | Read the message; check the model name, tool schemas and strict mode | | `ModelContextOverflow` | `model-context-overflow` | no | Send fewer tokens: a `CompressionPolicy` with `if_still_too_long` retries once ([Context compression](https://agent-adapter.veloceai.in/concepts/context-compression/)) | | `ModelContentFiltered` | `model-content-filtered` | no | Change the prompt or input; do not retry it unchanged | | `ModelOutputInvalid` | `model-output-invalid` | no | Retry with a clearer prompt, or loosen the output schema | | Symptom or message | Cause | Fix | |---|---|---| | pip backtracks for minutes, or builds a Rust sdist | `dspy` (LiteLLM caps `openai<3`) installed with the `openai` extra | install `dspy` in its own venv | | pip refuses `crewai`, `strands`, `semantic-kernel` or `adk` beside `[all]` | each pins a dependency the others need newer (`mcp`, `pydantic`, `opentelemetry`) | one venv each; see the [support matrix](https://agent-adapter.veloceai.in/support-matrix/) | | `integration 'crewai' needs crewai: pip install "agent-adapter[crewai]"` (`MissingDependency`, also an `ImportError`) | the extra is not installed; the message names it | `pip install "agent-adapter[]"` | | `IntegrationError: ... is outside the supported range` | the framework release is outside the extra's range | install a version inside the range in [support matrix](https://agent-adapter.veloceai.in/support-matrix/) | | `ToolNotFound moved: from agent_adapter.errors import ToolNotFound` | most errors left the top level in 1.0 | import from `agent_adapter.errors` ([Versioning](https://agent-adapter.veloceai.in/guides/versioning/)) | | `tool 'rate' is already registered` | a re-run notebook cell, a module imported twice, two tools with one name | one `Registry()` per module or test; `registry.remove(name)` before re-adding; `name=` to rename | | `tool 'double' needs a description or a docstring` | the model would get no description | add a docstring or `description=` | | `$.counts: open map (dict) is not portable` | some providers reject open maps | a pydantic model with named fields, or `portable=False` if you only target lenient providers | | `no tool named 'clos_account'; known: [...]` | the model named a tool you did not register (or not in the registry you passed) | return the error to the model; it usually corrects itself | | `account_id: Input should be a valid string` | the arguments fail validation; values are never echoed | return it to the model: it says what to fix | | `approval denied ...: no approval port configured` | a `confirm` tool and an executor with no `approval=` | pass `CallbackApproval`, `DeferredApproval` or `AutoDeny` ([Human in the loop](https://agent-adapter.veloceai.in/how-to/human-in-the-loop/)) | | `tool 'parse_invoice' failed after 1 attempt` | the tool raised a foreign exception; its text is hidden | the log line names the class; enable `DEBUG` on `agent_adapter.exec.pipeline` for the text; raise `ToolError("...")` for messages the model should see | | `cached: False False` | `cache_scope="run"` and no `ctx`: every call is a new run | pass one `ctx` for the run, or `cache_scope="thread"` / `"global"` ([Caching](https://agent-adapter.veloceai.in/how-to/caching/)) | | a `ToolError` is never retried | `ToolError` is for the model to fix | raise a subclass with `retryable = True` ([Retry a flaky tool](https://agent-adapter.veloceai.in/cookbook/retry-flaky-tool/)) | | `run-limit-exceeded` | `once_per_run` or `RunLimits(max_calls=)` | expected; a new run gets a new count | | `budget-exceeded` one call over the cap | the hook checks before each call | set `max_usd` one call below the hard limit | | `pending 2`: the resume opened a second review | resumed with a new run id (or call id) | keep `run_id` and the call id with the approval id | | `call id reused with different tool/arguments` | the same call id with other arguments | a new call id for a new call; edits go in `Decision(edited_args=...)` | | `approval already used by a concurrent request` | two resumes raced for one decision | expected: the loser is denied; one call runs | | `ApprovalAlreadyDecided` | a second `decide` | the first decision wins | | a framework says "approved" but the call is denied | framework approval never counts alone | resolve it through the bridge (`resolve_*`), which issues a one-use token ([Security](https://agent-adapter.veloceai.in/guides/security/)) | | `hooks must be Hook instances` | a function passed as a hook | subclass `Hook` | | `BudgetHook has no ledger in the tool context; budget not enforced` (WARNING) | no `ledger=` | `Executor(..., ledger=Ledger(prices=...))` | | `no price for model 'house'; cost recorded as unknown` (WARNING, once) | a model missing from the price table | `Prices.default().merged({...})` ([Cost](https://agent-adapter.veloceai.in/concepts/cost/)) | | `asyncio.run() cannot be called from a running event loop` (Jupyter) | the notebook already runs a loop | `await` directly, or `run_sync(...)`: it moves to a worker thread | | `... attached to a different loop` / `Event loop is closed` | an httpx or redis client kept in a module global, used from another `run_sync` | keep it in a `LoopLocal` ([Sync code](https://agent-adapter.veloceai.in/how-to/sync-backends/#loop-bound-clients)) | | `TimeoutError` from `run_sync` | the work ran past `timeout` | raise the timeout, or give tools their own `timeout=` | | 401 `unauthorized` | no or bad bearer token | send `Authorization: Bearer ...` | | 403 `forbidden` | another principal owns the run, or you may not decide | omit `run_id` or use an unguessable one; reviewers need `approvals:decide` | | 403 `non-local-client` | an anonymous app reached from another host | add authentication; `allow_anonymous_public=True` only on purpose | | 409 `request-in-progress` | an `Idempotency-Key` still running | retry after it ends | | 409 `approval-already-decided` | a second decision | read the record instead | | 413, 408 | body over 1 MiB, or slower than 30 s | raise `max_body_bytes` / `body_timeout_s` on the service | | 422 `tool-args-invalid` | bad arguments | fix the body; `errors` lists each field | | `model-rate-limited` | a 429; `.retry_after` holds the provider's hint | it is retryable: `FallbackModel` or your own backoff | | the model sends `null` for an optional field | OpenAI strict mode makes every field required and nullable | expected: your default is applied ([Strict mode and nulls](https://agent-adapter.veloceai.in/concepts/strict-mode/)) | | `model-auth-failed` | bad or missing API key | check the client the port was built with | ## CLI commands - `agent-adapter init fastapi my-service`: write a starter project from a tested template - `agent-adapter doctor`: check Python, extras, optional backends, integrations and the pip index - `agent-adapter guide --out docs/agent-adapter.md`: print the guide for AI coding agents (AGENTS.md) - `agent-adapter ui`: browse recorded runs, or chat with an agent, in a local web page - `agent-adapter eval app.chat:agent eval_cases.jsonl --min-score 0.9 --junit evals.xml`: run eval cases on an agent, grade them, and fail below a score - `agent-adapter migrate src/ --diff`: list framework tool definitions and suggest registry code (read-only) - `agent-adapter policy check policy.yaml --env prod`: validate a policy file and print the rules it sets - `agent-adapter audit verify .agent_adapter/audit`: verify a signed audit log, or export it as JSONL or CSV - `agent-adapter eval app:agent cases.jsonl --save`: also save the report; `agent-adapter ui` shows it under Evals (`--evals FOLDER`) - `agent-adapter guide --full`: the guide plus every public signature - `python -m agent_adapter guide --section recipes`: the same CLI without the script - `python -m agent_adapter.server --registry mypkg.tools:registry`: serve a registry over HTTP (tokens from `AGENT_ADAPTER_TOKENS`) - `python -m agent_adapter.optimiser --host mypkg.evals:build_host --dry-run`: plan an optimiser run without calling a model ## API index Every public name, grouped by the module to import it from. Signatures come from `inspect.signature`; long default values are shortened to `...`. ### `agent_adapter` Agent Adapter: framework-independent tools, parallel execution and flows for LLM agents. - `AdapterError` (error): Base class for every error raised by Agent Adapter. - `ApprovalDecision(*, approved: bool, edited_args: dict[str, Any] | None = None, reason: str = '', decided_by: str = '', decided_at: float = 0.0) -> None`: A reviewer's decision; `edited_args` replaces the arguments when set. - `ApprovalDenied` (error): A reviewer denied a tool call. - `ApprovalPending` (error): A tool call waits for a decision that arrives later (deferred approval). - `ApprovalRequest(*, approval_id: str, run_id: str, call_id: str, tool: str, arguments: dict[str, Any] = {}, kind: Literal['tool', 'node'] = 'tool', hint: str = '', metadata: dict[str, Any] = {}, created_at: float = 0.0) -> None`: A call (or flow node) waiting for a human decision. - `AutoApprove(*, reason: str = 'auto') -> None`: ApprovalPort that approves every request. - `AutoDeny(*, reason: str = 'denied by policy') -> None`: ApprovalPort that denies every request. - `BudgetHook(max_usd: float, *, on_exceed: OnExceed = 'stop', scope: BudgetScope = 'run', per: Per | None = None, scope_key: ScopeKey | None = None, store: BudgetStore | None = None, budget: str = 'budget', estimate_usd: Estimate | None = None, prices: PriceTable | None = None, hold_s: float = 900.0, clock: Callable[[], float] = time) -> None`: Caps spend per run, thread, user, team or everything, optionally per UTC day or month. - `CallableModel(fn: Callable[[LLMRequest], Awaitable[HostResult] | HostResult], *, provider: str = 'callable', model: str = 'host', stream_fn: Callable[[LLMRequest], AsyncIterator[StreamEvent]] | None = None) -> None`: Wraps `fn(request)`; it returns an `LLMResponse`, the reply text, or a response mapping. - `CallbackApproval(fn: ApprovalCallback) -> None`: ApprovalPort over a sync or async function returning a decision or a bool. - `Decision`: alias of `ApprovalDecision` - `DeferredApproval(store: ApprovalStore) -> None`: ApprovalPort for out-of-band review (HTTP server, queues); emits no events itself. - `Event(type: str, run_id: str = '', call_id: str = '', parent_id: str = '', ts: float = , data: Mapping[str, Any] = ) -> None`: A trace event (see `tracing.events` for the catalogue). - `Executor(registry: Registry | None = None, *, hooks: Sequence[Hook] = (), approval: ApprovalPort | None = None, cache: CachePort | None = None, ledger: LedgerPort | None = None, sink: EventSink | None = None, limits: RunLimits | None = None, backoff: Backoff | None = None, namespace: str = 'aa', clock: Callable[[], float] = monotonic, sleep: Callable[[float], Awaitable[None]] = sleep, rng: Callable[[], float] = random, max_tracked_runs: int = 10000, policy: ToolPolicy | None = None) -> None`: Runs tools with run guards, hooks, approval, cache, retries, ledger and events. - `FallbackModel(models: Sequence[LLMPort], *, fall_on: tuple[type[ModelError], ...] = ..., sink: EventSink | None = None) -> None`: An `LLMPort` over `models`: the next one answers after a `fall_on` error. - `Flow(name: str, *, state: type[S], description: str = '') -> None`: A portable graph flow over a pydantic state model; every builder method returns `self`. - `FlowRunner(executor: Executor, *, approval: ApprovalPort | None = None, hooks: Sequence[Hook] = (), sink: EventSink | None = None, max_steps: int = 100, concurrency: int | None = None, model: LLMPort | None = None, checkpoint_key: bytes | None = None, seal_encoder: Callable[[object], object] | None = None, seal_decoder: Callable[[str, object], object] | None = None, trace_state: bool = False, checkpoints: CheckpointStore | None = None) -> None`: Runs a `Flow` locally: nodes on the executor, supersteps, merges, routes and pauses. - `GuardrailHook(check: GuardrailCheck, *, on: Iterable[Stage] | Stage = ('args',)) -> None`: `check(stage, payload)`: True passes; False or a reason string raises GuardrailBlocked. - `Hook()`: Override any phase; the defaults do nothing (return None). - `LLMRequest(*, messages: tuple[Message, ...], model: str | None = None, tools: tuple[ToolDef, ...] = (), tool_choice: Literal['auto', 'none', 'required'] | str = 'auto', parallel_tool_calls: bool | None = None, output_schema: OutputSchema | None = None, temperature: float | None = None, top_p: float | None = None, max_output_tokens: int | None = None, seed: int | None = None, stop: tuple[str, ...] = (), reasoning: Literal['minimal', 'low', 'medium', 'high'] | None = None, stream: bool = False, stage: str = '', response_cache: bool = True, extra: dict[str, Any] = {}, metadata: dict[str, str] = {}, compression: Any = None) -> None`: One provider-neutral model call. - `LLMResponse(*, provider: str, model: str, id: str = '', text: str = '', tool_calls: tuple[ToolCall, ...] = (), parsed: Any = None, finish_reason: Literal['stop', 'length', 'tool_calls', 'content_filter', 'error', 'other'] = 'stop', raw_finish_reason: str = '', usage: Usage = ..., message: Message, latency_ms: float = 0.0, cached: bool = False) -> None`: A model reply, normalised across providers. - `Ledger(*, prices: Prices | None = None, sinks: Sequence[LedgerSink] = (), max_runs: int = 1000) -> None`: A `LedgerPort`: idempotent on (run_id, entry_id); keeps the `max_runs` latest runs. - `LedgerEntry(*, run_id: str, call_id: str, entry_id: str = '', kind: Literal['llm', 'tool', 'api'], stage: str = '', provider: str = '', model: str = '', input_tokens: int = 0, cached_input_tokens: int = 0, cache_write_input_tokens: int = 0, cache_write_by_ttl: dict[str, int] = {}, output_tokens: int = 0, reasoning_tokens: int = 0, units: float = 0.0, cost_usd: float | None = None, latency_ms: float = 0.0, started_at: float = 0.0, ended_at: float = 0.0, ok: bool = True, error_kind: str = '', cache_hit: bool = False, attempts: int = 1, thread_id: str = '', price_version: str = '', metadata: dict[str, str] = {}) -> None`: One billable call; idempotent on (run_id, entry_id). - `LoggingHook(*, logger: Logger | None = None, level: int = 20, redact: Iterable[str] = ...) -> None`: Logs `.start|finish|error` lines; errors log at WARNING or above. - `MemoryApprovalStore(*, max_records: int = 10000) -> None`: In-process ApprovalStore; one `threading.Lock` makes it safe across event loops. - `MemoryCache(*, max_entries: int = 10000, clock: Callable[[], float] = monotonic) -> None`: LRU byte cache bounded by `max_entries`; TTLs in seconds on `clock` (monotonic). - `Message(*, role: Literal['system', 'user', 'assistant', 'tool'], content: str = '', tool_calls: tuple[ToolCall, ...] = (), tool_call_id: str = '', name: str = '', raw: ProviderRaw | None = None, cache_breakpoint: bool = False, pinned: bool = False) -> None`: One chat message. - `MissingDependency` (error): An optional extra needed for this call is not installed. - `Prices(table: Table) -> None`: An immutable price table; build one with `default()`, `load()` or `merged()`. - `RateLimitHook(per_kind: Mapping[str, Rate], *, clock: Callable[[], float] = monotonic, sleep: Callable[[float], Awaitable[None]] = sleep) -> None`: Delays tool calls by `spec.kind` and model calls by `"llm"`; unlisted kinds never wait. - `RedisApprovalStore(client_factory: Callable[[], Any], *, prefix: str = 'aa:approval:', ttl_s: float | None = 604800.0) -> None`: ApprovalStore on Redis (`agent-adapter[redis]`), shared by every worker and host. - `Registry() -> None`: Holds ToolSpecs by name; executors and exports read from here. - `RunLimits(max_calls: int | None = None, max_concurrency: int = 8, per_kind: Mapping[str, int] = ) -> None`: Per-run limits; `per_kind` caps concurrent calls of one tool kind within a run. - `SQLiteApprovalStore(path: str | PathLike[str], *, max_records: int = 10000, timeout: float = 5.0) -> None`: ApprovalStore in a SQLite file (WAL), safe across threads, loops and processes. - `Step(id: str, tool: str, args: StepArgs = , depends_on: Sequence[str] = ()) -> None`: One tool call of a plan; callable `args` receive the results of `depends_on`. - `ToolCall(*, name: str, arguments: dict[str, Any] = {}, id: str = '') -> None`: One tool call requested by a model or a host. - `ToolContext(run_id: str = '', call_id: str = '', state: Mapping[str, Any] = , extras: dict[str, Any] = , thread_id: str = '', ledger: LedgerPort | None = None, cache: CachePort | None = None, sink: EventSink | None = None, approval: ApprovalDecision | None = None, user_id: str = '') -> None`: Run-scoped data a tool may read; never part of the tool's schema. - `ToolDef(*, name: str, description: str, parameters: dict[str, Any], strict: bool = True) -> None`: A tool as model ports receive it. - `ToolError` (error): Raised by tool code to report a failure; the message is shown to the model. - `ToolResult(call_id: str, name: str, status: ToolStatus, value: Any = None, error: ErrorInfo | None = None, exception: BaseException | None = None, attempts: int = 0, cached: bool = False, latency_ms: float = 0.0) -> None`: Outcome of one tool call. - `ToolSpec(name: str, description: str, args_model: type[BaseModel], func: Callable[..., Any], context_param: str | None = None, fixed: Mapping[str, Any] = , llm_to_python: Mapping[str, str] = , result: Callable[[Any], Any] | None = None, returns: Any = None, kind: str = 'tool', tags: frozenset[str] = frozenset(), parallel_safe: bool = False, once_per_run: bool = False, confirm: ConfirmRule = False, timeout: float | None = None, retries: int = 0, cache_ttl: float | None = None, prompt_hint: str | None = None, portable: bool = True, cache_scope: CacheScope = 'run', targets: frozenset[str] | None = None, input_schema: Mapping[str, Any] | None = None, output_schema: Mapping[str, Any] | None = None, effects: Effects | None = None, idempotent: bool | None = None) -> None`: A registered tool: callable, argument model and execution policy. - `Usage(*, input_tokens: int = 0, cached_input_tokens: int = 0, cache_write_input_tokens: int = 0, cache_write_by_ttl: dict[str, int] = {}, output_tokens: int = 0, reasoning_tokens: int = 0, units: float = 0.0) -> None`: Normalised token usage of one or more LLM calls. - `async acall(tool: ToolRef, /, **args: Any) -> Any`: Run `tool` (a function, spec or name) on the default executor and return its value. - `attach(target_or_name: Any, registry: Registry | None = None, *, executor: ToolRunner | None = None, **opts: Any) -> Any`: The framework's native tools for `registry` (default: `default_registry`). - `build_spec(func: Callable[..., Any], *, name: str | None = None, description: str | None = None, fixed: Mapping[str, Any] | None = None, hide: Iterable[str] | str = (), rename: Mapping[str, str] | None = None, result: Callable[[Any], Any] | None = None, args_model: type[BaseModel] | None = None, **policy: Any) -> ToolSpec`: Build a ToolSpec from a function's signature and docstring. - `call(tool: ToolRef, /, **args: Any) -> Any`: `acall` from sync code, on the process's shared loop; raises like `Executor.run`. - `configure(registry: Registry | None = None, **options: Any) -> Executor`: Make `Executor(registry, **options)` the default (`call`, integrations) and return it. - `default_registry`: `Registry` instance - `run_sync(work: Awaitable[T] | Callable[[], Awaitable[T]], *, timeout: float | None = None) -> T`: Run `work` to completion on a fresh event loop and return its result. - `async run_tool_loop(model: LLMPort, registry: Registry, messages: Sequence[Message], *, max_steps: int = 8, executor: Executor | None = None, ctx: ToolContext | None = None, output_schema: OutputSchema | None = None, tool_choice: Literal['auto', 'none', 'required'] | str = 'auto', concurrency: int | None = None, stage: str = 'tool_loop', request_options: Mapping[str, Any] | None = None, compression: CompressionPolicy | None = None, on_text: TextCallback | None = None, on_event: EventCallback | None = None, run_id: str | None = None, checkpoints: CheckpointStore | None = None, checkpoint_key: bytes | None = None) -> ToolLoopResult`: Call the model, run its tool calls, feed the results back, until it answers. - `set_default_executor(executor: Executor) -> None`: Make `executor` the default for every integration that is not given one. - `spec_from_schema(name: str, description: str, input_schema: Mapping[str, Any], func: Callable[..., Any], *, output_schema: Mapping[str, Any] | None = None, portable: bool | None = None, context_param: str | None = None, **policy: Any) -> ToolSpec`: A tool whose arguments are described by `input_schema` instead of a signature. - `tool(func: F | None = None, /, **options: Any) -> F | Callable[[F], F]`: Decorator: register a function as a tool and return it unchanged. - `wrap(func: Callable[..., Any], **options: Any) -> ToolSpec`: Register existing code as a tool without editing it. ### `agent_adapter.errors` Every error Agent Adapter raises; all subclass `AdapterError`. - `AdapterError`: see `agent_adapter` - `ApprovalAlreadyDecided` (error): The approval request already has a decision. - `ApprovalDenied`: see `agent_adapter` - `ApprovalNotFound` (error): No approval request has this id. - `ApprovalPending`: see `agent_adapter` - `ApprovalStoreCorrupt` (error): A stored approval row is unreadable or disagrees with itself; the store fails closed. - `AuthError` (error): Authentication or authorisation failed. - `BudgetExceeded` (error): Spend reached the configured limit. - `CheckpointConflict` (error): Another worker saved this run's checkpoint first, or the run is not in a state to go on. - `CheckpointNotFound` (error): No checkpoint is stored for this run id. - `CheckpointStoreCorrupt` (error): A stored checkpoint is unreadable, wrongly signed or disagrees with itself; fails closed. - `ConfigError` (error): Invalid configuration. - `DuplicateTool` (error): A tool with this name is already registered. - `ErrorInfo(*, kind: str, message: str, exception: str = '', retryable: bool = False, details: dict[str, Any] = {}) -> None`: JSON-safe description of a failure; never holds input values. - `FlowCompileError` (error): A flow cannot be compiled for a target framework. - `FlowDefinitionError` (error): A flow definition is invalid; `problems` lists every issue found. - `FlowError` (error): A flow failed while running. - `FlowLoopExceeded` (error): A flow node was visited more often than its loop limit allows. - `Forbidden` (error): Valid credentials without permission. - `GuardrailBlocked` (error): A guardrail rejected a payload (`stage`: args, result, llm_request, llm_response). - `IntegrationError` (error): A framework integration failed or is unknown. - `MissingDependency`: see `agent_adapter` - `ModelAuthError` (error): The provider rejected the credentials. - `ModelBadRequest` (error): The provider rejected the request as invalid. - `ModelContentFiltered` (error): The provider withheld the output for safety reasons. - `ModelContextOverflow` (error): The request has more tokens than the model's context window. - `ModelError` (error): A model provider call failed. - `ModelOutputInvalid` (error): The model output does not match the requested schema. - `ModelRateLimited` (error): The provider rate-limited the call. - `ModelTimeout` (error): The provider call timed out. - `ModelUnavailable` (error): 5xx, overload or a connection error. - `OptimiserError` (error): The optimiser could not complete. - `PlanInvalid` (error): A plan of steps cannot run (unknown tools, cycles, bad references). - `RecoveryNeedsDecision` (error): A crashed run left calls of tools not marked idempotent in doubt; a person must decide. - `RunLimitExceeded` (error): A run guard refused the call (`limit` is "once_per_run" or "max_calls"). - `SchemaNotPortable` (error): A schema uses features that some providers reject. - `ToolArgsInvalid` (error): Arguments supplied for a tool failed validation. - `ToolDefinitionError` (error): A function cannot be turned into a tool. - `ToolError`: see `agent_adapter` - `ToolFailed` (error): A tool raised a non-adapter exception; raised `from` that cause. - `ToolNotFound` (error): No tool is registered under the requested name. - `ToolTimeout` (error): A tool call ran past its timeout. - `ToolUsageError` (error): Host code called the tool incorrectly (not the model's fault). - `Unauthorized` (error): No valid credentials. ### `agent_adapter.approval` Approval (HITL): confirm rules, approval ports, the approval store and approval memory. - `AcceptedRule(rule: ProposedRule, accepted_by: str, accepted_at: float, config: dict[str, Any], code: str, confirm: RuleConfirm) -> None`: A rule a person accepted: commit `code` (or `config`) and pass `confirm` as `confirm=`. - `ApprovalCallback = Callable[[ApprovalRequest], ApprovalDecision | bool | Awaitable[ApprovalDecision | bool]]` - `ApprovalContext(*, tool: str, similar: int = 0, approved: int = 0, denied: int = 0, deciders: tuple[str, ...] = (), last_decided_at: float | None = None, examples: tuple[SimilarDecision, ...] = ()) -> None`: What reviewers decided before on requests like this one: advice, never a decision. - `ApprovalDecision`: see `agent_adapter` - `ApprovalPort` (protocol: request): Decides whether a tool call may run; may raise ApprovalPending. - `ApprovalRecord(*, request: ApprovalRequest, status: Literal['pending', 'approved', 'denied'], decision: ApprovalDecision | None = None, consumed: bool = False, args_digest: str = '') -> None`: One approval request, its status, its decision and whether a resume used it. - `ApprovalRequest`: see `agent_adapter` - `ApprovalStatus = Literal['pending', 'approved', 'denied']` - `ApprovalStore` (protocol: consume, create, decide, find, get, list): Where deferred approvals wait; swap the memory store for a database in production. - `ApprovalStoreCorrupt`: see `agent_adapter.errors` - `AutoApprove`: see `agent_adapter` - `AutoDeny`: see `agent_adapter` - `CallbackApproval`: see `agent_adapter` - `Condition(*, field: str, op: Literal['==', 'in', 'between'], values: tuple[str | int | float | bool, ...]) -> None`: One argument bounded to the evidence: `==` one value, `in` a set, `between` lo..hi. - `ConfirmRule = bool | Callable[[ToolCall, ToolContext], bool | Awaitable[bool]]` - `Decision`: alias of `ApprovalDecision` in `agent_adapter` - `DeferredApproval`: see `agent_adapter` - `DynamoDBApprovalStore(client_factory: Callable[[], Any], *, table: str, ttl_s: float | None = 604800.0) -> None`: ApprovalStore on one DynamoDB table (aioboto3 or aiobotocore), shared by every host. - `MemoryApprovalStore`: see `agent_adapter` - `PreparedApproval` (protocol: consume, prepare): A port that can find-or-create a record without consuming it (`DeferredApproval`). - `ProposedRule(*, tool: str, conditions: tuple[Condition, ...], evidence: RuleEvidence, review_days: float) -> None`: Data only: skip asking for `tool` when every condition holds; inert until `accept`. - `RedisApprovalStore`: see `agent_adapter` - `RuleConfirm(tool: str, conditions: tuple[Condition, ...], review_by: float, clock: Callable[[], float] = time) -> None`: A `confirm=` callable: False (no ask) only for `tool`, every field covered and in bounds, before `review_by`; True (ask) for anything else. - `RuleEvidence(*, approved: int, denied: int, denied_outside: int, deciders: tuple[str, ...], first_decided_at: float, last_decided_at: float, sample: tuple[str, ...]) -> None`: The decisions a proposal rests on, and those against it. - `SQLAlchemyApprovalStore(engine: str | Callable[[], AsyncEngine], *, table: str = 'aa_approvals', create_schema: bool = True) -> None`: ApprovalStore on a SQLAlchemy async engine (`agent-adapter[sqlalchemy]`), every host. - `SQLiteApprovalStore`: see `agent_adapter` - `SimilarDecision(*, approval_id: str, approved: bool, edited: bool = False, decided_by: str = '', decided_at: float = 0.0, similarity: float, matched: tuple[str, ...] = ()) -> None`: One earlier decision on a similar request; `matched` names equal fields (nested: `a.*`). - `accept(rule: ProposedRule, *, accepted_by: str) -> AcceptedRule`: A person's explicit acceptance: config and code to commit, and the `confirm=` callable. - `approval_context(request: ApprovalRequest, records: Iterable[ApprovalRecord], *, min_similarity: float = 0.5, shown: int = 5) -> ApprovalContext`: Earlier decisions on the same tool with similar non-secret arguments, best first. - `async ask_each(port: ApprovalPort, requests: Sequence[ApprovalRequest], *, vet: _Vet | None = None) -> list[ApprovalDecision]`: The port's decision for each request, in order; `vet` sees each before any is used. - `confirm_from_config(config: Mapping[str, Any]) -> RuleConfirm`: The `confirm=` callable of a committed rule config (`AcceptedRule.config`). - `async evaluate_confirm(rule: ConfirmRule, call: ToolCall, ctx: ToolContext) -> bool`: Whether `call` needs approval under a `ToolSpec.confirm` rule; the one implementation. - `propose_rules(records: Iterable[ApprovalRecord], *, min_count: int = 5, min_deciders: int = 2, max_denied: int = 0, review_days: float = 90.0, tools: Iterable[str] | None = None) -> list[ProposedRule]`: Rules the approvals support: one per tool and argument shape, bounded to the seen values. - `request_digest(request: ApprovalRequest) -> str`: SHA-256 hex of the canonical JSON of a request's kind, tool and original arguments. ### `agent_adapter.audit` Audit export: a signed, tamper-evident log of approvals, budgets, guards, memory and more. - `AuditEntry(action: str, ts: float, run_id: str = '', call_id: str = '', data: Mapping[str, Any] = , source: str = 'sdk') -> None`: One security-relevant fact, before it is signed: what, when, which run and call. - `AuditRecord = dict[str, Any]` - `AuditSink(store: AuditStore, *, fail_closed: bool = False, max_queue: int = 10000) -> None`: Queues security-relevant events for `store` on a writer thread; ignores all others. - `AuditStore` (protocol: close, digest, write): Keeps audit entries signed and in order. - `AuditUnavailable` (error): A fail-closed audit log lost a record (or could not store it in time); no call runs. - `AuditVerification(ok: bool, segments: int, records: int, events: int, sealed: bool, problems: tuple[str, ...] = (), log_id: str = '', last_seq: int = -1, last_hash: str = '') -> None`: The result of `verify`: `ok` only when nothing at all is wrong. - `CallableAuditStore(fn: Callable[[AuditRecord], None], *, key: str | bytes | None = None) -> None`: Signs and chains entries into one stream, then calls `fn(record)` for each. - `FileAuditStore(folder: str | PathLike[str], *, key: str | bytes | None = None, max_bytes: int = 10000000, rotate_daily: bool = True, fsync: bool = True) -> None`: Append-only, chained, signed JSONL segments in `folder`; one writer per folder. - `KEY_ENV = 'AA_AUDIT_KEY'` - `audit(executor: Executor | None = None, *, folder: str | PathLike[str] = Path('.agent_adapter/audit'), key: str | bytes | None = None, store: AuditStore | None = None, max_bytes: int = 10000000, fsync: bool = True, fail_closed: bool = False) -> AuditSink`: Attach a signed audit log of `executor` (or the default one) under `folder` or `store`. - `export(path: str | PathLike[str], *, since: Moment = None, until: Moment = None, format: Format = 'jsonl', key: str | bytes | None = None) -> str`: Verified event records with `since <= ts < until` as text (large logs: `export_to`). - `export_to(out: TextIO, path: str | PathLike[str], *, since: Moment = None, until: Moment = None, format: Format = 'jsonl', key: str | bytes | None = None) -> int`: Stream `export`'s records to `out` after a full check; the count written. - `read_entries(path: str | PathLike[str], key: str | bytes | None = None) -> Iterator[dict[str, Any]]`: The event records of a verified log, oldest first, one at a time. - `verify(path: str | PathLike[str], key: str | bytes | None = None, *, anchor: str | None = None) -> AuditVerification`: Check the audit folder `path` line by line; `anchor` (an earlier one) catches rollbacks. - `verify_records(records: Iterable[Mapping[str, Any]], key: str | bytes | None = None) -> AuditVerification`: Check shipped records in the order received; interleaved streams are checked apart. ### `agent_adapter.cache` Caching: backends, keys, envelopes, the tool and LLM caches, and the prompt layout. - `CachePort` (protocol: delete, get, set): Byte cache. - `Envelope(kind: EnvelopeKind, stored_at: float, payload: Any) -> None`: A decoded cache entry. - `KEY_VERSION = 1` - `LLMResponseCache(port: CachePort, *, namespace: str = 'aa', ttl: float | None = None) -> None`: Stores normalised `LLMResponse`s under `llm_key` (provider, model, request, fields). - `MemoryCache`: see `agent_adapter` - `PromptLayout(static: str, dynamic: str = '', sections: Mapping[str, str] = ) -> None`: Static prefix (system prompt, tool catalogue, few-shots) plus per-request text. - `RedisCache(url: str | None = None, *, client_factory: Callable[[], Any] | None = None, prefix: str = '', socket_timeout: float = 5.0, retries: int = 3) -> None`: Byte cache on Redis; `RedisError` is logged at WARNING and treated as a miss. - `ToolResultCache(port: CachePort, *, namespace: str = 'aa') -> None`: Stores tool results under `tool_key`, scoped to a run, a thread or globally. - `canonical_json(obj: Any) -> str`: Deterministic JSON (sorted keys, no spaces, UTF-8). - `decode(data: bytes, kind: EnvelopeKind) -> Envelope | None`: The envelope in `data`; None for bad JSON, another version or another kind. - `digest(obj: Any) -> str`: First 32 hex characters of the SHA-256 of `canonical_json(obj)`. - `encode(kind: EnvelopeKind, payload: Any) -> bytes`: Wrap `payload` as `{"v", "kind", "stored_at", "payload"}` JSON bytes. - `gcache_key(namespace: str, model: str, prefix_digest: str) -> str`: Name under which a Gemini explicit cache for one static prompt prefix is stored. - `llm_key(namespace: str, request: LLMRequest, *, provider: str, model: str, fields: Mapping[str, Any]) -> str`: Key for an LLM response; `fields` is extra key material from the port and plug-ins. - `tool_key(namespace: str, tool: str, args: Mapping[str, Any], *, scope: CacheScope, run_id: str, thread_id: str) -> str`: Key for a tool result; "" when the call cannot be cached (no run or thread id). ### `agent_adapter.chat` The chat agent contract, shared by the chat page (`agent_adapter.viewer`) and evals. - `Attachment(name: str, media_type: str, size: int, path: Path, id: str = '') -> None`: A file the user attached; `path` is a local copy the server stored. - `ChatAgent` (protocol): `agent(message, session)`: async or sync; returns text or an async iterator of chunks. - `ChatMessage(text: str, attachments: tuple[Attachment, ...] = ()) -> None`: The user's message of one turn. - `ChatReply = str | AsyncIterator[str | ToolCallDelta]` - `ChatSession(id: str, history: tuple[Message, ...], state: dict[str, Any], executor: Executor, run_id: str, user_id: str = '', memories: tuple[MemoryHit, ...] = ()) -> None`: One conversation as the agent sees it during a turn; `state` is live and mutable. ### `agent_adapter.context` Context compression: shrink the messages sent to a model, under fixed safety invariants. - `BaseCompressionStep()`: Base class for steps: a fixed `name`, `apply`, and `config()` for cache keys and logs. - `CallableReranker(fn: RerankFn, *, provider: str = 'callable', model: str = 'host', timeout_s: float | None = None, stage: str = 'compress', ledger: LedgerPort | None = None, sink: EventSink | None = None) -> None`: Wraps `fn(query, texts)` as a `RerankerPort` that writes a ledger entry and events. - `CompressingModel(model: LLMPort, policy: CompressionPolicy, when: When | None = None, *, caps: CapabilityLookup | None = None) -> None`: An `LLMPort` that compresses every request's messages with `policy` before `model`. - `CompressionLimits(start_when_tokens_over: int, shrink_to_tokens: int) -> None`: The two token counts one compression works with. - `CompressionPolicy(*, model_max_tokens: int | None = None, start_when_full_percent: int | None = None, shrink_to_percent: int | None = None, start_when_tokens_over: int | None = None, shrink_to_tokens: int | None = None, steps: Sequence[CompressionStep] = (), count_tokens: TokenCounter | None = None, if_still_too_long: CompressionPolicy | None = None, summary_model: LLMPort | None = None, cache: CachePort | None = None, on_step_error: StepErrorMode = 'skip') -> None`: When and how far to compress, and with which steps; give percents or token counts. - `CompressionReport(tokens_before: int, tokens_after: int, steps_used: tuple[str, ...], summary_cost_usd: float, skipped_reason: SkipReason, target_tokens: int, messages_before: int, messages_after: int, steps_failed: tuple[StepFailure, ...] = ()) -> None`: What one compression did; counts only, never message text. - `CompressionStep` (protocol: apply, name): One stage of a compression pipeline. - `ContextSummary(*, summary: str, facts: tuple[str, ...] = ()) -> None`: The summariser's structured output. - `CutLongMessages(*, max_tokens_per_message: int = 2000, keep: Literal['start', 'end', 'start_and_end'] = 'start', include_system_prompt: bool = False) -> None`: Cut the text of messages over `max_tokens_per_message`, oldest first, keeping one end. - `EmbeddingScorer(embedder: EmbeddingPort) -> None`: Cosine similarity of your embedder's vectors; negative similarity counts as 0. - `KeepMostRelevant(scorer: Scorer, *, compare_to: str = 'latest_user_message', keep_count: int = 10) -> None`: Score every unprotected unit against `compare_to`; keep the `keep_count` best ones. - `MyOwnStep(function: StepFunction, *, include_system_prompt: bool = False) -> None`: Your function as a step: `list[Message] -> list[Message]`, sync or async. - `MyScorer(fn: ScoreFn) -> None`: Your function `fn(query, texts) -> list[float]` in 0..1; a sync one runs in a thread. - `Protection(messages: Sequence[Message]) -> None`: Which messages no step may change or remove, fixed on the input of one compression. - `RemoveDuplicates(*, match: Literal['exact', 'similar'] = 'exact', scorer: Scorer | None = None, same_if_score_above: float = 0.92) -> None`: Drop an older copy of a later message or tool round, oldest first, only as many as needed. - `RemoveOldMessages(*, keep_first: int = 1, keep_last: int = 8) -> None`: Drop the oldest messages between the first and last ones kept, only as many as needed. - `RerankResult(*, scores: tuple[float, ...], model: str = '', usage: Usage = ...) -> None`: Scores in input order, plus what the call used, for billing. - `RerankerPort` (protocol: rerank): Scores how relevant each text is to `query`; one float per text, in order. - `RerankerScorer(reranker: RerankerPort) -> None`: Your reranker's scores, which must be in 0..1 (apply a sigmoid to raw logits first). - `Scorer` (protocol: score): Scores each text against `query`; one float in 0..1 per text, in order. - `ShrinkToolResults(*, older_than_turns: int = 2, bigger_than_tokens: int = 5000, save_to: CachePort | None = None, save_for_seconds: float | None = 86400.0) -> None`: Shorten tool results over `bigger_than_tokens` older than `older_than_turns`, oldest first. - `SkipReason = Literal['', 'under_threshold', 'under_target', 'nothing_to_compress']` - `StepContext(target_tokens: int, meter: TokenMeter, protection: Protection, ctx: ToolContext | None = None, summary_cost_usd: float = 0.0, cache: CachePort | None = None) -> None`: What a step may read during one `compress` call; the same object for every step. - `StepFailure(step: str, error_kind: str, latency_ms: float) -> None`: A step that raised and was skipped (`on_step_error="skip"`); no exception text. - `Summarize(model: LLMPort, *, keep_recent_messages: int = 6, summary_style: SummaryStyle = 'bullet_points', summary_max_tokens: int = 800, instructions: str | None = None, cache: CachePort | None = None, cache_for_seconds: float | None = 86400.0) -> None`: Replace the oldest messages with summaries written by your model. - `SummarizeLongMessage(model: LLMPort, *, bigger_than_tokens: int = 20000, summary_max_tokens: int = 2000, instructions: str | None = None, cache: CachePort | None = None, cache_for_seconds: float | None = 86400.0, include_system_prompt: bool = False) -> None`: Replace the text of each message over `bigger_than_tokens` with your model's summary. - `TokenCounter = Callable[[list[Message]], int]` - `TokenMeter(count_tokens: TokenCounter | None = None) -> None`: Counts with one counter and caches single-message counts while the messages live. - `Unit(start: int, stop: int, kind: UnitKind, protected: bool, messages: tuple[Message, ...]) -> None`: Messages that are kept, changed or removed together; `messages[start:stop]`. - `carries_approval_state(message: Message) -> bool`: True for a tool result recording an approval or policy decision, or approval raw items. - `async compress(messages: Sequence[Message], policy: CompressionPolicy, *, model_max_tokens: int | None = None, force: bool = False, ctx: ToolContext | None = None) -> tuple[list[Message], CompressionReport]`: The compressed conversation and a report; the input is returned as is when not needed. - `async compress_for(messages: Sequence[T], policy: CompressionPolicy, *, format: FormatName | str = 'auto', pinned: Pinned | None = None, model_max_tokens: int | None = None, ctx: ToolContext | None = None) -> tuple[list[T], CompressionReport]`: `compress()` for any message format: the compressed list in that format, and a report. - `compress_for_sync(messages: Sequence[T], policy: CompressionPolicy, *, format: FormatName | str = 'auto', pinned: Pinned | None = None, model_max_tokens: int | None = None, ctx: ToolContext | None = None) -> tuple[list[T], CompressionReport]`: `compress_for` for sync code (Flask, Celery, scripts, a sync LangGraph node). - `enforce_invariants(before: Sequence[Message], after: object, protection: Protection, *, step: str, allow_system_prompt_shortening: bool = False) -> list[Message]`: `after` with every change that breaks an invariant skipped (the input message kept). - `estimate_tokens(messages: Sequence[Message]) -> int`: About chars/4 per message plus a fixed per-message overhead. - `has_approval_marker(native: object) -> bool`: True when a native message (any format) holds an approval or confirmation item. - `recall_tool(store: CachePort) -> ToolSpec`: The `recall_context(id)` tool: the full text of a result saved in `store` by this run. - `split_units(messages: Sequence[Message], protection: Protection | None = None) -> list[Unit]`: `messages` as units in order; `protected` follows `protection` (default: of `messages`). ### `agent_adapter.cost` Cost: the price table, the ledger, its sinks and summaries. - `CallbackSink(fn: Callable[[LedgerEntry], None]) -> None`: Calls `fn(entry)` for every entry, e.g. to insert into a database. - `JsonlSink(path: str | PathLike[str]) -> None`: Appends one JSON line per entry (UTF-8), flushed and closed on every write. - `Ledger`: see `agent_adapter` - `LedgerPort` (protocol: entries, record, total_usd): Cost ledger; `record` is idempotent on (run_id, entry_id) and never raises. - `LedgerSink` (protocol: write): Receives every entry the ledger records; may raise (the ledger logs and continues). - `LedgerSummary(*, run_id: str | None, total_usd: float, unknown_cost_entries: int, by_stage: dict[str, StageSummary], by_kind: dict[str, StageSummary], prompt_cache_hit_rate: float, response_cache_hit_rate: float, tool_cache_hit_rate: float, wall_ms: float, busy_ms: float, parallelism: float) -> None`: A run's (or every run's) cost, cache effectiveness and achieved parallelism. - `MemorySink() -> None`: Keeps every written entry, duplicates included, in `entries`. - `Prices`: see `agent_adapter` - `ResolvedPrice(model: str, provider: str, input: float, cached_input: float, cache_write: float, cache_write_by_ttl: Mapping[str, float], output: float, version: str, per_call: float = 0.0, per_second: float = 0.0, free: bool = False) -> None`: Token prices (USD per 1M tokens) for one model, tier and date. - `StageSummary(*, calls: int, cost_usd: float, input_tokens: int, cached_input_tokens: int, cache_write_input_tokens: int, output_tokens: int, latency_ms: float, errors: int) -> None`: Totals over one group of entries (a stage or a kind); unknown costs count as 0. - `read_jsonl(path: str | PathLike[str]) -> list[LedgerEntry]`: Entries from a JSON-lines file; the last line per (run_id, entry_id) wins. ### `agent_adapter.embeddings` Embeddings: the `EmbeddingPort`, `CallableEmbedder` for any host function, and its cache. - `CallableEmbedder(fn: EmbedFn, *, model: str = 'host', provider: str = 'callable', batch_size: int = 64, timeout_s: float | None = None, cache: CachePort | None = None, cache_ttl: float | None = None, namespace: str = 'aa', ledger: LedgerPort | None = None, sink: EventSink | None = None) -> None`: Wraps `fn(texts)` with batching, an optional per-text cache, a ledger entry and events. - `EmbeddingCache(port: CachePort, *, namespace: str = 'aa', ttl: float | None = None) -> None`: Stores vectors under `embed_key`; backend failures and bad entries are misses. - `EmbeddingPort` (protocol: embed): Turns texts into vectors; raises `ModelError` subclasses. - `EmbeddingResult(*, vectors: tuple[tuple[float, ...], ...], model: str = '', usage: Usage = ..., cache_hits: int = 0) -> None`: Vectors in input order, the model that made them, its usage and the cache hits. - `embed_key(namespace: str, text: str, *, provider: str, model: str) -> str`: Key for one text's vector; the text appears only as a digest. ### `agent_adapter.evals` Evals: run cases on any agent, grade the runs, and report for people and CI. - `CASE_EVENT = 'custom.eval.case'` - `CallableTarget(fn: Callable[..., Any], *, name: str = '') -> None`: `fn(input)` or `fn(input, ctx=ctx)`, sync or async; take `ctx` so tool calls join the run. - `CaseChange(*, case_id: str, before_passed: bool, after_passed: bool, before_score: float, after_score: float) -> None`: One case that got worse or better between two reports. - `CaseResult(*, case_id: str, tags: tuple[str, ...] = (), run_id: str = '', passed: bool, score: float | None, grades: tuple[GradeResult, ...] = (), input: str = '', expected: str = '', output: str = '', error: str = '', cost_usd: float = 0.0, latency_s: float = 0.0, tool_calls: tuple[str, ...] = (), measured: bool = True) -> None`: One case: its run id (open it in the viewer), verdict, grades, cost and time. - `ChatTarget(agent: Callable[..., Any], *, name: str = '') -> None`: A chat-page agent `fn(message, session)`; each case is a new one-turn conversation. - `DEFAULT_EVALS_FOLDER = '.agent_adapter/evals'` - `EvalCase(*, id: str, input: Any, tags: tuple[str, ...] = (), expected: Any = None) -> None`: One evaluation input; `expected` is for the graders only. - `EvalComparison(*, score_delta: float, regressions: tuple[CaseChange, ...], improvements: tuple[CaseChange, ...], added: tuple[str, ...], removed: tuple[str, ...]) -> None`: How a report differs from a baseline: regressions, improvements, added and removed cases. - `EvalReport(*, name: str, started_at: str, wall_s: float, cases: tuple[CaseResult, ...]) -> None`: Every case result of one eval, with totals; render it as JUnit, Markdown or JSON. - `EvalRun(case_id: str, output: Any, run_id: str = '', events: tuple[Event, ...] = (), tool_calls: tuple[str, ...] | None = (), cost_usd: float | None = 0.0, latency_s: float = 0.0, metrics: Mapping[str, float] = ) -> None`: What graders see of one case run; `tool_calls` and `cost_usd` are None when unobserved. - `FlowTarget(flow: Flow[Any], *, initial_state: Callable[[EvalCase], BaseModel | Mapping[str, Any]], output: Callable[[Any], Any] | None = None, model: LLMPort | None = None, name: str = '') -> None`: A `Flow` run by `FlowRunner`; `initial_state(case)` builds the state, `output` reads it. - `GradeResult(*, grader: str, score: float | None, passed: bool, notes: str = '') -> None`: One grader's verdict on one case; `score` None means it did not grade. - `Grader = Callable[[EvalCase, Any], Awaitable[Score | None]]` - `GroupSummary(*, n: int, n_passed: int, mean_score: float) -> None`: Totals of a group of cases (one tag, or one grader). - `LLMJudge(model: LLMPort, rubric: str, *, stage: str = 'judge', show_expected: bool = True, trust_passed: bool = False) -> None`: A grader that asks `model` to score each answer against `rubric`; it never raises. - `Outcome(output: Any, unfinished: str = '') -> None`: A target's output, and why it did not finish ("" when it did); unfinished is an error. - `RecordedRun(key: str, run_id: str, status: str, capture_content: bool, events: tuple[dict[str, Any], ...], tags: tuple[str, ...] = ()) -> None`: One recorded run: its key, id, status, whether content was kept, events and tags. - `RunCase(case: EvalCase, source_run: str, run_key: str) -> None`: An eval case made from one recorded run; `source_run` is that run's id. - `RunCases(cases: tuple[RunCase, ...], skipped: tuple[SkippedRun, ...]) -> None`: The cases made from a runs folder, and the runs skipped. - `Score(*, value: float, passed: bool | None = None, notes: str = '') -> None`: A grader's verdict on one run; `value` is in [0, 1] and `notes` is the reason. - `SkippedRun(run_key: str, run_id: str, reason: str) -> None`: A run that gave no case, and why. - `Target` (protocol: run_case): Runs one case in the given run (`ctx`) on `executor`; returns the output or an Outcome. - `ToolLoopTarget(model: LLMPort, registry: Registry, *, system: str = '', max_steps: int = 8, output_schema: OutputSchema | None = None, name: str = 'tool_loop') -> None`: `run_tool_loop` over `registry` with an optional system prompt; output is the answer. - `as_target(target: object) -> Target`: `target` as a Target: a Target as is, a 2-argument callable as a chat agent, else a callable. - `assert_eval(target: object, case: EvalCase, graders: Sequence[Grader], *, executor: Executor | None = None, timeout_s: float | None = 300.0, pass_at: float = 1.0) -> CaseResult`: Run one case (in a test, say); the result, or AssertionError with the reasons. - `cases_from_runs(folder: str | PathLike[str], *, run_keys: Collection[str] | None = None, status: str | Collection[str] | None = None, tags: Collection[str] = (), expected_from: ExpectedFrom = 'output') -> RunCases`: Eval cases from the recorded runs in `folder` (a runs folder or one run file). - `eval_executor() -> Executor`: The default eval executor: an empty registry (no stray tools) and a fresh ledger. - `graders` (module): Ready-made graders `async (case, run) -> Score | None`; they never raise (an error is None). - `load_cases(path: str | PathLike[str], *, id_field: str = 'id', input_field: str = 'input', expected_field: str = 'expected', tags_field: str = 'tags') -> tuple[EvalCase, ...]`: Cases from JSONL (one object per line), JSON (a list of objects) or CSV (a header row). - `async run_eval(target: object, cases: Cases, graders: Graders, *, concurrency: int = 4, executor: Executor | None = None, name: str = '', timeout_s: float | None = 300.0, pass_at: float = 1.0, save_to: str | PathLike[str] | None = None, capture_content: bool = True) -> EvalReport`: Run each case on `target`, grade it with every grader, and return the report. - `run_eval_sync(target: object, cases: Cases, graders: Graders, **options: Any) -> EvalReport`: `run_eval` for sync code (scripts, tests). - `save_report(report: EvalReport, folder: str | PathLike[str] = '.agent_adapter/evals') -> Path`: Write `report` as `/.json` (atomically) and return the path. - `write_cases(path: str | PathLike[str], cases: Iterable[RunCase]) -> tuple[int, list[str]]`: Append cases to a `.jsonl` file, skipping ids it already holds; (written, skipped ids). ### `agent_adapter.exec` Executor: runs tools with guards, hooks, approval, cache, retries, batches, plans and loops. - `AgentDepthExceeded` (error): An agent tool was called deeper than its `max_depth` (agents calling agents). - `Backoff(base_s: float = 0.5, max_s: float = 30.0, multiplier: float = 2.0, jitter: float = 0.25) -> None`: Exponential backoff with jitter; delays in seconds. - `Executor`: see `agent_adapter` - `HandoffModel(model: LLMPort, handoffs: Registry | Iterable[str]) -> None`: Wraps a model: once a handoff tool has answered, the loop ends with that answer. - `LoopAgent(model: LLMPort, registry: Registry, instructions: str = '', max_steps: int = 8, request_options: Mapping[str, Any] | None = None) -> None`: A `run_tool_loop` agent: its model, the tools it may use and its instructions. - `OnError = Literal['collect', 'fail_fast']` - `RunLimits`: see `agent_adapter` - `Step`: see `agent_adapter` - `SyncTwins()`: `run_sync`, `run_call_sync`, `run_many_sync` and `resume_sync` for the Executor. - `ToolLoopResult(messages: tuple[Message, ...], final: LLMResponse, steps: int, finish: LoopFinish, tool_results: tuple[ToolResult, ...], usage: Usage) -> None`: Where a tool loop ended: the full history, the last reply and every tool result. - `acall`: see `agent_adapter` - `agent_depth() -> int`: How many agent tools are running around the current call (0 outside any). - `agent_tool(agent: LoopAgent | TaskAgent, *, name: str, description: str, executor: Executor | None = None, max_depth: int = 3, timeout: float | None = None, confirm: ConfirmRule = False, tags: Iterable[str] = ()) -> ToolSpec`: A tool (kind "agent") that runs `agent` on the model's `task` inside the calling run. - `call`: see `agent_adapter` - `configure`: see `agent_adapter` - `default_executor() -> Executor`: The default Executor, created on first use, on the default registry. - `handoff(to: LoopAgent | TaskAgent, *, name: str, description: str, executor: Executor | None = None, max_depth: int = 3, timeout: float | None = None) -> ToolSpec`: An agent tool tagged "handoff": under a `HandoffModel`, its answer is the loop's answer. - `is_control_flow(exc: BaseException) -> bool`: True for CancelledError, a registered type, a class named GraphBubbleUp in the MRO, or an exception group holding any of them. - `async recover_tool_loop(model: LLMPort, registry: Registry, run_id: str, *, checkpoints: CheckpointStore, checkpoint_key: bytes | None = None, rerun_in_doubt: bool = False, max_steps: int = 8, executor: Executor | None = None, ctx: ToolContext | None = None, output_schema: OutputSchema | None = None, tool_choice: Literal['auto', 'none', 'required'] | str = 'auto', concurrency: int | None = None, stage: str = 'tool_loop', request_options: Mapping[str, Any] | None = None, compression: CompressionPolicy | None = None, on_text: TextCallback | None = None, on_event: EventCallback | None = None) -> ToolLoopResult`: Continue a durable tool loop from its last checkpoint; finished tool calls replay. - `register_control_flow(*types: type[BaseException]) -> None`: Treat `types` (and subclasses) as control flow in the executor and the approval ports. - `reset_default_executor() -> None`: Drop the default Executor and its default sinks; the next use starts fresh. - `run_tool_loop`: see `agent_adapter` - `runner_for(executor: R | None) -> R | Executor`: `executor` when given, else the default Executor. - `set_default_executor`: see `agent_adapter` ### `agent_adapter.flow` Portable graph flows: a builder, merge rules, sub-flows, a node cache, a graph, a runner. - `Call(tool: str | ToolSpec, args: Mapping[str, Any] | Callable[[Any], Mapping[str, Any]] = , into: str | None = None) -> None`: Run one tool through the executor; `args` may be a function of the state. - `Flow`: see `agent_adapter` - `FlowCheckpoint(flow: str, state: BaseModel, frontier: tuple[str, ...], visits: Mapping[str, int], loops: Mapping[str, int], steps: int, pending: ApprovalRequest | None, decisions: Mapping[str, ApprovalDecision] = ) -> None`: Where a paused flow stopped; pass it to `FlowRunner.resume` (JSON: `dump_checkpoint`). - `FlowGraph(name: str, state_model: type[BaseModel], start: str, nodes: Mapping[str, NodeSpec], edges: Mapping[str, tuple[str, ...]], routes: Mapping[str, RouteSpec], ends: frozenset[str], rules: Mapping[str, MergeRule]) -> None`: A validated flow: nodes in declaration order, static edges, routes, ends and merge rules. - `FlowResult(state: S, status: FlowStatus, steps: int, visits: Mapping[str, int], checkpoint: FlowCheckpoint | None = None, pending: ApprovalRequest | None = None, denied_at: str | None = None) -> None`: How a run ended: the state, the status, supersteps run and visits per node. - `FlowRunner`: see `agent_adapter` - `Join(sources: Sequence[str], fn: NodeFn[Any] | None = None) -> None`: Fan-in: runs once nothing else is pending; `fn` is an optional reducer step. - `Map(over: str, tool: str | ToolSpec | None = None, tool_from: str | None = None, args_from: str = 'args', args_fn: Callable[[Any], Mapping[str, Any]] | None = None, fn: Callable[..., Any] | None = None, into: str | None = None, on_error: Literal['fail', 'skip'] = 'fail') -> None`: Fan out over the list field `over`: one tool call (or `fn` call) per item. - `Merge()`: The built-in merge rules; annotate a state field with one (`Annotated[T, Merge.sum]`). - `MergeRule` (protocol: merge): Combines the updates one superstep makes to a field; `updates` are in deterministic order. - `NodeCache(ttl: float | None = None, *, key: Callable[[Any], Any] | None = None, reads: Sequence[str] | None = None, scope: CacheScope = 'run') -> None`: Reuse a node's updates for equal inputs: the state, its `reads` fields, or `key(state)`. - `NodeFn = Callable[[S], Any] | Callable[[S, ToolContext], Any]` - `NodeSpec(name: str, kind: NodeKind, action: Any, concurrency: int | None, timeout: float | None, retries: int, pause: bool | Callable[..., Mapping[str, Any]], prompt: str | None, cache: Any = None, child: FlowGraph | None = None) -> None`: One node: its action and run options. - `Route(fn: Callable[[S], str | Sequence[str]], targets: Sequence[str] | Mapping[str, str]) -> None`: Pick the next node(s): `fn(state)` returns a label or labels found in `targets`. - `RouteSpec(source: str, fn: Callable[..., Any], targets: Mapping[str, str], max_loops: int | None, on_exhausted: str | None) -> None`: A conditional exit: `fn(state)` returns labels; `targets` maps label -> node. - `SubFlow(flow: Any, *, input: Mapping[str, str] | Callable[[Any], Mapping[str, Any]] | None = None, into: str | Mapping[str, str] | Callable[[Any], Mapping[str, Any]] | None = None) -> None`: Run another `Flow` as one node: parent state in through `input`, its result out by `into`. - `apply_updates(state: S, updates: Sequence[Mapping[str, Any]], rules: Mapping[str, MergeRule]) -> S`: Merge `updates` (in order) into a copy of `state` and re-validate it. - `keyed(by: str) -> MergeRule`: A list of mappings or models; a later item with the same `by` key replaces earlier. - `merge_rules(model: type[BaseModel]) -> dict[str, MergeRule]`: The merge rule of every field of `model`, from `Annotated` metadata (default `replace`). - `node_model(ctx: ToolContext) -> LLMPort | None`: The flow runner's model, instrumented by its executor and bound to this node, or None. - `node_prompt(ctx: ToolContext) -> str | None`: The running node's prompt, or None. ### `agent_adapter.handlers` Handlers: run tool calls from queue messages, serverless events and pipelines. - `Envelope(tool: str = '', arguments: dict[str, Any] = , run_id: str = '', call_id: str = '', approval_id: str = '') -> None`: A parsed envelope: a call (`tool` set) or a resume (`approval_id` set). - `MAX_ENVELOPE_BYTES = 1048576` - `Reply = dict[str, Any]` - `RunSync()`: A `Runner` with one fresh event loop per invocation (`run_sync`); nothing stays warm. - `Runner` (protocol: run): Runs async work from sync code: a shared `LoopRunner` by default, or `RunSync`. - `async handle_envelope(executor: Executor, payload: Mapping[str, Any] | str | bytes | Envelope, *, tools: Collection[str] | None = None, message_id: str = '', source: str = '') -> Reply`: Run one call (or resume one decided approval) and return a JSON-safe reply. - `parse_envelope(payload: Mapping[str, Any] | str | bytes, *, message_id: str = '', source: str = '') -> Envelope`: Validate `payload`; a call without ids gets ids derived from `message_id`, else fresh. - `tool_handler(executor: Executor, *, tools: Collection[str] | None = None, timeout: float = 60.0, margin_s: float = 1.0, runner: Runner | None = None) -> Handler`: A sync `handler(event, context=None)` that runs envelopes through `executor`. ### `agent_adapter.hooks` Hooks: code that runs before, after and on errors of tool, LLM and flow-node calls. - `BudgetHook`: see `agent_adapter` - `CallableGuard(fn: GuardFn, *, on: Iterable[Stage] | Stage = ('args', 'result', 'llm_response'), action: Action = 'block') -> None`: Wraps `fn(stage, payload)`: True passes; False, a reason or a GuardVerdict says no. - `ContentGuard(*, on: Iterable[Stage] | Stage = ('args', 'result', 'llm_response'), action: Action = 'block') -> None`: Base guard: `inspect(stage, payload)` decides; `action` blocks, redacts or asks. - `DEFAULT_REDACT = ...` - `GuardVerdict(allowed: bool, reason: str = '', replacement: Any = NO_REPLACEMENT, kinds: tuple[str, ...] = (), count: int = 0) -> None`: A guard's answer: allowed, or a reason and (to redact) the rewritten payload. - `GuardrailHook`: see `agent_adapter` - `Hook`: see `agent_adapter` - `LLMInvocation(request: LLMRequest, provider: str, model: str, ctx: ToolContext) -> None`: A model call as hooks see it. - `LoggingHook`: see `agent_adapter` - `NemoGuard(rails: Any = None, *, config: str | Path | None = None, on: Iterable[Stage] | Stage = ('args', 'result', 'llm_response'), action: Action = 'block') -> None`: NeMo Guardrails as a guard: pass `rails` (an `LLMRails`) or `config` (its folder). - `NodeInvocation(flow: str, node: str, state: BaseModel, ctx: ToolContext) -> None`: A flow node run as hooks see it. - `PIIGuard(*, kinds: Iterable[str] = ('email', 'phone', 'card', 'iban', 'secret'), patterns: Mapping[str, str | Pattern[str]] | None = None, on: Iterable[Stage] | Stage = ('args', 'result', 'llm_response'), action: Action = 'block', placeholder: str = '[{kind}]') -> None`: Blocks, redacts or approves payloads holding personal data or secrets (regexes only). - `PresidioGuard(*, analyzer: Any = None, anonymizer: Any = None, entities: Iterable[str] | None = None, language: str = 'en', threshold: float = 0.5, on: Iterable[Stage] | Stage = ('args', 'result', 'llm_response'), action: Action = 'block') -> None`: Presidio as a guard; pass your own `analyzer` and `anonymizer` engines or get defaults. - `Rate(calls: int, per: float) -> None`: At most `calls` calls per `per` seconds, with bursts of up to `calls`. - `RateLimitHook`: see `agent_adapter` - `ShortCircuit(value: Any) -> None`: Returned by a `before_*` / `on_*_error` hook: skip the call and use `value`. - `ToolInvocation(spec: ToolSpec, call: ToolCall, ctx: ToolContext) -> None`: A tool call as hooks see it; `call.arguments` are validated, LLM-facing, JSON mode. - `hook_name(hook: Hook) -> str`: The hook's `name`, or its class name. - `redact(value: Any, patterns: Iterable[str] = ..., *, match: Callable[[str], bool] | None = None) -> Any`: A copy of `value` whose secret mapping keys are `***`. - `async run_after(hooks: Sequence[Hook], method: str, inv: Any, value: Any) -> Any`: Thread `value` through each hook; a None return keeps the current value. - `async run_before(hooks: Sequence[Hook], method: str, inv: Any) -> ShortCircuit | None`: Call `method` on each hook; the first ShortCircuit wins and stops the phase. - `async run_on_error(hooks: Sequence[Hook], method: str, inv: Any, error: BaseException) -> ShortCircuit | None`: Offer `error` to each hook; the first ShortCircuit recovers and stops the phase. ### `agent_adapter.memory` Memory: the `MemoryPort`, SQLite, callable, Mem0, Zep and Graphiti backends, and memory tools. - `CallableMemory(*, add: MemoryFn, search: MemoryFn, get: MemoryFn | None = None, delete: MemoryFn | None = None, export: MemoryFn | None = None, backend: str = 'callable', retention_days: float | None = None, capture_content: bool = False) -> None`: Wraps host functions (sync or async) as a `MemoryPort`. - `GraphitiMemory(graphiti: Any, *, retention_days: float | None = None, capture_content: bool = False) -> None`: Graphiti behind the memory port: episodes in a hashed group per user; search returns facts. - `GuardedMemory(*, retention_days: float | None = None, capture_content: bool = False)`: The base of every backend: checks inputs, drops out-of-scope or expired output. - `Mem0Memory(client: Any = None, *, infer: bool = False, retention_days: float | None = None, capture_content: bool = False) -> None`: Mem0 2.x behind the memory port: user, session (`run_id`) and agent scopes. - `MemoryHit(*, record: MemoryRecord, score: float = 0.0) -> None`: A search result: the record and its score (higher is better, backend-specific). - `MemoryPort` (protocol: add, delete, export, get, search): Stores and finds texts per scope; every backend here also has `*_sync` twins. - `MemoryRecord(*, id: str = '', scope: MemoryScope, text: str, metadata: dict[str, Any] = {}, created_at: datetime | None = None, expires_at: datetime | None = None, source: str = '') -> None`: One remembered text in its scope; `id` and `created_at` are filled in on `add`. - `MemoryScope(*, user_id: str = '', session_id: str = '', agent_id: str = '') -> None`: Whose memory: a user, a session (chat thread) and an agent; at least one is set. - `SQLiteMemory(path: str | PathLike[str], *, embedder: EmbeddingPort | None = None, retention_days: float | None = None, timeout: float = 5.0, capture_content: bool = False) -> None`: Memory in one SQLite file: keyword search, optional vectors, deletes that leave no text. - `ScopeFrom = Callable[[ToolContext], MemoryScope | None]` - `ZepMemory(client: Any = None, *, retention_days: float | None = None, capture_content: bool = False) -> None`: Zep behind the memory port: records are text episodes in a Zep user of our own (hashed id). - `memory_tools(memory: GuardedMemory, *, scope_from: ScopeFrom | None = None, confirm_writes: ConfirmRule = False, forget: bool = True, across_sessions: bool = True, k: int = 5) -> list[ToolSpec]`: `remember`, `recall` and `forget` tools whose scope is `scope_from(ctx)`, never an argument. - `scope_from_context(ctx: ToolContext) -> MemoryScope | None`: The signed-in caller's scope (`ctx.user_id`, else the host's principal), or None. - `thread_scope(ctx: ToolContext) -> MemoryScope | None`: Opt-in memory for anonymous chats: the thread only; safe only with unguessable ids. - `user_scope(ctx: ToolContext) -> MemoryScope | None`: The caller's user-only scope (for backends that partition by user), or None. ### `agent_adapter.models` Model ports: provider adapters, a host-function port, fallback, instrumentation, usage. - `AnthropicModel(model: str, *, client: Any | None = None, client_kwargs: Mapping[str, Any] | None = None, cache_plugin: PromptCachePlugin | None = None, max_tokens: int = 4096, defaults: Mapping[str, Any] | None = None) -> None`: Anthropic as an `LLMPort` (`anthropic>=2.3`; `pip install "agent-adapter[anthropic]"`). - `CallableModel`: see `agent_adapter` - `CapabilityLookup = Callable[[str], ModelCaps]` - `DEFAULT_FALL_ON = ...` - `FallbackModel`: see `agent_adapter` - `GeminiModel(model: str, *, client: Any | None = None, client_kwargs: Mapping[str, Any] | None = None, cache_plugin: PromptCachePlugin | None = None, thinking: Mapping[str, Any] | None = None, defaults: Mapping[str, Any] | None = None) -> None`: Gemini as an `LLMPort` (`google-genai>=2.30`; `pip install "agent-adapter[gemini]"`). - `InstrumentedModel(model: LLMPort, *, hooks: Sequence[Hook] = (), ledger: LedgerPort | None = None, cache: CachePort | None = None, cache_ttl: float | None = None, sink: EventSink | None = None, namespace: str = 'aa', ctx: ToolContext | None = None) -> None`: An `LLMPort` that runs `model` with hooks, a response cache, ledger entries and events. - `LLMPort` (protocol: cache_key_fields, generate, stream): A model provider; `generate` raises ModelError subclasses. - `ModelCaps(*, prompt_cache_key: bool = False, prompt_cache_retention_24h: bool = False, prompt_cache_options: bool = False, explicit_cache: bool = False, cache_control: bool = False, min_cache_tokens: int = 1024, structured_output: bool = True, reasoning: bool = False, context_window: int | None = None, max_output_tokens: int | None = None) -> None`: Capability flags of one model; the defaults disable every gated feature. - `OpenAIModel(model: str, *, api: Literal['responses', 'chat'] = 'responses', client: Any | None = None, client_kwargs: Mapping[str, Any] | None = None, cache_plugin: PromptCachePlugin | None = None, strict_tools: bool = True, defaults: Mapping[str, Any] | None = None) -> None`: OpenAI as an `LLMPort` (`openai>=3.41`; `pip install "agent-adapter[openai]"`). - `OutputSchema(*, name: str, json_schema: dict[str, Any], strict: bool = True) -> None`: Structured-output schema for a request; ports apply provider transforms. - `PartialOutput(*, type: Literal['partial_output'] = 'partial_output', value: Any) -> None`: The structured output parsed so far (streams with `output_schema` only); not validated. - `PromptCachePlugin` (protocol: apply, key_fields): Edits provider SDK kwargs to use that provider's prompt cache. - `StreamDone(*, type: Literal['done'] = 'done', response: LLMResponse) -> None`: The last stream item: the assembled response. - `StreamEvent = TextDelta | ToolCallDelta | PartialOutput | StreamDone` - `TextDelta(*, type: Literal['text'] = 'text', text: str) -> None`: A chunk of streamed text. - `ToolCallDelta(*, type: Literal['tool_call'] = 'tool_call', index: int, id: str = '', name: str = '', arguments_delta: str = '') -> None`: A chunk of a streamed tool call. - `normalise_usage(provider: str, raw: Any, *, api: str = '') -> Usage`: Dispatch to the normaliser of `provider` (and OpenAI `api`: "chat" or "responses"). - `usage_anthropic(raw: Any) -> Usage`: Usage of an Anthropic `Message`; `input_tokens` already excludes cache reads and writes. - `usage_gemini(raw: Any) -> Usage`: Usage of a Gemini `GenerateContentResponse` (snake_case or camelCase). - `usage_openai_chat(raw: Any) -> Usage`: Usage of a Chat Completions response (`CompletionUsage`). - `usage_openai_responses(raw: Any) -> Usage`: Usage of a Responses API response (`ResponseUsage`). - `usage_voyage(raw: Any) -> Usage`: Usage of a Voyage embed/rerank result; `units` is millions of tokens (price unit). ### `agent_adapter.optimiser` Offline harness optimiser: Propose -> Probe -> Compose over a host's eval cases. - `BudgetUsage(*, usd: float, case_runs: int, wall_s: float, proposer_calls: int, merge_calls: int, stopped: str = '') -> None`: What a run spent; `stopped` names the limit that ended it early ("" when none did). - `CallableHost(run_case: RunCase, *, surface: Mapping[str, str], cases: Sequence[EvalCase], judge: Judge | None = None, domain: str = 'default', caps: Mapping[str, int] | None = None, required: Iterable[str] = (), validate: Callable[[HarnessTree], list[str]] | None = None, withhold: Iterable[str] = ()) -> None`: Optimise any agent you can call: `run_case` reads the tunable texts from `tree`. - `Candidate(*, label: str, dim: Literal['procedure', 'knowledge', 'compose', 'base'], edits: tuple[Edit, ...], tree: HarnessTree, parent_label: str, notes: str = '', fell_back: bool = False, error: str = '', cost_usd: float = 0.0, attempts: int = 0) -> None`: A harness proposal (or the base): its edits, the tree they produce and how it was made. - `CaseRun(*, case_id: str, label: str, ok: bool, delivered: bool, output: str, transcript: str, metrics: dict[str, float] = {}, score: float | None = None, passed: bool | None = None, judge_notes: str = '', error: str = '') -> None`: One eval case run under one harness: output, transcript, metrics and the judge's view. - `Edit(*, path: str, content: str | None, reason: str) -> None`: One proposed change: `content` None deletes the path; `reason` cites the evidence. - `EvalCase`: see `agent_adapter.evals` - `FlowHost(flow: Flow[Any], *, initial_state: Callable[[EvalCase], BaseModel], output: Callable[[BaseModel], str], cases: Sequence[EvalCase], judge: Judge | None = None, hooks: Sequence[Hook] = (), domain: str = 'default', executor: Executor | None = None, allow_safety_tuning: bool = False) -> None`: Runs a flow with `FlowRunner`; edits node prompts (`Flow.with_prompts`) and hook configs. - `HarnessTree(*, files: dict[str, str]) -> None`: Every editable harness component as path -> text. - `Host` (protocol: caps, cases, run, surface, validate): What the optimiser needs from an application: its harness, its cases, and a way to run. - `Judge = Callable[[EvalCase, CaseRun], Awaitable[Score | None]]` - `Lesson(*, id: str, domain: str, created_at: str, cue: str, lesson: str, evidence: str, confidence: Literal['low', 'medium', 'high'], component_paths: list[str], run: str, adopted: bool) -> None`: A transferable cue from one run, stored in `lessons.json` for later runs. - `LessonsStore(path: str | PathLike[str]) -> None`: `lessons.json`: lessons of every run, rewritten atomically on each `add`. - `NullModel()`: The agent model for hosts whose agent brings its own: every call raises `ConfigError`. - `OptimiseResult(*, base: ProbeReport, candidates: dict[str, Candidate], probes: dict[str, ProbeReport], finals: list[str], chosen: str | None, decisions: list[DecisionLine], patch: str, report_md: str, lessons_added: list[Lesson], ledger_summary: LedgerSummary, budget: BudgetUsage, errors: list[str]) -> None`: Everything one optimiser run produced; nothing in it has been applied to the host. - `OptimiserBudget(*, max_usd: float | None = None, max_case_runs: int | None = None, max_wall_s: float | None = None) -> None`: Limits for one optimiser run; None means unlimited. - `OptimiserConfig(*, k: int = 1, mode: Literal['parallel', 'sequential'] = 'parallel', compose_mode: Literal['greedy', 'llm'] = 'greedy', min_gain: float = 0.0, rollout_concurrency: int = 4, case_timeout_s: float | None = 300.0, evidence_chars: int = 60000, obs_trunc: int = 4000, show_scores_to_proposer: bool = False, max_lessons: int = 8, lessons_path: str = '.agent_adapter/lessons.json', out_dir: str = 'runs/opt', namespace: str = 'aa-opt', seed: int = 0, budget: OptimiserBudget = ...) -> None`: Settings of one optimiser run (`k` is the main cost knob: `(1 + 3k) * N` case runs). - `RegistryHost(registry: Registry, *, layout: PromptLayout, cases: Sequence[EvalCase], judge: Judge | None = None, hooks: Sequence[Hook] = (), domain: str = 'default', max_steps: int = 8, executor: Executor | None = None, allow_safety_tuning: bool = False) -> None`: Runs `run_tool_loop` over a registry; edits descriptions, hints, prompts and hook configs. - `RunCase = Callable[[HarnessTree, EvalCase, LLMPort], Awaitable[str | CaseRun]]` - `Score`: see `agent_adapter.evals` - `SurfaceCaps(*, max_chars: dict[str, int], kinds: frozenset[str], hook_schemas: dict[str, dict[str, Any]] = {}) -> None`: Character caps per path pattern, the editable kinds, and JSON schemas of hook configs. - `apply_tree(registry: Registry, tree: HarnessTree) -> Registry`: A copy of `registry` with the tree's descriptions and prompt hints; tools without paths stay. - `graders` (module): Ready-made judges: exact, contains, regex, code_check, llm_judge and all_of (from evals). - `layout_from_tree(base: PromptLayout, tree: HarnessTree) -> PromptLayout`: The tree's system prompt, sections and tool hints; only `base.dynamic` is kept. - `load_cases`: see `agent_adapter.evals` - `load_harness(out_dir: str | PathLike[str]) -> HarnessTree`: The tree an optimiser run chose (or its base), read from `/harness.after.json`. - `async optimise(host: Host, config: OptimiserConfig, *, proposer: LLMPort, model: LLMPort, composer: LLMPort | None = None, cache: CachePort | None = None, ledger: Ledger | None = None) -> OptimiseResult`: Run Propose -> Probe -> Compose over the host's cases and write a reviewable patch. ### `agent_adapter.policy` Policy: one file that sets approvals, budgets, guardrails, limits and access per tool and env. - `BudgetRule(*, max_usd: float, name: str = 'budget', scope: Literal['run', 'thread', 'global', 'principal', 'user', 'team'] = 'run', per: Literal['day', 'month'] | None = None, on_exceed: Literal['warn', 'stop'] = 'stop', store: str = 'memory', envs: list[str] | None = None) -> None`: A `BudgetHook`: a spend limit per scope, optionally per UTC day or month. - `Condition(text: str) -> None`: A parsed condition: comparisons (`== != < <= > >= in not in`), `and`, `or`, `not`. - `ConditionError` (error): A condition cannot be parsed, or cannot be decided for these arguments. - `ENV_VAR = 'AA_ENV'` - `Explanation(tool: str, env: str, allowed: Tri, lines: tuple[str, ...]) -> None`: The rules for one tool: `allowed` is None when it depends on the caller. - `GuardRule(*, tools: list[str] = , kinds: list[str] | None = None, tags: list[str] | None = None, envs: list[str] | None = None, teams: list[str] | None = None, users: list[str] | None = None, guard: str, on: list[Literal['args', 'result', 'llm_request', 'llm_response']] | None = None, action: Literal['block', 'redact', 'approve'] | None = None, entities: list[str] | None = None) -> None`: A content guard on the selected tools' arguments or results (and model calls). - `LimitsRule(*, max_calls: int | None = None) -> None`: Run limits: at most `max_calls` distinct calls per run. - `MemoryRule(*, retention_days: float | None = None, save: Literal['allow', 'confirm', 'deny'] = 'allow') -> None`: How long memories are kept, and whether the `remember` tool may save, with approval. - `Policy(spec: PolicySpec, *, env: str | None = None, source: str = '', allow_relax: bool = False, guards: Mapping[str, ContentGuard] | None = None, stores: Mapping[str, BudgetStore] | None = None, team_of: TeamOf | None = None, base_dir: Path | None = None) -> None`: A validated policy for one environment, ready to `apply` to an executor. - `PolicyChain(parts: tuple[Any, ...]) -> None`: Several policies at once: each rewrites the spec in turn, so all of them apply. - `PolicyDenied` (error): A policy does not allow this tool here; a denial like any other (`approval-denied`). - `PolicyError` (error): A policy is malformed, unknown, or would relax a code default; nothing is applied. - `PolicySource = str | PathLike[str] | Mapping[str, Any] | PolicySpec` - `PolicySpec(*, version: Literal[1] = 1, default: Literal['allow', 'deny'] | dict[str, Literal['allow', 'deny']] = 'allow', environments: list[str] | None = None, team_key: str = 'team', rules: list[ToolRule] = , guards: list[GuardRule] = , budgets: list[BudgetRule] = , rate_limits: list[RateRule] = , limits: LimitsRule | None = None, memory: MemoryRule | None = None, sandbox: SandboxRule | None = None, relax: list[Literal['limits.max_calls', 'memory.retention_days']] = ) -> None`: A whole policy, as written in the file. - `RateRule(*, kind: str, calls: int, per: float, envs: list[str] | None = None) -> None`: A `RateLimitHook` bucket: at most `calls` per `per` seconds for one tool kind (or llm). - `SandboxRule(*, require_isolated: bool = False, deny_network: bool = False) -> None`: What a code sandbox must be: isolated from the host, and without network access. - `TeamOf = Callable[[ToolContext], str]` - `ToolRule(*, tools: list[str] = , kinds: list[str] | None = None, tags: list[str] | None = None, envs: list[str] | None = None, teams: list[str] | None = None, users: list[str] | None = None, allow: bool | None = None, confirm: Literal[True] | Condition | None = None, reason: str = '') -> None`: Allow or deny tools, or ask for approval always (`true`) or on a condition. - `load_policy(source: PolicySource, *, env: str | None = None, allow_relax: bool = False, guards: Mapping[str, ContentGuard] | None = None, stores: Mapping[str, BudgetStore] | None = None, team_of: TeamOf | None = None) -> Policy`: A validated `Policy` from a file path (.yaml, .yml, .toml, .json), a dict or a PolicySpec. - `team_from(key: str) -> TeamOf`: `ctx.extras[key]`, else the principal's `claims[key]`; "" when neither is a string. ### `agent_adapter.retrieval` Retrieval (RAG): the `RetrieverPort`, `Document`, backends and a cited `retriever_tool`. - `CallableRetriever(fn: RetrieveFn, *, name: str = '', timeout_s: float | None = None, capture_content: bool = False) -> None`: Wraps `fn(query, k, filters)`; a sync `fn` runs in a worker thread with the contextvars. - `Document(*, id: str, text: str, score: float | None = None, source: Source = Source(uri='', title=''), metadata: dict[str, Any] = ) -> None`: One retrieved passage: a stable id, its text, a score (higher is better) and its source. - `EmbeddingRetriever(embedder: EmbeddingPort, documents: Iterable[Document | str] = (), *, scope_key: str | None = None, name: str = '', timeout_s: float | None = None, capture_content: bool = False) -> None`: In-memory cosine search; with `scope_key`, documents and searches need a non-empty scope. - `FilterValue = str | int | float | bool` - `Filters = Mapping[str, str | int | float | bool]` - `GuardedRetriever(*, name: str = '', timeout_s: float | None = None, capture_content: bool = False) -> None`: A `RetrieverPort` that checks its inputs and never returns a document outside `filters`. - `LangChainRetriever(retriever: Any, *, search_kwargs: SearchKwargs | None = None, name: str = '', timeout_s: float | None = None, capture_content: bool = False) -> None`: Calls `retriever.ainvoke(query, **search_kwargs(k, filters))` (or `invoke` in a thread). - `LlamaIndexRetriever(index_or_retriever: Any, *, native_filters: NativeFilters | None = None, name: str = '', timeout_s: float | None = None, capture_content: bool = False) -> None`: Searches an index (`as_retriever(similarity_top_k=k, filters=...)`) or a retriever. - `MAX_K = 100` - `RETRIEVAL_KIND = 'retrieval'` - `RetrieveFn = Callable[[str, int, dict[str, str | int | float | bool]], Any]` - `RetrieverPort` (protocol: retrieve): Finds up to `k` documents for `query` that match `filters`, best first. - `Source(*, uri: str = '', title: str = '') -> None`: Where a document came from, for citations: a link or path and a title. - `ToolFilters = Mapping[str, str | int | float | bool] | Callable[[ToolContext], Mapping[str, str | int | float | bool]]` - `retriever_tool(retriever: RetrieverPort, *, name: str = 'search_docs', description: str = ..., k: int = 5, filters: ToolFilters | None = None, include_metadata: bool = False, max_chars: int | None = None, cache_ttl: float | None = None, cache_scope: CacheScope = 'run', timeout: float | None = None, tags: Iterable[str] = ()) -> ToolSpec`: A tool (kind "retrieval") that searches `retriever` for the model's `query`. ### `agent_adapter.runtime` Runtime helpers: sync facade, long-lived loop, per-loop values, the current context and ids. - `LoopLocal(factory: Callable[[], T], *, close: Callable[[T], Awaitable[None]] | None = None) -> None`: Lazily creates one `factory()` value per running event loop. - `LoopRunner(*, name: str = 'agent_adapter-loop') -> None`: Runs async work from sync code on one event loop that lives as long as the process. - `current_context() -> ToolContext | None`: The context set by the innermost `use_context`, or None. - `new_id(prefix: str = '') -> str`: `prefix` + a random uuid4 hex. - `run_sync`: see `agent_adapter` - `use_context(ctx: ToolContext) -> Iterator[ToolContext]`: Make `ctx` current inside the block; child tasks read it, writes never flow back up. ### `agent_adapter.sandbox` Sandboxed code execution: `SandboxPort`, Docker, callable and dev-only local backends. - `CODE_FILES`: `dict` instance - `CODE_KIND = 'code'` - `CallableSandbox(fn: RunFn, *, name: str = '', languages: Iterable[str] = ('python',), network: bool = False, limits: Limits | None = None, grace_s: float = 30.0, capture_content: bool = False) -> None`: Wraps `fn(request)`; the service does the isolating, this class checks and bounds. - `DockerSandbox(image: str = 'python:3.12-slim', *, cpus: float = 1.0, memory_mb: int = 512, pids: int = 128, network: bool = False, user: str = '65534:65534', runtime: str | None = None, workdir_mb: int = 64, tmp_mb: int = 16, python: str = 'python3', docker: str = 'docker', languages: Iterable[str] = ('python',), name: str = '', limits: Limits | None = None, grace_s: float = 30.0, capture_content: bool = False) -> None`: Runs code in a throwaway container: no network, read-only root, no capabilities. - `DockerStatus(available: bool, client_version: str = '', server_version: str = '', reason: str = '') -> None`: Whether Docker can run Linux containers here, with the versions found. - `GuardedSandbox(*, name: str = '', languages: Iterable[str] = ('python',), network: bool = False, limits: Limits | None = None, grace_s: float = 30.0, capture_content: bool = False) -> None`: A `SandboxPort` that checks every request and re-applies its limits to every result. - `InputFiles = Mapping[str, bytes | str]` - `Limits(max_files: int = 16, max_file_bytes: int = 1000000) -> None`: Bounds on what a backend returns: produced files and their bytes. - `LocalSubprocessSandbox(*, allow_unsafe_local: bool = False, python: str = ..., name: str = '', limits: Limits | None = None, grace_s: float = 10.0, capture_content: bool = False) -> None`: DEVELOPMENT ONLY: Python in a temporary folder with a time limit and output caps. - `OnResult = Callable[[SandboxResult, ToolContext], Any]` - `RunFn = Callable[[SandboxRequest], Any]` - `SandboxFile(*, name: str, data: bytes, size: int) -> None`: A file the code left in its work directory; `data` may be cut (see `size`). - `SandboxPort` (protocol: run): Runs untrusted code in isolation, within a time limit and an output limit. - `SandboxRequest(code: str, language: str, timeout_s: float, max_output_bytes: int, network: bool, files: Mapping[str, bytes] = , env: Mapping[str, str] = , limits: Limits = Limits(max_files=16, max_file_bytes=1000000)) -> None`: A checked run: what a backend's `_run` receives. - `SandboxResult(*, exit_code: int, stdout: str = '', stderr: str = '', stdout_truncated: bool = False, stderr_truncated: bool = False, timed_out: bool = False, duration_ms: float = 0.0, files: tuple[SandboxFile, ...] = (), files_truncated: bool = False, backend: str = '') -> None`: What one run produced; output and files are bounded by the run's limits. - `code_tool(sandbox: SandboxPort, *, name: str = 'run_code', description: str | None = None, languages: Iterable[str] = ('python',), timeout_s: float = 30.0, max_output_bytes: int = 64000, network: bool = False, env: Mapping[str, str] | None = None, files: InputFiles | None = None, on_result: OnResult | None = None, tags: Iterable[str] = ()) -> ToolSpec`: A tool (kind "code") that runs the model's code in `sandbox`; every run needs approval. - `docker_status(docker: str = 'docker', *, timeout_s: float = 10.0) -> DockerStatus`: Probe the `docker` CLI and its daemon; never raises. ### `agent_adapter.server` HTTP tool server: the registry over REST + SSE + OpenAPI, with MCP mounted beside it. - `APPROVER_SCOPE = 'approvals:decide'` - `Authenticator = Callable[[RequestInfo], Awaitable[Principal | None]]` - `Authorizer = Callable[[Principal, ToolSpec, Mapping[str, Any] | None], Awaitable[bool]]` - `Claim(state: ClaimState, token: str = '', fingerprint: str = '', stored: Stored | None = None) -> None`: `new` (run it; `token` completes or releases the lease), `in_progress` or `done`. - `DecisionAuthorizer = Callable[[Principal, ApprovalRecord], Awaitable[bool]]` - `EventStream` (protocol: subscribe): An event sink that replays and follows the events of one run. - `IdempotencyStore` (protocol: claim, complete, release, update): Atomic per-key leases and replay entries shared by every worker of a service. - `MemoryEventStream(*, max_events_per_run: int = 1000, max_runs: int = 1000) -> None`: In-memory `EventStream`: the last `max_events_per_run` events of the last `max_runs` runs. - `MemoryIdempotencyStore(*, max_entries: int = 10000, clock: Callable[[], float] = monotonic) -> None`: In-process `IdempotencyStore` (one worker); oldest replay entries go first past `max_entries`, leases are never evicted. - `PROBLEM_MEDIA_TYPE = 'application/problem+json'` - `Principal(id: str, scopes: frozenset[str] = frozenset(), claims: Mapping[str, Any] = ) -> None`: An authenticated caller; `id` scopes every run, call and approval it touches. - `Problem(*, type: str, title: str, status: int, detail: str = '', **extra_data: Any) -> None`: An RFC 9457 problem; extensions (`tool`, `errors`, `approval_id`, ...) are extra fields. - `Readiness(*, needs_lifespan: bool) -> None`: The `/readyz` state of one `create_app` app, found at `app.state.readiness`. - `RedisIdempotencyStore(client_factory: Callable[[], Any], *, prefix: str = '') -> None`: `IdempotencyStore` on Redis (`agent-adapter[redis]`), shared by every worker. - `RequestInfo(method: str, path: str, headers: Mapping[str, str], query: Mapping[str, str], client: str | None) -> None`: A framework-neutral view of one HTTP request (header names in any case). - `ServiceResponse(status: int, body: Any = None, media_type: str = 'application/json', headers: Mapping[str, str] = , stream: AsyncIterator[bytes] | None = None) -> None`: A framework-neutral response: JSON `body`, or an SSE `stream` of bytes. - `Stored(fingerprint: str, status: int, body: Any, media_type: str = 'application/json', headers: Mapping[str, str] = ) -> None`: A response kept for replay, with the fingerprint of the request that produced it. - `ToolService(registry: Registry, *, executor: Executor | None = None, approvals: ApprovalStore | None = None, events: EventStream | None = None, ledger: LedgerPort | None = None, idempotency: IdempotencyStore | CachePort | None = None, authenticate: Authenticator | None = None, authorize: Authorizer | None = None, authorize_decision: DecisionAuthorizer | None = None, allow_anonymous: bool = False, idempotency_ttl: float = 86400, title: str = 'agent-adapter tools') -> None`: The HTTP tool server's behaviour behind `handle(request, body)`. - `bearer_auth(tokens: Mapping[str, Principal] | TokenVerifier) -> Authenticator`: An authenticator for `Authorization: Bearer `. - `create_app(registry: Registry, *, mcp: bool = True, mcp_path: str = '/mcp', prefix: str = '', **service_options: Any) -> Any`: A Starlette app for `ToolService(registry, **service_options)`; MCP at `prefix + mcp_path`. - `create_wsgi_app(registry: Registry, **options: Any) -> WsgiApp`: `a2wsgi.ASGIMiddleware(create_app(registry, **options))` with the app's lifespan running. - `is_invoker(principal: Principal, record: ApprovalRecord) -> bool`: True when `principal` made the call `record` waits on (from the record's metadata). - `openapi_document(registry: Registry, *, title: str, version: str, server_url: str | None = None, dialect: Dialect = '3.1') -> dict[str, Any]`: OpenAPI for the tools of `registry` exported to `http` and every server route. - `problem_for(exc: BaseException) -> Problem`: The problem for an exception: `detail` is `ErrorInfo.message` (the class name only for exceptions that are not `AdapterError`), plus `tool`, `errors`, `approval_id`. - `serve(app: Any, *, host: str = '127.0.0.1', port: int = 8000) -> None`: Run `app` with uvicorn (blocking); a `create_app` app learns its real bind host. - `set_log_key(key: bytes) -> None`: Share the `principal_ref` key between workers so their logs correlate (>= 16 bytes). ### `agent_adapter.tracing` Tracing: the event catalogue, event sinks and the OpenTelemetry sink. - `CallbackEventSink(fn: Callable[[Event], None]) -> None`: Calls `fn(event)`; a failing callback is logged, never raised. - `EventLog(*, max_events: int = 10000) -> None`: Keeps the latest `max_events` events in memory, oldest dropped first. - `EventSink` (protocol: emit): Receives trace events; `emit` is sync, non-blocking and never raises. - `EventType()`: Event type strings; anything else must start with `custom.`. - `FanoutSink(*sinks: EventSink) -> None`: Sends each event to every sink in order; a failing sink does not stop the others. - `LoggingSink(logger: Logger | None = None, *, level: int = 10, capture_content: bool = False) -> None`: Logs each event at `level`; content keys only with `capture_content` and at DEBUG. - `OTelSink(*, tracer_provider: Any | None = None, meter_provider: Any | None = None, capture_content: bool = False, instrumentation_name: str = 'agent_adapter') -> None`: An `EventSink` exporting GenAI-convention spans and metrics (needs opentelemetry-api). - `REQUIRED_DATA`: `dict` instance - `SinkHub(base: EventSink | None = None, extra: tuple[EventSink, ...] = ()) -> None`: An executor's stable sink: `base` (the constructor's sink), then every added sink. - `emit(sink: EventSink | None, event: Event) -> None`: Send `event` to `sink`; a no-op for None. - `make_event(type: str, ctx: ToolContext | None = None, *, call_id: str | None = None, **data: Any) -> Event`: An event with ids from `ctx`; an explicit `call_id` makes `ctx.call_id` the parent. ### `agent_adapter.viewer` Run viewer: record runs to JSONL files, browse them and chat locally (`agent-adapter ui`). - `ApprovalDesk(path: str | PathLike[str], reviewer: str) -> None`: Decides tool approvals in a `SQLiteApprovalStore` file; it never runs a tool. - `Attachment`: see `agent_adapter.chat` - `ChatAgent`: see `agent_adapter.chat` - `ChatMessage`: see `agent_adapter.chat` - `ChatSession`: see `agent_adapter.chat` - `DEFAULT_FOLDER = '.agent_adapter/runs'` - `LOOPBACK = '127.0.0.1'` - `Recorder(folder: str | PathLike[str] = '.agent_adapter/runs', *, capture_content: bool = False, capture_confirm_content: bool = False, keep_runs: int = 200, max_events_per_run: int = 50000, max_queue: int = 10000, registry: RegistrySource = None, source: str = '', policy: PolicySource = None) -> None`: Writes events under `folder`, one segment file per run and process (append-only). - `RunFolder(target: str | Path) -> None`: A runs folder, or a single run file, read again on each `refresh` from where it stopped. - `ViewerServer(folder: RunFolder, desk: ApprovalDesk | None, port: int, chat: ChatHost | None = None, evals: EvalFolder | None = None, cases_dir: Path | None = None) -> None`: A ThreadingHTTPServer holding the runs folder, the approval desk and its secrets. - `assign_lanes(spans: Iterable[dict[str, Any]]) -> int`: Give each span the lowest free lane at its start (greedy interval colouring). - `build_spans(events: Sequence[Mapping[str, Any]]) -> list[dict[str, Any]]`: One span per call (tool, LLM, node, flow, compression), in start order, with lanes. - `check_host(host: str) -> None`: Refuse any bind address but the IPv4 loopback. - `export(target: str | Path, out: str | Path, *, run: str | None = None) -> Path`: Write the runs under `target` (or only `run`) into the HTML file `out`; return its path. - `import_otlp(source: str | Path, folder: str | Path) -> list[str]`: Write one run file per trace of the OTLP JSON file `source` into `folder`; run keys back. - `layout(graph: Mapping[str, Any], *, depth: int = 0) -> dict[str, Any]`: A copy of `graph`: nodes get `layer`, `order` and `x`; edges get `via` (bend x per layer). - `make_server(target: str | Path, *, host: str = '127.0.0.1', port: int = 8780, approvals: str | Path | None = None, reviewer: str = '', chat: ChatHost | None = None, evals: str | Path = '.agent_adapter/evals', cases_dir: str | Path = '.agent_adapter/chats') -> ViewerServer`: A bound, not yet serving, viewer server (`port=0` picks a free port); `chat` turns chat on. - `record(executor: Executor | None = None, *, folder: str | PathLike[str] = '.agent_adapter/runs', capture_content: bool = False, keep_runs: int = 200, source: str = '', capture_confirm_content: bool = False) -> Recorder`: Record every run of `executor` under `folder`. - `serve(agent: ChatAgent | None = None, *, model: LLMPort | None = None, registry: Registry | None = None, executor: Executor | None = None, folder: str | PathLike[str] = '.agent_adapter/runs', port: int = 8780, open_browser: bool = True, system: str = '', max_steps: int = 8, turn_timeout: float = 300.0, approval_timeout: float = 600.0, reviewer: str = 'local', chat_dir: str | PathLike[str] = '.agent_adapter/chats', stream: bool = True, evals: str | PathLike[str] = '.agent_adapter/evals', user_id: str = '', memory: GuardedMemory | None = None, memory_save: str = 'none', memory_every: int = 1, recall_k: int = 5, out: TextIO | None = None) -> None`: Chat with `agent` (or the built-in loop over `model` and `registry`) in the viewer. ### `agent_adapter.integrations.a2a` A2A: serve registry tools as an A2A agent, and import a remote agent's skills as tools. - `A2AConnection(card: AgentCard, client: Client, http: Any) -> None`: An open A2A client, the agent card it was built from, and its HTTP client. - `A2AIntegration()`: Exports one `AgentSkill` per tool; `a2a_app` / `a2a_executor` run the calls. - `CARD_PATH = '/.well-known/agent-card.json'` - `Caller(id: str, scopes: frozenset[str] = ) -> None`: A minimal principal: `id` scopes tasks, runs and approvals (`""` is anonymous). - `HostRefused` (error): An outbound A2A request to a host the guard does not allow. - `INTEGRATION`: `A2AIntegration` instance - `PRINCIPAL_STATE = 'aa.a2a.principal'` - `ResponseTooLarge` (error): A remote A2A response over the byte limit. - `TOOLS_EXTENSION = 'urn:agent-adapter:a2a:tools:v1'` - `ToolAgent(registry: Registry, executor: ToolRunner | None = None, *, client_approval: bool = False, authorize: Authorizer | None = None, max_data_bytes: int = 262144) -> None`: Reads a `{tool, arguments}` data part, runs it, and reports through `TaskUpdater`. - `a2a_app(registry: Registry, *, url: str, executor: ToolRunner | None = None, authenticate: Authenticator | None = None, authorize: Authorizer | None = None, allow_anonymous: bool = False, host: str = '127.0.0.1', allowed_hosts: Iterable[str] | None = None, public_card: bool = False, client_approval: bool = False, name: str = 'agent-adapter', description: str = 'Tools served by agent-adapter.', version: str = '1.1.0', max_body_bytes: int = 1048576, max_data_bytes: int = 262144, task_store: Any = None) -> Any`: A Starlette app: the agent card at `CARD_PATH`, JSON-RPC A2A at `/`. - `a2a_executor(registry: Registry, *, executor: ToolRunner | None = None, client_approval: bool = False, authorize: Authorizer | None = None, max_data_bytes: int = 262144) -> AgentExecutor`: An `a2a-sdk` `AgentExecutor` running `registry.for_target("a2a")` tools. - `agent_card(registry: Registry, *, url: str, name: str = 'agent-adapter', description: str = 'Tools served by agent-adapter.', version: str = '1.1.0') -> AgentCard`: An AgentCard for `registry.for_target("a2a")`, served at `url` over JSON-RPC. - `agent_skill(spec: ToolSpec) -> AgentSkill`: The skill for one tool: id and name are the tool name. - `bounded_task_store(*, ttl: float = 86400.0, max_tasks: int = 10000, max_per_owner: int = 1000, clock: Callable[[], float] = monotonic) -> Any`: An SDK `TaskStore` in memory: tasks expire `ttl` s after their last update. - `async call_remote(connection: A2AConnection, skill_id: str, parts: list[Any], *, timeout_s: float = 300.0, poll_interval_s: float = 1.0) -> Any`: Send one message, poll its task to a final or interrupted state, return its result. - `async connect_a2a(url: str, *, headers: Mapping[str, str] | None = None, auth: Any = None, allow_private: bool = False, trusted_hosts: Iterable[str] = (), allow_cross_origin: bool = False, timeout_s: float = 30.0, max_response_bytes: int = 4194304, http_transport: Any = None) -> A2AConnection`: Fetch the agent card at `url` + `CARD_PATH` and open a non-streaming A2A client. - `async import_a2a(registry: Registry, connection: A2AConnection, *, prefix: str = '', names: Iterable[str] | None = None, task_timeout_s: float = 300.0, poll_interval_s: float = 1.0, **policy: Any) -> list[ToolSpec]`: Register one tool per remote skill (all, or the skill ids in `names`). - `static_bearer(tokens: Mapping[str, str]) -> Authenticator`: An authenticator for `Authorization: Bearer ` over a `{token: caller id}` table. ### `agent_adapter.integrations.adk` Google ADK 2.x: tools as `BaseTool`s with a confirmation bridge, flows as a `Workflow`. - `AdkIntegration()`: Exports `BaseTool`s and compiles flows to ADK `Workflow`s. - `INTEGRATION`: `AdkIntegration` instance - `TARGET = 'adk'` - `adk_tool(spec: ToolSpec, *, executor: ToolRunner | None = None) -> Any`: An ADK `BaseTool` running `spec` through the executor; `confirm` gates via ADK. - `adk_tools(registry: Registry, *, executor: ToolRunner | None = None) -> list[Any]`: An ADK `BaseTool` for every registry tool exported to `adk`. - `bind_context(tool_context: Any) -> ToolContext`: Our context for an ADK `ToolContext` (run = invocation id, call = function call id). - `compile_flow(flow: Flow[Any], *, executor: Executor | None = None, name: str | None = None, max_steps: int = 100, model: LLMPort | None = None) -> Any`: `flow` as an ADK `Workflow` (`START -> aa_driver`); run it with `run_workflow`. - `async resolve_confirmations(events: Iterable[Any], port: ApprovalPort, registry: Registry) -> Any`: The user `Content` answering every open tool confirmation in `events`, from `port`. - `resume_message(interrupt_id: str, decision: ApprovalDecision) -> Any`: The user `types.Content` answering one interrupt with `decision`. - `async run_workflow(workflow: Any, state: Any, *, session_service: Any = None, user_id: str = 'user') -> FlowResult[Any]`: Run a `compile_flow` Workflow from `state`, or continue it with a `resume_message`. ### `agent_adapter.integrations.ag2` AG2 (the `ag2` package): each ToolSpec as a `FunctionTool`; approval as tool middleware. - `AG2Integration()`: Exports `FunctionTool`s; `bind_approval` gives no gate (use `ag2_tool(approval=)`). - `INTEGRATION`: `AG2Integration` instance - `ag2_compress_policy(policy: CompressionPolicy, *, pinned: Pinned | None = None, model_max_tokens: int | None = None) -> CompressAssemblyPolicy`: An assembly policy for `Agent(..., assembly=[ag2_compress_policy(policy)])`. - `ag2_tool(spec: ToolSpec, *, executor: ToolRunner | None = None, approval: ApprovalPort | None = None, middleware: Iterable[Any] = ()) -> Any`: An AG2 `FunctionTool` for `spec`. - `ag2_tools(registry: Registry, *, executor: ToolRunner | None = None, approval: ApprovalPort | None = None, middleware: Iterable[Any] = ()) -> list[Any]`: AG2 `FunctionTool`s for every registry tool exported to this target. ### `agent_adapter.integrations.ag_ui` AG-UI 1.0: stream runs as AG-UI events; approvals become interrupts, resumes ask the port. - `AGUIBridge(executor: Executor, *, port: ApprovalPort | None = None, interrupts: InterruptTable | None = None, max_events: int = 10000, trace: bool = True) -> None`: Streams AG-UI runs of agent functions over one executor; holds the open interrupts. - `AGUIInputInvalid` (error): A `RunAgentInput` that is not valid JSON, fails the schema, or exceeds a bound. - `AGUIIntegration()`: Describes registry tools as AG-UI `Tool`s; runs and approvals go through `AGUIBridge`. - `AGUIRun(run_input: RunAgentInput, *, executor: Executor, principal: str = '', max_events: int = 10000, trace: bool = True) -> None`: One AG-UI run: its input, an executor and context bound to its event stream. - `Agent = Callable[[AGUIRun], Awaitable[Any]]` - `Authenticate = Callable[[Any], Awaitable[str | None]]` - `INTEGRATION`: `AGUIIntegration` instance - `InterruptTable(*, ttl: float = 900.0, max_interrupts: int = 1024, max_per_principal: int = 64, clock: Callable[[], float] = monotonic, wall: Callable[[], float] = time) -> None`: The bridge's open interrupts, each answerable once, within its TTL, by its owner. - `PendingCall(interrupt_id: str, thread_id: str, principal: str, run_id: str, call_id: str, tool: str, arguments: Mapping[str, Any] | None = None, approval_id: str = '', expires_at: float = 0.0) -> None`: One paused tool call, bound to its thread, the principal who started it and our run. - `TARGET = 'ag_ui'` - `agui_endpoint(bridge: AGUIBridge, agent: Agent, *, authenticate: Authenticate, max_body_bytes: int = 1048576, allowed_hosts: Iterable[str] | None = None) -> Callable[[Any], Awaitable[Any]]`: A Starlette endpoint: authenticate, read a bounded JSON body, stream the run as SSE. - `async anonymous(request: Any) -> str`: An `Authenticate` that lets everyone in as one anonymous principal (local use only). - `parse_input(body: bytes | str | Mapping[str, Any] | RunAgentInput, *, max_bytes: int = 1048576) -> RunAgentInput`: A validated, bounded `RunAgentInput` from a request body or a parsed mapping. - `to_messages(run_input: RunAgentInput) -> list[Message]`: The input's conversation as adapter `Message`s; text parts only, other roles skipped. ### `agent_adapter.integrations.agent_framework` Microsoft Agent Framework: ToolSpecs as `FunctionTool`s, approval, a compression format. - `AgentFrameworkIntegration()`: Exports `FunctionTool`s; MAF's function loop pauses `confirm` tools for approval. - `INTEGRATION`: `AgentFrameworkIntegration` instance - `agent_framework_tool(spec: ToolSpec, *, executor: ToolRunner | None = None) -> Any`: A `FunctionTool` for `spec`; any `confirm` rule sets `approval_mode="always_require"`. - `agent_framework_tools(registry: Registry, *, executor: ToolRunner | None = None) -> list[Any]`: `FunctionTool`s for every registry tool exported to this target. - `async resolve_approvals(response: Any, port: ApprovalPort, registry: Registry, *, session: Any = None) -> list[Any]`: Answer each `function_approval_request` of `response` from `port`, in order. ### `agent_adapter.integrations.agno` Agno: each ToolSpec as a `Function`; approval through `requires_confirmation`. - `AgnoIntegration()`: Exports `Function`s; an Agno agent pauses `confirm` tools until they are resolved. - `INTEGRATION`: `AgnoIntegration` instance - `agno_compressed_model(model: Any, policy: CompressionPolicy, *, pinned: Pinned | None = None, model_max_tokens: int | None = None) -> Any`: A copy of an Agno model that compresses the messages of each provider call. - `agno_function(spec: ToolSpec, *, executor: ToolRunner | None = None) -> Any`: An Agno `Function` for `spec`; any `confirm` rule sets `requires_confirmation`. - `agno_functions(registry: Registry, *, executor: ToolRunner | None = None) -> list[Any]`: Agno `Function`s for every registry tool exported to this target. - `async resolve_requirements(output: Any, port: ApprovalPort, registry: Registry) -> None`: Confirm or reject each confirmation `output` paused on, from `port`. ### `agent_adapter.integrations.anthropic` Render tools for the Anthropic Messages API as plain dicts. - `AnthropicIntegration()`: Exports Messages API tool dicts; the host runs the model's `tool_use` blocks. - `INTEGRATION`: `AnthropicIntegration` instance - `anthropic_tool(spec: ToolSpec) -> dict[str, Any]`: One tool in Anthropic Messages format. - `anthropic_tools(registry: Registry) -> list[dict[str, Any]]`: Every registry tool exported to Anthropic, in Messages format. - `tool_result_blocks(results: Sequence[ToolResult]) -> list[dict[str, Any]]`: `tool_result` content blocks answering the model's `tool_use` blocks, in order. ### `agent_adapter.integrations.autogen` AutoGen 0.7: registry tools as `BaseTool` subclasses that run through the executor. - `AutoGenCall(call_id: str, cancellation_token: Any, tool: Any = None) -> None`: One AutoGen tool call, as `ctx.extras["autogen"]` holds it. - `AutoGenIntegration()`: Exports `BaseTool`s; AutoGen has no per-tool approval gate, so the executor's port decides. - `INTEGRATION`: `AutoGenIntegration` instance - `autogen_model_context(policy: CompressionPolicy, *, initial_messages: Sequence[Any] | None = None, pinned: Pinned | None = None, model_max_tokens: int | None = None) -> Any`: A `ChatCompletionContext` that keeps every message and returns them compressed. - `autogen_tool(spec: ToolSpec, *, executor: ToolRunner | None = None) -> Any`: One `autogen_core.tools.BaseTool` for `spec`. - `autogen_tools(registry: Registry, *, executor: ToolRunner | None = None) -> list[Any]`: A `BaseTool` for every registry tool exported to AutoGen. ### `agent_adapter.integrations.base` The integration protocol: what every framework bridge provides, and a helper base class. - `FlowCompiler` (protocol: compile_flow): An integration that also compiles flows; `flow` is a `FlowSpec` (typed Any here). - `Integration` (protocol: bind_approval, bind_context, export_tool, extra, name, requires): A framework bridge; `name` is the canonical target name and the entry-point name. - `IntegrationBase()`: Helper base for built-in and third-party integrations; subclasses add `export_tool`. - `MODEL_VISIBLE_ERRORS = ...` - `ToolRunner` (protocol: run_spec): Runs one tool call through the full policy pipeline (an `Executor` is one). - `model_visible_error(exc: BaseException) -> dict[str, Any]`: `{"error": {"kind", "message"}}` for a tool result; foreign errors show only their type. ### `agent_adapter.integrations.camel` CAMEL-AI: ToolSpecs as `FunctionTool`s (the executor's port approves), a compression format. - `CamelIntegration()`: Exports `FunctionTool`s; `confirm` tools are decided by the executor's port. - `INTEGRATION`: `CamelIntegration` instance - `camel_tool(spec: ToolSpec, *, executor: ToolRunner | None = None, strict: bool = True) -> Any`: A CAMEL `FunctionTool` for `spec`, with the OpenAI-transformed schema. - `camel_tools(registry: Registry, *, executor: ToolRunner | None = None, strict: bool = True) -> list[Any]`: CAMEL `FunctionTool`s for every registry tool exported to this target. - `openai_tool_schema(spec: ToolSpec, *, strict: bool = True) -> dict[str, Any]`: The Chat Completions tool schema CAMEL sends to the model for `spec`. ### `agent_adapter.integrations.claude_agent` Claude Agent SDK: tools on an in-process SDK MCP server; approval through `can_use_tool`. - `ClaudeAgentIntegration()`: Exports SDK MCP tools; a bridge's `can_use_tool` asks an approval port for Claude. - `ClaudeSdkBridge(name: str, server: dict[str, Any], can_use_tool: CanUseTool, run_id: str, hooks: dict[str, list[Any]]) -> None`: An SDK MCP server, the `can_use_tool` gate its tools trust, and the gate's hooks. - `INTEGRATION`: `ClaudeAgentIntegration` instance - `can_use_tool(port: ApprovalPort, registry: Registry, *, server: str = 'agent_adapter') -> CanUseTool`: A permission-only `can_use_tool`: it grants no tokens, so a `confirm` tool is asked again by the executor's port when it runs (use `claude_sdk_bridge` to ask once). - `claude_sdk_bridge(registry: Registry, port: ApprovalPort, *, executor: ToolRunner | None = None, name: str = 'agent_adapter') -> ClaudeSdkBridge`: A server named `name` with its gate, for one client session: `ClaudeAgentOptions( mcp_servers={bridge.name: bridge.server}, can_use_tool=bridge.can_use_tool, hooks=bridge.hooks)`. - `claude_sdk_server(registry: Registry, *, executor: ToolRunner | None = None, name: str = 'agent_adapter') -> dict[str, Any]`: An SDK MCP server config without a gate (see `claude_sdk_bridge` for approvals). - `claude_sdk_tool(spec: ToolSpec, *, executor: ToolRunner | None = None) -> Any`: An `SdkMcpTool` for `spec`; a `confirm` tool is approved by the executor's own port. - `claude_sdk_tools(registry: Registry, *, executor: ToolRunner | None = None) -> list[Any]`: `SdkMcpTool`s for every registry tool exported to this target. ### `agent_adapter.integrations.crewai` CrewAI: registry tools as `BaseTool`s; `_run` bridges to the executor with `run_sync`. - `CrewAIIntegration()`: Exports `BaseTool`s; CrewAI has no call id at the tool boundary. - `INTEGRATION`: `CrewAIIntegration` instance - `crewai_tool(spec: ToolSpec, *, executor: ToolRunner | None = None) -> BaseTool`: A `BaseTool` with `args_schema=spec.args_model`: `_arun` is async, `_run` uses `run_sync`. - `crewai_tools(registry: Registry, *, executor: ToolRunner | None = None) -> list[BaseTool]`: `crewai_tool` for every registry tool exported to `crewai`. ### `agent_adapter.integrations.dbos` DBOS: a `Flow` as a `@DBOS.workflow`, one superstep per `@DBOS.step`; approvals from your store. - `ADVANCE = 'agent_adapter.flow.advance'` - `ApprovalNotice(*, approval_id: str) -> None`: A wake-up for a paused run: re-check the approval store for this approval's decision. - `DbosFlows(flows: DurableFlows, *, max_attempts: int = 5, interval_s: float = 1.0) -> None`: The DBOS workflow and steps for `flows`; build it once per process, before `DBOS.launch()`. - `DurableFlows(runner: FlowRunner, flows: Sequence[Flow[Any]]) -> None`: Runs registered flows one superstep per call, for a durable engine's activities or steps. - `FlowStepReport(*, run_id: str, flow: str, status: Literal['running', 'paused', 'done', 'denied', 'failed'], steps: int = 0, state: dict[str, Any] | None = None, pending: PendingApproval | None = None, denied_at: str | None = None) -> None`: Where a durable run stands after one step; `state` is set once it is done or denied. - `NOTIFY_TOPIC = 'agent_adapter.notify'` - `PENDING_EVENT = 'agent_adapter.pending'` - `PendingApproval(*, approval_id: str, kind: Literal['tool', 'node'], tool: str, node: str = '', hint: str = '') -> None`: What a paused run waits for; the tool arguments stay out of engine history. - `RESUME = 'agent_adapter.flow.resume'` - `RunIdentity(*, user_id: str = '', thread_id: str = '', principal: str = '') -> None`: Who a durable run acts for; sealed into its checkpoints, so every step must match. - `StepRetry` (error): A step failed on something passing (a store or network blip): DBOS runs it again. - `WORKFLOW = 'agent_adapter.flow'` ### `agent_adapter.integrations.discovery` Find integrations through the `agent_adapter.integrations` entry-point group. - `GROUP = 'agent_adapter.integrations'` - `available_integrations(*, refresh: bool = False) -> dict[str, Integration]`: Every integration that loads and conforms, by name; cached until `refresh=True`. - `get_integration(name: str) -> Integration`: The integration registered as `name`, with its required modules importable. ### `agent_adapter.integrations.dspy` DSPy: registry tools as `dspy.Tool`s that run through the executor. - `DSPyIntegration()`: Exports `dspy.Tool`s; DSPy has no approval gate, so the executor's port decides. - `INTEGRATION`: `DSPyIntegration` instance - `dspy_tool(spec: ToolSpec, *, executor: ToolRunner | None = None) -> Any`: One `dspy.Tool` for `spec`; `acall` is native, sync `__call__` goes through `run_sync`. - `dspy_tools(registry: Registry, *, executor: ToolRunner | None = None) -> list[Any]`: A `dspy.Tool` for every registry tool exported to DSPy. ### `agent_adapter.integrations.gemini` Gemini: function declarations, genai automatic-function-calling proxies, call dispatch. - `GeminiIntegration()`: Exports function declaration dicts; the host runs the calls (`run_function_calls`). - `INTEGRATION`: `GeminiIntegration` instance - `afc_callable(spec: ToolSpec, *, executor: ToolRunner | None = None) -> Callable[..., Awaitable[Any]]`: An async function for genai's `GenerateContentConfig(tools=[...])` that runs `spec`. - `afc_callables(registry: Registry, *, executor: ToolRunner | None = None) -> list[Callable[..., Awaitable[Any]]]`: `afc_callable` for every registry tool exported to Gemini. - `function_declaration(spec: ToolSpec) -> dict[str, Any]`: One tool as a Gemini function declaration dict. - `function_declarations(registry: Registry) -> list[dict[str, Any]]`: Every registry tool exported to Gemini, as function declaration dicts. - `genai_tool(registry: Registry) -> Any`: A `google.genai.types.Tool` holding every registry tool exported to Gemini. - `async run_function_calls(calls: Iterable[Any], *, executor: ToolRunner | None = None, ctx: ToolContext | None = None, registry: Registry | None = None) -> list[Any]`: Run the model's function calls; one function-response `types.Part` per call, in order. ### `agent_adapter.integrations.haystack` Haystack: each ToolSpec as a `Tool`; approval through a `before_tool` confirmation hook. - `HaystackIntegration()`: Exports Haystack `Tool`s; `confirmation_hook` asks an approval port before calls. - `INTEGRATION`: `HaystackIntegration` instance - `confirmation_hook(port: ApprovalPort, registry: Registry) -> Any`: A `before_tool` Agent hook: asks `port` about `registry` tools whose rule says so. - `haystack_tool(spec: ToolSpec, *, executor: ToolRunner | None = None) -> Any`: A Haystack `Tool` for `spec` with both `function` (via `run_sync`) and `async_function`. - `haystack_tools(registry: Registry, *, executor: ToolRunner | None = None) -> list[Any]`: Haystack `Tool`s for every registry tool exported to this target. ### `agent_adapter.integrations.langchain` LangChain: registry tools as `StructuredTool`s, and LangChain tools imported as `ToolSpec`s. - `INTEGRATION`: `LangChainIntegration` instance - `LangChainIntegration()`: Exports `StructuredTool`s; the executor's approval port decides `confirm` tools. - `import_langchain(registry: Registry, tools: Iterable[Any], *, prefix: str = '', **policy: Any) -> list[ToolSpec]`: Add LangChain `BaseTool`s to `registry` (all or nothing) and return their specs. - `structured_tool(spec: ToolSpec, *, executor: ToolRunner | None = None) -> Any`: `spec` as a LangChain `StructuredTool` that runs through the executor. - `structured_tools(registry: Registry, *, executor: ToolRunner | None = None) -> list[Any]`: `structured_tool` for every registry tool exported to LangChain. ### `agent_adapter.integrations.langgraph` LangGraph: tool nodes, `Send` tasks, reducers, interrupt approvals, flows, compression. - `INTEGRATION`: `LangGraphIntegration` instance - `LangGraphApproval(key: bytes | None = None) -> None`: An ApprovalPort that pauses the graph with `interrupt()`; a signed resume value decides. - `LangGraphCompressor(policy: CompressionPolicy, *, pinned: Pinned | None = None, model_max_tokens: int | None = None) -> None`: Compresses LangChain messages before your node calls its model; the state is untouched. - `LangGraphIntegration()`: Exports tool nodes and compiles flows; approvals pause the graph with `interrupt()`. - `ToolNode(specs: Mapping[str, ToolSpec], runner: ToolRunner, messages_key: str, concurrency: int | None) -> None`: Runs tool calls as ToolMessages; `tools` are the definitions to bind to a model. - `ToolTask(spec: ToolSpec, runner: ToolRunner, into: str) -> None`: A `Send` target: runs one tool with the `Send` argument and writes `{into: value}`. - `compile_flow(flow: Flow[Any], *, executor: Executor | None = None, checkpoint_key: bytes | None = None) -> Any`: `flow` as a LangGraph `StateGraph`; compile it with a checkpointer to pause and resume. - `fan_out(target: str, over: str | Callable[[Any], Iterable[Mapping[str, Any]]]) -> Callable[[Any], list[Any]]`: A conditional-edge path sending each item (a mapping) of `over` to node `target`. - `langgraph_compressor(policy: CompressionPolicy, *, pinned: Pinned | None = None, model_max_tokens: int | None = None) -> LangGraphCompressor`: A compressor for `state["messages"]`: `await c(messages)` or `c.sync(messages)`. - `one_use(node: Callable[..., Any]) -> Callable[..., Any]`: Your own LangGraph node, run as one task execution: each approval answer is used once. - `reducer(rule: MergeRule) -> Any`: A LangGraph channel applying `rule` to all writes of a superstep: `Annotated[T, reducer(r)]`. - `async run_compiled(app: Any, state: Any, config: Mapping[str, Any]) -> FlowResult[Any]`: Run a graph compiled from `compile_flow` until it is done, paused or denied. - `tool_node(registry: Registry, *, executor: ToolRunner | None = None, messages_key: str = 'messages', concurrency: int | None = None) -> ToolNode`: A LangGraph node that runs the last AI message's tool calls through the executor. - `tool_task(spec: ToolSpec, *, executor: ToolRunner | None = None, into: str) -> ToolTask`: A node for `Send(node, args)`: runs `spec` with `args`, returns `{into: value}`. ### `agent_adapter.integrations.llamaindex` LlamaIndex: registry tools as `FunctionTool`s, and the `llamaindex` compression format. - `INTEGRATION`: `LlamaIndexIntegration` instance - `LlamaIndexIntegration()`: Exports `FunctionTool`s; LlamaIndex has no call id at the tool boundary. - `llamaindex_tool(spec: ToolSpec, *, executor: ToolRunner | None = None) -> FunctionTool`: A `FunctionTool` with `fn_schema=spec.args_model`; `acall` is async, `call` uses `run_sync`. - `llamaindex_tools(registry: Registry, *, executor: ToolRunner | None = None) -> list[FunctionTool]`: `llamaindex_tool` for every registry tool exported to `llamaindex`. ### `agent_adapter.integrations.mcp` MCP: serve registry tools to any MCP client, and import tools from MCP servers. - `APPROVAL_ID_META = 'agent-adapter/approval_id'` - `CALL_ID_META = 'agent-adapter/call_id'` - `ERROR_KIND_META = 'agent-adapter/error_kind'` - `INTEGRATION`: `MCPIntegration` instance - `MCPIntegration()`: Exports `mcp.types.Tool` definitions; `mcp_server` runs the calls. - `MemoryTaskStore(*, max_entries: int = 100000, clock: Callable[[], float] = monotonic) -> None`: In-process `TaskStore`: shared by the servers of one process only. - `MemoryTokenStorage() -> None`: Tokens and client registration for one server, in this process only; never logged. - `RUN_ID_META = 'agent-adapter/run_id'` - `RedisTaskStore(client_factory: Callable[[], Any], *, prefix: str = 'aa:mcp-task:') -> None`: `TaskStore` on Redis (`agent-adapter[redis]`), shared by every worker and host. - `TASKS_EXTENSION = 'io.modelcontextprotocol/tasks'` - `TASK_ID_META = 'agent-adapter/task_id'` - `TaskOptions(threshold_s: float | None = 5.0, ttl_s: float = 3600.0, poll_interval_s: float = 2.0, approval_url: str | None = None, max_tasks: int = 10000, max_per_principal: int = 100, store: TaskStore | None = None) -> None`: How `mcp_server(tasks=...)` answers 2026-07-28 clients that accept tasks. - `TaskStore` (protocol: delete, flag, flagged, get, put): Task states (written by the worker running the task) and signals (from any worker). - `client_metadata_document(client_metadata_url: str, *, redirect_uris: Sequence[str], client_name: str = 'agent-adapter', scope: str | None = None) -> dict[str, Any]`: The JSON to publish at `client_metadata_url`; its `client_id` is that URL. - `async import_mcp(registry: Registry, client: Client, *, prefix: str = '', names: Iterable[str] | None = None, send_ids: bool = False, **policy: Any) -> list[ToolSpec]`: Register the tools of an open `mcp.Client` (all of them, or the remote `names`). - `mcp_client(target: Any, *, headers: Mapping[str, str] | None = None, auth: Auth | None = None, tasks: bool = False, task_wait_s: float = 30.0) -> Client`: An unopened `mcp.Client` for an http(s) URL (Streamable HTTP), a `StdioServerParameters`, an in-process `Server` / `MCPServer`, or a client transport. - `mcp_server(registry: Registry, *, executor: ToolRunner | None = None, name: str = 'agent-adapter', version: str = '1.1.0', instructions: str | None = None, client_approval: bool = False, request_state_keys: Sequence[bytes | str] | None = None, principal: PrincipalHook | None = None, allow_anonymous: bool | None = None, approval_store: ApprovalStore | None = None, tasks: TaskOptions | bool = False) -> Server`: A lowlevel MCP `Server` listing and calling `registry.for_target("mcp")`. - `mcp_tool(spec: ToolSpec) -> Tool`: The MCP tool definition for `spec`: schemas, wrapped output schema, annotations. - `oauth_client(server_url: str, *, client_metadata_url: str, redirect_uri: str, redirect_handler: Callable[[str], Awaitable[None]], callback_handler: Callable[[], Awaitable[AuthorizationCodeResult]], storage: Any = None, client_name: str = 'agent-adapter', scope: str | None = None) -> OAuthClientProvider`: An `httpx2.Auth` for `mcp_client(server_url, auth=...)`: authorization code + PKCE. - `output_schema_for(spec: ToolSpec) -> dict[str, Any] | None`: `outputSchema`: the result schema, wrapped under `result` unless it is an object. - `streamable_http_app(registry: Registry, *, executor: ToolRunner | None = None, path: str = '/mcp', stateless: bool = True, json_response: bool = True, name: str = 'agent-adapter', version: str = '1.1.0', instructions: str | None = None, client_approval: bool = False, request_state_keys: Sequence[bytes | str] | None = None, principal: PrincipalHook | None = AUTHENTICATED, allow_anonymous: bool = False, approval_store: ApprovalStore | None = None, tasks: TaskOptions | bool = False, **http_options: Any) -> Any`: An ASGI (Starlette) app serving `mcp_server(registry, ...)` over Streamable HTTP at `path`. - `tool_annotations(spec: ToolSpec) -> dict[str, bool]`: MCP `ToolAnnotations` fields (snake_case) from `effects`, `confirm` and `idempotent`. ### `agent_adapter.integrations.openai` Render tools for the OpenAI Chat Completions and Responses APIs as plain dicts. - `INTEGRATION`: `OpenAIIntegration` instance - `OpenAIIntegration(*, api: Literal['chat', 'responses'] = 'chat', strict: bool = True) -> None`: Exports Chat Completions (default) or Responses tool dicts; the host runs the calls. - `chat_tool(spec: ToolSpec, *, strict: bool = True) -> dict[str, Any]`: One tool in Chat Completions format. - `chat_tools(registry: Registry, *, strict: bool = True) -> list[dict[str, Any]]`: Every registry tool exported to OpenAI, in Chat Completions format. - `responses_tool(spec: ToolSpec, *, strict: bool = True) -> dict[str, Any]`: One tool in Responses API format (flat, no `function` wrapper). - `responses_tools(registry: Registry, *, strict: bool = True) -> list[dict[str, Any]]`: Every registry tool exported to OpenAI, in Responses API format. ### `agent_adapter.integrations.openai_agents` OpenAI Agents SDK: registry tools as `FunctionTool`s, and a bridge for its approval gate. - `INTEGRATION`: `OpenAIAgentsIntegration` instance - `OpenAIAgentsIntegration()`: Exports `FunctionTool`s; the SDK's `needs_approval` gate is the approval bridge. - `function_tool(spec: ToolSpec, *, executor: ToolRunner | None = None, strict: bool = True) -> FunctionTool`: A `FunctionTool` that runs `spec` through the executor; `confirm` maps to `needs_approval`. - `function_tools(registry: Registry, *, executor: ToolRunner | None = None, strict: bool = True) -> list[FunctionTool]`: `function_tool` for every registry tool exported to `openai_agents`. - `async resolve_interruptions(result: Any, port: ApprovalPort, *, run_id: str = '') -> RunState[Any]`: Ask `port` about each pending approval and record its decision on the run state. ### `agent_adapter.integrations.openapi` OpenAPI 3.x importer: one tool per operation, called over httpx. - `Auth()`: Adds credentials to one request's headers or query parameters. - `BearerAuth(token: TokenSource) -> None`: `Authorization: Bearer `; a sync or async `token` callable is asked per call. - `HeaderAuth(name: str, value: str) -> None`: A fixed header, e.g. - `INTEGRATION`: `OpenAPIIntegration` instance - `OpenAPIIntegration()`: Imports tools (`import_openapi`); it has no tool export. - `QueryAuth(name: str, value: str) -> None`: A fixed query parameter, e.g. - `import_openapi(registry: Registry, spec: Mapping[str, Any] | str | PathLike[str], *, base_url: str | None = None, auth: Auth | None = None, headers: Mapping[str, str] | None = None, operations: Iterable[str] | None = None, prefix: str = '', timeout: float = 30.0, client_factory: Callable[..., Any] | None = None, **policy: Any) -> list[ToolSpec]`: Add one tool per operation of an OpenAPI 3.x document (all or nothing). ### `agent_adapter.integrations.pydantic_ai` Pydantic AI: registry tools as `Tool`s, and a bridge for its deferred-approval flow. - `INTEGRATION`: `PydanticAIIntegration` instance - `PydanticAIIntegration()`: Exports `Tool`s; Pydantic AI's deferred tool requests are the approval bridge. - `pydantic_ai_history_processor(policy: CompressionPolicy, *, pinned: Pinned | None = None, model_max_tokens: int | None = None) -> Callable[[list[Any]], Awaitable[list[Any]]]`: A history processor for `Agent(capabilities=[ProcessHistory(...)])`. - `pydantic_ai_tool(spec: ToolSpec, *, executor: ToolRunner | None = None) -> Tool[Any]`: A `Tool` that runs `spec` through the executor. - `pydantic_ai_tools(registry: Registry, *, executor: ToolRunner | None = None) -> list[Tool[Any]]`: `pydantic_ai_tool` for every registry tool exported to `pydantic_ai`. - `async resolve_deferred(requests: DeferredToolRequests, port: ApprovalPort, *, run_id: str = '') -> DeferredToolResults`: Ask `port` about each call in `requests.approvals`; the results resume the agent. ### `agent_adapter.integrations.semantic_kernel` Semantic Kernel: ToolSpecs as kernel functions, an approval filter, a compression format. - `INTEGRATION`: `SemanticKernelIntegration` instance - `SemanticKernelIntegration()`: Exports kernel functions; `approval_filter` asks an approval port before auto-calls. - `approval_filter(port: ApprovalPort, registry: Registry, *, plugin: str | None = 'agent_adapter') -> Filter`: An `AUTO_FUNCTION_INVOCATION` filter backed by `port`. - `kernel_function(spec: ToolSpec, *, executor: ToolRunner | None = None) -> Any`: A `KernelFunctionFromMethod` for `spec`, its parameters taken from the JSON schema. - `kernel_plugin(registry: Registry, *, name: str = 'agent_adapter', executor: ToolRunner | None = None) -> Any`: A `KernelPlugin` of the registry's tools for this target; the model sees `-`. ### `agent_adapter.integrations.smolagents` smolagents: registry tools as `smolagents.Tool`s whose sync `forward` runs the executor. - `INTEGRATION`: `SmolagentsIntegration` instance - `SmolagentsIntegration()`: Exports `smolagents.Tool`s; no native approval gate, so the executor's port decides. - `smolagents_compressed_model(model: Any, policy: CompressionPolicy, *, pinned: Pinned | None = None, model_max_tokens: int | None = None) -> Any`: A copy of a smolagents model whose `generate` and `generate_stream` get fewer messages. - `smolagents_tool(spec: ToolSpec, *, executor: ToolRunner | None = None) -> Any`: One `smolagents.Tool` for `spec`; its sync `forward` runs the executor via `run_sync`. - `smolagents_tools(registry: Registry, *, executor: ToolRunner | None = None) -> list[Any]`: A `smolagents.Tool` for every registry tool exported to smolagents. ### `agent_adapter.integrations.strands` Strands Agents: each ToolSpec as an `AgentTool`; approval through a before-tool-call hook. - `INTEGRATION`: `StrandsIntegration` instance - `StrandsIntegration()`: Exports `AgentTool`s; `strands_approval_hook` asks an approval port before a call. - `strands_approval_hook(port: ApprovalPort, registry: Registry) -> Any`: A `BeforeToolCallEvent` callback: asks `port` about `registry` tools whose rule says so. - `strands_compress_middleware(policy: CompressionPolicy, *, pinned: Pinned | None = None, model_max_tokens: int | None = None) -> Callable[[Any], Awaitable[Any]]`: An Input handler: `agent.add_middleware(InvokeModelStage.Input, handler)`. - `strands_tool(spec: ToolSpec, *, executor: ToolRunner | None = None) -> Any`: An `AgentTool` for `spec`; results are Strands tool results (`status` error or success). - `strands_tools(registry: Registry, *, executor: ToolRunner | None = None) -> list[Any]`: `AgentTool`s for every registry tool exported to this target. ### `agent_adapter.integrations.temporal` Temporal: a `Flow` as a workflow, one superstep per activity; approvals from your store. - `ADVANCE = 'agent_adapter.flow.advance'` - `ApprovalNotice`: see `agent_adapter.integrations.dbos` - `DurableFlows`: see `agent_adapter.integrations.dbos` - `FlowStepReport`: see `agent_adapter.integrations.dbos` - `FlowWorkflow() -> None`: The flow workflow: `run(request)` drives one flow run to its end (no I/O here). - `NOTIFY = 'notify'` - `PENDING = 'pending'` - `PendingApproval`: see `agent_adapter.integrations.dbos` - `RESUME = 'agent_adapter.flow.resume'` - `RunIdentity`: see `agent_adapter.integrations.dbos` - `WORKFLOW = 'agent_adapter.flow'` - `flow_activities(flows: DurableFlows) -> list[Callable[..., Any]]`: The two activities a worker registers: advance a run, and resume it from the store. - `flow_worker(client: Client, flows: DurableFlows, *, task_queue: str, **options: Any) -> Worker`: A Temporal `Worker` for the flow workflow and its activities; `options` go to `Worker`. - `flow_workflow() -> type[FlowWorkflow]`: A `FlowWorkflow` subclass with Temporal's decorators applied (built once). - `async notify(handle: WorkflowHandle[Any, Any], approval_id: str, *, wait: bool = True) -> None`: Wake a paused run after you decided `approval_id` in the approval store. - `async pending(handle: WorkflowHandle[Any, Any]) -> PendingApproval | None`: The approval the run waits for (no arguments), or None. - `async result(handle: WorkflowHandle[Any, Any]) -> FlowStepReport`: Wait for the run to end and return its report (done or denied). - `async start_flow(client: Client, flow: str, state: Mapping[str, Any], *, run_id: str, task_queue: str, approval_timeout_s: float | None = None, activity_timeout_s: float = 600.0, max_attempts: int = 0, identity: RunIdentity | None = None) -> WorkflowHandle[Any, Any]`: Start a flow run as a workflow whose id is the run id; `max_attempts=0` retries forever.