jabuti

status: earlyMITself-hostedTypeScript, no agent framework

A self-hosted agent harness you can read end to end.

Several agents, each with its own model, prompt, tools, skills, MCP connectors and permissions, running real work in a sandboxed shell, behind a web interface you own.

curl -fsSL https://raw.githubusercontent.com/guilhermemauro/jabuti/main/deploy/install.sh | sh

As root on any Ubuntu or Debian VPS. Prints the address and the setup code.

traces/conversation.jsonl · one line per step
{
  "agent": "blog",
  "step": 2,
  "toolCalls": [{
    "name": "mcp__gsc__search_analytics",
    "input": { … }
  }],
  "toolResults": […],
  "inputTokens": …,
  "cacheReadTokens": …,
  "outputTokens": …,
  "costUsd": …,
  "durationMs": …
}

Abridged. Input, tool calls, results, tokens, cost and duration. Traces never leave your machine.

Jabuti /ʒa.buˈtʃi/ is the Brazilian tortoise: slow to start, hard to break, and it always gets there. The harness is built the same way.

Why Jabuti

The loop, written by hand.

Most agent stacks are a framework wrapped around a loop you never see. Jabuti is the loop, small enough to read in an afternoon, and strict about what an agent may do.

No agent framework

No LangChain, LangGraph, CrewAI, Mastra or agent SDKs. The loop, tool registry, context management, permissions, skills loader and agent switching are plain TypeScript. The only libraries are the model API client, the MCP SDK, zod, React, a WebSocket server and Vitest.

One agent per site or project

Each agent is a profile on disk: model, system prompt, budget, tools, shell mode, subagents, MCP connectors and skills, with its own brief, notes and workspace. Switch agents mid-conversation: the history is shared, the capabilities are not.

Real work in a shell

Commands run in a Linux container with Python, one workspace per agent, with no secret from the server. Per agent and per conversation, they run on their own (auto) or wait for your approval on each (ask).

Secrets stay in the server

Keys and tokens are encrypted at rest and reach only the MCP connectors of the agents in their scope. The model never sees them, and the shell never inherits them, unless you mark a variable for an agent's shell.

Anything that speaks MCP

Built in: Google Search Console, Google Analytics 4, PageSpeed, Serper, DataForSEO, Wikipedia and image generation. Or register any remote MCP server, with a token header or an OAuth login, and pick which tools ask before each call.

Skills, loaded only when needed

A skill is a method in a Markdown file. The prompt lists only each skill's name and description; the full method enters the context when a task needs it.

Memory you can read and edit

A brief and dated notes in plain Markdown. After a conversation, a small model proposes what to remember, and nothing is saved until you approve it.

Autonomy with a leash

Milestones you approve, each change proposed as an experiment with a measured baseline, routines on a schedule. Unattended runs never get an approval: what they needed waits in your inbox. Changes ship as pull requests you merge.

Long conversations that stay cheap

Append-only history. Old tool results are cleared from the model's view (and saved to the workspace), and past a larger size the conversation is summarized. Request prefixes stay byte-identical between steps, so prompt caching holds.

What "done" looks like

An SEO agent that does the analysis, not just the chat.

Jabuti runs real SEO work for its author. The case that defines "done" is a single conversation:

  1. mcp__gsc__search_analytics

    Pulls the data

    The agent queries your Search Console property through the built-in MCP connector. The token stays in the server.

  2. shell

    Writes a script

    It writes and runs Python in its own sandboxed workspace to analyze the export: no secrets, no access to other agents' files.

  3. answer

    Hands you the quick wins

    A report of quick wins in the interface, with every step traced, so you can see what it ran and what it cost.

Not into SEO? The same pieces (agents, shell, MCP, skills) work for any project you want an agent you control to work on.

How it works

One server, one loop per conversation, one sandbox per agent.

 browser ──HTTP + WebSocket──▶ server (Node) ──────▶ model provider
   UI      owner session only   ├─ agent registry   profiles on disk
                                ├─ loop             one per running conversation
                                ├─ tools            built-in, MCP, skills
                                ├─ mcp clients      hold connector credentials
                                ├─ agent state      brief, notes, plan
                                ├─ autopilot        routines, inbox, publishing
                                └─ tracer           JSONL per conversation
                                        │
                                        ▼
                               shell runner (container)
                               one workspace per agent, no secrets

A turn

  1. You send a message.
  2. The loop builds the request from the active agent's profile and calls the model.
  3. It runs the tool calls the model returns, asking you when a rule says so.
  4. Every message is appended to the history.
  5. Repeat until the model answers without a tool call, or the turn's budget runs out.

