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.

POST /v1/chat
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.

RouteWhat
POST /v1/chatOne full agent turn: retrieval, tool loop, proposals. Body: {message, conversation_id?, agent_key?, project_id?, incognito?}.
GET /v1/conversations · GET /v1/conversations/:id/messagesEpisodic memory — conversations and their messages.
GET /v1/memory · POST /v1/memory · POST /v1/memory/archiveTyped durable memory: list (?kind=&limit=), remember {kind, summary, body}, soft-archive {id} — never hard-deletes.
POST /v1/knowledge/search · POST /v1/knowledge/traverseHybrid 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/decideThe approval queue. Decide with {decision: approve|reject, note?}; approve dispatches inline.
GET /v1/goals · POST /v1/goalsLong-running goals the agent plans against.
GET /v1/secrets · PUT /v1/secrets/:name · DELETE /v1/secrets/:nameWrite-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/personasThe 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/:keyAuthor/overwrite one prompt section (immutable versions) of a persona you own; delete a persona you own.
Write-only secrets
# 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
Decide a proposal
# 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.

wss:// — live chain of thought
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.

MCP client config
{
  "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.