Developers
A governance harness with an API.
A default-deny harness around any model: propose → approve → dispatch, a write-only secrets vault, residency-aware routing, everything audit-logged. REST /v1 + WebSocket + MCP.
Base URL https://monopea-runtime.fly.dev
Quickstart
Auth, then one call
Pass your key as Authorization: Bearer …. A brain_live_ tenant key scopes every call to your tenant; a customer_live_ sub-user key scopes to one customer within it, with that customer's usage metered separately.
brain_live_… — tenant key
Full tenant scope: chat, memory, knowledge, proposals, goals, secrets, personas. The hard security boundary — a tenant never sees another tenant's anything (RLS-enforced).
customer_live_… — sub-user key
A private scope within your tenant for a downstream customer: their conversations, memory, documents, and personas are invisible to other sub-users. Denied on the secrets vault and persona authoring.
curl https://monopea-runtime.fly.dev/v1/chat \
-H "Authorization: Bearer brain_live_..." \
-H "Content-Type: application/json" \
-d '{"message":"Reconcile this week'\''s invoices and propose the transfers"}'REST
The core /v1 surface
Everything the agent knows and does, over plain HTTP with a bearer token. The same routes back the dashboard, the WebSocket, and the MCP server — tenant scoping and isolation live in one place.
| Route | What |
|---|---|
| POST /v1/chat | One full agent turn: retrieval, tool loop, proposals. Body: {message, conversation_id?, agent_key?, project_id?, incognito?}. |
| GET /v1/conversations · GET /v1/conversations/:id/messages | Episodic memory — conversations and their messages. |
| GET /v1/memory · POST /v1/memory · POST /v1/memory/archive | Typed durable memory: list (?kind=&limit=), remember {kind, summary, body}, soft-archive {id} — never hard-deletes. |
| POST /v1/knowledge/search · POST /v1/knowledge/traverse | Hybrid semantic search {query, top_k?} → {entities, documents, claims}; graph walk {entity_ids?|query?, max_hops?} → graded facts. |
| GET /v1/proposals?status= · POST /v1/proposals/:id/decide | The approval queue. Decide with {decision: approve|reject, note?}; approve dispatches inline. |
| GET /v1/goals · POST /v1/goals | Long-running goals the agent plans against. |
| GET /v1/secrets · PUT /v1/secrets/:name · DELETE /v1/secrets/:name | Write-only vault. PUT {value, description?}; GET returns name/description/last-4 only — a value can never be read back. |
| GET /v1/personas · GET /v1/personas/:key · POST /v1/personas | The caller’s persona set; one persona’s prompt sections + assembled prompt; author a tenant-owned persona (returns its agent_key). |
| POST /v1/personas/:key/prompts · DELETE /v1/personas/:key | Author/overwrite one prompt section (immutable versions) of a persona you own; delete a persona you own. |
# write-only: PUT a value; GET returns name/description/last-4 only
curl -X PUT https://monopea-runtime.fly.dev/v1/secrets/HUBSPOT_KEY \
-H "Authorization: Bearer brain_live_..." \
-H "Content-Type: application/json" \
-d '{"value":"pat-...","description":"HubSpot private app token"}'
# the agent references it as {{secret:HUBSPOT_KEY}} — plaintext is substituted
# only at dispatch and scrubbed from tool results; the model never sees it# the run is blocked_on_user — list what's waiting, then decide
curl "https://monopea-runtime.fly.dev/v1/proposals?status=pending" \
-H "Authorization: Bearer brain_live_..."
curl -X POST https://monopea-runtime.fly.dev/v1/proposals/PROPOSAL_ID/decide \
-H "Authorization: Bearer brain_live_..." \
-H "Content-Type: application/json" \
-d '{"decision":"approve"}' # approve dispatches inline; or {"decision":"reject","note":"..."}The gate, as an API
The proposals lifecycle
Mutation-capable tool calls are never dispatched inline. The agent drafts the exact call as a pending proposal and the run blocks — status blocked_on_user — until someone with the authority decides. Approval dispatches it; rejection records why. Unknown tools fail closed to review.
1 · pending
An outward mutation queues as a proposal — tool name, arguments, target — and the run reports blocked_on_user. Nothing has executed yet.
2 · approve / reject
POST /v1/proposals/:id/decide with {decision: approve|reject, note?} — from your dashboard, your own UI, or a script. The decision is logged either way.
3 · dispatch
Approval dispatches the call inline and the run resumes with the real result. Per-tool, per-persona autonomy grants can skip the stop for calls you trust — the grants are themselves logged.
WebSocket
Watch the chain of thought, live
Connect to wss://monopea-runtime.fly.dev/v1/ws?key=… (browsers can't set headers on upgrade) and stream every step of a turn as it happens — thinking, tool calls, tool results, proposals — then steer the run mid-flight with a steer frame.
const ws = new WebSocket('wss://monopea-runtime.fly.dev/v1/ws?key=brain_live_...')
ws.onopen = () =>
ws.send(JSON.stringify({ type: 'chat', message: 'Audit the inbound funnel' }))
// server frames, in order:
// ready -> accepted{conversation_id} -> turn_start{tools_offered}
// -> step{thinking, tool_calls} (per LLM step)
// -> tool_call{...} / tool_result{...} (per tool)
// -> proposal{id, ...} (an outward mutation queued for approval)
// -> done{answer, stop_reason, steps} -> result{...}
// steer a run that's already in flight — folded in at the next step boundary:
ws.send(JSON.stringify({ type: 'steer', message: 'Skip the EU leads', conversation_id }))MCP
The brain as an MCP server
@monopea/mcp-server mirrors the /v1 surface as Model Context Protocol tools, so Claude Desktop, Cursor, or any MCP client can query and act through your monopea tenant — with the same scoping, gate, and audit trail, because every tool proxies the runtime rather than re-implementing it.
Tools
- ask_brain
- search_knowledge
- traverse_knowledge
- list_memory
- remember
- archive_memory
- list_experiments
- list_evidence
- list_personas
- get_persona
- create_persona
- set_persona_prompt
- list_jobs
- get_run_tree
- tail_agent
Writes (ask_brain, remember, create_persona, set_persona_prompt) go through the runtime and its gate; archive_memory is a soft archive, never a hard delete.
{
"mcpServers": {
"monopea": {
"command": "npx",
"args": ["-y", "@monopea/mcp-server"],
"env": {
"MONOPEA_RUNTIME_URL": "https://monopea-runtime.fly.dev",
"MONOPEA_API_KEY": "brain_live_..."
}
}
}
}Isolation
Incognito turns & project scoping
Two isolation controls ride on the chat call itself: a no-retention flag for turns that must leave nothing behind, and a project lens that keeps one tenant's parallel workstreams from bleeding into each other.
incognito: true
The turn runs the full loop and still reads existing knowledge, but persists nothing durable — no conversation row, messages, memory, embeddings, summary, or trace. It is still metered. Works on POST /v1/chat and WebSocket chat frames.
project_id
One brain, many projects: a turn scoped to a project recalls and writes within that project only, with no cross-project awareness. Sub-agents inherit the project. The tenant boundary stays the hard one; projects are a working lens inside it.