Boitata

To understand more the reasoning on the project read my blog post about it.

Boitata is a one-shot, end-to-end coding agent written in Rust. It is inspired by Stripe's Minions and Block's Goose, and it runs tasks unattended with little human input.

What it does

Give Boitata a task in plain language and it plans, reads and edits files, runs commands, and iterates against the compiler and test suite until the work is done. Then it hands you a summary and a full audit trail of every action it took.

  • Unattended runs. A task goes in, a result comes out. No mid-run hand-holding.
  • Determinism first. It leans on real tools (cargo clippy, cargo fmt, cargo test, rg, git) instead of asking the LLM to do everything.
  • Auditable. Every run appends a structured JSONL event log, so you can reconstruct exactly what happened.
  • Composable workflows. Blueprints stitch agent, tool, script, and human-approval steps into graphs (fan-out, retry, verify loops).
  • Extensible. Connect any Model Context Protocol server and its tools become available to the agent loop.

In a hurry?

git clone https://github.com/cats-of-the-world/boitata.git
cd boitata
cargo build --release
export BOITATA_API_KEY="your-key"
./target/release/boitata run "Summarize the files in the current directory"

See Quick Start for the full walkthrough.

This book

SectionWhat you'll find
Getting StartedInstall, configure, and run your first task
ConceptsThe reasoning behind the design
ReferenceEvery knob, tool, and integration
InterfacesThe HTTP server and web UI
ProjectWhere Boitata is headed

Installation

Boitata is a Rust workspace. Building it needs the Rust toolchain plus two external tools: ripgrep (backs the search tool) and git (backs the git_* tools).

Prerequisites

ToolWhyMinimum
Rust toolchainBuild the projectpinned by rust-toolchain.toml
ripgrep (rg)The search tool14.x
gitThe git_* toolsany recent release

The toolchain channel is pinned in rust-toolchain.toml. rustup reads it automatically, so local builds, CI, and new machines all use the same compiler. Crate versions are locked by the committed Cargo.lock.

Automated setup

To set up a new machine deterministically:

./scripts/setup.sh

This installs the exact pinned Rust toolchain, installs the pinned ripgrep release, and checks for git.

Build from source

git clone https://github.com/cats-of-the-world/boitata.git
cd boitata
cargo build --release

The binary lands at ./target/release/boitata.

There is no published binary or crate yet, so build from source for now.

Verify the install

./target/release/boitata --help

Next, configure a provider in Quick Start.

Quick Start

This page takes you from a fresh build to a completed task in a few minutes.

1. Create a config file

Boitata reads its settings from a TOML file. Start from the template:

cp boitata.example.toml boitata.toml

boitata.toml is git-ignored, so your API key never gets committed. The CLI looks for it in the current directory by default; override with --config <path> or the BOITATA_CONFIG environment variable.

A minimal config points at a provider, a model, and an endpoint:

provider = "openai"
model    = "glm-4.6"
base_url = "https://api.z.ai/api/paas/v4/chat/completions"
max_tokens = 4096

Every field is documented in the Configuration reference.

2. Provide an API key

For real secrets, leave api_key blank in the file and export it instead. The environment variable takes precedence over the file:

export BOITATA_API_KEY="your-key"

3. Run a task

# Uses ./boitata.toml
./target/release/boitata run "List the files in the current directory and summarize them"

# Point at a specific config file
./target/release/boitata run --config prod.toml "Read Cargo.toml and tell me the crate name"

The agent loops over LLM calls and tool executions until the task is done, then prints the tool calls it made and a final summary.

4. Read the audit log

Every run appends structured events to boitata-audit.log. Print the log with jq:

jq -c . boitata-audit.log

See Audit Log for the full event schema.

Where to go next

  • Pick a provider. See Providers for Anthropic, OpenAI-compatible, and local Ollama setups.
  • Lock it down. Security covers the tool permission policy and path confinement.
  • Automate a workflow. Blueprints stitch agent, tool, and script steps into retryable graphs.
  • Run it as a service. The Server & Web UI page covers the HTTP/SSE backend.

Determinism First

A core principle of Boitata is to use deterministic tools whenever possible instead of asking the LLM to do everything.

Why

Asking a language model to hand-edit code that a formatter, linter, or test runner can already handle is slow, expensive, and unreliable. A deterministic tool:

  • Reduces token cost. It runs without an LLM round-trip.
  • Improves reliability. The same inputs always produce the same outputs.
  • Enables faster iteration. Quick feedback loops without API calls.

What gets offloaded

