Skip to content

Architecture

The core is small and depends only on pydantic. Everything else plugs in around it.

Layers

flowchart TB
  APP["Your app<br/>functions · bound methods · partials · callable objects"]
  subgraph CORE["core + schema · pydantic only · available"]
    direction LR
    REG["Registry<br/>@tool · wrap() · subset · catalogue"] --> SPEC["ToolSpec<br/>validate_args · call · invoke"]
    REG --> SCH["schema<br/>check_portable · transforms"]
  end
  subgraph RUN["execution · available"]
    direction LR
    EX["Executor<br/>run · run_many · run_plan"] ~~~ FLOW["Flow spec<br/>local runner"] ~~~ PORTS["hooks · approval<br/>cost · cache · models"]
  end
  subgraph INT["integrations"]
    direction LR
    R3["OpenAI · Gemini · Anthropic<br/>renderers · available"] ~~~ BR["LangChain · MCP · OpenAPI · agent frameworks<br/>available"] ~~~ FW["LangGraph · ADK<br/>available"]
  end
  APP --> CORE
  CORE --> INT
  CORE --> RUN
  RUN --> INT

Package map

Path Holds Status
core/tool.py ToolSpec, build_spec Available
core/registry.py Registry: add, get, remove, subset, catalogue Available
core/context.py ToolContext Available
core/errors.py AdapterError and subclasses Available
core/error_base.py, model_errors.py the AdapterError root and logging helpers; the ModelError family (both re-exported by core/errors.py) Available
core/_signature.py, _docstring.py, _policy.py, _nulls.py signature → fields, docstrings, policy checks, strict nulls Available (private)
schema/portable.py check_portable Available
schema/transforms.py to_openai_parameters, to_gemini_parameters, to_anthropic_input_schema Available
integrations/openai.py, gemini.py, anthropic.py renderers Available
exec/, hooks/, approval/, runtime/ executor, hooks, approval port, run_sync Available
cost/, cache/, models/ ledger, caches, model ports, InstrumentedModel Available
flow/ flow spec, local runner Available
integrations/langchain.py, mcp/, openapi/, openai_agents.py, ... framework bridges and importers Available
integrations/langgraph/ LangGraph tool node, approval bridge, flow compiler Available
integrations/adk/ ADK tools, confirmation bridge, flow compiler Available
server/ HTTP tool server, WSGI app, server CLI Available
tracing/ events, OpenTelemetry Available
optimiser/ offline harness optimiser Available

Import rules

An AST test (tests/test_boundaries.py) enforces these rules on every run.

flowchart LR
  BASE["core.errors<br/>core.context<br/>(base layer)"]
  SCH["schema"]
  REGM["core.registry"]
  CORE["other core modules"]
  INTM["integrations.x"]
  FWK["framework SDK"]
  SCH --> BASE
  CORE --> BASE
  REGM --> SCH
  REGM --> CORE
  INTM --> REGM
  INTM --> CORE
  INTM --> SCH
  INTM --> BASE
  INTM -. "lazy import only" .-> FWK
  • core and schema never import integrations or any framework. No dynamic imports.
  • Base-layer modules (core.errors, core.context, core.types, ...) import only the base layer. The error modules import only each other: core.error_base is the leaf.
  • schema imports only the base layer from core.
  • core.registry is the only core module that imports schema.
  • integrations.<x> imports its framework lazily: inside functions or under TYPE_CHECKING.
Page Shows
Tool lifecycle function → registry → render → LLM → tool call → executor
Schema pipeline portable check and per-provider rewrites
Executor and flows the executor pipeline, fan-out, waves and the flow runner
HITL, cost and cache how approval, the ledger, the caches and the model ports wire together