Every tool call gets exactly one result, including on error, timeout or denial.

The Contract

Its promises are tests.

Every guarantee is an obligation in the Contract (C1–C21), each a test run against a scripted fake model: deterministic, free, and run on every commit. A few of them:

C1
The loop ends when the model replies without a tool call, or past the budget (steps or cost): a clean stop with a message, never a crash.
C2
Every tool call receives exactly one tool result, including on error, timeout or user denial.
C3
Shell commands run in a container without secrets, each agent confined to its own workspace.
C4
The system prompt lists only the name and description of each enabled skill. A skill's body enters the context only when the skill is loaded.
C6
History is shared across agents; capabilities are not: a tool call runs only if the current agent has that tool and permission.
C7
Between agent switches, the request prefix is byte-identical across steps, so prompt caching holds.
C11
Every failure (tool error, MCP crash, budget stop, denied approval) appears in the UI with a next step, never only in logs.
C13
The loop is provider-neutral: a provider SDK is imported only in its adapter folder.

Read all 21 obligations in AGENTS.md →

Install

Your server, your keys, your data.

Each option runs deploy/compose.yml: Jabuti, its shell runner and Caddy, which gets HTTPS certificates on its own. You need only a server with ports 80 and 443 open.

Any VPS

As root on Ubuntu or Debian, run the line below. It prints the address and the setup code.

curl -fsSL https://raw.githubusercontent.com/guilhermemauro/jabuti/main/deploy/install.sh | sh

Hostinger

Deploy on Hostinger

The setup code is in Docker Manager → jabuti → logs.

AWS

CloudFormation → Create stack → uploaddeploy/aws.yml. The address is in the stack's Outputs; the setup code in the instance's system log.

DigitalOcean

Create a Droplet (Ubuntu 24.04, 4 GB) and, under Advanced options → Add initialization scripts, paste this line:

curl -fsSL https://raw.githubusercontent.com/guilhermemauro/jabuti/main/deploy/install.sh | sh

Then open the Droplet's console and run this for the setup code:

cd /opt/jabuti && docker compose logs jabuti

Your machine

You need Docker with Compose and anAnthropic API key.

git clone https://github.com/guilhermemauro/jabuti.git
cd jabuti
cp .env.example .env          # set ANTHROPIC_API_KEY (or set it later in Settings)
docker compose up -d --build
docker compose logs jabuti    # prints the one-time setup code

Open http://localhost:4317, enter the setup code and choose the owner password. This setup binds to 127.0.0.1 only.

Then

  1. Open https://<the server's IP, dots as dashes>.sslip.io, such ashttps://203-0-113-10.sslip.io. HTTPS works before you have a domain.
  2. Enter the setup code and choose the owner password right away: until then, whoever has the code claims the instance.
  3. Settings → Model keys: add your Anthropic key.
  4. Settings → Domain: point a domain of your own at the server (anA record) and save it there.

Platforms whose disk is wiped on restart, like Heroku, do not fit: Jabuti keeps its database, notes and agents' workspaces on disk. Full instructions, configuration and the secret key setup are in the README.

What's inside

Batteries, listed.

Built-in tools
shell packages web_read page_structure site_crawl view_images delegate memory plan agents skill
Connectors
gsc ga4 pagespeed serper dataforseo wikipedia nanobanana gptimage plus any remote MCP server you register
Skills
data-study (turn company or public data into a study others cite), plus your own
Models
Claude Opus, Sonnet, Haiku and Fable, through a provider-neutral loop

Security is part of the design

Jabuti runs code that a model writes, so:

  • One owner. Every API route and the WebSocket require the owner's session and refuse other origins.
  • Secrets in one process. Encrypted at rest (AES-256-GCM), never sent to the browser, the model or the shell.
  • Isolated shells. A separate runner container, one Unix user and workspace per agent, never as root.
  • Approvals. Risky commands, package installs, profile, skill and brief changes wait for you, with a diff or the exact arguments.

Found a vulnerability? Report it privately, as described inSECURITY.md.

Project status

Early, and built in public.

It runs real SEO work for its author, but expect rough edges, breaking changes in the data format (handled by numbered migrations) and only one model provider for now. The direction is in SLICES.md, and every non-obvious choice, with its reason, is in DECISIONS.md.

Contribute

From a typo to a new connector. Small pull requests, the Contract first, and no technical debt merged. Open an issue first for anything larger than a fix.

Contributing guideStart with AGENTS.md