TaskLLM approachDeterministic approach
Fix lint errors"Read file, identify errors, fix them manually"cargo clippy --fix
Format code"Identify formatting issues and apply fixes"cargo fmt
Run tests"Generate test code and execute"cargo test
Add dependency"Edit Cargo.toml with correct syntax"cargo add
Fix imports"Parse file, find missing imports, add them"cargo fix --allow-dirty

What the LLM still does

Boitata's tool layer prefers these deterministic operations and uses the LLM only for what genuinely needs judgment:

  • Planning: deciding which tools to use and in what order.
  • Interpretation: understanding tool results and deciding what to do next.
  • Complex changes: non-routine code modifications that lack a deterministic tool.

This division is what lets Boitata run unattended. The boring, mechanical, error-prone steps are pinned to deterministic tools, and the LLM spends its budget on the parts that actually need it.

This principle also shapes the Blueprint system, where a deterministic script node (for example cargo test) can gate an agent node in a retry loop. The agent only re-runs when the deterministic check fails.

Architecture

Boitata is a Rust workspace of small crates. Each crate has one job, and the dependencies point inward toward boitata-core.

Workspace layout

crates/
  boitata-core/          # Config, providers, tools, runtime, audit
  boitata-agent/         # The agent loop (LLM + tools) and the ACP agent server
  boitata-acp/           # Agent Client Protocol: serve an agent or drive one
  boitata-orchestrator/  # Blueprint graphs over the agent (plus sandbox nodes)
  boitata-cli/           # The `boitata` binary
  boitata-server/        # HTTP/SSE server and embedded web UI

Dependency flow

  CLI / Web        HTTP Server        (interfaces)
       \               /
        \             /
         Orchestrator                (blueprints)
              |
            Agent                    (LLM + tools)
              |
    +---------+---------+
    |         |         |
Providers   Tools     Audit          (core)
(Anthropic, (file,    (JSONL
 OpenAI,     cargo,    event log)
 Ollama)     git,
             search,
             MCP)

The crates

boitata-core

The foundation. It holds everything the other crates build on:

  • Config: TOML loading, env-var overrides, and manual Debug impls that redact secrets (api_key, MCP auth_token).
  • Providers: the Provider trait and the Anthropic, OpenAI, and Ollama implementations.
  • Tools: the ToolRegistry, the built-in tools, and the ToolPolicy that gates every call.
  • Runtime: the shared assembly (boitata_core::runtime) that builds the provider, tools, and policy once so the CLI and server share identical setup.
  • Audit: the append-only JSONL event log.

boitata-agent

The core execution engine. It accepts a task description, then iterates through LLM calls and tool executions while maintaining a token-budgeted conversation context, and returns a structured result.

It also ships the boitata-agent binary: the agent exposed as an ACP server over TCP, for running the agent inside a sandbox. See Sandboxed Execution.

boitata-acp

The Agent Client Protocol integration: a serve() that runs an agent as an ACP server, and a run_prompt() client the orchestrator uses to drive an agent running elsewhere. Boitata's audit events ride inside the protocol's message chunks, so the existing audit and SSE stream is unchanged.

boitata-orchestrator

The blueprint system. It compiles a YAML blueprint into a typed graph of agent, tool, script, human, and sandbox (provision / checkout / exec / agent_sandbox) nodes, then executes it with parallel super-steps, in-memory checkpointing, and retry. It talks to sandboxes through a Sandbox backend trait (Docker today, Firecracker planned). See Blueprints and Sandboxed Execution.

boitata-cli

The boitata binary. It parses arguments, loads config via boitata_core::runtime, and either runs a single task or a blueprint, locally or scheduled on a remote server (--remote).

boitata-server

An HTTP/SSE backend with an embedded web UI. It reuses boitata_core::runtime to build the provider, tools, and policy once, then serves them to concurrent runs. See Server & Web UI.

One assembly, many front-ends

Because the CLI and the server both build their runtime through boitata_core::runtime, a task runs identically whether launched from the terminal, from the web UI, or scheduled remotely by the CLI's --remote mode. The agent loop, tools, policy, and audit log are the same in every case.

Sandboxed Execution

By default the agent runs in-process on the host: the tools it calls (file_write, execute_command, git_*, and so on) act on the host filesystem, confined only by the workspace root. That is ideal for a trusted, single-user setup.

Boitata is also growing a second execution model: run the task inside an isolated sandbox, so the untrusted, tool-wielding agent can't touch the host. This page explains how that works and how the pieces fit together.

