Skip to content

3.0. Packaging

Why package the agent before adding features?

An importable package gives the ADK CLI, tests, MCP server, A2A server, and container one implementation to execute. Without that boundary, examples drift into scripts with different paths, dependencies, and startup behavior.

How is the project organized?

agents/python/
  pyproject.toml       Runtime/dev dependencies and tool configuration
  uv.lock              Exact dependency resolution
  mise.toml            Stable development commands
  Dockerfile           Non-root A2A runtime image
  src/agent/
    __init__.py        ADK root_agent discovery
    agent.py           Composition root
    budget.py          Token accounting and per-session budget
    config.py          Typed environment boundary
    config_check.py    Masked effective-configuration diagnostic
    model.py           OpenAI-compatible or optional Gemini selection
    models.py          Trusted domain types
    data.py            Seed-to-runtime data access
    tools.py           Read-only incident/log tools
    skills.py          Least-privilege Agent Skills
    mcp_server.py      stdio/HTTP MCP server
    mcp_client.py      ADK MCP client adapter
    longterm.py        Explicit cross-session incident notes
    memory.py          Runbook retrieval
    retrieval.py       Optional local semantic retrieval
    report.py          Schema-validated triage report
    resilience.py      Read/model deadlines and retry policy
    structured_report/ Dedicated structured-output sub-agent package
    workflow.py        Explicit workflow graph
    delegation.py      In-process specialist delegation
    guardrails.py      Tool/model policy and safe errors
    actions.py         Approved writes and audit
    pii.py             Boundary redaction callbacks
    telemetry.py       OpenTelemetry setup
    server.py          Persistent A2A application
  tests/               Offline tests
  evals/               Model-backed ADK/MLflow evaluation

The sibling agents/data/ directory is immutable seed input. .state/ is generated writable state and is ignored by Git.

Why use a src layout?

With module-root = "src", imports resolve to the installed package rather than accidentally resolving a same-named directory from the working tree:

[tool.uv.build-backend]
module-root = "src"
module-name = "agent"

That catches missing packaging metadata early and makes the container use the same import contract as tests.

Which entrypoints are stable?

adk run src/agent             Interactive terminal agent
adk web src                   ADK developer UI
python -m agent.mcp_server    MCP over configured transport
python -m agent.server        Persistent A2A ASGI server

mise.toml wraps each command so learners and CI do not have to remember implementation details.

Why separate runtime and development dependencies?

The final image needs ADK, MCP, state, security, and telemetry libraries. Ruff, ty, pytest, pip-audit, MLflow evaluation, and ADK eval extras are development gates and should not expand the runtime attack surface. uv sync --locked --no-dev in the container enforces that separation.

How do data paths stay portable?

config.py derives repository defaults relative to the installed source and lets deployments override them:

AGENT_DATA_DIR=/app/data
AGENT_STATE_DIR=/app/state

The image bundles /app/data read-only. A writable volume owns /app/state. No machine-specific absolute path is committed.

What is the packaging checkpoint?

cd agents/python
uv run python -c 'import agent; print(agent.root_agent.name)'
mise run check

Expected name: agentops_agent. Then run the same import from a directory outside src/ to prove the installed package, rather than the current working directory, supplies it.