Quickstart
See JamJet block an unsafe tool call in 60 seconds, then scaffold and run your first governed agent.
Quickstart
In 60 seconds, see JamJet block an unsafe tool call before execution.
pip install jamjet
jamjet demo unsafe-tool-callYou'll see the blocked tool, the matched policy rule, and an audit JSON file
written under .jamjet-demo/runs/. No API key. No cloud account. The model is
mocked; the enforcement path is real.
Three more demos:
jamjet demo approval # pause for human approval
jamjet demo budget-cap # $0.05 cost cap
jamjet demo mcp-tool-policy # MCP-shaped policyOnce the demos make sense, scaffold a real agent.
Looking for JamJet Cloud (governance, dashboard, audit trail)? See the Cloud Quickstart instead.
1. Install
pip install 'jamjet[model]'The [model] extra pulls in the model seam so agent.run() can call a provider.
The Java agent surface is published on Maven Central as dev.jamjet:jamjet-agent.
Prerequisites: Java 21+ and Maven 3.9+ or Gradle 8+.
Maven:
<dependency>
<groupId>dev.jamjet</groupId>
<artifactId>jamjet-agent</artifactId>
<version>0.4.0</version>
</dependency>Gradle (Kotlin DSL):
implementation("dev.jamjet:jamjet-agent:0.4.0")Make sure your project targets Java 21 or later. In Maven:
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>2. Scaffold an agent
jamjet create my-agent
cd my-agentThis writes a minimal project: agent.py, pyproject.toml, and a README. The
generated agent.py is a governed Agent:
# agent.py
import asyncio
from jamjet import Agent, tool
@tool
async def add(a: float, b: float) -> str:
"""Add two numbers and return the sum."""
return f"{a + b:g}"
agent = Agent(
"my-agent",
model="anthropic/claude-sonnet-4-6",
tools=[add],
instructions="You are a helpful assistant. Use the add tool when asked to sum numbers.",
strategy="react",
)
async def main() -> None:
result = await agent.run("What is 7 plus 35?")
print(result.output)
if __name__ == "__main__":
asyncio.run(main())The @tool decorator exposes a typed Python function to the agent, and the
Agent ties a model, tools, instructions, and a reasoning strategy together.
Governance (PII redaction, signed audit, and receipts) is on by default. See
Build an agent and Governance.
Define a tool, the bridge between your agent and the outside world:
import dev.jamjet.agent.Tool;
public final class SearchTools {
@Tool(name = "web_search", description = "Search the web for a topic.")
public String webSearch(String query) {
// In production, call your search API here.
return "Results for '" + query + "': JamJet is a safety layer "
+ "for AI agents.";
}
}@Tool goes on a method, and the schema the model sees is derived from the
method's parameters. name is optional — it defaults to the method name, and is
worth setting when the idiomatic Java name (webSearch) should reach the model
under a different identifier (web_search).
Build an agent with a reasoning strategy and runtime-enforced limits:
import dev.jamjet.agent.Agent;
var agent = Agent.builder("researcher")
.model("anthropic/claude-sonnet-4-6")
.tools(new SearchTools())
.instructions("You are a helpful research assistant. "
+ "Always search first, then provide a thorough summary.")
.strategy("react")
.build();Two details differ from the Python surface. .tools(...) takes tool-holder
instances, not class literals — the agent reflects over each holder's
@Tool methods. And the model is a provider-routed reference
(anthropic/claude-sonnet-4-6), the same string the Python SDK uses; a bare
model name has no provider to route to.
3. Run it in-process
agent.run() runs the loop in your own process, with no server to start. It
needs only a model provider key:
export ANTHROPIC_API_KEY=sk-ant-...
python agent.pyJava has no in-process run. Where Python offers both agent.run() and
agent.run_durable(), the Java surface has only the durable path: a Java agent
emits Model nodes and the engine routes model calls through the governed
model-seam sidecar, so there is no Java model client to run standalone.
Start the engine first (jamjet dev), then:
public static void main(String[] args) {
var agent = Agent.builder("researcher")
.model("anthropic/claude-sonnet-4-6")
.tools(new SearchTools())
.instructions("You are a helpful research assistant. "
+ "Always search first, then provide a thorough summary.")
.strategy("react")
.build();
var result = agent.runDurable("What is JamJet?");
System.out.println(result.output());
System.out.printf("Tool calls: %d%n", result.toolCalls().size());
}export ANTHROPIC_API_KEY=sk-ant-...
mvn compile exec:java -Dexec.mainClass=com.example.MyAgentYour @Tool methods run on a Java tool worker that claims work over the same
durable queue the Python worker uses, so tool calls are exactly-once across a
crash.
4. Run it on the durable engine
The same agent runs on the event-sourced JamJet engine. A durable run dispatches
your @tool functions on a separate worker process, and the worker resolves them
by importing the module they live in. So the tools must be importable: call
run_durable() from a small runner that imports the agent, rather than running
agent.py directly (where the tools would belong to __main__).
# run.py
import asyncio
from agent import agent # imports agent.py, where the @tool functions live
async def main() -> None:
result = await agent.run_durable("What is 7 plus 35?")
print(result.output)
if __name__ == "__main__":
asyncio.run(main())The durable stack runs a model sidecar, so install the sidecar extra first:
pip install 'jamjet[sidecar]'Then start the local dev stack with one command, pointing the worker at your
tool module so it can resolve the @tool functions:
jamjet dev --modules agentjamjet dev brings up the model sidecar (health-gated), the durable engine on
http://127.0.0.1:7700, and the python_tool worker. Press Ctrl+C to stop the
whole stack.
Then run the runner in a second terminal:
export ANTHROPIC_API_KEY=sk-ant-...
python run.pyEvery turn is recorded to SQLite, so you can inspect and replay the run. When the
worker picks up the run, the jamjet dev logs print its execution id on a
Claimed ... exec=exec_... line. Copy that id (it looks like exec_...) and pass
it to jamjet inspect and jamjet events:
jamjet inspect exec_...
jamjet events exec_...The run is governed the same way as in-process, and the durable engine adds the
event log, deterministic replay, idempotency, and content-addressed artifacts.
See Reliability. The
react-agent-durable
example shows the full setup.