The target model

  CLI            Server (orchestrator)             Sandbox
 (thin  --schedule-->  --run blueprint-->      +----------------+
  client)                                      | repo + deps    |
        <-- stream events (SSE) -- <-- ACP -->  | boitata-agent  |
                                               | (ACP server)   |
                                               +----------------+
  1. The CLI schedules a task on the server (boitata run ... --remote <url>). It does not run the agent itself.
  2. The server orchestrates: it runs a blueprint that provisions a sandbox, gets the code into it, and delegates the actual work to an agent running inside the sandbox.
  3. The agent runs in the sandbox, exposed as an Agent Client Protocol (ACP) server. The orchestrator drives it over ACP and streams its events back to the web UI and audit log, identically to a local run.

The agent is self-contained: it has its own tools and works on the sandbox's own filesystem, so it never needs the host.

Status: this is the target model. The container blueprint runs today locally via the CLI (boitata run --blueprint examples/blueprints/containerized_task.yaml) and on the server when it is started with a trusted blueprints directory (boitata-server --blueprints-dir examples/blueprints), which is how the server-orchestrated flow above is enabled. The server offers only those vetted blueprints by name, never an arbitrary path from a network request.

Its image must carry the in-container boitata-agent. Build it with docker build -f examples/boitata-agent-rust.Dockerfile -t boitata-agent-rust:latest .

The pieces

Sandboxes (Sandbox trait)

boitata-orchestrator talks to sandboxes through a small backend trait: provision / exec / endpoint / destroy. The current backend is Docker (via bollard); a Firecracker microVM backend is planned behind the same trait for VM-grade isolation. The backend connects lazily, so blueprints that use no sandbox never require a daemon. The executor auto-destroys every sandbox a run provisioned when the run ends, whether it succeeds, fails, or is cancelled.

Container blueprint nodes

Four node types move a run off the host (see Blueprints):

  • provision: create an ephemeral sandbox from an image. Its output is the sandbox id, referenced downstream as {name}.
  • checkout: git clone a repo into the sandbox.
  • exec: run a shell command inside the sandbox, routing on its exit code.
  • agent_sandbox: run the agent inside the sandbox over ACP (see below).

The ACP agent (boitata-agent + boitata-acp)

boitata-agent is a binary that exposes the agent as an ACP server over TCP. It builds a provider and tools from local config (the same boitata_core::runtime wiring the CLI and server use), then answers ACP session/prompt requests by running the agent and streaming its events.

boitata-acp is the shared protocol crate: a serve() that runs the agent as an ACP server, and a run_prompt() client the orchestrator uses. Boitata's own audit events ride inside the protocol's message chunks, so the orchestrator's audit log and web UI stream work unchanged.

The agent_sandbox node

This is the node that ties it together. Given a provisioned sandbox, it:

  1. launches the agent inside it (boitata-agent --addr 0.0.0.0:<port>);
  2. resolves the sandbox's address and waits for the agent to accept connections;
  3. drives it with the ACP client, teeing the agent's events into the run's audit stream;
  4. reports the agent's final message as the node's output and routes on success.
name: containerized_agent
entry: box
nodes:
  box:   { type: provision, image: "your-image-with-boitata-agent" }
  code:  { type: checkout, container: "{box}", repo: "{task}" }
  work:  { type: agent_sandbox, container: "{box}", prompt: "make the tests pass" }
edges:
  - { from: box, to: code }
  - { from: code, when: success, to: work }
  - { from: work, to: END }

The image must contain the boitata-agent binary and a config with provider credentials. Building such an image is part of the upcoming Firecracker/rootfs work.

Isolation and security

The sandbox gives OS-level isolation for the work done inside it. Note the current limits, tracked as follow-ups (see Security):

  • The provision node does not yet apply resource limits or capability drops; vanilla containers share the host kernel. Firecracker or Kata microVMs are the planned answer for a strong trust boundary.
  • The server binds loopback by default and refuses a non-loopback bind unless an API token is configured (api_token / BOITATA_API_TOKEN); every request then requires it. Multi-tenant authentication and authorization (per-user tokens, sandbox tenancy) are still first-class next steps.
  • Provider credentials inside a sandbox are the sandbox's; don't hand a tenant a shared key.

These are why the roadmap treats Firecracker isolation and multi-tenant auth as first-class next steps.

Configuration

Boitata reads its settings from a TOML file (default: boitata.toml in the current directory) so credentials never need to be passed on the command line. The file is git-ignored; commit boitata.example.toml as a template instead.

Finding the config file

The CLI resolves the config file in this order (later wins):

  1. ./boitata.toml
  2. the path passed to --config <path>
  3. the path in the BOITATA_CONFIG environment variable

Start from the template:

cp boitata.example.toml boitata.toml

Secrets

Prefer environment variables for real secrets. They take precedence over the file and keep credentials off disk:

export BOITATA_API_KEY="your-key"
Env varOverridesPurpose
BOITATA_CONFIGconfig file pathPoint at a non-default config
BOITATA_API_KEYapi_keyAPI key (takes precedence over the file)

