Skip to main content

CommonADK — define agents once, build on any agent SDK

CommonADK

CommonADK is a LiteLLM-style layer for agent SDKs: define an agent system once, in a framework-neutral common/ folder, and materialize it into any supported agent SDK — no hand-written per-SDK agent code, no copy-pasted prompts or tool wrappers per framework.

Hypothesis: an agent system can be defined once and built, unmodified, on multiple SDKs. The bet CommonADK makes is that common/ can be the single source of truth, with a thin runtime adapter per SDK doing the translation — the same way LiteLLM lets you call any LLM provider through one interface. The v1 success criterion (see plan.md): the same common/ folder builds and runs unmodified on both Google ADK and the OpenAI Agents SDK. It does — examples/research-crew is proof.

Demo

Terminal recording of commonadk validate and examples/demo.py building the same common/ folder on all six SDK targets

Real, captured output — commonadk validate on the shipped research-crew example, then python3 examples/demo.py building the same coordinator agent, unmodified, on all six SDK targets. Run it yourself with examples/demo.py, or read the full unedited transcripts in docs/demo-runs.md.

Quickstart

commonadk is on PyPI from v0.0.1: pip install commonadk gets you the core (loader, validator, mermaid renderer, CLI) with no agent SDK attached. Install with the extra(s) for the SDK(s) you want to build against:

pip install "commonadk[google]"   # Google ADK target
pip install "commonadk[openai]"   # OpenAI Agents SDK target
pip install "commonadk[claude]"   # Claude Agent SDK target
pip install "commonadk[crewai]"   # CrewAI target
pip install "commonadk[autogen]"   # AutoGen target
pip install "commonadk[langgraph]"   # LangGraph target
pip install "commonadk[google,openai,claude,crewai,autogen,langgraph]"   # all six

Installing multiple extras together. All six extras have been verified to resolve and install together (this repo runs its full test suite against all six at once), but two dependency tensions are worth knowing about — both harmless for everything commonadk itself touches: crewai pins openai<3, so installing it alongside openai-agents (which prefers openai>=3) resolves openai down to a 2.x release — pip then reports that as an unsatisfied requirement (pip check will flag it), but no code path this project exercises needs openai>=3. And autogen-core pins protobuf~=5.29, which pulls protobuf down from the newer major version several of google-adk's own dependencies (grpcio-status, google-api-core, ...) would otherwise prefer — again flagged by pip check, again with no observed effect on this project's own code. Neither tension breaks import commonadk, commonadk validate|render, or any adapter's build().

Point it at the shipped example, a three-agent research crew (coordinator → researcher → writer):

import commonadk

project = commonadk.load("examples/research-crew/common")   # parse + validate
agent = project.build("coordinator", target="google-adk")   # live google.adk.Agent
agent = project.build("coordinator", target="openai")       # live agents.Agent
options = project.build("coordinator", target="claude")     # claude_agent_sdk.ClaudeAgentOptions
crew = project.build("coordinator", target="crewai")         # live crewai.Crew
team = project.build("coordinator", target="autogen")        # live autogen_agentchat.teams.Swarm
graph = project.build("coordinator", target="langgraph")     # compiled langgraph.graph.StateGraph

(target="claude" needs each agent's agent-config.yaml to carry a targets.claude.model override, since the example's default models are gemini — see "Supported targets" below.)

Or from the command line:

commonadk validate examples/research-crew/common
commonadk render examples/research-crew/common
commonadk run examples/research-crew/common --target openai "Research electric vehicle adoption"
commonadk new examples/research-crew/common reviewer --from writer --type handoff

validate loads and checks a project, printing each agent's resolved model, tools, and required env vars (flagging which are actually set in your shell). render regenerates interaction-layer.md from interactions.yaml so the diagram never drifts from the spec. run builds one agent for a target SDK and executes a single turn — this one needs real API keys. new scaffolds a conforming agent folder (skill.md, tools.py, agent- config.yaml) inside an existing common/ — output that passes commonadk validate immediately — and refuses to overwrite an existing folder; --from <agent> --type {delegate,handoff} additionally appends an edge to interactions.yaml and regenerates interaction-layer.md for you.

For a runnable, offline tour of all of the above — including project.build() against all six targets and real captured CLI output — see examples/demo.py and docs/demo-runs.md.

The common/ folder

common/
├── config.yaml              # project-wide config
├── interactions.yaml        # typed interaction edges (source of truth)
├── interaction-layer.md     # GENERATED mermaid rendering of interactions.yaml
└── <agent-name>/
    ├── skill.md              # instructions/persona -> system instructions
    ├── tools.py               # plain typed Python functions with docstrings
    └── agent-config.yaml     # per-agent config

config.yaml — project name, entry agent, and the model-alias table every agent's model: resolves against:

name: my-project
entry: coordinator
targets: [google-adk, openai]
default_model: fast
model_aliases:
  fast: gemini/gemini-2.5-flash
  smart: anthropic/claude-sonnet-5

