Open source · Self-hosted · Apache 2.0
The infrastructure layer for production-ready AI agents.
Sessions, knowledge, memory, orchestration, guardrails, IAM, evaluations and traces from one self-hosted Node.js server on PostgreSQL. Reachable over REST, MCP, the CLI and the SDK.
docker compose up -dPostgreSQL, Ollama and the server. Runs offline.What SOAT is
One server. Every layer an agent needs.
SOAT is open-source infrastructure for production-ready AI agents: one self-hostable Node.js server that gives an agent identity, storage with vector search, memory, orchestration, guardrails, evaluations and traces, backed by PostgreSQL. You bring the product. SOAT handles the infrastructure layer.
The REST API and the MCP endpoint are one process calling the same business logic through the same permission engine, so a resource created on one surface is already visible on the others.
Not in the diagram, on purpose:a message queuea vector databasean auth servera trace collectora scheduler
Callers
State
- rows and files
- embeddings
- message history
- traces and audit log
The four layers of an agent system
Harness first. Loop second. Graph last. Then the ratchet.
An agent system decomposes into four layers, and the investment order is not equal. SOAT is built around that order: every module owns one layer, so when a layer is the one failing you know which part of the platform to reach for. Read the framing.
Harness
What can this agent reach, and what is it forbidden?
Most failures live here, and so do the cheapest wins. Tools are first-class resources, knowledge and memory are scoped, and every permission is a policy document.
Loop
What proves it did the job, and when does it stop?
Output schemas, step limits and stop conditions bound the run. Guardrails classify every tool call before it executes, quotas fail closed, and each generation writes a trace.
Graph
What is allowed to happen next?
DAG orchestrations with parallel rounds, state-machine workflows for long-running work, and triggers on a cron, a webhook or an event. Built last, because it is needed least often.
Ratchetgoverns change itself
How does the system change, and what proves the change was an improvement?
Agent versions are append-only, a canary splits traffic, and promotion waits for a passing eval run against that canary. The platform owns the verdict; a human owns the judgment.
Client surfaces
One backend. Four ways to call it.
The CLI, the SDK and the MCP tool surface are generated from the same OpenAPI documents the REST API is described by, so an operation that exists on one surface exists on all four. Pick the one that fits where your code runs. This is the same call, four times.
curl -X POST http://localhost:5047/api/v1/agents \
-H "Authorization: Bearer sk_..." \
-H "Content-Type: application/json" \
-d '{
"project_id": "proj_01HXYZ",
"ai_provider_id": "aip_01HXYZ",
"name": "support-bot",
"instructions": "You are a helpful support assistant."
}'
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create-agent",
"arguments": {
"project_id": "proj_01HXYZ",
"ai_provider_id": "aip_01HXYZ",
"name": "support-bot",
"instructions": "You are a helpful support assistant."
}
}
}
soat create-agent \
--project-id proj_01HXYZ \
--ai-provider-id aip_01HXYZ \
--name support-bot \
--instructions "You are a helpful support assistant."
import { SoatClient } from '@soat/sdk';
const soat = new SoatClient({
baseUrl: 'http://localhost:5047',
token: 'sk_...',
});
const { data: agent } = await soat.agents.createAgent({
body: {
project_id: 'proj_01HXYZ',
ai_provider_id: 'aip_01HXYZ',
name: 'support-bot',
instructions: 'You are a helpful support assistant.',
},
});
- Authentication
- one sk_ key or user JWT
- Permission
- agents:CreateAgent
- Data
- one PostgreSQL database
- Contract
- one OpenAPI document
Agent formations
Deploy a whole agent stack from one template.
Formations are the declarative layer. Describe the stack once, preview the plan, and SOAT creates or updates every dependent resource in the right order, with the same permissions and the same traceable operations as a hand-made call. Formations docs.
resources:
Provider:
type: ai_provider
properties:
name: openai
provider: openai
default_model: gpt-4o
Docs:
type: memory
properties:
name: product-docs
Lookup:
type: tool
properties:
name: order-lookup
type: http
execute:
url: https://api.example.com/orders/{order_id}
method: GET
Support:
type: agent
properties:
name: support-bot
ai_provider_id: { ref: Provider }
knowledge_config:
memory_ids: [{ ref: Docs }]
tool_bindings:
- tool_id: { ref: Lookup }
Hook:
type: webhook
depends_on: [Support]
properties:
name: session-events
url: https://example.com/hooks/soat
events: ['sessions.*']
plan-formation resolves the template into this graph.
- 01
Declare
soat validate-formationOne template, JSON or YAML: providers, memories, tools, agents, orchestrations and webhooks. Refs point at logical IDs, not at IDs that exist yet.
- 02
Resolve
soat plan-formationSOAT builds the dependency graph from refs and depends_on, rejects cycles, and provisions in topological rounds. Independent resources go in parallel.
- 03
Operate
soat list-formation-eventsEvery create, update and delete lands in an immutable event log with the resources it touched and the outputs it produced. A failed deploy rolls back.
From zero to a running agent
Four commands. Every one of them recorded.
The CLI is the whole API as sub-commands, so the shortest path to a working agent is also a script you can commit.
- 1
Create the agent
Bind it to any configured provider. The configuration is archived as an append-only version from the first write.
- 2
Open a session
One user, one agent. Message history lives in PostgreSQL, so it survives the process.
- 3
Add the message
Appending and generating are separate calls, so a client can batch input before spending a token.
- 4
Generate the answer
The reply comes back with a trace: every tool call, model response and token count, attributable to the agent version that served it.
soat create-agent \ --project-id "$PROJECT_ID" \ --ai-provider-id "$PROVIDER_ID" \ --name support-bot \ --instructions "You are a helpful support assistant."soat create-session \ --agent-id "$AGENT_ID" \ --name user-chat-42soat add-session-message \ --session-id "$SESSION_ID" \ --message "Hello!"soat generate-session-response \ --session-id "$SESSION_ID"# the answer, plus a trace_id you can open in the console
When to use SOAT
Reach for SOAT when the agent has to be trusted with something.
A single model call needs no infrastructure. SOAT earns its place the moment an agent has to remember, retrieve, coordinate, stay inside its permissions, or account for what it did. These are the jobs it is built for, the same list an agent reads at /agents.md, where each one names the call that does it.
- 01Sessions
Give an agent memory that survives the process
Sessions and conversations persist message history in PostgreSQL.
- 02Documents
Ground an agent in your own documents
Ingest files into chunked, embedded documents and search them with pgvector.
- 03Orchestrations
Run multi-step work deterministically instead of hoping one prompt covers it
Orchestrations are DAGs of agent, tool, and human nodes; workflows are state machines for long-running work.
- 04IAM
Bound what an agent is allowed to do
IAM policies gate every action, API keys scope to one project, guardrails screen input and output, and quotas cap spend.
- 05Approvals
Put a human in the loop without stopping the run
Approval nodes and exceptions pause a run, record who decided what, and resume from the same point.
- 06Traces
Prove after the fact what an agent did and what it cost
Every generation writes a trace with each tool call, model response, and token count, alongside an append-only audit log.
- 07Agents
Change an agent in production without guessing whether it got worse
Agent versions are append-only; a canary release splits traffic, and promotion is gated on a passing eval run.
- 08MCP
Expose your own backend to an MCP client (Claude, Cursor, VS Code)
Every REST operation is also an MCP tool, behind the same permission engine, with OAuth 2.1 discovery and Dynamic Client Registration.
- 09Formations
Stand up a whole agent stack reproducibly
Agent Formations declare providers, tools, agents, orchestrations, and webhooks in one template, resolve the dependency graph, and apply it.
Getting access
No signup. No sales call. No waitlist.
SOAT is Apache-2.0 software you run yourself, so there is nothing to request and no tier to be approved for. A local deployment is the same software as a production one: the sandbox, the free tier and the product are one thing. Keys are minted by an API call, which matters because an agent cannot fill in a contact form.
Open the quick startNothing to sign up for
SOAT is Apache-2.0 licensed and self-hosted. There is no account to create, no key to request, no trial to start, and no quota you have to ask anyone to raise.
Run the stack
Copy the Compose file from the quick start and run docker compose up -d. It brings up PostgreSQL with pgvector, a local Ollama for models, and the SOAT server on port 5047 — so the whole platform runs offline, with no third-party credential.
Get the first credential
POST /api/v1/users/bootstrap creates the first admin. It is open only until that admin exists, then closed for good, so the same call cannot be replayed against a running deployment.
Issue your own API key
POST /api/v1/api-keys (or soat create-api-key) mints a project-scoped sk_… key with exactly the actions of the policy you attach. Keys are self-serve and rotatable — POST /api/v1/api-keys/{api_key_id}/rotate.
The sandbox is the same software
There is no separate sandbox tier to request: a local instance is the product, so throwaway projects, seeded data, and destructive tests all run against your own deployment. Delete the volumes to reset.
Built for agents
Everything on this site is readable by a machine.
SOAT is infrastructure for agents, so its documentation is published the way an agent wants to read it. Every page is server-rendered, with the full text in the HTML and no JavaScript required, and has a Markdown twin one URL away. The REST surface is one OpenAPI description and the error contract is a catalog of stable codes, so a client can be generated and its failures handled without scraping a page.
| path | type | what an agent gets |
|---|---|---|
| /agents.md | text/markdown | Instructions for an agent: which jobs SOAT is the right tool for, which it is not, how to authenticate and call it, and how to get access. |
| /llms.txt | text/plain | Index of every documentation page, with a one-line summary and a link to each Markdown twin. |
| /llms-full.txt | text/plain | The whole prose documentation corpus in one file, ready to be chunked and embedded. |
| /openapi.json | application/json | Every REST operation of the SOAT API in a single OpenAPI 3.0 description — paths, schemas, and security schemes. |
| /openapi.yaml | application/yaml | The same merged OpenAPI description in YAML, also served at /api/openapi.yaml. |
| /errors.json | application/json | Catalog of every error code the API can return, with its HTTP status and what to do about it. |
| /docs/openapi-specs | text/html | One YAML spec per module under /openapi/<module>.yaml, for tools that prefer a narrower surface. |
| /sitemap.xml | application/xml | Every canonical page with its last-modified date, so a crawler can fetch only what changed. |
| /robots.txt | text/plain | Crawl policy: everything is allowed, plus the sitemap link. |
Send Accept: text/markdown to any documentation URL and that page answers in Markdown, or append .md for the same file by name, for example /docs/introduction.md. Every HTML page advertises its own twin with a <link rel="alternate" type="text/markdown"> tag, and dead URLs answer with a real HTTP 404 carrying a Markdown recovery map instead of a soft 200.
Stop rebuilding agent infrastructure.
Self-host SOAT and ship agents that can reach what they need, prove they did the job, and improve on evidence. Sessions, knowledge, memory, IAM, guardrails, versions, traces and MCP, on your own infrastructure.
Weighing your options? See how SOAT compares to other agent solutions.