The Config struct implements Debug by hand to redact api_key, and McpServerConfig redacts auth_token plus the values of env/headers. The secrets are never written to the audit log either.

Fields

Core

FieldTypeDefaultDescription
providerstringnone"anthropic", "openai", or "ollama"
modelstringnoneModel identifier (e.g. "glm-4.6")
api_keystring?noneAPI key. Prefer BOITATA_API_KEY
base_urlstring?provider defaultOverride the provider's endpoint
max_tokensusize?conservativeOutput token budget per request
max_iterationsusize?50Maximum agent iterations before giving up
system_promptstring?sensible defaultCustom system prompt for the agent

Logging

FieldTypeDefaultDescription
audit_logstring?boitata-audit.logPath to the JSONL audit log

See Audit Log for what gets written.

Context management

FieldTypeDefaultDescription
auto_compact_thresholdfloat?0.8Fraction of the context window at which older turns are summarized. 0.0 disables compaction

Tools and security

FieldTypeDefaultDescription
allow_execute_commandbool?trueRegister the arbitrary execute_command tool
workspace_rootstring?current dirRoot that path-taking tools are confined to
confine_toolsbool?trueConfine path-taking tools to workspace_root
tool_policystring?"allow_all""allow_all" or "read_only"
denied_commands[string]?[]Regexes; a matching execute_command is denied

See Security for how these compose.

MCP servers

A list of [[mcp_servers]] blocks. Each server's tools are discovered at startup and exposed to the agent. See MCP Servers.

FieldTypeDescription
namestringNamespacing identifier (e.g. "git")
commandstring?Executable to spawn (selects the stdio transport)
args[string]Arguments for command
envmapExtra env vars for the server process
urlstring?Remote endpoint (selects the Streamable HTTP transport)
auth_tokenstring?Bearer token for HTTP
headersmapExtra HTTP headers

Set exactly one of command or url per server. Setting both, or neither, is a config error that aborts the run.

Blueprints

Only used with --blueprint:

FieldTypeDefaultDescription
blueprint_max_stepsusize?executor limitCap on super-steps (bounds cyclic graphs)
blueprint_max_retriesusize?0Times to retry a failed super-step, restoring pre-step state

See Blueprints.

Complete example

provider   = "openai"
model      = "glm-4.6"
base_url   = "https://api.z.ai/api/paas/v4/chat/completions"
max_tokens = 4096

# audit_log          = "boitata-audit.log"
# max_iterations     = 50
# auto_compact_threshold = 0.8

# --- security ---
# allow_execute_command = true
# confine_tools         = true
# workspace_root        = "/path/to/project"
# tool_policy           = "read_only"
# denied_commands       = ['rm\s+-rf\s+/', 'sudo\b']

# --- MCP servers ---
# [[mcp_servers]]
# name    = "filesystem"
# command = "npx"
# args    = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]

Tools

Built-in tools are organized by category. Every tool runs with a timeout and captured output. On Unix, a timed-out or cancelled command's whole process group is killed so nothing is orphaned.

File system (deterministic)

ToolDescription
file_readRead file contents (line-numbered; paged with offset/limit)
file_writeCreate or overwrite a whole file
file_editReplace a unique, exact occurrence of text in a file
list_directoryList directory contents

Code operations (deterministic)

ToolDescription
cargo_checkRun cargo check
cargo_clippyRun cargo clippy (optional fix)
cargo_fmtFormat code with cargo fmt (optional check)
cargo_testRun tests (optional filter)
cargo_addAdd dependencies (optional features, dev)

Search (deterministic)

ToolDescription
searchCode search via ripgrep

Git (deterministic)

ToolDescription
git_statusCheck git status
git_diffShow changes (unstaged or staged)
git_commitCommit changes (optional all; never pushes)
git_branchList, create, or switch branches

Command execution (semi-deterministic)

ToolDescription
execute_commandRun shell commands with the agent's privileges

Enabled by default; disable with allow_execute_command = false.

Output handling

Every command-based tool keeps its output lean:

  • The tail of the output is kept in the result.
  • When output is truncated, the full output is written to a temp file and its path is included in the result.
  • No interactive stdin.
  • Non-zero exits (compiler, linter, or test failures) come back as output, not errors, so the agent can read them and iterate rather than crashing the run.

How the LLM uses them

The LLM never hand-rolls what a tool can do. It uses the tools for the mechanical work and spends its budget on planning, interpretation, and complex changes. See Determinism First.

Permission policy

Before every tool call the agent consults a policy. Two composable controls gate which tools may run. See Security for the full policy model, read-only mode, and the command denylist.

