Zett Agent
Agents that forge the tools they need.
Documentation · Quickstart · Examples · 中文文档
Zett Agent is a small Python runtime for tool-calling agents. One provider-neutral loop calls a model, runs the tools it asks for, and streams every step back to your application. Start with a model and a few functions. Add skills, session storage, approvals, or delegation without adopting an application framework.
- Provider-neutral. OpenAI, Anthropic, Google, DeepSeek, and Ollama behind one protocol.
- Tools from functions. Annotate a Python function; the schema, validation, and dispatch are generated.
- Capabilities on demand. Load skill instructions when needed; discover deferred tools on supported Responses endpoints.
- Streaming first. Partial text, reasoning, tool calls, and results for terminals and UIs.
- Durable when you want it. In-memory history by default, SQLite or your own store on request.
- Your application stays yours. Embed the loop in a script, terminal, UI, or service; add lifecycle behavior with extensions.
Read the documentation or start with the complete tool-calling example below. Python 3.10+, typed, MIT licensed.
Install
pip install zett-agent
uv add zett-agent
Quickstart
Set OPENAI_MODEL to a model that supports tool calling and
OPENAI_API_KEY to your credential. This example makes a real network request
and may incur charges. Save it as agent.py and run python agent.py:
import asyncio
import os
from zett_agent.client import create_agent
from zett_agent.providers.openai import OpenAIProvider
from zett_agent.tools.base import tool
@tool(guidelines="Use for exact integer addition.")
def add(left: int, right: int) -> int:
"""Add two integers exactly."""
return left + right
async def main() -> None:
model = OpenAIProvider(
model=os.environ["OPENAI_MODEL"],
api_key=os.environ["OPENAI_API_KEY"],
)
try:
client = await create_agent(model, tools=[add])
reply = await client.run("Use add to calculate 20 plus 22.")
print(reply.content)
finally:
await model.aclose()
asyncio.run(main())
The model can request add; the runtime validates its arguments, runs it,
returns the result, and asks the model to finish. reply.content contains the
final answer. Calling client.run(...) again continues the same in-memory
conversation. The exact answer and tool choice depend on the model.
No API key yet? Your first agent is a complete, deterministic tutorial with no network access, and Streaming tools exercises the same tool round trip offline.
Choose a provider
Swap the adapter without changing local tools, callbacks, or the client API.
Use response=True on an OpenAI-compatible adapter only when your endpoint
supports Responses; Chat Completions is the default.
| Provider | Module |
|---|---|
| OpenAI (Chat Completions or Responses) | zett_agent.providers.openai |
| Anthropic | zett_agent.providers.anthropic |
| Google GenAI | zett_agent.providers.google |
| DeepSeek | zett_agent.providers.deepseek |
| Ollama | zett_agent.providers.ollama |
See Connect a model provider for complete setup with each provider, reasoning effort, retries, proxies, and cache routing. Model features are endpoint-specific; a common adapter protocol does not guarantee identical capabilities.
Stream to a terminal or UI
Dispatch callbacks receive events as they arrive; run() still returns the final
answer.
from zett_agent.dispatcher import AgentEventDispatcher
from zett_agent.events import AgentEvent
class Console(AgentEventDispatcher):
async def on_text_delta_event(self, event: AgentEvent) -> None:
print(event.delta, end="", flush=True)
client = await create_agent(model, event_dispatcher=Console())
await client.run("Summarize this repository.")
If you need cancellation or forwarding, consume the stream directly with
async with aclosing(client.stream(...)) as events. See
Streaming.
Tools
Decorate a typed function; Zett Agent builds the schema and turns the docstring into model-facing guidance.
from zett_agent.tools.base import tool
@tool
def add(left: int, right: int) -> int:
"""Add two integers exactly.
Args:
left: First operand.
right: Second operand.
Guidelines:
- Use for exact integer addition.
"""
return left + right
Register it with create_agent(model, tools=[add]). The model can then request
the call, and the runtime validates arguments, runs it, appends the result, and
asks the model to continue. Filesystem and shell tools live in FileSystemExtension
and CodingExtension. See
Define and register tools.
Each tool needs typed arguments, a description, and at least one guideline.
Use @tool(guidelines="...") for a short function or a Guidelines section
in its docstring. Guidelines help the model choose tools; they do not enforce
permissions.
Evolve a toolbox during a session
Opt in to JITHarnessExtension to let a model create Python tools or reusable
Markdown workflows, compose existing tools, improve its capabilities, and unload
or reload them. Both
Responses and Chat Completions are supported on function-calling models.
from zett_agent.agent import AgentRunConfig
from zett_agent.extensions.memory import InMemoryMessageAccumulator
from zett_agent.jit_harness import JITHarnessExtension
jit = JITHarnessExtension()
client = await create_agent(
model,
config=AgentRunConfig(session_id="tool-builder"),
tools=[add],
extensions=[InMemoryMessageAccumulator(), jit],
)
await client.run("Create and use a reusable tool that adds an integer to itself.")
jit.get_harness("tool-builder").save("harness.json")
Use this inside an async function with a configured model. Each session evolves
independently; saving is explicit. A tool can be a small package of modules, and
its code can call other loaded tools directly, so one model call can loop over
dozens of tool calls while only the final result enters the conversation.
Snapshots retain source and unload state,
not conversation history or live application resources. Session-local profiling
records call counts, latency, and input/output character sizes; the model can
inspect these measurements and LRU-bounded recent errors before creating or
improving tools. Inspection tools are deferred by default, so an endpoint that
supports deferred loading fetches them on demand while every other protocol
receives them normally; inspection_tools can also hide them or send them up
front. Generated Python is
trusted code with your process's privileges, not sandboxed code. Read
JIT capabilities
for composition, loading, and a complete offline example.
When exploration is expensive, a text tool can retain the verified path, its applicability, pitfalls, and checks for later tasks. Workflows load on demand; reading one does not execute its steps or inject its entire body into every prompt.
Keep and restore conversations
AgentRunConfig.session_id identifies a conversation. Add a persistence
extension to survive restarts:
from zett_agent.agent import AgentRunConfig
from zett_agent.extensions.sqlite import SQLiteSessionExtension
config = AgentRunConfig(session_id="project-notes")
storage = SQLiteSessionExtension("sessions.sqlite")
try:
client = await create_agent(model, config=config, extensions=[storage])
reply = await client.run("Remember my preference for Python.")
finally:
await storage.close()
Run this inside an async function with a configured model. Reopen the same
database and session ID to continue after restart. Explicit extension lists
replace default history and tool guidance; include ToolGuidelinesExtension()
if you register tools. Close the provider separately when finished. See
Keep and restore conversations.
Embed it safely
Your application owns credentials, session access, and tool permissions. File, shell, and MCP tools are not sandboxed. Prompts, AGENTS.md, and skills guide behavior but do not authorize actions.
Set application deadlines, close partially consumed streams, and serialize each conversation. Cancellation stops unfinished work; it does not undo already completed tool side effects. Follow Integrate an application for concurrency, errors, timeouts, and shutdown.
Built-in extensions
Pass the ones you need; extensions=[...] replaces the optional defaults.
| Extension | Adds |
|---|---|
InMemoryMessageAccumulator |
Process-local conversation history (default). |
SQLiteSessionExtension |
Durable raw history and compaction checkpoints. |
JSONLExtension |
One self-contained audit record per request. |
ToolGuidelinesExtension |
Tool snippets and usage rules in the prompt. |
AgentsMdExtension |
Working-directory AGENTS.md instructions. |
FileSystemExtension / CodingExtension |
Read, search, edit, and shell tools. |
CompactionExtension |
Summarize older turns before the context limit. |
AskUserExtension |
Let the model ask the UI a question and wait. |
PlanModeExtension |
Model-proposed planning with user approval. |
SubAgentExtension |
Delegate to configured child profiles. |
SkillExtension |
Discover and load local SKILL.md instructions. |
McpExtension |
Connect MCP servers as tools. |
TodoWriteExtension |
Ordered, observable task progress. |
GoalExtension |
Continue explicitly armed Goal Mode work with an evaluator. |
ToolSearchExtension |
Load optional tools on demand instead of up front. |
Use an extension when behavior belongs inside the request lifecycle; use an
AgentEventDispatcher for display-only callbacks. You never subclass Agent or
write a second loop. See
Extend the runtime.
Documentation
| Get started | Install, first agent, providers, streaming, tools, sessions. |
| Configure your agent | Defaults, per-request settings, metadata, and extension composition. |
| Integrate an application | Timeouts, cancellation, concurrent sessions, and resource ownership. |
| Test your agent | Deterministic models and offline provider tests. |
| Capabilities on demand | Editable skills, deferred tools, and endpoint compatibility. |
| How it works | Ownership, lifecycle, context, events. |
| Extend the runtime | Extensions, hooks, middleware, model and storage adapters. |
| Examples | Runnable programs for streaming, approvals, compaction, delegation. |
| API reference | Every public class, function, and field. |
Requirements
- Python 3.10 or newer (CI covers 3.10 through 3.14).
- No Node.js or external service is required by the library itself.
Development
uv sync
uv run pytest
uv run ruff check src tests examples
make docs-live # preview the manual with live reload at http://127.0.0.1:8000
make docs-serve # build once, then serve the static HTML
make check # lint, tests with coverage, and documentation checks
make docs-live rebuilds on every edit under docs/ or src/, and reloads the
open browser page. Pass DOCS_PORT=8080 to change the port and DOCS_OPEN=
to skip opening a browser. make docs builds once without serving.
The repository layout:
src/zett_agent/— the runtime published aszett-agent.docs/— the Sphinx manual;make docs-servepreviews it locally.examples/— runnable programs, most of which need no API key.
ZettCode, the terminal coding agent built on this runtime, lives in its own repository: https://github.com/Chang-LeHung/zettcode.
License
MIT
Metadata
Release files for zett-agent 0.1.17
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| zett_agent-0.1.17.tar.gz | 285.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| zett_agent-0.1.17-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 640.7 kB
Release files / zett_agent-0.1.17.tar.gz
| Download URL | zett_agent-0.1.17.tar.gz |
|---|---|
| Size | 285.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d153003312f9c8035e2b4aea8c979cbb7afca0c8796d24902cecc51637ee70f9
|
|
BLAKE2b-256 checksum How to use checksums |
75c37a7ff6ee89645ea0a3faf53775c69105ac32d7fe66adece18798ecaaec41
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / zett_agent-0.1.17-py3-none-any.whl
| Download URL | zett_agent-0.1.17-py3-none-any.whl |
|---|---|
| Size | 355.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f31ec717db981cad8a61fd784fcf34e75eb32c8e951f08717398bdab8505645b
|
|
BLAKE2b-256 checksum How to use checksums |
64373f3e0841613a972480d105c681ad4ed51d40abcb80df5e17a6f37f303e24
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|