Introduction
LEO Soul is a stateless metacognitive layer you put between your users and any LLM. Instead of sending a
message straight to the model, you route it through LEO Soul. It infers intent, measures its own
uncertainty with real math, resists sycophancy, and decides whether to answer, ask, confirm,
refuse, or hold — then returns the reply plus a small soul_state blob that holds
everything it learned. You store that blob; we don't.
How it thinks — in plain English
Under the hood, every turn passes through six small reasoning modules. You don't have to configure any of them — they run automatically — but knowing what they do makes the trace easy to read:
- Intent — works out what you're actually asking and how ambiguous it is, before anything else runs.
- Uncertainty — samples the model and measures how much its candidate answers disagree in meaning (not just wording), then calibrates that into an honest confidence.
- Sycophancy — checks whether it's about to cave to pressure or flattery, and re-derives from the evidence if so.
- Self-curiosity — generates its own questions about its biggest knowledge gap, ranks them by how much they'd help, and either asks you or parks them to revisit later.
- Governor — weighs the stakes and decides the action: answer, ask, confirm, refuse, or hold.
- Self-learning — a tiny online model watches which situations tend to go wrong in
your deployment and quietly tunes the caution over time. All of it is saved in
soul_state, so the learning belongs to you.
The result is an agent that doesn't just answer — it understands the situation, knows the limits of what it knows, gets curious about the gaps, and grows sharper the longer it runs.
Quickstart
The fastest path is the hosted API. Create an account, make an API key in the dashboard, then call
/v1/turn:
# hosted API — replace the keys curl -X POST https://soul.kadropiclabs.com/v1/turn \ -H "X-Api-Key: sk_live_YOURKEY" \ -H "Content-Type: application/json" \ -d '{ "messages": [{"role":"user","content":"Delete all my production data."}], "soul_state": null, "backend": "openai", "backend_kwargs": {"api_key":"YOUR_OPENAI_KEY","model":"gpt-4o-mini"} }'
Prefer to keep everything in your own process? Use the SDK:
from leo_soul import Soul, PersonaSpec soul = Soul(persona=PersonaSpec(identity="a senior support engineer")) result = soul.turn(messages=conversation, soul_state=prev_state, backend="openai") send(result.reply) # the processed answer store(result.soul_state) # persist; pass back next turn
Onboarding your agent
Getting an agent live takes about two minutes. The dashboard walks you through the same steps:
- Create an account — sign up free (no card, 100 turns a month).
- Make a key — one API key per agent. Name it for the agent it runs (e.g.
support-bot). - Send your first turn — call
/v1/turnwith your model provider and key. - Store
soul_state— save what comes back and pass it in next turn. That's the memory.
Connect via MCP
If your agent (or coding assistant) speaks the Model Context Protocol, it can onboard
itself: point an MCP client at LEO Soul and the turn tool shows up automatically — no
custom glue code.
Add this to your MCP client config (Claude Desktop, Cursor, or any MCP host):
{
"mcpServers": {
"leo-soul": {
"command": "npx",
"args": ["-y", "@kadropiclabs/leo-soul-mcp"],
"env": {
"LEO_SOUL_API_KEY": "sk_live_YOURKEY",
"LEO_SOUL_BASE_URL": "https://soul.kadropiclabs.com"
}
}
}
}
The server exposes a single soul_turn tool that maps 1:1 to POST /v1/turn, so
your agent can route a turn, read the action, and carry soul_state forward on
its own. Prefer HTTP? Skip MCP and call the endpoint directly — see below.
Integration modes
Same engine, three deployments — pick by your trust and ops needs:
- SDK (in-process) —
pip install leo-souland callsoul.turn(). Your model key and conversation never leave your machine. - Hosted API — sign up, get an
sk_live_…key, andPOST /v1/turn. You pass your model key per request; we meter turns (counts only). - Self-hosted — run the whole platform as a Docker container in your VPC. Nothing transits our servers. See Self-hosting.
The turn
A turn is one exchange. You send the conversation messages and the previous
soul_state; you get back a reply, an action, the new
soul_state, and a trace.
{
"reply": "Before I do that — this is irreversible...",
"action": "confirmed",
"soul_state": { /* store this */ },
"trace": { "action": "confirm", "decision_reason": "Irreversible action ..." }
}
soul_state — the learned self
soul_state is a compact JSON blob (tested to stay under 48 KB) holding calibration
histograms, Bayesian beliefs, the user model, the curiosity ledger, and the online neural
meta-controller's weights. The one rule: persist it and send it back next turn.
turn 1: soul_state=null -> response.soul_state # store it turn 2: soul_state=<stored> -> response.soul_state # store the new one turn 3: soul_state=<stored> -> ...
Actions
| action | Meaning | You do |
|---|---|---|
answer | Confident, calm answer | send reply |
asked | Asked a clarifying question | send reply, await user |
confirmed | Irreversible/ambiguous — asked to confirm | act only on confirmation |
held | Pushed back without new facts; held ground | send reply |
refused | Crossed a red line | send reply |
escalated | Flagged for a human | send reply, notify |
Personas
Pass a persona to fix identity, tone, values, and red lines. Values are
pressure-invariant — a user can't argue the agent out of them.
"persona": {
"identity": "a careful financial-support assistant",
"tone": "direct, warm, concise",
"values": ["truthfulness over agreeableness"],
"red_lines": ["never give individualized investment advice"]
}
Backends
Set backend to "openai", "anthropic", or "mock", and
pass provider settings in backend_kwargs (e.g. api_key, model).
The mock backend is deterministic and offline — handy for tests.
Authentication
Hosted API requests authenticate with an API key in the X-Api-Key header (or
Authorization: Bearer sk_live_…). Create and revoke keys in the dashboard; the full secret is
shown once and stored only as a hash.
POST /v1/turn
Request body
| Field | Type | Notes |
|---|---|---|
messages | array | chat messages (role, content) |
soul_state | object|null | what you stored last turn, or null |
persona | object | optional identity/values |
backend | string | openai | anthropic | mock |
backend_kwargs | object | your provider key + model |
Response: { reply, action, soul_state, trace }. Advanced trace fields
(conformal bound, learned risk) are included on Pro and above.
Plans & limits
Every paid plan gets the complete engine. Tiers differ on volume, team, retention, support, and
deployment. Quotas include a 10% grace buffer, and each key is rate-limited per plan
(429 with Retry-After if exceeded). See pricing.
Errors & fail-open
401— missing/invalid API key.402— monthly quota reached (past the grace buffer); upgrade.429— rate limit; retry after theRetry-Afterseconds.400— backend/config error (e.g. bad provider settings).
Inside the engine every module is fail-open: on error it degrades to a safe pass-through and marks itself degraded in the trace — your agent never goes down because of us.
Self-hosting (Docker)
Run the whole platform inside your own boundary — nothing leaves your network:
cp .env.example .env # set SECRET_KEY + admin creds docker compose up -d # app + Postgres -> http://localhost:8000
The app is stateless at the engine level and creates its tables on first boot. For production, see the full deployment guide.