<agent>/agent-config.yaml — model (an alias above, or a raw LiteLLM-format string), tools, and required env vars. requires.env declares names only — never values; commonadk validate and every adapter's build-time preflight check presence, not content:

name: researcher
model: gemini/gemini-2.5-pro
tools:
  - search_web
  - fetch_page
requires:
  env:
    - name: TAVILY_API_KEY
      description: Search API key used by search_web
      required: true

<agent>/skill.md — plain Markdown instructions, passed through as the agent's system prompt (optional YAML frontmatter is stripped).

<agent>/tools.py — plain functions. Type hints on every parameter and a docstring are required, checked at validate time — they're what every adapter turns into that SDK's native tool schema:

def search_web(query: str) -> str:
    """Search the web for a query and return a short summary of top results.

    Args:
        query: The search query.
    """
    ...

interactions.yaml — the interaction graph, source of truth:

entry: coordinator
edges:
  - from: coordinator
    to: researcher
    type: delegate     # v1: delegate | handoff
  - from: researcher
    to: writer
    type: handoff

interaction-layer.md — GENERATED. commonadk render regenerates its mermaid block from interactions.yaml, so the diagram documents the graph instead of drifting from it:

flowchart TD
    coordinator(["coordinator (entry)"])
    researcher["researcher"]
    writer["writer"]
    coordinator -- delegate --> researcher
    researcher -. handoff .-> writer

Supported targets

Target target= Native model path LiteLLM-wrapped otherwise
Google ADK google-adk bare model id when the LiteLLM string is gemini/... google.adk.models.lite_llm.LiteLlm
OpenAI Agents SDK openai bare model id when the LiteLLM string is openai/... agents.extensions.models.litellm_model.LitellmModel
Claude Agent SDK claude bare model id when the LiteLLM string is anthropic/... no LiteLLM path — any other provider is a clear build-time error
CrewAI crewai crewai.LLM(model=...) takes the resolved LiteLLM-format string directly, for every provider — no allowlist, no build-time error routes to a native provider client when it recognizes one, else falls back to litellm's completion() itself
AutoGen autogen bare model id for openai/..., anthropic/..., and gemini/... (native autogen_ext model clients) no LiteLLM path — any other provider is a clear build-time error
LangGraph langgraph "provider:model" via init_chat_model for gemini/... (→ google_genai), openai/..., and anthropic/... no LiteLLM path — any other provider is a clear build-time error

Every agent's model: (an alias from config.yaml or a raw LiteLLM string like anthropic/claude-sonnet-5) resolves the same way regardless of target; each adapter then decides whether its SDK can speak that provider natively or needs the SDK's own LiteLLM wrapper. A per-agent targets.<sdk>.model override in agent-config.yaml bypasses resolution entirely and is passed through as-is, for when you need an SDK-native model form commonadk doesn't infer.

The Claude Agent SDK runs Anthropic models only — it has no LiteLLM wrapper at all, so an agent whose model resolves to any provider other than anthropic/... fails to build for target="claude" with a clear error naming the agent, its resolved model string, and how to fix it (an anthropic/... model, a different alias, or a targets.claude.model override). This is why research-crew's agent-config.yaml files each carry a targets.claude.model: claude-sonnet-5 override — the project's default models are gemini.

AutoGen is similar in spirit: no LiteLLM path, but three native model clients (openai/..., anthropic/..., gemini/...) instead of one, so the shipped example builds unmodified with no targets.autogen.model overrides needed at all. Any other provider still fails to build with the same style of clear, actionable error.

Delegate and handoff, per SDK

interactions.yaml has one edge vocabulary — delegate and handoff — but each of the six SDKs expresses "one agent routes work to another" through a genuinely different data shape, and each adapter honors that shape rather than flattening it away. Starting with the two SDKs v1 targeted: Google ADK's sub_agents form a strict tree: an agent instance can have exactly one parent, so a common/ graph reachable from the build root must itself be a tree, or the adapter raises before constructing anything (a cycle, or an agent reachable from two parents, is rejected up front). The OpenAI Agents SDK's handoffs are a plain list of references: the same Agent instance can sit in more than one parent's handoffs list, and cycles are just references wired up after construction. So the identical interactions.yaml — including graphs with a shared sub-agent or even a cycle — builds happily as an OpenAI Agents handoff graph while the same shape can be legitimately rejected for Google ADK. (v1 does not yet distinguish delegate from handoff within either adapter — both edge types map to the one mechanism each SDK exposes today.)

