JamJet ADK
The agent kit that's governed by default, and durable by one call.
Build an agent in a dozen lines. PII redaction at the model seam, an audit record, and a verifiable receipt are on before you configure anything. Make the same agent crash-proof by changing one call.
import asyncio
from jamjet import Agent, tool
@tool
def get_weather(city: str) -> str:
return fetch_weather(city)
weather = Agent(
"weather",
model="anthropic/claude-opus-4-8",
instructions="Answer weather questions.",
tools=[get_weather],
)
print(weather.run_sync("What should I pack for Tokyo this weekend?"))
# same agent, durable: survives worker death, resumes mid-run
result = asyncio.run(weather.run_durable("What should I pack?")) Governed. Audited. Receipted. You wrote none of it.
- pii redacted
- audit recorded
- receipt minted
- crash recovery: run_durable
Proof
Kill the worker. The agent finishes.
On run_durable, JamJet checkpoints every turn to a durable
event log. When the process dies, another worker restores from the last
checkpoint and continues. The completed run mints a verifiable receipt.
$ python agent.py # calls agent.run_durable(...) run_id: run_8f2a1c · worker_id: w-01 [turn 1] model call started... [turn 1] tool: get_weather("Tokyo") ok [turn 1] checkpoint committed [turn 2] model call started... SIGTERM received · worker w-01 terminated scheduler: lease expired on run_8f2a1c worker w-02: restoring from checkpoint... [turn 2] resumed from turn-1 snapshot [turn 2] tool: get_weather("Tokyo") skipped (idempotent) [turn 2] model call completed run complete · receipt: ab3f7c91...
Recorded simulation of the crash-recovery sequence. The idempotency key
on get_weather prevents the tool from re-running on resume.
Lost state on crash
A worker dies mid-run. JamJet restores from the last committed turn and continues. No work is lost; no step reruns unnecessarily.
Skipped approvals
A risky tool reaches for production. The run pauses at a durable hold. Once a person approves, it continues from exactly that point.
Runaway cost
A reflection loop keeps calling the model. Budget caps and loop-detection halt the run before it crosses the configured ceiling.
Quickstart
Up and running in five minutes.
Install the SDK and scaffold a project. The agent runs in-process with governance on and no infrastructure at all; when you want durability, one command brings up the whole local stack.
- Scaffold $ jamjet create myagent
A runnable agent and a project layout, ready to go.
- Run it $ python agent.py
Runs in-process. PII redacted at the model seam, audited, and a receipt minted. You wrote none of that, and nothing is running but Python.
- Go durable $ jamjet dev
Model sidecar, durable engine, and tool worker in one command. Switch the call to
run_durableand every turn is recorded, resumable, and replayable.
Lock the behavior in: jamjet eval trajectory-diff re-runs a case
and fails CI when the tool sequence changes.
$ pip install jamjet $ jamjet create myagent created myagent/ agent.py pyproject.toml README.md $ cd myagent && python agent.py in-process pii redacted receipt 3e9f1d2a $ jamjet dev model sidecar ready durable engine ready :7700 python worker ready # switch agent.py to run_durable to record turns here
Build
Agents, tools, teams, memory.
The authoring surface stays out of your way. One import, one class, one decorator. Add capabilities by adding arguments.
Agent
The front door for most agents. Supply a model, instructions, and tools. Everything else is defaults you can override.
agent = Agent(
"reporter",
model="openai/gpt-4o",
instructions="...",
tools=[search, file_read],
)
result = agent.run_sync("Summarise last week's reports") @tool
Any Python function becomes a tool. Schema inference from the type hints on every path; on the durable engine each call also gets a deterministic idempotency key, so a crash never sends the mail twice.
@tool
def send_email(to: str, body: str) -> str:
# audited; exactly-once on run_durable
return mailer.send(to, body) Sessions and memory
A session is a long-running, resumable conversation thread, persisted in
a SessionStore. Add memory=True and the Engram
bridge retrieves and records around each turn, keyed by the session id.
store = SessionStore()
session = store.create("user-42")
result = asyncio.run(session.run(agent, "What did we discuss?")) MCP tools
Declare MCP servers in jamjet.toml and call their tools from
workflow nodes. jamjet dev connects to every configured server
on start, and jamjet tools list shows what they expose.
# jamjet.toml
[[mcp.servers]]
name = "brave-search"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-brave-search"] Multi-agent
Compose agents into a team.
Wire specialists into a sequence, fan them out in parallel, or let a
coordinator route to the right one. Each sub-agent is its own governed
run, and its own durable run when you orchestrate with
run_durable.
Sequential
Chain agents end to end. Each agent's output becomes the next one's input.
pipeline = Sequential(
agents=[draft, review, publish],
)
asyncio.run(pipeline.run_durable("Ship the Q3 note")) Parallel
Fan one input out to many agents at once, then merge their results.
collect keeps them all; first takes the
fastest.
board = Parallel(
agents=[legal, finance, risk],
merge="collect",
) Coordinator
A coordinator agent reads the input and routes it to the right specialist. One front door, many experts.
desk = Team(
agents=[billing, support, sales],
coordinator=router,
)
asyncio.run(desk.run("Where is my refund?")) Loop
Run one agent in a loop, refining its own output until a predicate passes or the iteration cap is hit.
refine = Loop(
critic,
until=is_clean,
max_iters=5,
)
Call .run(input) to orchestrate in-process, or
.run_durable(input) to run each sub-agent on the engine.
Both return a TeamResult carrying every step's output.
Governance
Policy. Approval. Audit. Receipts.
Governance is not a library you bolt on. Some of it runs on every call with nothing configured, some is one argument away, and the gating controls are enforced by the engine. Here is exactly which is which.
On every run, nothing configured
- PII redaction Outbound messages are redacted at the model seam before the provider
sees them, fail-closed: redact or deny.
pii=Falseopts out. - Audit record Every run lands an audit record with content hashes. Signed once you provision a signing key.
- Receipts Every run mints an AgentBoundary receipt binding prompt, agent, and
model. Verifiable by anyone with
agentboundary.validate_receipt.
One argument each
- Model allowlist
policy="strict"keeps calls on Anthropic providers. Pass a dict for your own allowlist. An unknown policy name is a hard error, never a silent allow-all. - Budget ceiling
budget=2.00caps a run at two dollars, enforced fail-closed at the model seam. There is no ceiling until you set one.
Enforced by the engine
- Blocked tools and approval Declare
require_approval_foron a workflow and the engine holds the run at the gated call, then resumes at exactly that point once approved by API, CLI, or the Cloud dashboard. Enforced forAgenttool dispatch as well as workflow tool calls, and the approval is bound to a content hash of the call, so it cannot be replayed against different arguments. This is therun_durablepath; the in-processrun()cannot hold a run at a gate, and warns rather than enforcing.
agent = Agent(
"travel",
model="anthropic/claude-opus-4-8",
tools=[search, send_email, book_flight],
policy="strict", # model allowlist
budget=2.00, # USD ceiling per run
) # policy= takes "open", "strict", or a dict
policy=
"require_approval_for": ["payments.*"],
A YAML workflow takes the same keys under a policy:
block. policy= on an Agent does not read a file:
a string must name a built-in or a registered policy, so a path raises
Unknown named policy.
The approval loop is shipped end to end: the engine holds the run, the Cloud endpoint takes the decision, and the CLI and dashboard both drive it. Runnable in examples/02-human-approval, documented at docs.jamjet.dev.
Any model
One string, any provider.
Pass a provider-routed string as the model. The ADK routes through a governed model seam that applies PII redaction, meters token usage, and checks whatever policy you set, regardless of provider.
anthropic/claude-opus-4-8 openai/gpt-4o gemini/gemini-2.0-flash bedrock/meta.llama3-70b ollama/llama3.2 User code never calls a provider directly. The seam is the enforcement point: every call passes the policy middleware, budget check, and audit node before it reaches the wire.
PII redaction and metering constrain every call. The allowlist starts
allow-all until you set policy=, and the budget check is
inert until you set budget=.
Reliability
Built for production failures, not just sunny paths.
Model calls are hundreds of milliseconds. Durable turn commits are sub-millisecond. Per-step durability and governance cost essentially nothing against model latency.
Crash recovery
shippedOn run_durable, every turn commits atomically to a durable event log. On worker death, another worker restores from the last committed turn and continues. O(1) resume from the latest snapshot. The in-process run() path is not durable.
Exactly-once tools
shippedEach tool call gets a deterministic idempotency key from (run_id, segment, step). On resume the runtime skips already-completed side effects. Paying twice after a crash is not a failure mode.
Budget caps and loop detection
shippedPass budget= and the ceiling is enforced fail-closed at the model seam, not advised. Reflection loops are detected and halted against the same ceiling. There is no implicit cap: an agent with no budget set has no ceiling.
Durable waits on provider outage
shippedWhen a model provider returns 429 or goes down, the run parks as a durable wait with backoff rather than failing. It resumes automatically on recovery, freeing the worker in the meantime.
Residency by design
shippedRun state and payloads stay in the region where the agent was dispatched. Only content hashes travel for the global audit index. Residency requirements are a first-class design property, not an afterthought.
Determinism contract
shippedThe boundary between recorded outputs (model responses, tool results, time, randomness) and deterministic orchestration is explicit and tested. jamjet eval trajectory-diff gates CI on it: re-run against a new model or prompt and it exits non-zero when the tool sequence changes.
This is a re-run-and-diff gate, not deterministic replay of recorded boundaries against a new model.
Languages
Python and Java. First-class.
Python
The primary authoring surface. pip install jamjet.
The Agent, @tool, Team, sessions,
the Engram memory bridge, the governed model seam, and audit all ship
in the Python SDK.
Java
First-class JVM authoring, at parity with Python. A fluent
Agent.builder(), a @Tool annotation on your
methods, and a Spring Boot starter. Tools run on the same governed
durable engine, executed exactly-once by a Java tool worker.
TypeScript
The @jamjet/cloud SDK gives TypeScript access to the
Cloud APIs and governance checks today. A full TypeScript authoring
surface, and Kotlin, are on the roadmap.
The Java surface, in full
@Tool
String sendEmail(String to, String body)
return mailer.send(to, body); // governed, exactly-once, audited
var agent = Agent.builder("support")
.model("anthropic/claude-sonnet-4-6")
.tools(new SupportTools())
.budget(new Budget(100_000, 2.50))
.approvalRequired(List.of("delete_*"))
.build();
var result = agent.runDurable("Refund order 7785");
The Spring Boot starter (jamjet-agent-spring-boot-starter)
auto-wires the worker: annotate your @Tool @Components, declare the Agent as a
@Bean, and durable governed tool calls run on startup.
Deploy
Local. Self-host. Cloud.
The same IR artifact runs on your laptop and on your own infrastructure. Connect Cloud when you want the governance surface on top. No rewriting.
Local
pip install + runSQLite. Zero infrastructure. Your laptop. The same IR runs identically here as in production.
Self-host
Docker + SQLiteDocker Compose or Kubernetes. The engine keeps its event log and snapshots in SQLite — there is no Postgres backend. You own the infra.
JamJet Cloud
Hosted control planeYour agents keep running where you run them. Cloud holds the policy dashboard, approval inbox, audit and cost analytics, and Engram memory, across tenants.
One artifact (the IR) is what you build locally and what you run in production. Cloud is the control plane over those runs, not a replacement for them.
Fits your stack
Keep your framework. Add JamJet where it counts.
LangGraph, CrewAI, Spring AI, Claude Code, OpenAI Agents SDK: keep what you have. Drop JamJet at the tool boundary for policy, approval, and audit. No rewrites.
Most kits help you author the loop. Durability, spend caps, approval gates, audit, and PII redaction are mostly left to you. Here is what each ships, not what you could wire up by hand. We score our own column by the same rule: a spend cap you have to ask for is a ◐, not a ●.
| Capability | LangGraph | Google ADK | CrewAI | JamJet |
|---|---|---|---|---|
| Author agents & tools | on by default | on by default | on by default | on by default |
| Durable replay after a crash | built-in but you wire it | built-in but you wire it | built-in but you wire it | built-in but you wire it |
| Token + $ budget that halts the run | your own code or a separate product | your own code or a separate product | your own code or a separate product | built-in but you wire it |
| Human approval that survives a crash | built-in but you wire it | built-in but you wire it | built-in but you wire it | built-in but you wire it |
| Model allowlist | your own code or a separate product | your own code or a separate product | your own code or a separate product | built-in but you wire it |
| PII redaction at the model seam | built-in but you wire it | your own code or a separate product | your own code or a separate product | on by default |
| Verifiable receipt per run | your own code or a separate product | your own code or a separate product | your own code or a separate product | on by default |
● on by default ◐ built-in, but you wire it (opt-in / not durable / partial) ○ your own code, or a separate product
JamJet isn't another column. It's the row underneath.
How to read this: sources and caveats
- Built-in defaults as of mid-2026: LangChain / LangGraph 1.x, Google ADK 2.3.0, CrewAI OSS 1.15.x. The CrewAI column is the open-source framework, not the paid AMP platform.
- Durable replay: each ships persistence (LangGraph checkpointers, ADK
ResumabilityConfig, CrewAI Flows@persist), but it is opt-in and not auto-resumed, so the default loses in-flight state. JamJet's engine is event-sourced and resumes in flight, but you reach it by callingrun_durable: opt-in too, which is why we score ourselves ◐ on this row. - Approval that survives a crash: each ships a human-in-the-loop primitive (LangGraph
interrupt(), ADKrequire_confirmation(experimental), CrewAIhuman_input). Surviving a restart needs a durable backend you wire (LangGraph), is unsupported on the persistent session backends (ADK), or is Enterprise-only (CrewAI). - Model allowlist: each exposes callback or middleware hooks to write a tool-deny yourself; none ships a declarative model allowlist.
- PII redaction: LangGraph ships a regex redactor at the model boundary; ADK and CrewAI route PII to a separate product (Google Model Armor / DLP, or CrewAI AMP trace redaction after the run).
- Signed receipt: tracing (LangSmith, OpenTelemetry, AgentOps) is not a signed, tamper-evident per-action record; in each, per-action signed audit is an open feature request. JamJet mints an AgentBoundary receipt per run with nothing configured, binding prompt, agent and model by content hash; provision a signing key and it is signed as well.
- Budget and model allowlist: both are engine-enforced and fail-closed, but neither is on until you pass
budget=orpolicy=— there is no implicit ceiling. Approval now coversAgenttool dispatch as well as workflow tool calls onrun_durable, and the approval is bound to a content hash of the call, so it cannot be replayed against different arguments. The non-durablerun()cannot hold a run at a gate at all: it warns and proceeds ungated, so reach forrun_durablewhen approval has to bind.
@jamjet/mcp-shim
Drop governance onto any MCP client (Claude Desktop, Cursor, any MCP host) without touching the client code.
@jamjet/claude-code-hook
PreToolUse hook for Claude Code. Every tool call passes JamJet policy before execution.
@jamjet/openai-guardrail
Guardrail wrapper for the OpenAI Agents SDK. Same policy engine, different host.
jamjet.integrations
Python guardrail for the OpenAI Agents SDK. Already in the jamjet package.
The same policy engine that governs JamJet ADK agents runs as an ext-authz PDP / guardrail webhook for other frameworks. One policy plane across all your agents.
Start building.
Install the SDK, write a dozen lines, and get a redacted, audited, receipted agent with nothing else running. Durability is one call further. Apache 2.0. No account needed to start.
Questions? Join the Discord or start a discussion.