Security

Boitata runs unattended with the privileges of the process that launches it, so its safety model matters. Three layers compose to constrain what it can do.

1. Tool permission policy

Before every tool call the agent consults a policy. This mirrors the allow/deny decision Goose's permission layer makes, but it is configured up front because Boitata runs unattended.

# "allow_all" (default) or "read_only" (deny any tool that may modify state:
# file_write/file_edit, git_commit, cargo_*, execute_command, ... leaving only
# the read-only tools like file_read, search, git_status).
tool_policy = "read_only"

A tool's read-only status comes from its annotations.

Command denylist

Regexes matched against execute_command command strings; a match is denied:

denied_commands = ['rm\s+-rf\s+/', 'sudo\b', ':\(\)\s*\{']

Denied calls never run. The model receives the reason (so it can adapt) and the denial is recorded in the audit log as a tool_denied event. An invalid denied_commands regex is a config error and aborts the run rather than silently dropping the control.

2. Path confinement

The path-taking tools (file_read, file_write, list_directory, search) are confined to a workspace root, which defaults to the directory Boitata runs in.

  • Absolute paths, .. traversal, and symlinks that escape the root are rejected.
  • Point it elsewhere with workspace_root.
  • Disable confinement entirely with confine_tools = false.
# workspace_root = "/path/to/project"
# confine_tools  = true   # default; set false to allow any path

execute_command runs real shell commands and is not bound by this confinement. It is enabled by default for full capability; for a locked-down deployment, combine confinement with allow_execute_command = false.

3. Secret handling

Credentials never leak through the machinery:

  • Config::Debug is implemented by hand to redact api_key. Never derive it, or the secret leaks anywhere the config is logged or formatted.
  • McpServerConfig redacts auth_token and the values of env/headers, which routinely carry credentials.
  • The audit log never contains your API key, and the log file itself is created owner-only (0600 on Unix) because it records full tool arguments and results, which routinely include secrets.
  • boitata.toml (which may hold secrets) is git-ignored by default.
  • HTTP API (boitata-server): loopback-only by default. Binding a non-loopback address requires an API token (api_token / BOITATA_API_TOKEN), and every request must then carry it (Authorization: Bearer <token>). Provider requests never follow HTTP redirects, so credentials can't be forwarded to a redirect target.

Putting it together

ControlWhat it stopsWhere
tool_policy = "read_only"Any state-mutating toolall tools
denied_commandsSpecific shell commandsexecute_command
confine_tools / workspace_rootAccess outside the workspacepath-taking tools
allow_execute_command = falseArbitrary shell entirelyexecute_command

For a read-only deployment, set tool_policy = "read_only". For a locked-down but still-capable deployment, keep allow_execute_command but add denied_commands and confine the path tools.

Audit Log

Every run appends structured events to a JSONL audit log (default boitata-audit.log, configurable via audit_log in the config file).

Each line is one JSON object tagged with a per-run run_id and an RFC 3339 timestamp, so you can reconstruct exactly what happened, including failures on unattended runs. The log is git-ignored and never contains your API key.

Event types

EventWhenNotable fields
run_startedRun beginstask, provider, model
llm_responseEach LLM call returnstoken usage
tool_callEach tool callarguments, result, read_only flag
tool_deniedA tool call was blocked by the policythe reason
run_completedRun finishessuccess/error, iterations, token totals

The read_only flag on tool_call comes from the tool's annotations, so you can tell observing calls from mutating ones at a glance.

Reading the log

Print with jq:

jq -c . boitata-audit.log

Filter to one run by its run_id:

jq -c 'select(.run_id == "c880...")' boitata-audit.log

Example lines

{"run_id":"c880...","timestamp":"2026-07-24T09:04:08+00:00","event":"run_started","task":"hello world","provider":"ollama","model":"llama3.2"}
{"run_id":"c880...","timestamp":"2026-07-24T09:04:08+00:00","event":"run_completed","success":false,"iterations":1,"error":"Provider error: ...connection refused...","total_input_tokens":0,"total_output_tokens":0}

MCP and remote runs

MCP tool calls go through the same agent loop as built-in tools, so MCP tool calls show up in the audit log too. Tasks scheduled on a remote server log identically to local runs.

Blueprints

A blueprint stitches agent, tool, script, and human-approval steps into a typed graph: hybrid deterministic/agentic workflows with fan-out, retry, and verify loops. This is how Boitata automates multi-step workflows instead of a single free-form agent run.

Blueprints are YAML files you provide. Point --blueprint at a path, either one of the ready-to-copy examples under examples/blueprints/ or your own file:

# a shipped example
boitata run "fix the failing test" --blueprint examples/blueprints/fix_test_failure.yaml

