agentwerk (Python)
Python bindings for agentwerk: a minimal library for running many agents in parallel.
agentwerk is a Rust crate; this package is a thin veneer over it, so the Python API mirrors the Rust API one to one. See the main README for the project overview.
Installation
pip install agentwerk
Configure an LLM provider through environment variables (ANTHROPIC_API_KEY,
OPENAI_API_KEY, MISTRAL_API_KEY, or LITELLM_API_KEY), the same as the Rust
crate.
Quick Start
import asyncio
from agentwerk import Agent, ReadFileTool, GrepTool
async def main():
agent = (
Agent()
.from_env()
.role("You are a Rust developer who explores source files to answer questions.")
.tool(ReadFileTool())
.tool(GrepTool())
.build()
)
agent.task("Find every `pub trait` defined under src/ and explain each in one sentence.")
work = await agent.finish()
print(work.last_result())
asyncio.run(main())
The run is driven by
await agent.finish(), so every snippet below that callsfinish(),wait_for_ticket(), or reads results runs inside anasyncfunction like this one.
API
- Agents: Pick up tickets and produce results.
- Tickets: Coordinate complex work across agents.
- Prompting: Role, context, and task shaping the work of an agent.
- Tools: Capabilities agents use to solve a ticket.
- Knowledge: Durable memory agents share across tickets and runs.
- Sessions: Working directory layout and how to reopen a run.
- Events: Lifecycle events emitted while agents work.
Agents
An Agent picks up tickets, uses tools to solve them, and writes the result
back onto each ticket. The builder is fluent: each call returns the builder, and
build() produces the agent.
from agentwerk import Agent, ReadFileTool
agent = (
Agent()
.name("agent_0")
.label("math")
.tool(ReadFileTool())
)
| Method | Description |
|---|---|
name(s) |
Set an identifier for assigning tickets. |
label(l) / labels([..]) |
Restrict the agent to tickets carrying matching labels. |
tool(t) |
Register a tool the agent may call. |
dir(d) |
Set the directory the agent works in. |
role and context are covered under Prompting; knowledge(store)
under Knowledge.
Providers
A Provider connects the agent to an LLM service. agentwerk ships providers for
Anthropic, OpenAI, Mistral, and a LiteLLM proxy.
from agentwerk import Agent, AnthropicProvider
agent = (
Agent()
.provider(AnthropicProvider(key))
.model("claude-sonnet-4-20250514")
)
# Or pick from environment variables.
agent = Agent().from_env()
Each provider constructor takes the API key and an optional base_url to
override the endpoint (useful for proxied or self-hosted OpenAI-compatible
models):
from agentwerk import OpenAiProvider
provider = OpenAiProvider(key, "https://my-endpoint.example/v1")
| Method | Description |
|---|---|
provider(p) |
Set the LLM provider. |
model(m) |
Set the model the provider runs. |
from_env() |
Detect provider and model in one call. |
To read only the provider from the environment (and set the model explicitly),
or only the model (and set the provider explicitly), use provider_from_env()
or model_from_env() on the builder.
Models
.model(m) accepts a model name or a Model. Names registered with a known
provider resolve to a context window automatically. For private or proxied
models, build a Model and pass an explicit window so automatic compaction
stays active:
from agentwerk import Agent, Model
agent = Agent().model(Model("my-local-model").context_window(128_000))
Set reasoning_effort on a reasoning-capable model to make it think before
answering. The thinking is recorded in the ticket transcript alongside the
answer. It is off unless you set it. Effort is one of "off", "low",
"medium", or "high":
from agentwerk import Agent, Model
agent = Agent().model(Model("claude-sonnet-4-6").reasoning_effort("high"))
Tickets
The TicketSystem coordinates collaboration between agents. A task is the work
itself; a Ticket wraps it with metadata like labels and schemas. Labels assign
work to matching agents.
from agentwerk import Agent, Ticket, TicketSystem, FetchUrlTool
tickets = TicketSystem()
for i in range(4):
tickets.agent(
Agent()
.name(f"researcher_{i}")
.label("research")
.from_env()
.tool(FetchUrlTool())
.build()
)
tickets.agent(
Agent().name("analyst").label("analysis").from_env().build()
)
for url in pricing_pages:
tickets.ticket(
Ticket(f"Fetch {url} and extract pricing tiers, limits, and features.").label("research")
)
tickets.ticket(
Ticket("Rank all products by value for a 10-person engineering team.")
.label("analysis")
.schema(comparison_schema)
)
| Method | Description |
|---|---|
agent(agent) |
Add an agent to this ticket system. |
task(t) |
Submit a task and return its ticket key. |
ticket(t) |
Submit a Ticket with custom labels, a schema, or a parent link. |
Also on TicketSystem: dir(d) to relocate persisted state, reply(key, c)
to continue a multi-turn conversation on one ticket.
Execution
Start, wait, and cancel a run:
tickets.start()
await tickets.finish()
answer = tickets.last_result()
| Method | Description |
|---|---|
start() |
Begin processing tickets in the background. |
await finish() |
Process every queued ticket and return. |
cancel() |
Cancel the run. |
finish_reason() |
Return why the most recent finish() returned, as a string: "Drained", "PolicyViolated(..)", or "Cancelled". |
Reacting to the run
Steer a run from the outside while agents work: end it early, or enqueue follow-up work. Predicates receive the finished ticket (or result, or event) and return a truthy value.
# Fail fast: end the run at the first malicious verdict.
tickets.cancel_on_result(lambda result: result["verdict"] == "malicious")
# Verify every analysis finding with a follow-up ticket for the review pool.
def review_finding(ticket):
if "analysis" in ticket["labels"]:
return Ticket("Verify this finding.").parent(ticket["key"]).label("review")
return None
tickets.create_ticket_on_result(review_finding)
| Method | Description |
|---|---|
cancel_on_event(p) |
End the run when an event matches. |
cancel_on_result(p) |
End the run when a finished result matches. |
cancel_label(l) |
Call off one label's agents. |
create_ticket_on_result(make) |
Enqueue a follow-up ticket from a finished ticket. |
save_trajectory_on_event(p) |
Write a ticket's trajectory to disk on a matching event. |
await wait_for_ticket(p) |
Wait for one matching ticket instead of draining the queue. |
Reading results
Query the system after await finish() returns. Results are native Python values
(dict, list, str, ...), not JSON strings:
await tickets.finish()
answer = tickets.last_result()
if answer is not None:
print(answer)
for ticket in tickets.tickets():
print(f"{ticket['key']}: {ticket['status']}")
| Method | Description |
|---|---|
last_result() |
Return the most recent finished ticket's result, or None. |
results() |
Return every finished ticket's result, in creation order. |
results_for_label(l) |
Return every finished ticket carrying the label's result. |
tickets() |
Return every ticket as a dict, in creation order. |
find_ticket(p) |
Return the earliest ticket matching the predicate. |
find_tickets(p) |
Return every ticket matching the predicate. |
get_ticket(key) |
Return one ticket by key, or None. |
Inspecting tickets
Each ticket is returned as a dict carrying its recorded result, labels, and
lifecycle timestamps. The status field is one of "Todo", "InProgress",
"Finished", or "Failed" (matching the persisted tickets.jsonl):
ticket = tickets.find_ticket(lambda t: "analysis" in t["labels"])
report = ticket["result"] # already a dict; no JSON parsing needed
print(report["title"])
Ticket dict fields: key, status, task, result, labels, parent,
reporter, and the four lifecycle timestamps (created_at, started_at,
finished_at, failed_at).
Policies
Configure execution policies on a ticket system. A breach emits a
policy_violated event and halts execution. Durations are in seconds:
tickets = TicketSystem()
(
tickets
.max_turns(40)
.max_time(300.0)
.max_input_tokens(200_000)
.max_output_tokens(50_000)
)
| Method | Description |
|---|---|
max_turns(n) |
Limit the total number of turns. |
max_time(seconds) |
Limit the total elapsed duration, in seconds. |
max_input_tokens(n) |
Limit the total input tokens. |
max_output_tokens(n) |
Limit the total output tokens. |
Also on TicketSystem for retry and per-request limits: max_schema_retries,
max_request_retries, request_retry_delay (seconds), max_request_tokens.
Schemas
A Schema constrains the result an agent must produce for a ticket. A violation
triggers a retry until max_schema_retries is exhausted.
from agentwerk import Schema, Ticket
schema = Schema({
"type": "object",
"properties": {"title": {"type": "string"}},
"required": ["title"],
})
tickets.ticket(Ticket("Write a report.").schema(schema))
Register a schema per label with tickets.schema_for_label(label, schema): every
ticket of that label validates against it (unless built with its own), so work
reached by a handover is checked like anything submitted directly.
Compaction
agentwerk compacts the transcript automatically when the model's context window
is near full; observe progress via the compaction_started,
compaction_progress, compaction_finished, and compaction_failed event
kinds.
Prompting
Every prompt has three parts: role (who the agent is), context (the situation
it operates in), and task (work it should perform). role and context are
set on the agent; the task body arrives per ticket via tickets.task(). The
structure follows the prompting guide.
agent = (
Agent()
.role("You are an arithmetic agent. Compute step by step and show your work.")
.context("- Stage 2 of a math-tutor pipeline.\n- Attempts remaining: 2.")
.template_variable("divisor", "8")
.from_env()
.build()
)
tickets.agent(agent)
tickets.task("Compute (47 * 92) / {divisor}, then round to the nearest integer.")
When context(...) is not set, agentwerk supplies a default block with the ticket
key, date, directory, platform, and remaining budgets.
Tools
Give agents access to tools. Each tool exposes an action the agent can choose to
take. Built-in tools are constructed and passed to .tool(...):
| Tool | Description | |
|---|---|---|
| File | ReadFileTool() |
Read a file with line numbers, offset, and limit. |
WriteFileTool() |
Create or overwrite a file. | |
EditFileTool() |
Replace text in a file. | |
| Search | GlobTool() |
Find files by pattern. |
GrepTool() |
Search file contents. | |
ListDirectoryTool() |
List files and directories. | |
CodegrepTool() |
Structural code search. | |
| Shell | BashTool(name, pattern) |
Run a shell command matching an allowed pattern. |
| Web | FetchUrlTool() |
Fetch a URL and read its body. |
| Tickets | HandoverTicketTool() |
Write the result, mark the ticket finished, and hand follow-up work to another agent. |
ManageTicketsTool() |
Read the ticket queue and create or edit tickets. | |
ReadTicketsTool() |
Read the ticket queue. |
FinishTicketTool and ManageKnowledgeTool are registered automatically on
every agent.
Bash
BashTool restricts execution to commands matching a glob pattern. The first
argument names the tool the model sees; the second is the allowed pattern.
from agentwerk import Agent, BashTool, UnrestrictedBashTool
agent = (
Agent()
.tool(BashTool("git", "git *"))
.tool(UnrestrictedBashTool())
)
UnrestrictedBashTool() removes the pattern check.
Custom tools
Define custom tools with the @tool decorator. The tool input object is passed to
the function as keyword arguments; the return value is sent back to the model.
Declare a JSON-Schema for the inputs with schema=:
from agentwerk import tool
@tool(read_only=True, schema={
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"],
})
def greet(name: str) -> str:
"""Say hello."""
return f"Hello, {name}!"
agent = Agent().tool(greet)
read_only=True allows the agent to run a tool concurrently with other read-only
calls in the same turn. A raised exception is reported back to the model as a
recoverable error, and the run continues. Async functions (async def) work too.
Knowledge
A Knowledge store is the agent's long-term memory. It is written to disk, can
be shared across multiple agents, and is curated by the agent through its
knowledge tool.
Each page is an Open Knowledge Format (OKF) v0.1 concept file. A compact index of
one-line descriptions goes into the system prompt, so the agent picks which pages
to read. Because the store is a plain OKF bundle, Knowledge.load can open one
authored elsewhere to seed an agent.
from agentwerk import Agent, Knowledge
# Open a store and share it across agents:
store = Knowledge.load("./.agentwerk")
alice = Agent().knowledge(store)
bob = Agent().knowledge(store)
# Raise the rendered-index char budget (default 12 000):
store = Knowledge.load("./.agentwerk").index_char_limit(24_000)
agent = Agent().knowledge(store)
Sessions
A TicketSystem writes every ticket, transcript, statistic, and lifecycle event
to its working directory (default ./.agentwerk). That directory is the session:
stop the process, and TicketSystem.load(dir) reopens it from disk and continues
from where it stopped.
tickets = TicketSystem.load(".agentwerk")
tickets.agent(my_agent)
tickets.start()
Layout:
.agentwerk/
├── stats.json run statistics
├── tickets.jsonl lifecycle events (one per line)
├── results.jsonl finished results (one per line)
├── tickets/
│ └── TICKET-1/
│ ├── ticket.json the ticket without its transcript
│ ├── replies.jsonl transcript
│ └── outputs/<tool_use_id>.txt full tool outputs spilled out of the transcript
├── pages/<slug>.md knowledge pages
└── index.md knowledge index
Events
Events report everything that happens while your agents work. Log them, display
them, or react to them. An Event carries kind (a snake_case string like
"ticket_finished"), agent_name, ticket_key, and a data dict with the
variant's payload.
def log(event):
if event.kind == "ticket_finished":
print(f"[{event.agent_name}] done {event.ticket_key}")
tickets.on_event(log)
| Kind | Description | |
|---|---|---|
| Ticket | ticket_started |
An agent claimed a ticket. |
ticket_finished |
A ticket finished successfully. | |
ticket_failed |
A ticket failed. | |
| Provider | request_finished |
A provider request finished and reported its token usage. |
request_retried |
A transient provider error triggered a retry. | |
| Tool | tool_call_finished |
A tool invocation finished. |
tool_call_failed |
A tool invocation failed but the ticket continues. | |
| Compaction | compaction_started |
Compaction is about to summarize the conversation tail. |
compaction_finished |
Compaction finished and replaced the tail with a summary. | |
| Run | policy_violated |
A policy limit was breached and execution stopped. |
Also: run_started, run_finished, turn_started, request_started,
request_failed, text_chunk_received, tool_call_started,
file_open_finished, file_open_failed, knowledge_used, knowledge_missed,
schema_retried, compaction_progress, compaction_failed.
Not yet in the Python bindings
The bindings cover the full agent, ticket, tool, provider, knowledge, and event
surface. A few Rust-only pieces are not exposed yet: run Stats
(tickets.stats()), the on_failure retry-message hook, cancel_on(future),
and cancel_label_on_event. Open an issue if you need one.
Development
Build and install the bindings from source with maturin:
make python # maturin develop, from the repo root
make python_test # build, then run the pytest suite
See the main README for the project overview and DEVELOPMENT.md for the workspace layout.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agentwerk-0.1.21.tar.gz.
File metadata
- Download URL: agentwerk-0.1.21.tar.gz
- Upload date:
- Size: 289.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46f8d8482f496c753a8045ac244449a58692272f181ca7c2f39c1dd89ce611dc
|
|
| MD5 |
2ee1bcb1b9dfe8eed1ae8e8b1325d1b3
|
|
| BLAKE2b-256 |
38134d0a25be2e2e6d44e49cf507e7b59b7d8d686fbd1d31f6bc0c22781fb23f
|
Provenance
The following attestation bundles were made for agentwerk-0.1.21.tar.gz:
Publisher:
release.yml on canvascomputing/agentwerk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agentwerk-0.1.21.tar.gz -
Subject digest:
46f8d8482f496c753a8045ac244449a58692272f181ca7c2f39c1dd89ce611dc - Sigstore transparency entry: 2218207426
- Sigstore integration time:
-
Permalink:
canvascomputing/agentwerk@14a3b202864c744a561ae9a3aa4705ebf745eb3e -
Branch / Tag:
refs/tags/v0.1.21 - Owner: https://github.com/canvascomputing
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@14a3b202864c744a561ae9a3aa4705ebf745eb3e -
Trigger Event:
push
-
Statement type:
File details
Details for the file agentwerk-0.1.21-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: agentwerk-0.1.21-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 4.1 MB
- Tags: CPython 3.8+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4f745faacd2705c03c2d0e41416b1779762c5566982484c34e76074e5fa8595f
|
|
| MD5 |
bb66e2e2e75fb655997f59b05cb0e15e
|
|
| BLAKE2b-256 |
6fdab3f48a3a24a5adc3b5d46c25bd5304534da0ddf007b793cdf5240ffa5fd6
|
Provenance
The following attestation bundles were made for agentwerk-0.1.21-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on canvascomputing/agentwerk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agentwerk-0.1.21-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
4f745faacd2705c03c2d0e41416b1779762c5566982484c34e76074e5fa8595f - Sigstore transparency entry: 2218207459
- Sigstore integration time:
-
Permalink:
canvascomputing/agentwerk@14a3b202864c744a561ae9a3aa4705ebf745eb3e -
Branch / Tag:
refs/tags/v0.1.21 - Owner: https://github.com/canvascomputing
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@14a3b202864c744a561ae9a3aa4705ebf745eb3e -
Trigger Event:
push
-
Statement type:
File details
Details for the file agentwerk-0.1.21-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: agentwerk-0.1.21-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 4.2 MB
- Tags: CPython 3.8+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8067c14c14d7f780fac4280bacd5c45c8486c3a873274c6a1b9440f03d8cc7f9
|
|
| MD5 |
27bf046acf945a1cb2bfa55879be8da1
|
|
| BLAKE2b-256 |
945f405ac00c800085ccbf7ac861664d293da116af3755a8241c6cb5b8c19d1a
|
Provenance
The following attestation bundles were made for agentwerk-0.1.21-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on canvascomputing/agentwerk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agentwerk-0.1.21-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
8067c14c14d7f780fac4280bacd5c45c8486c3a873274c6a1b9440f03d8cc7f9 - Sigstore transparency entry: 2218207444
- Sigstore integration time:
-
Permalink:
canvascomputing/agentwerk@14a3b202864c744a561ae9a3aa4705ebf745eb3e -
Branch / Tag:
refs/tags/v0.1.21 - Owner: https://github.com/canvascomputing
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@14a3b202864c744a561ae9a3aa4705ebf745eb3e -
Trigger Event:
push
-
Statement type:
File details
Details for the file agentwerk-0.1.21-cp38-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: agentwerk-0.1.21-cp38-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 3.8 MB
- Tags: CPython 3.8+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8f5cf9a7a82aa9031531f4f979eb81eed2b9a83151ac94de8c2ccc31adf0d266
|
|
| MD5 |
abae5e3d41612dc05e97f9b19bc038dd
|
|
| BLAKE2b-256 |
dd5d8d352098015a95c8fb53e3086da8ff142ba85765f5a0def1e39cd5959b6b
|
Provenance
The following attestation bundles were made for agentwerk-0.1.21-cp38-abi3-macosx_11_0_arm64.whl:
Publisher:
release.yml on canvascomputing/agentwerk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agentwerk-0.1.21-cp38-abi3-macosx_11_0_arm64.whl -
Subject digest:
8f5cf9a7a82aa9031531f4f979eb81eed2b9a83151ac94de8c2ccc31adf0d266 - Sigstore transparency entry: 2218207465
- Sigstore integration time:
-
Permalink:
canvascomputing/agentwerk@14a3b202864c744a561ae9a3aa4705ebf745eb3e -
Branch / Tag:
refs/tags/v0.1.21 - Owner: https://github.com/canvascomputing
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@14a3b202864c744a561ae9a3aa4705ebf745eb3e -
Trigger Event:
push
-
Statement type: