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
(queryKnowledge → sophia.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.
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.