# your own file
boitata run "prepare the repo" --blueprint ./my-blueprint.yaml

Agent nodes in a blueprint inherit the same provider, tools, policy, and agent settings the single-agent path uses.

Schema

A blueprint is a YAML document with a name, an entry node, a map of nodes, and a list of edges:

name: default
entry: main
nodes:
  main:
    type: agent
    prompt: "{task}"
  fmt:
    type: tool
    tool: cargo_fmt
  verify:
    type: script
    run: "cargo check"
edges:
  - {from: main, to: fmt}
  - {from: fmt, to: verify}
  - {from: verify, when: failure, to: main}
  - {from: verify, when: success, to: END}

Unknown fields are rejected (deny_unknown_fields), so a typo fails loudly at load time rather than silently misbehaving.

Node types

Every node is tagged by type (snake_case):

TypeFieldsWhat it does
agentprompt, tools?Run the LLM agent loop on prompt, optionally scoped to a subset of tools
tooltool, args?Invoke a registered tool with arguments (defaults to {})
scriptrunRun a shell script deterministically, routing on its exit code
humanprompt, mode?Pause for human input (human-in-the-loop); mode defaults to input
provisionimage, env?Create an ephemeral sandbox from image; its output is the sandbox id, referenced downstream as {name}. env is a list of variable names to forward into the sandbox (see below)
checkoutcontainer, repo, ref?, path?git clone a repo into a sandbox (path defaults to /workspace)
execcontainer, run, workdir?Run a shell command inside a sandbox, routing on its exit code
agent_sandboxcontainer, prompt, port?, command?Run the agent inside the sandbox over ACP, streaming its events into the run

The last four move a run off the host into an isolated sandbox. See Sandboxed Execution. A sandbox a run provisions is destroyed automatically when the run ends.

Forwarding secrets into a sandbox: a provision node's env lists environment variable names (for example env: [ANTHROPIC_API_KEY]). Their values are read from the orchestrator's environment at run time and injected into the container's environment only. A value is never written in the blueprint, shown in the graph view (which displays the names), or logged. Set the named variables where you run boitata, and keep secrets out of the YAML.

Prompts

{task} in an agent or human prompt is substituted with the run's task description. This is how a generic blueprint adapts to the task you pass on the command line.

Edges

Edges out of a node are either:

  • a single unconditional to, or
  • a set of conditional edges each tagged when: success | failure.

The loader folds the conditional set into one status router. to: END (any case) routes to the terminal sentinel; that path ends the run.

edges:
  - {from: verify, when: failure, to: fix}   # loop back on failure
  - {from: verify, when: success, to: END}   # finish on success

Several edges sharing a when fan out (parallel super-steps); a status with no edge yields an empty set and that path ends.

Execution model

  • Parallel super-steps. Edges that fan out from one node run concurrently (fan-out / fan-in).
  • Checkpoint and retry. A failing super-step can be retried, each attempt restoring the pre-step state from an in-memory checkpoint. Bounded by blueprint_max_retries.
  • Step limit. Cyclic graphs are bounded by blueprint_max_steps.
  • Human-in-the-loop. A human node with mode: approval prompts for a yes/no; an affirmative reply routes onward, anything else ends the run. Requires an interactive stdin; on a non-interactive run the approval node errors rather than proceeding unattended.

Human-in-the-loop nodes are not yet supported over the web UI.

Example blueprints

Boitata ships ready-to-copy example blueprints in examples/blueprints/. They are plain YAML files, not compiled into the binary, so copy one as a starting point and point --blueprint at your copy (or run it in place):

BlueprintShape
defaultagent -> cargo_fmt -> cargo check, looping back on a failed check
fix_lint_errorsagent -> cargo_fmt -> clippy (-D warnings), looping until clean
fix_test_failureagent -> cargo test, looping until the suite passes
setup_devboxscript (devbox init && install) -> agent
human_approvalhuman (approval) -> agent, ending on a negative reply
containerized_taskprovision -> checkout -> agent (in-container) -> cargo test, the agent fixing failures until they pass

Writing your own

Drop a .yaml file and point --blueprint at its path. A common pattern is the verify loop: an agent node does the work, a deterministic script node checks it, and a failure edge routes back to the agent:

name: fmt_then_verify
entry: work
nodes:
  work:
    type: agent
    prompt: |
      Refactor the logging module for clarity. Task context: {task}
  fmt:
    type: tool
    tool: cargo_fmt
  verify:
    type: script
    run: "cargo test && cargo clippy -- -D warnings"
edges:
  - {from: work, to: fmt}
  - {from: fmt, to: verify}
  - {from: verify, when: failure, to: work}
  - {from: verify, when: success, to: END}

