Baize is a single-process Agent runtime written in Go that sits alongside your existing systems. Early on it was little more than a single "think, pick a tool, act" loop. As multi-client access, long-running tasks, connector groups, and other capabilities landed, the architecture converged onto five core abstractions: Runtime, Agent, Tool, Connector, and Run. This article walks through the current structure with one layered diagram: how the different entry points connect, the three execution forms of the Run engine, the three connector types with bidirectional MCP, and the runtime surface that makes every Run auditable, observable, and recoverable.
1. What it is: a standalone Agent runtime
To understand Baize's architecture, the first thing to be clear about is that it is not a library embedded inside your business application. It is a standalone Agent runtime that runs as a sidecar or gateway next to the systems you already have.
In one sentence: it consolidates the LLM, tools, legacy HTTP APIs, and human checkpoints into auditable Runs.
Concretely, it reads API documentation and turns existing system endpoints into capabilities the Agent can call. For write operations such as creating an order or changing a status, it pauses at the web console and waits for human confirmation before executing. The process is visible in the conversation or messaging channel, and full credentials never appear in conversation replay. Usually, no business code needs to change.
This shape gives it a few immediate properties:
- Platforms and integrators connect over REST / HTTP, with no lock-in to any particular language (there is no official language SDK).
- It is a single process with externalized configuration; once stopped, it leaves almost nothing behind on the business side.
- The web console, mobile clients, messaging channels, and incoming alerts all share the same approval, memory, and outbound-reply surface.
Once the product grows from a single conversation loop into multiple clients, systems, and tasks, a straight-line flow can no longer cover everything. The layered diagram below shows the current shape of Baize.
2. The five core abstractions: Runtime, Agent, Tool, Connector, Run
The most fundamental change in the architecture is the convergence onto five abstractions: Runtime, Agent, Tool, Connector, and Run.
- Runtime is the single process itself, responsible for orchestration, scheduling, and lifecycle management.
- Agent is the carrier of conversation and policy; it understands intent, calls the LLM, and decides the next action.
- Tool is a callable capability. Reading an order, checking inventory, or raising an approval are all, from the engine's perspective, uniform tools.
- Connector describes how an external system is connected, how credentials are carried, and where its capabilities come from.
- Run is one complete execution, and the most important abstraction in the system. It records everything a conversation depends on from start to finish — the model, the tool contracts, connector revisions, and human checkpoints — so it can be audited, observed, and recovered.
There is a deliberate choice here: Skills, Memory, Channels, and linear workflows are not promoted to a sixth core abstraction. They live as configuration or plugin forms, layered on top of the five.
The benefit is a stable core: adding a skill, integrating a channel, or defining a workflow does not require changing the Runtime kernel — it is all extension in the configuration and plugin layer. For a project that evolves over a long time, "a small, stable core with extension happening at the outer layer" is what allows it to keep adding capabilities without modules colliding.
3. The three execution forms of the Run engine
Of the five abstractions, the Run engine is where the actual work happens. One engine carries three execution forms, each matching a different kind of task.
The first is the single-Agent ReAct loop, which is the default. The engine advances as "choose a tool, execute, record the trajectory"; the model acts step by step as it reasons, deciding the next move from the previous result. It suits open-ended tasks where the goal is clear but the path must be judged on the fly — for example, "find out why this batch of orders is stuck, and fix the ones you can." When there are many systems, you can enable tool pre-filtering (the "smart acceleration" setting) so a shorter candidate list is handed to the main model; if that fails, it falls back to the full list.
The second is a linear workflow inside a Skill. When a process is a fixed sequence that can be designed up front, you write it into a Skill's workflow.yaml, and the engine executes the steps in order — each step optionally carrying a human approval. These are linear sequences with no branching or loops. It suits predictable, consistency-focused standard processes, such as "verify qualifications, create the order, then notify the contact." Crystallizing a deterministic process into linear steps reduces uncertainty during execution and gives every step a clear checkpoint.
The third is a connector-group sub-run (opt-in). Connector groups themselves only affect the candidate set during tool narrowing; they do not spawn sub-runs automatically. Once "allow sub-runs within a group" is enabled, the model can call spawn_group_run: it derives a sub-run exactly one level deep for a given connector group. The sub-run's tool surface is limited to that group's connectors, and it has its own run binding and its own approvals; the parent enters waiting_child and resumes when the sub-run reports back. Nested sub-runs are not allowed. This is for when business domains such as finance or transactions need their approvals not to collide — the emphasis is on isolation, with each domain's actions and approvals kept separate rather than two orchestrations running concurrently as parent and child. When the same turn only needs to fan out read-only investigations and aggregate results, that uses the engine's built-in parallel read-only calls instead, without consuming a sub-run.
All three forms sit on the same Run engine, the same trajectory, and the same audit model. You do not maintain two frameworks for "open exploration" and "fixed process"; you pick the form that fits the task. The default ReAct loop and the optional linear workflow and group sub-runs can be combined as needed, with group sub-runs always gated by the switch and the one-level constraint.
4. Connector as a first-class citizen: three connector types and bidirectional MCP
Baize is built for the reality of "many systems, a large share of them legacy," which is why connectors are first-class citizens. There are three standard ways to bring an external system in.
OpenAPI wildcard. Upload or point to an OpenAPI / Swagger document, and each interface operation becomes a tool. This is the most direct way to integrate legacy REST systems; names prefer the operationId, and credentials, login, and change comparison are all handled uniformly at the connector layer. If an enterprise already has its own execution entry point and does not want to implement full plugin discovery, it can instead configure an execution callback for the connector: the Runtime POSTs the tool name, arguments, and run_id to that endpoint, which performs the work — no need to implement the /v0/tools discovery endpoint.
HTTP plugin protocol v0. When the API document cannot cover some legacy logic, or a capability needs to be implemented on the business side, you write a small service (a sidecar Connector) that exposes three endpoints per protocol v0: a health check, a tool list, and a tool invocation. The Runtime calls it over the same network, carrying the run identifier, tenant identifier, and protocol version in the headers; how the capability is implemented is entirely up to the business side.
MCP bridge. Baize acts as an MCP client, connecting to an existing MCP Server and pulling its tools into its unified catalog. This lets MCP tools already in the ecosystem be used by Baize directly.
These three sources, together with manually maintained extra tools, are unified into the tool catalog (sources tagged spec, plugin, mcp, and extra). The engine and human checkpoints only read the enabled entries in the Registry; tool names across connectors are required to be globally unique, and the catalog API can still show disabled rows.
Worth calling out on its own: MCP in Baize is bidirectional. The MCP bridge above is Baize acting as a client to consume other services' capabilities. In the reverse direction, Baize can also act as an MCP server, exposing a read-only subset of its tool catalog through /v0/mcp/export to MCP-capable Agent clients such as Cursor and Claude Desktop. This path does not go through Baize's own LLM; it safely shares already-integrated business capabilities with external Agents. An export Key can bind identity slots by connector and role, with the permission boundary corresponding to the downstream business account. When downstream authentication fails, it first guides the user through hosted login rather than pasting a password into the conversation.
5. How a single Run is auditable, observable, and recoverable
Moving an Agent from a demo to long-term operation is not primarily about single-turn capability — it is about every Run leaving a trace, being observable, and being resumable. Baize provides a set of capabilities that work together at this layer.
Run binding (run.binding). Before the first main-model call, every Run records the model endpoint and profile in effect, the policy parameters, memory injection, the narrowed tool contract fingerprint, and the connector catalog revision. When you later trace the basis of a decision in that run, you get the real snapshot from that moment rather than the current configuration, which has since changed. Tool-call events carry the contract fingerprint and catalog revision, so you can compare directly before and after an interface fix.
Human-in-the-loop (HITL). Write operations can be marked as requiring approval; the Run enters a waiting state and resumes after an operator approves or rejects on a card. When a downstream system needs login, the conversation can pause and guide the user to log in via the browser, skip that system for the turn, or end the turn. Long tasks also release the slot while a gate is pending, so a single waiting approval does not tie up a worker thread for the whole Run.
Long-task resilience. For long-running task stability, the engine combines step-budget wind-down, retries and fallback after LLM call failures, timeout splitting, and parallel read-only tool calls. Within a turn it also performs context compression, conversation retrieval, and skill archiving, so a task can keep its goal and continue after many rounds. After user confirmation, one-time, daily, or weekly scheduled instructions can be saved; when the time comes, a new round opens in the original conversation, and write operations still go through approval.
Context management. Long conversations get a rolling summary, and an optional "keep the key points of long conversations" projection changes only what the model sees without rewriting the original messages. Exceptionally large tool results or pasted content are offloaded to the session workspace's offload/ directory; the model only receives a preview and a path, and can read it back in sections with read_file when needed, instead of blowing up the context in one go.
Observability. Structured slog logging, Prometheus metrics, and optional OpenTelemetry tracing cover the Run and tool calls. After a connector is re-imported, the interface change comparison is returned and can be pushed asynchronously via Webhook. Most of these observability options can be viewed and hot-reloaded from the settings page, without editing the config file and restarting each time.
Each piece of this layer deserves its own article; here it is only positioned within the overall architecture, and later posts will expand on them one by one.
6. The second client beyond the server: two mobile paths
Once the Runtime was reliably carrying the server side, mobile was not implemented as a simple mobile web page. Instead, there are two paths with different positioning.
Path one: remote-control console. Open the reachable /ui directly in a mobile browser, or use the Capacitor shell in the repository to wrap a WebView that loads the remote console. Conversation, settings, approvals, and browser login are identical to the desktop. The shell does not run a conversation engine; it effectively turns the phone into a remote control, which suits internal teams integrating with zero development.
Path two: on-device SDK. The Go orchestration core is compiled via gomobile into an Android AAR and an iOS XCFramework (the public binding package is mobileagent) and linked into your own app. On the device it runs a slimmed-down conversation and tool loop with foreground human approval. The app holds the keys and places them in OS-backed secure storage (such as the iOS Keychain or Android encrypted storage), then injects them into the on-device runtime's memory; configuration can be synced from a remote endpoint, and the UI is drawn entirely by the integrator. This path deliberately does not ship the full baize serve stack into the phone — there is no connector farm, MCP, channels, or full HITL product surface; it focuses on in-memory conversations and a small tool set. It suits teams that want to embed Agent capabilities into their own product and have their own requirements for UI and key management.
The two paths do not replace each other: one is a portable entry point to the full console, the other is on-device capability embedded in a self-built app. The full mobile getting-started guide and build steps will be covered in a later post.
7. Storage and deployment
Baize uses a single process with externalized configuration. The main database, middleware (task queue / event bus / rate limiter), and Blob storage all use pluggable drivers: implement the unified interface, register it with RegisterDriver, and select it by name with driver in the configuration, so the Runtime is not locked to any particular database or message middleware. The implementations built into the repository today are:
- Main database: SQLite by default, with Postgres and in-memory implementations.
- Task queue, event bus, and rate limiter: in-memory by default, switchable to Redis when scaling horizontally (multiple replicas).
- Files and attachments (Blob): local files, S3, or in-memory.
Other databases or message queues (for example MySQL or Kafka) can be added under the same driver contract without changing the Runtime kernel — the extension surface is the driver layer, not the orchestration layer. On the model side, any OpenAI-compatible API is supported, and the decision model can run on local Ollama, which further lowers the deployment bar.
This combination lets the project evolve smoothly from "run it on one machine" to "plug into the team's infrastructure": it works with zero dependencies by default, and when you connect to existing team middleware you simply add or switch a driver. For messaging, IM channels are integrated through external adapters using declarative configuration; the repository ships reference adapter examples in the examples directory for you to deploy as needed.
Closing
The thread through this round of architecture evolution is clear: consolidate the LLM, tools, legacy interfaces, and human checkpoints into auditable Runs; keep the core abstractions few and stable; and let the frequently changing parts — skills, memory, channels, and workflows — extend at the configuration and plugin layer. The five abstractions, three execution forms, three connector types, and bidirectional MCP, along with the server Runtime and the mobile client, are all built around that thread.
Next, I'll write separate posts on the two mobile paths, long-task resilience, and the evaluation corpus, explaining the design trade-offs and implementation based on the actual code.
Baize is an open-source project under continuous development (written in Go, MIT licensed). Repository: github.com/rebornace/baize, site: rebornace.github.io/baize. If you are also deploying Agents and working on integrating legacy systems and keeping them running reliably over the long term, you're welcome to open an issue or share your own Agent runtime practices in the comments. I'll keep following up and documenting the architecture as it evolves.

Top comments (0)