The Claude Agent SDK is session/query-based, not agent-object-based: instead of a live agent, project.build(..., target="claude") returns a claude_agent_sdk.ClaudeAgentOptions — the config object you pass to that SDK's query(). Its subagents (options.agents) are declared once, in a single flat dict[str, AgentDefinition], not a nested tree — and per the SDK's own docs, any agent whose tools include the Agent tool can invoke any name in that flat registry, not just a parent-declared child. So this adapter registers every agent reachable from the build root (the whole transitive closure, however deep) into that one flat dict, and grants Agent tool access only to the reachable agents that actually have an outgoing edge in interactions.yaml — a deep edge (e.g. researcher -> writer when building coordinator) is fully representable this way, and — like OpenAI Agents' reference-based handoffs — a shared sub-agent or a cycle needs no special rejection either: the flat dict just holds one entry per logical agent name. See adapters/claude_agent.py's module docstring for the full investigation and the tool-isolation mechanics (each agent's own tools.py functions become an in-process MCP server it alone can see).

CrewAI is the one target where interactions.yaml's edge targets are coarsened, not fully honored: project.build(..., target="crewai") builds the requested agent as a hierarchical crew's manager_agent (or, if it has no reachable agents at all, as the sole member of a solo sequential crew) with every other reachable agent as a flat crew member — but CrewAI's one delegation mechanism (allow_delegation=True) is crew-wide: an agent that can delegate can reach any other crew member, not just the agents interactions.yaml actually points it at. Both delegate and handoff edges map to this one mechanism. What the graph still controls: whether an agent can delegate at all (only agents with an outgoing edge get allow_delegation=True) and scope (only agents reachable from the build root join the crew). See adapters/crewai_adapter.py's module docstring for the full investigation, including why the manager role can't carry its own tools.

LangGraph is the one target where interactions.yaml's edge targets are honored precisely, not coarsened — the opposite end of the spectrum from CrewAI above: project.build(..., target="langgraph") gives each reachable agent a prebuilt react-agent node (langchain.agents.create_agent) in one compiled langgraph.graph.StateGraph, and for every outgoing interactions.yaml edge it adds exactly one clearly-named handoff tool (transfer_to_<destination>) to the source agent — an agent can reach only the destinations the graph actually names, nothing more. Both delegate and handoff edges map to this one mechanism (the same v1 intersection decision every other adapter makes). The handoff itself uses LangGraph's own Command(goto=<destination>, graph=Command.PARENT) primitive — no langgraph-supervisor/langgraph-swarm dependency needed. Multi-parent graphs and cycles need no special handling: every reachable agent is built once into a flat, name-keyed node registry, so a shared destination or a cycle back to the build root is just another named handoff tool pointing at a node that already exists. See adapters/langgraph_adapter.py's module docstring for the full investigation, including why langchain.agents.create_agent is used instead of the now-deprecated langgraph.prebuilt.create_react_agent.

Mixed-target spawning

Every example above builds one common/ project under one SDK at a time. project.build_mixed(agent_name, default_target=...) relaxes that: any agent can pin itself to its own SDK with runtime: in its agent-config.yaml, and agents that leave it unset fall back to default_target — a project with no runtime: set anywhere behaves exactly as before. Agents sharing a runtime and connected by interactions.yaml edges are still built as one true native sub-graph by their own adapter (an ADK sub_agents tree stays a tree); only the edges that cross runtimes are bridged, as a plain callable "transfer to <agent>" tool on the crossing edge's source agent — the same "every SDK wraps a plain typed function into a tool" mechanism this whole project is built on. google-adk, openai, crewai, and claude can source a cross-runtime edge in v1; autogen and langgraph cannot yet (see docs/mixed-target-design.md for exactly why, verified against each installed SDK) — every target can be the destination of one either way. See examples/mixed-crew for a small two-runtime project, and the design doc for the full three-layer model, the island algorithm, and the failure modes. This is in-process only — no network protocol between SDKs yet; that's future work (issue #9).

Roadmap

The full feature map — shipped, next up, and planned — lives in ROADMAP.md, with a GitHub issue per open item where discussion happens. Headliners: mixed-target spawning (pinning individual agents to different SDKs within one project, #9) and richer edge semantics (pipelines, loops, shared state, #10). Design history and settled decisions stay in plan.md.

Metadata

Release files for commonadk 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for commonadk 0.0.1
File Size Uploaded Provenance Actions
commonadk-0.0.1.tar.gz 510.9 kB Without provenance Details for commonadk-0.0.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for commonadk 0.0.1
File Interpreter ABI Platform Actions
commonadk-0.0.1-py3-none-any.whl Python 3 none any Details for commonadk-0.0.1-py3-none-any.whl

Total release size: 580.2 kB

Release files / commonadk-0.0.1.tar.gz

Download URL commonadk-0.0.1.tar.gz
Size 510.9 kB
Tags Source
SHA-256 checksum
How to use checksums
158069a623ab45ad2be8e4497e0f1bf60b9c600dd698e07760cd6d1d3396cd1b
BLAKE2b-256 checksum
How to use checksums
26a3959fed7d15ace6600535c42c283210c1c9df03cd11e8f7568133c1e1a78c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / commonadk-0.0.1-py3-none-any.whl

Download URL commonadk-0.0.1-py3-none-any.whl
Size 69.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
09c412437e325939053977451a43555e1dee2a7dc024c74a187e16328a3b9604
BLAKE2b-256 checksum
How to use checksums
b116f02be571a77bb54700917c44eb498864cc87fb4a0e07816b43475f75531a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Security for commonadk 0.0.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page