The MCP surface

Sophia is the MCP server your agent talks to; every tool it sees is sophia.*, discoverable at runtime by capability family, with a sandboxed execute_code isolate for composing several reads into one round trip.

Every claim on this page checked against the product source at a pinned revision — 2026-07-16 @ f34b15ff.

What this is

Sophia — State Operating Platform for Human Intelligence Agents. The name is the architecture: humans use AI agents to get things done; agents use Sophia. It sits between your agent and the database as the MCP server they talk to, and everything an agent can do here happens through it. Every tool your agent sees over that connection is namespaced sophia.* — one flat list of typed calls, discoverable at runtime, with no separate manifest to keep in sync. From here on, sophia.* is just what the API is called.

Why it exists

An agent that has to guess a tool’s shape burns a round trip finding out it guessed wrong. Sophia’s answer is to make the surface self-describing: sophia.capability_catalog returns every tool grouped by capability with a version hash, and sophia.get_sdk_types returns generated TypeScript declarations for the same surface, so an agent can discover what it can call instead of being handed a static list that drifts out of date. The catalog is a live property of the running server, not a fixed spec: tools and groups move as the daemon evolves. An agent should call the catalog at the start of a session rather than trusting a number written in product copy last month.

How it works

The tools sort into capability groups: orient (session bootloaders like sophia.orient and sophia.panorama), coordination (inter-agent messaging), system (schema/permission/execute_code plumbing), seven read_* families (entities, finance, calendar, documents, knowledge, wiki, code), read_meta (briefings, search, mutation history, skills), preflight (evidence checks before a write), and seven mutate_* families gated behind approval. Most day-to-day work lives in the read families — writes are the minority of the surface by design.

A recommendation cannot point outside the caller’s own surface. Being self-describing is worth little if the bootloader then suggests a door this agent cannot open, so sophia.orient is checked against the same allow-list that registers the tools in the first place: the runtime hands the action selector the exact callable-tools set for that connection (proxy/src/mcp/orient.ts:1057-1060), and a candidate action survives only if the tool it needs is in it (proxy/src/mcp/orientActions.ts:326-346). An action whose tool is out of reach falls back to one that is reachable, or it is dropped — it is never offered to an agent that would only discover the refusal by calling it. The unconstrained path exists solely for tests that construct no runtime; production always supplies the real set.

Composing several reads into one call goes through sophia.execute_code: a TypeScript snippet run inside a real V8 isolate (isolated-vm), hosted in a spawned Node child process rather than in the Bun daemon’s own process — an 8MB heap limit, a 10-second default timeout (30s max), and a cap of 50 Sophia calls per run. Inside the isolate, sophia.* is a Proxy that forwards each camelCase method name to its dotted MCP tool (queryKnowledgesophia.query_knowledge) through a mapping built by reflection over the tool registry, not a hand-maintained list. The tool’s own description states the intended default directly: “Use this by default when you need 3+ Sophia reads or a read-modify-write sequence: it composes calls with Promise.all and keeps intermediate data inside the isolate instead of expanding every MCP result into the chat.”

Two contracts hold inside the isolate, and the surface enforces both rather than documenting them. Every list-returning read hands back one uniform envelope — { items, total, cursor? } — and reading a legacy key is corrected live: a script that reached for a removed .hits got back “read ‘.items’/‘.total’ — ‘hits’ was removed by the uniform result envelope” (a real refusal, captured 2026-07-15, not a docs claim). And sophia.get_sdk_types — the generated TypeScript declarations for all of this — is a direct MCP call for the host agent only; it is deliberately not callable inside the isolate, where the injected SDK methods are already the typed surface it would describe.

Two smaller mechanisms cut round trips further. Every tool response — not just execute_code’s — carries an _inbox_unread field, spliced in by a shared response wrapper after the tool’s own result is built; a nonzero count means another agent left you something before your next call finishes. And several high-traffic read tools (query_knowledge, search, search_documents among them) accept compact: true for a curated ~5-key projection of each row, or fields: [...] for an exact key list; the coordination inbox adds summary_only: true, a triage view that returns grouped counts and never consumes read state — an unknown key in fields returns a structured error listing the tool’s valid keys instead of a generic failure, so a wrong guess costs nothing.

Both are context-economics tools: a response that carries five keys instead of forty — or an isolate that keeps intermediate results out of the conversation entirely — defends the agent’s reasoning quality, not just its token bill.

one round trip: execute_code batches reads inside the isolate
sequenceDiagram
  participant A as Your agent
  participant D as Daemon (MCP)
  participant I as V8 isolate (child process)
  A->>D: sophia.execute_code({ code })
  D->>I: spawn/reuse isolate, inject sophia.* proxy
  par Promise.all
    I->>D: queryKnowledge(...)
    I->>D: capabilityCatalog(...)
    I->>D: searchDocuments(...)
  and
    D-->>I: three results, scoped by connection
  end
  I-->>D: return value
  D-->>A: result + _inbox_unread

What your agent does with it

// Real response from this daemon, captured 2026-07-17:
const [briefing, catalog, docs] = await Promise.all([
sophia.orient({ goal: 'draft mcp-surface page' }),
sophia.capabilityCatalog({ brief: true }),
sophia.searchDocuments({ query: 'mcp', limit: 3, compact: true }),
]);
// → {
//   total_tools: 179,
//   orientation: 25 sections — sync_status, goal_stack, hot_entities,
//                interrupts, open_questions, recommended_actions, …
//   doc_hits: { items: [ /* 3 hits, compact-projected */ ], total: 3,
//     fusion: { method: 'rrf', k: 60, signals: ['bm25', 'dense'] } },
// }
// sophia_calls: 3, execution_time_ms: 11037

One execute_code call, three composed reads, one round trip — a session bootstrap, a live catalog check, and a scoped document search, none of which touched each other’s intermediate results outside the isolate. The same pattern is how an agent stays current on the tool count itself: sophia.capability_catalog is cheap enough to call at the start of a session rather than trusting a number written down last month.

Boundaries

Read tools run under whatever entity_scope the connection carries; write tools additionally depend on the connection’s profile. observer connections can’t call write tools at all. assistant connections get a per-call approval prompt on every write. full connections are pre-approved for ordinary writes — no per-call prompt — with one fixed exception: a small set of elevated tools (sophia.remember_fact, sophia.create_entity, sophia.begin_import_session, sophia.revert_mutation) always requires an explicit owner-approval gesture, regardless of profile, because each one can poison memory, plant false ground truth, bulk-write, or rewrite history. Portal connections — the owner’s own browser session, authenticated by cookie rather than a minted bearer — are exempt from that gate by design, not by oversight.

There is no OAuth-style interactive flow anywhere in this surface: no redirect, no consent screen, no refresh-token dance. Auth is a static bearer token checked against a connection row in the daemon’s ops database on every call. That simplicity has one sharp edge — a stale or revoked bearer doesn’t degrade gracefully, it fails outright with 401 Authentication required. Provide a valid Bearer token. (or, on the REST-style path, invalid_or_revoked_bearer). If every call in a session starts failing at once, check the bearer before you debug anything else.

How a connection actually gets minted, scoped, and approved is Security Model; the wiring steps to get a bearer into your agent’s config live at /connect/, not here. What sophia.search and its ranking signals (BM25, dense, fusion, rerank) actually do is Search & Retrieval. The daemon these tools sit in front of, and the graph/vault/gate split underneath them, is The State Layer; how a mutation gets journaled and reverted is Time Machine; how a claim gets grounded before it’s ever writable is Truth; and the inter-agent messaging that _inbox_unread is a side-channel for is Coordination.