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 SDK is published on Maven Central as dev.jamjet:jamjet-sdk.

Prerequisites: Java 21+ and Maven 3.9+ or Gradle 8+.

Maven:

<dependency>
    <groupId>dev.jamjet</groupId>
    <artifactId>jamjet-sdk</artifactId>
    <version>0.5.0</version>
</dependency>

Gradle (Kotlin DSL):

implementation("dev.jamjet:jamjet-sdk:0.5.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.tool.Tool;
import dev.jamjet.tool.ToolCall;

@Tool(description = "Search the web for information about a topic")
record WebSearch(String query) implements ToolCall<String> {
    public String execute() {
        // In production, call your search API here
        return "Results for '" + query + "': JamJet is a safety layer "
                + "for AI agents.";
    }
}

Build an agent with a reasoning strategy and runtime-enforced limits:

import dev.jamjet.agent.Agent;

var agent = Agent.builder("researcher")
        .model("claude-haiku-4-5-20251001")
        .tools(WebSearch.class)
        .instructions("You are a helpful research assistant. "
                + "Always search first, then provide a thorough summary.")
        .strategy("react")
        .maxIterations(5)
        .build();

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
public static void main(String[] args) {
    var agent = Agent.builder("researcher")
            .model("claude-haiku-4-5-20251001")
            .tools(WebSearch.class)
            .instructions("You are a helpful research assistant. "
                    + "Always search first, then provide a thorough summary.")
            .strategy("react")
            .maxIterations(5)
            .build();

    var result = agent.run("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

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