On the server

The web UI / server runs blueprints too, but by name rather than by path: start it with --blueprints-dir <dir> and it offers every .yaml in that directory (by file stem) in the run form and at GET /api/blueprints. Only those vetted names are accepted; the server never reads an arbitrary path from a network request. Point it at examples/blueprints, or your own directory:

boitata-server --blueprints-dir examples/blueprints

MCP Servers

Boitata connects to MCP servers using the official rmcp client.

Each server's tools are discovered at startup and exposed to the agent, namespaced as <server>_<tool>, and called through the same agent loop as built-in tools. So MCP tool calls show up in the audit log just like built-in ones. A server that fails to start is logged and skipped, so one broken server can't abort a run.

Transports

The transport is inferred from which field you set on a [[mcp_servers]] block:

FieldTransport
commandstdio: the server is spawned as a subprocess
urlStreamable HTTP: connect to a remote server

Set exactly one of the two per server.

Configuring a server

stdio (subprocess)

[[mcp_servers]]
name    = "filesystem"
command = "npx"
args    = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]

[[mcp_servers]]
name    = "git"
command = "uvx"
args    = ["mcp-server-git"]
env     = { GIT_AUTHOR_NAME = "boitata" }

remote (Streamable HTTP)

[[mcp_servers]]
name       = "remote"
url        = "https://mcp.example.com/mcp"
auth_token = "your-token"          # sent as `Authorization: Bearer <token>`
headers    = { X-Workspace = "acme" }   # optional extra headers

On startup you'll see a line like MCP server \filesystem` connected: 12 tool(s). Credentials (auth_token) live in the git-ignored boitata.tomland are never logged;McpServerConfigredacts them in itsDebug` output.

Resources

When a server advertises the MCP resources capability, Boitata also registers two tools per server so the agent can gather context on demand:

ToolPurpose
<server>_list_resourcesReturns the available resource URIs, names, and descriptions
<server>_read_resourceReads one resource by URI

These count toward the tool total reported at startup and, like all tool calls, appear in the audit log.

See the configuration reference for the full field list.

Providers

Boitata's provider layer is a multi-provider abstraction over the Provider trait. Three providers ship built-in:

Providerprovider valueModels
Anthropic"anthropic"Claude (Sonnet, Opus, Haiku)
OpenAI"openai"GPT (GPT-4o, GPT-4o-mini), and any OpenAI-compatible endpoint
Ollama"ollama"Local models via Ollama

z.ai (GLM models)

z.ai exposes both an OpenAI-compatible and an Anthropic-compatible API, so either provider works against it.

Matches the openai provider exactly:

provider = "openai"
model    = "glm-4.6"
base_url = "https://api.z.ai/api/paas/v4/chat/completions"
max_tokens = 4096

Anthropic-compatible

Uses the native Anthropic Messages API:

provider = "anthropic"
model    = "glm-4.6"
base_url = "https://api.z.ai/api/anthropic/v1/messages"
max_tokens = 4096

Then:

export BOITATA_API_KEY="your-z.ai-key"
./target/release/boitata run "Say hello and confirm the connection works"

The anthropic provider authenticates with the x-api-key header. If z.ai's Anthropic endpoint rejects it in favor of Authorization: Bearer, prefer the OpenAI-compatible config above.

Local models with Ollama

No API key required; just point at a running Ollama instance:

provider = "ollama"
model    = "llama3.2"
base_url = "http://localhost:11434"

API key precedence

For any provider, leave api_key blank in the file and export it instead. The BOITATA_API_KEY env var takes precedence over the config file:

export BOITATA_API_KEY="your-key"

Server & Web UI

boitata-server is an HTTP/SSE backend with an embedded web UI for running agent tasks and blueprints from a browser. It reuses the CLI's runtime assembly (boitata_core::runtime) to build the provider, tools, and policy once, then serves them to concurrent runs. A task runs identically from the terminal, the web UI, or the CLI's remote mode.

Blueprints

The server offers blueprints by name from a directory you point it at with --blueprints-dir. It never reads an arbitrary path from a network request (a path-traversal risk), so only the vetted files in that directory are runnable. Each file is compiled at startup, so a malformed blueprint fails fast. Without the flag, the server runs the single-agent path only and /api/blueprints is empty.

Run

# 1. Build the web UI (once, or after frontend changes). Output goes to
#    frontend/dist and is embedded into the server binary at compile time.
cd crates/boitata-server/frontend && npm install && npm run build && cd ..

