Durable execution and guardrails for AI agents

Get startedGitHubOpen source, MIT licensed

Run agents in production with crash recovery, enforced policies, and human approvals. Use it with any agent framework.

researcherblocked
01a062b1
  1. llm_callanthropic/claude-opus-5succeeded
  2. tool_callweb_searchsucceeded
  3. worker restarted, 2 steps replayed
  4. llm_callanthropic/claude-opus-5succeeded
  5. tool_callsend_emailawaiting approval
Waiting for your approval

Rebuno sits between your agent and the calls it makes

Your framework still runs the loop. Every model and tool call passes through Rebuno first.

Your agentRebunoYour tools
  1. read_file

    report.md

    allow

    File read

    Result recorded

  2. search_docs

    “agent frameworks”

    replay

    Not called again

    Saved result returned

  3. send_email

    to [email protected]

    hold

    Waiting for approval

  4. drop_table

    users

    deny

    Blocked

    Denied by policy

Record
Every model and tool call is saved with its result.
Check
Each call is matched against your policy before it runs.
Hold
Runs pause for approval. Your agent doesn't need to stay running.
Resume
A crashed run picks up from its saved results.

Where Rebuno fits

Crash recovery, human in the loop, and restricted access.

Recovery

Coding agent

Its worker is killed halfway through a fix. The run resumes without redoing the edit or rerunning the tests.

  1. edit_filesrc/client/retry.tsrecorded
  2. shellnpm testrecorded
  3. out of memory, dispatched again
  4. edit_filesrc/client/retry.tsreplayed
  5. shellnpm testreplayed
  6. shellgit commit -m "Retry on 429"recorded
Coding agents on Rebuno →

Approvals

Support agent

All refunds need human approval.

  1. get_ticketZD-48213allowed
  2. get_chargech_3RmK8xL2allowed
  3. issue_refund4200.00 USDawaiting approval
  4. approved by finance
  5. issue_refund4200.00 USDallowed

Restricted access

Incident agent

Can run kubectl, but its policy only allows commands that read.

  1. shellkubectl get pods -n prodallowed
  2. shellkubectl logs deploy/api --since=1hallowed
  3. shellkubectl rollout restart deploy/apidenied

Works with the framework you already use

Add the highlighted parts to your agent code.

agent.py

from langchain.agents import create_agentfrom langchain_openai import ChatOpenAIfrom rebuno import Agent, http_client, tool @tool("send_email", idempotency="at_most_once")async def send_email(body: str) -> dict:    """Email a written brief to a colleague."""    return await mail.send("[email protected]", body) async def process(query: str) -> dict:    llm = ChatOpenAI(model="gpt-5.5", http_async_client=http_client())    graph = create_agent(model=llm, tools=[send_email])    result = await graph.ainvoke({"messages": [{"role": "user", "content": query}]})    return {"answer": result["messages"][-1].content} agent = Agent("mailer")agent.run(process)

agent.py

from openai import AsyncOpenAIfrom pydantic_ai import Agent as PydanticAgentfrom pydantic_ai.models.openai import OpenAIResponsesModelfrom pydantic_ai.providers.openai import OpenAIProviderfrom rebuno import Agent, http_client, tool @tool("send_email", idempotency="at_most_once")async def send_email(body: str) -> dict:    """Email a written brief to a colleague."""    return await mail.send("[email protected]", body) async def process(query: str) -> dict:    client = AsyncOpenAI(http_client=http_client())    model = OpenAIResponsesModel("gpt-5.5", provider=OpenAIProvider(openai_client=client))    result = await PydanticAgent(model, tools=[send_email]).run(query)    return {"answer": result.output} agent = Agent("mailer")agent.run(process)

agent.py

