SDK Reference
The Zil SDK reads your project’s manifest.yaml, identity files, and adapter config, then wires them into a running agent. One function call replaces manual configuration.
zil.create_agent()
import zil
agent = zil.create_agent()This returns a fully-configured agent (the type depends on your framework — e.g., an ADK LlmAgent for the adk backend) with:
- Name and description from
manifest.yaml→metadata.name,metadata.description - Model resolved from
adapters/llm.yaml→ mapped to an ADK-compatible model string - Instruction composed from
identity/persona.md+identity/instructions.md+identity/guardrails.yaml - Telemetry configured from
observability/config.yaml(if endpoint set) - Guardrail engine loaded from
identity/guardrails.yaml→ attached asagent._zil_guardrails - Cost tracking initialized from
spec.cost→ accessible viazil.costandagent._zil_cost - Environment config populated via
zil.configfromspec.envdeclarations - Sub-agents built from
spec.agents— each with their own identity, model, MCP servers, and skills - Runtime dependencies parsed from
spec.runtime.dependencies→ accessible viaProjectContext.runtime_deps
How it works
manifest.yaml
├── metadata.name → agent name
├── metadata.description → agent description
├── spec.runtime.llm.adapter
│ └── adapters/llm.yaml
│ ├── provider → model prefix (e.g. "anthropic/")
│ └── model → model id (e.g. "claude-sonnet-4-20250514")
├── spec.runtime.dependencies → ProjectContext.runtime_deps
├── spec.identity
│ └── identity/
│ ├── persona.md → ┐
│ ├── instructions.md → ├→ composed instruction string
│ └── guardrails.yaml → ┘
├── spec.agents → sub-agent LlmAgents (AgentTool)
└── spec.skills → SkillToolset (filtered per sub-agent)Parameters
All parameters are optional — by default, everything is read from the project files.
| Parameter | Type | Default | Description |
|---|---|---|---|
tools | list[Callable] | [] | Tool functions to attach to the agent |
project_dir | str | Path | auto-detect | Project root (walks up from cwd looking for manifest.yaml) |
name | str | from manifest | Override agent name |
description | str | from manifest | Override description |
model | str | from adapter | Override model string |
instruction | str | composed | Override the entire instruction |
thinking_budget | int | from manifest | Token budget for Gemini thinking mode. Enables chain-of-thought reasoning when set. Falls back to spec.thinking_budget in manifest |
enable_telemetry | bool | True | Auto-setup OTel tracing from observability/config.yaml |
enable_guardrails | bool | True | Load runtime guardrail engine from identity/guardrails.yaml |
enable_cost_tracking | bool | True | Track token usage and enforce budgets from spec.cost |
raw | bool | False | Return a WiredAgent wrapper instead of the framework-specific agent. Required when using zil.Session. |
Adding tools
Tools are plain Python functions. Define them and pass them to create_agent():
import zil
def lookup_order(order_id: str) -> dict:
"""Look up an order by its ID."""
return {"order_id": order_id, "status": "shipped"}
def cancel_order(order_id: str) -> dict:
"""Cancel an order."""
return {"order_id": order_id, "cancelled": True}
root_agent = zil.create_agent(
tools=[lookup_order, cancel_order],
)The framework backend automatically generates tool descriptions from the function docstrings and type hints.
Model resolution
The SDK maps adapters/llm.yaml to ADK model strings:
| Provider | Model | ADK string |
|---|---|---|
anthropic | claude-sonnet-4-20250514 | anthropic/claude-sonnet-4-20250514 |
openai | gpt-4o | openai/gpt-4o |
vertex-ai | gemini-2.0-flash | gemini-2.0-flash |
gemini / vertex-ai | gemini-3.5-flash | gemini-3.5-flash |
Anthropic and OpenAI use the LiteLLM prefix convention. Vertex/Gemini models are passed directly (ADK native).
Unknown providers fall through as provider/model.
Identity composition
The SDK composes a single instruction string from three files, separated by ---:
persona.md— who the agent is (personality, expertise, tone)instructions.md— how the agent behaves (rules, format, boundaries)guardrails.yaml— hard rules, converted to natural-language directives
Guardrails are translated into explicit instructions:
hard_blocks→ “You MUST follow these rules… Blocked topics: …”escalation_triggers→ “When condition: message”output_constraints→ “Maximum response length: N characters.”
Overrides
You can override any resolved value:
# Use a different model for testing
agent = zil.create_agent(
model="openai/gpt-4o-mini",
)
# Enable Gemini thinking mode with a 2048-token budget
agent = zil.create_agent(
thinking_budget=2048,
)
# Completely replace the instruction
agent = zil.create_agent(
instruction="You are a test agent. Always respond with 'OK'.",
)
# Point to a different project
agent = zil.create_agent(
project_dir="/path/to/my-agent",
)Prerequisites
The SDK requires a framework backend. Install one via an optional extra:
# Google ADK (default)
uv pip install 'zil-ai[adk]'
# OpenHands
uv pip install 'zil-ai[openhands]'Projects scaffolded with zil init include the correct extra in requirements.txt automatically. See Frameworks for details on each backend.
Generated agent.py
When you run zil init my-agent, the generated my_agent/agent.py uses the SDK:
import zil
root_agent = zil.create_agent(
tools=[], # add your tool functions here
)The root_agent module-level variable is the convention for agent discovery. zil serve loads this entry point automatically. The agent lives inside a Python package (my_agent/) for proper module resolution.
Note: Zil manifests use kebab-case names (e.g.,
my-agent), but the module directory uses snake_case (my_agent) to be a valid Python package. The SDK handles this conversion automatically.
Multi-agent support
When spec.agents is declared in the manifest, create_agent() automatically builds sub-agents and attaches them as AgentTool instances on the root agent.
Each sub-agent gets:
- Its own identity (persona, instructions, guardrails) from its identity directory
- Its own model (resolved from
llm.model/model_env_varor the root adapter) - A filtered subset of MCP servers from
spec.tools.mcp_servers - A filtered
SkillToolsetfrom theskills/directory
import zil
# Sub-agents are automatically added as tools
root_agent = zil.create_agent(
tools=[my_custom_tool],
)
# root_agent.tools includes: [my_custom_tool, AgentTool(vta), AgentTool(vtd)]See Multi-Agent & HITL for the full manifest schema and advanced usage.
HITL SDK
The zil.sdk.hitl module provides primitives for pausing agent execution and waiting for human input:
from zil.sdk.hitl import request_human_input, HumanInputRequest
async def approve_plan(plan: str, tool_context) -> str:
request = HumanInputRequest(
question="Please review this plan.",
context={"plan": plan},
options=["approve", "reject", "modify"],
)
response = await request_human_input(request, tool_context)
if response.choice == "__pending__":
return "Waiting for human approval."
return f"Human responded: {response.choice}"See Multi-Agent & HITL — HITL for the complete API reference.
Skills
When spec.skills is set in the manifest, the SDK discovers skill definitions from the skills/ directory and makes them available via ADK’s SkillToolset:
# Skills are loaded and attached automatically
root_agent = zil.create_agent(tools=[...])In multi-agent projects, skills are filtered per sub-agent based on spec.agents[].tools.skills. See Skills for the full guide.
Session API
The zil.Session class provides a framework-neutral interface for invoking agents programmatically, with streaming support:
import zil
# Create the agent with raw=True to get a WiredAgent
agent = zil.create_agent(tools=[...], raw=True)
# Open a session
session = zil.Session(agent=agent, workspace="/tmp/work")
# Stream events in real time
async for event in session.stream("What files are in this repo?"):
print(f"[{event.type}] {event.text or event.tool_name}")
# Or get an aggregated response
response = await session.send("Summarize the README")
print(response.text) # Final text
print(response.events) # Full event list
print(response.session_id) # Session identifier
# Clean up
session.close()SessionEvent
| Field | Type | Description |
|---|---|---|
type | "text" | "tool_call" | "tool_result" | "error" | "done" | Event kind |
text | str | None | Text content (for text and error types) |
tool_name | str | None | Tool name (for tool_call and tool_result) |
args | dict | None | Tool arguments (for tool_call) |
result | Any | None | Tool return value (for tool_result) |
metadata | dict | None | Backend-specific data (token counts, session ID) |
SessionResponse
| Field | Type | Description |
|---|---|---|
text | str | Final concatenated text response |
events | list[SessionEvent] | Full ordered event list |
session_id | str | Session identifier |
token_usage | dict | None | Token consumption data |
zil serve uses this same Session API internally. See Frameworks for event type details across backends.
ProjectContext
The SDK’s internal ProjectContext holds all resolved project configuration. Access it directly for advanced use cases:
from zil.sdk.loader import load_project
ctx = load_project()
# Core fields
ctx.project_dir # Path — project root
ctx.manifest # dict — raw manifest.yaml
ctx.identity # IdentityContext — persona, instructions, guardrails
ctx.llm_adapter # dict — resolved LLM adapter config
ctx.observability # dict | None — observability/config.yaml
ctx.env_declarations # list[dict] — spec.env entries
ctx.cost_config # dict | None — spec.cost
ctx.tools_config # dict | None — spec.tools (MCP servers, host deps)
ctx.agents # list[AgentSpec] — spec.agents entries
ctx.service_config # dict | None — spec.runtime.service
ctx.skills_dir # Path | None — resolved spec.skills directory
ctx.thinking_budget # int | None — spec.thinking_budget (Gemini thinking mode)
ctx.runtime_deps # list[dict] — spec.runtime.dependencies entriesruntime_deps format
Each entry in ctx.runtime_deps is a dict with:
{"name": "nodejs", "type": "apt-nodesource", "version": "20"}
{"name": "pnpm", "type": "npm-global"}
{"name": "git", "type": "apt"}See Runtime Dependencies for the full specification.