# 2. Build and run the server (reads boitata.toml / $BOITATA_CONFIG like the CLI).
#    Pass --blueprints-dir to offer blueprints by name in the API and web UI.
cargo run -p boitata-server -- --addr 127.0.0.1:8787 --blueprints-dir examples/blueprints

Then open http://127.0.0.1:8787 and pick a blueprint (or "Single agent") in the run form.

The backend builds and runs without Node. If the UI hasn't been built, the API still works and the root serves a build hint. For UI development with hot-reload, run npm run dev in frontend/ (it proxies /api to :8787).

Screenshots

Dashboard — run form and run list

Run view — collapsible step groups with live events

Blueprint graph — deterministic and probabilistic steps

HTTP API

MethodPathPurpose
POST/api/runsStart a run: { "task": "...", "blueprint": "name"? } returns { id }. blueprint is a configured name; an unknown name is rejected
GET/api/runsList runs (newest first)
GET/api/runs/{id}Run detail: summary, result, full event log
GET/api/runs/{id}/eventsLive events (Server-Sent Events)
POST/api/runs/{id}/cancelRequest cancellation
GET/api/blueprintsNames of the configured blueprints (empty without --blueprints-dir)

Runs are held in memory (v1); restarting the server forgets history.

Scheduling from the CLI

The boitata CLI can schedule a task on a running server instead of executing locally, streaming the same events to your terminal:

boitata run "fix the failing test" --remote http://127.0.0.1:8787

It POSTs to /api/runs, tails /api/runs/{id}/events, prints the result, exits non-zero if the run failed, and cancels the run on Ctrl-C. The remote run logs to the audit log just like a local one. Over --remote, a --blueprint value must be the name of one the server was started with (--blueprints-dir); to run an arbitrary local .yaml file, drop --remote and run it locally.

Roadmap

Where Boitata is headed. Checked items are done.

Sprint 1: Foundation

  • Provider trait with multi-provider support
  • Agent loop with context management
  • Tool registry and first built-in tools
  • File system tools (read, write, list)

Sprint 2: Tools

  • Code operations (cargo check, clippy, fmt, test, add)
  • Search tools (ripgrep integration)
  • Git operations (status, diff, commit, branch)
  • Command execution with safety checks (timeout, output cap, opt-out)

Sprint 3: MCP Integration

  • MCP client implementation (via rmcp)
  • Tool discovery and registration
  • Remote transport (Streamable HTTP) and stdio
  • Resource access for context gathering

Sprint 4: Blueprint System

  • Hybrid deterministic/agentic workflows (agent/tool/script/human node graphs)
  • YAML blueprint definitions (--blueprint <path> to a user-provided .yaml)
  • Example blueprints under examples/blueprints/ (default, fix_lint_errors, fix_test_failure, setup_devbox, human_approval, containerized_task)
  • Parallel super-steps (fan-out / fan-in), in-memory checkpoint and retry, human-in-the-loop

Sprint 5: Interfaces

  • Cargo workspace of focused crates (core / agent / orchestrator / cli / server)
  • HTTP/SSE server with an embedded React web UI for task monitoring
  • CLI --remote mode: schedule a task on a server and stream its progress

Sprint 6: Sandboxed Execution

  • Container blueprint nodes (provision / checkout / exec) via Docker, with automatic teardown at run end
  • Sandbox backend trait so other backends can slot in
  • boitata-agent as an Agent Client Protocol (ACP) server and client (boitata-acp)
  • agent_sandbox node: run the agent inside a sandbox over ACP
  • Firecracker microVM Sandbox backend (ACP over vsock) and a rootfs image
  • End-to-end: a task spins up a VM with the code, its deps, and an agent that edits, compiles, and tests inside it

Later

  • Container/VM hardening (resource limits, dropped capabilities, network policy)
  • Multi-tenant authentication and per-run credential isolation
  • Snapshot/restore for retries; a durable task queue

Contributing

Contributions are welcome. Feel free to open a pull request.

Development setup

See Installation to get a working build. The development loop is standard for a Rust workspace:

cargo fmt --all              # format
cargo clippy --all-targets --all-features   # lint
cargo test --all-features    # tests

CI runs all three on every pull request (pinned to the same toolchain via rust-toolchain.toml), so a green local run is a good signal.

Editing this book

The documentation lives in docs/ and is built with mdBook:

cargo install mdbook      # if you don't have it
mdbook serve docs/        # live preview at http://localhost:3000
mdbook build docs/        # render to docs/book/

The table of contents is docs/src/SUMMARY.md. Add a chapter by creating a Markdown file and listing it there.

Acknowledgments

  • Stripe's Minions, for the blueprint architecture and determinism-first approach.
  • Block's Goose, for the modular Rust architecture and MCP integration patterns.

License

MIT License. See the LICENSE file for details.