from crewai import LLM, Agent as CrewAgent, Crew, Taskfrom crewai.tools import tool as crewai_toolfrom rebuno import Agent, execution, tool @crewai_tool("send_email")@tool("send_email", idempotency="at_most_once")async def send_email(body: str) -> dict:    """Email a written brief to a colleague."""    return await mail.send("[email protected]", body) async def process(query: str) -> dict:    # CrewAI builds its own HTTP client, so Rebuno intercepts at a gateway.    ctx = execution()    llm = LLM(        model="openai/gpt-5.5",        base_url=GATEWAY_URL,        extra_headers={            "rebuno-execution-id": ctx.id,            "rebuno-dispatch-id": ctx.dispatch_id,            "rebuno-dispatch-attempt": str(ctx.dispatch_attempt),            "rebuno-agent-id": ctx.agent_id,            "rebuno-agent-secret": SECRET,        },    )    writer = CrewAgent(role="writer", goal=query, llm=llm, tools=[send_email])    task = Task(description=query, expected_output="a short brief", agent=writer)    result = await Crew(agents=[writer], tasks=[task]).kickoff_async()    return {"answer": str(result)} agent = Agent("mailer")agent.run(process)

agent.ts

import { createOpenAI } from "@ai-sdk/openai";import { generateText, tool } from "ai";import { z } from "zod";import { Agent, defineTool, rebunoFetch } from "rebuno"; const sendEmail = defineTool({  name: "send_email",  idempotency: "at_most_once",  execute: async ({ body }: { body: string }) => mail.send("[email protected]", body),}); const emailTool = tool({  description: "Email a written brief to a colleague.",  inputSchema: z.object({ body: z.string() }),  execute: sendEmail,}); async function process(input: { query: string }) {  const openai = createOpenAI({ fetch: rebunoFetch });  const { text } = await generateText({    model: openai("gpt-5.5"),    prompt: input.query,    tools: { send_email: emailTool },  });  return { answer: text };} const agent = new Agent("mailer");await agent.serve({ port: 5000 }, process);

agent.ts

import { createOpenAI } from "@ai-sdk/openai";import { Agent as MastraAgent } from "@mastra/core/agent";import { createTool } from "@mastra/core/tools";import { z } from "zod";import { Agent, defineTool, rebunoFetch } from "rebuno"; const sendEmail = defineTool({  name: "send_email",  idempotency: "at_most_once",  execute: async ({ body }: { body: string }) => mail.send("[email protected]", body),}); const emailTool = createTool({  id: sendEmail.name,  description: "Email a written brief to a colleague.",  inputSchema: z.object({ body: z.string() }),  execute: sendEmail,}); async function process(input: { query: string }) {  const openai = createOpenAI({ fetch: rebunoFetch });  const assistant = new MastraAgent({    id: "mailer",     name: "Mailer",    instructions: "Email the brief when asked.",    model: openai("gpt-5.5"),    tools: { send_email: emailTool },  });  const { text } = await assistant.generate(input.query);  return { answer: text };} const agent = new Agent("mailer");await agent.serve({ port: 5000 }, process);

agent.ts

import { ChatOpenAI } from "@langchain/openai";import { tool } from "@langchain/core/tools";import { createAgent } from "langchain";import { z } from "zod";import { Agent, defineTool, rebunoFetch } from "rebuno"; const sendEmail = defineTool({  name: "send_email",  idempotency: "at_most_once",  execute: async ({ body }: { body: string }) => mail.send("[email protected]", body),}); const emailTool = tool(sendEmail, {  name: sendEmail.name,  description: "Email a written brief to a colleague.",  schema: z.object({ body: z.string() }),}); async function process(input: { query: string }) {  const model = new ChatOpenAI({ model: "gpt-5.5", configuration: { fetch: rebunoFetch } });  const graph = createAgent({ model, tools: [emailTool] });  const result = await graph.invoke({    messages: [{ role: "user", content: input.query }],  });  return { answer: result.messages.at(-1)?.content };} const agent = new Agent("mailer");await agent.serve({ port: 5000 }, process);

Get started

Requirements: Go 1.26+, and Python 3.11+ or Node 22+

  1. 01

    Start the dev kernel

    Install the CLI and clone the repository for its examples.

    $ go install github.com/rebuno/rebuno/cmd/rebuno@latest$ git clone https://github.com/rebuno/rebuno && cd rebuno$ rebuno dev --config examples/rebuno.dev.yaml
  2. 02

    Start the example agent

    Install the SDK and start an agent that waits for runs.

    Python

    $ pip install rebuno$ python examples/python/hello.py

    TypeScript

    $ npm install rebuno$ npx tsx examples/typescript/hello.ts
  3. 03

    Start a run

    Create a run, then watch its tool calls get checked and recorded.

    $ rebuno exec create hello '{"query": "hello world"}'$ rebuno exec watch <id>
Rebuno