Skip to content

Repository files navigation

substructure.ai

Build production-ready AI agents in any language with no SDK

cli

Pre-1.0: APIs and the wire protocol may change between releases.

Substructure is an open-source engine that drives the agent loop over HTTP. It keeps sessions durable and streams AG-UI events to your frontend. Your code stays on your infrastructure. It's just HTTP, so the engine is language agnostic. You don't even need an SDK.

Why use it

  • In an existing codebase: Your agent is just an HTTP endpoint. Tools are functions in your codebase, so they already have your database and your permissions.
  • In a new project: The engine already handles sessions, history, streaming, and the client API. You write the agent and its tools, and that's most of the backend.
  • Long-running work: First class support for async tools & async interrupt continuation. So tools can be background jobs and human approval can wait for days if needed.
  • Durability: Every step is saved before it runs, so a deploy, a crash or a client reconnect doesn't lose anything.

See it in action

Install the CLI and declare an agent.

npm install -g @substructure.ai/cli
subs init engine

That writes a substructure.toml. The part that matters is four lines:

[llm.claude]
type = "anthropic"

[agent.assistant]
llm = "claude"
model = "claude-sonnet-4-5"

Send it a message:

export ANTHROPIC_API_KEY=sk-ant-...
subs run --agent assistant -o pretty "hi"

There is no worker yet — the engine derives every step of the turn and, because this agent has no worker URL, takes its own proposal.

Add a worker when you outgrow the file. A worker is an HTTP endpoint the engine asks before each step, so you can override any decision it would have made. Point one agent at it:

[agent.assistant]
llm = "claude"
model = "claude-sonnet-4-5"
worker = "http://localhost:4444"
// A complete worker, served with Node's built-in http server. No dependencies.
import { createServer } from "node:http";

function decide({ proposed }) {
    // The declared agent arrives as the `session.start` proposal, and every
    // other proposal is the step the engine would have taken. Echo them all,
    // then start overriding the ones you care about.
    return proposed;
}

const server = createServer((req, res) => {
    let body = "";
    req.on("data", (chunk) => (body += chunk));
    req.on("end", () => {
        const decision = decide(JSON.parse(body));
        res.writeHead(200, { "content-type": "application/json" });
        res.end(JSON.stringify(decision));
    });
});

server.listen(4444, () =>
    console.log("worker listening on http://localhost:4444"));
node server.mjs                    # one terminal
subs run --agent assistant "hi"    # another

The run command spins up an engine, which drives the turn and streams the response back to the terminal.

Next:

Features

Write only the logic you care about

At each step of the loop, the engine tells your code what it plans to do next. Your code can approve it or do something else. A working agent is a few lines. Custom behavior is added by overriding the proposed decision.

Docs: Core concepts

Examples: Node, Python

Any language

Your agent is an HTTP endpoint. There is no SDK to install, and typed bindings can be generated from the published JSON schema.

Docs: Typed bindings

Examples: Go, Python, TypeScript, Elixir

Crash recovery

Every step is saved before it runs. If the engine or your service dies mid-run, the run continues where it stopped. Submitting the same message twice won't run the turn twice. Client reconnects are handled.

Docs: Durability

Waiting for humans

An agent can stop and wait for a person to respond or approve then pick up where it left off. A waiting agent costs nothing to keep around.

Docs: Interrupts

Tools that take hours

A tool doesn't have to answer right away. Your service can acknowledge the call, do the work on its own schedule and report the result later. The run waits and resumes when the answer arrives.

Docs: Deferred tools

Full chat support

History, editing, regeneration, and branching are built into the engine. You don't build a chat backend. Users can edit an earlier message and go a new direction without losing the original thread.

Docs: Conversations

Examples: Node, Python

Works with existing chat UIs

The engine streams AG-UI events directly, so frontends like assistant-ui and CopilotKit connect to it.

Docs: AG-UI

Examples: assistant-ui, CopilotKit

Tools that run in the browser

A tool can execute in the user's browser instead of on your server. The run pauses until the browser answers, then continues.

Docs: Client-side tools

Examples: Node

Agent state without a database

The engine stores your agent's working state next to the conversation. Your code gets it on every request and can write back changes.

Docs: Agent state

Examples: Node, Python

Sub-agents

An agent can hand work to other agents. Each one runs in its own session, and the cost and token usage roll up to the parent.

Docs: Sub-agents

Examples: Node, Python

Any LLM

The engine can call Anthropic, OpenAI, or OpenRouter with your keys or you can make the LLM calls yourself, through your own gateway or a provider the engine doesn't know about right inside your worker.

Docs: LLMs

Examples: Node + Anthropic, Node + OpenAI, Node + OpenRouter, Python + Anthropic, Python + OpenAI

Retries and timeouts

Set a timeout and retry policy on any tool or LLM call. The engine enforces them, including across restarts.

Docs: Retries and timeouts

Validated tool calls

Give a tool an input and output schema and the engine checks every call against it. Malformed calls go back to the model to fix itself instead of reaching your code.

Docs: Tool calls

Examples: Node, Python, Go

MCP servers

Your worker can connect to an MCP server, expose its tools to the model, and forward each call back to it. The engine never needs to know MCP exists.

Examples: Node, Python, Go

The system pieces

  • Server: The engine that drives the agent loop, written in Rust. It can be run locally on your machine, embedded in process, or as a cloud hosted version available at https://app.substructure.ai. The server drives the loop, handles durability, retries, llm calls (optionally), realtime streaming, subagent supervision and more.
  • Workers: Your agent logic. Receives a decision trigger, returns actions. Runs in your codebase with your dependencies. Can be an HTTP endpoint for use with the cloud/local server, or a callback passed to embedded substructure.
  • Clients: Submit work and stream events back, backend-to-backend or straight from the browser. The engine also serves a native AG-UI endpoint, so any AG-UI chat frontend connects and streams directly.
  • CLI: Substructure comes with a CLI to help you provision, observe, and debug from the terminal. You can also start a local server.

Install

The CLI is available at:

npm i -g @substructure.ai/cli

Docs

Full documentation in docs/.

About

Substructure is an open-source engine that drives your agent loop with JSON webhooks. It keeps sessions durable and streams AG-UI events to your clients. It's language agnostic. You don't even need an SDK.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages