JamJet
Open Source

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-call

You'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 policy

Once 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-agent

This 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.py

Java 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.MyAgent

Your @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 agent

jamjet 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.py

Every 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.

Next steps

On this page