Architecture
Status: the ownership, layers and .maia scopes below are binding. The
operation registry and maia help --json record what is implemented today; the
roadmap records what is planned. Choices and their reasons are
in the decision log.
Ownership
Maia is the durable control plane and product harness. The official Pi SDK supplies the agent loop, model, tool, provider and session primitives (decision).
| Maia owns | Pi SDK provides |
|---|---|
| Workspaces, Projects, execution environments | model and provider integration |
| Sessions, activations, attempts and workers | agent loop and model response streams |
| durable Session and turn identities | conversation-session mechanics |
| operation schemas and authorization | tools, Skills, prompts and extensions |
| event order, receipts and recovery | steering, follow-up and compaction |
| A2A mapping and external identity | model-facing context construction |
The Maia worker runs outside maia-node and embeds the Pi SDK behind a narrow
adapter. The Node uses only explicit Maia-owned state and never treats Pi as
containment.
Product vocabulary
Workspace is reserved for the organizational scope in
Maia → Workspace → Project. Execution resources are not organizational
layers.
| Concept | Meaning |
|---|---|
| Workspace | Groups Projects; owns shared knowledge and coordinator context |
| Project | Owns focused durable work, knowledge and workers; a repository is optional |
| Execution Environment | Project-owned checkout, Git worktree or admitted private working directory |
| Execution Node | Computer or server hosting execution capacity |
| Execution Profile | Permission and containment policy for execution |
Do not use Container or Execution Workspace as new product labels.
Today's container.* (organization) and workspace.* (execution) APIs keep
their meaning until the
versioned naming migration
(R1b) moves them to workspace.* and environment.*. IDs, history and
receipts are preserved.
Layers
packages/protocolis the source of truth for operation IDs, Effect schemas, errors, events, risk and receipts.packages/sdkprovides typed clients for the same registry.apps/cliis the first complete client and the reference JSON surface.services/nodeowns durable state, transports, ordering, leases and projections.packages/agentimplements the Maia agent on official Pi SDK primitives.- Voice, the Tauri-hosted Expo Desktop app, Expo iOS/Android, the presentation-only site and remote workers are replaceable clients or capacity (decision).
Maia Node remains TypeScript/Effect. Rust is limited to the terminal screen engine/client and the native process/PTY host; Desktop retains its Tauri shell (D-182). Native hosts are bounded adapters, and the terminal client uses the registry-generated binding. Authorization, durable state, orchestration and database migrations stay in the Node.
The external-brain flow today (Claude Code or Codex drives workers through the
maia plugin):
Claude Code or Codex (brain) a person
maia plugin: Skill + MCP tools terminal or Desktop app
| |
maia mcp maia CLI Desktop (Tauri) and Expo client
+------------------+--------------------+
@maiaos/sdk (operations from @maiaos/protocol)
| authenticated loopback HTTP
Maia Node: one per machine and profile
validation, state, events, receipts, accounts
| native process host
+--------------+--------------+
Claude Code worker Codex worker
(Agent SDK) (codex app-server)
one Git worktree and one named account per Session
The Node alone validates, authorizes, stores, executes, orders events and writes receipts. A brain combines create, start, send, wait, observe, interrupt, stop and resume in Skills; Maia adds no hidden planner or agent loop.
Contracts are normative for cross-domain schemas and evolution; operations is normative for deployment, durability, identity, observability, retention and recovery. Domain documents may narrow these contracts but never redefine them.
A business mutation never originates in React, Tauri, Expo, a voice prompt, an A2A handler or a Pi extension; those surfaces submit a protocol operation. Consequential operations follow the mutation rules.
There is no standalone web control application. apps/client is the shared
Expo renderer; its web export is local content in apps/desktop and a test
artifact. apps/site is documentation and marketing only.
Shared Maia configuration
Maia has two .maia scopes. Neither belongs to one component.
| Scope | Location | Purpose | Versioned |
|---|---|---|---|
| user/machine | ~/.maia/ | machine defaults, Project inventory, reusable Agents and Routines, safe references | no |
| Project | <project-root>/.maia/ | portable versioned intent for one Project | yes |
Today no Node code resolves config.jsonc or profiles/<name>.jsonc: the
unwired resolver and writer were removed in F7, and S13 owns the configurator
that replaces them. The Node keeps only the filesystem helpers in
services/node/src/configuration. The agent reads its resources from
.maia/maia.json (see agent resources). The layout
below is the target; there are no competing per-component configs such as
.maia/agent, .maia/node or .maia/swarm. The ~/.maia home itself (installs,
profiles, runtime files) is specified in release
(decision).
User-level Swarm inventory
~/.maia/
config.jsonc machine-local defaults (Maia coordinator Agent, capacity, timezone, Connection refs)
projects.jsonc Project inventory: stable Project ID -> exact root on this machine
agents/<agent-id>/agent.jsonc
routines/<routine-id>/routine.jsonc
Skills are not in .maia: the skills CLI installs them where each harness
reads them and pins them in its lock files (Skills,
decision).
- The inventory accepts absolute canonical paths only: no globs, environment interpolation, home expansion or duplicate active roots. Repository entries also record the normalized Git remote and object format; no path string alone changes Project identity.
- It holds no credentials, leases, health, Session state or admission results. The file is owner-only; locator paths never enter Agent context, provider payloads or ordinary trace exports.
- Both files are candidates, not hot-applied truth. Validation produces a resolved snapshot and diagnostics; admission applies one exact digest with revision compare-and-set and a receipt. Path, capacity, Maia coordinator, authority, Connection or provider changes need preview and confirmation. Missing entries never delete Projects. Relocation is blocked while a Project owns live assignments or unresolved effects. A restart reloads the last admitted snapshot.
Project-local versioned contract
AGENTS.md
skills-lock.json
.agents/skills/<skill-id>/SKILL.md (and .claude/skills/<skill-id>/SKILL.md)
.maia/
config.jsonc
agents/<agent-id>/agent.jsonc, EVAL.md
routines/<routine-id>/routine.jsonc, EVAL.md
| Input | Consumer | Contract |
|---|---|---|
.maia/config.jsonc | Protocol, Swarm, Node, Agent, Providers | Project identity, defaults, limits, context, maintenance, allowed Connection refs |
.maia/agents/*/agent.jsonc | Swarm and Agent | Agent definitions, tool grants, Skills, peer policy, limits, home-Session intent |
skills-lock.json, Skill folders | every harness and the Agent | standard operating procedures, pinned by folder hash (Skills) |
.maia/routines/*/routine.jsonc | Swarm | manual or cron start-or-wake intent and target Agent |
Agent/Routine EVAL.md | Node evaluation | evaluator-only rubric, never Agent context |
root AGENTS.md | Agent context compiler | minimal repository instructions, never authority or configuration |
repository .maia candidate
-> protocol parse and deterministic validation
-> Swarm admission decision
-> Node immutable admitted snapshot
-> bounded Agent, Routine, evaluation and Provider views
packages/protocol owns the schemas for both scopes. No component rereads an
ambient "latest" file during a managed activation; a source change creates a
new candidate snapshot. Runtime state, tasks, goals, events, receipts, traces,
transcripts, credentials, leases and generated artifacts never belong in
.maia/. An optional ignored .maia/state/exports/ holds explicit redacted
exports and is never read as authority. The prompt contract is one root
AGENTS.md plus progressively loaded Skills, with no rules/ tree
(decision).
Agent resources
The implemented maia agent keeps provider credentials in ~/.maia/auth.json,
the default model in ~/.maia/config.toml and Pi's agent directory in
~/.maia/agent (MAIA_HOME moves all three; MAIA_AGENT_HOME moves only the
agent directory). Pi resource discovery is off; Project extensions and Skills
come only from digest-pinned paths in .maia/maia.json, and an extension
loading error stops startup. The details are in the
Agent architecture.
The target replaces .maia/maia.json with the shared scopes above.
Agents, Swarm and coordinators
- One Maia Agent kernel serves local use and every managed Session. Shared code is not a shared brain: Maia, Workspace, Project and worker Sessions each have their own principal, history, grants, credentials, budgets and resources (coordinator ownership, decision).
- Maia Swarm is not another harness. It decides Project, Routine, Session-tree,
queue, budget, wake-up and placement policy; the Node owns and enforces the
durable rows, revisions, events, leases and receipts. Fleet,
Workflow, Task-graph, Bot and Subagent are not public runtimes or CLI roots;
taskis a Session-local planning tool, and durable nested tasks (S53) are run ledger tasks (D-119). - Vendor executors (Codex CLI, Claude Code) are the v1 workers (vendor executors). An external brain drives Maia through the public CLI, MCP and Skills (coordination). Maia is the coordination hub where any agent or harness delegates to another through a Node (decision).
- Worker accounts are named Connections; model providers are per-provider
credentials in
~/.maia/auth.json; onemaia logincovers both (providers, decision). Voice is optional input and output for the exact selected Session (decision). - Semantic evaluation (Choice, Score, Boolean) is one planned Node service (R10a); coordinators use LLM + Jev, workers stay LLM-only (routing). Required AI failure blocks only the affected automation; manual controls keep working.
- The terminal UI is a replaceable debugging client: local
maia agentuses PiInteractiveMode; plannedmaia session attach(S12) renders the Node Session timeline (decision).
Authority and state
Project intent lives in <project-root>/.maia; machine inventory and reusable
user configuration live in ~/.maia. Authoritative runtime data (tasks,
goals, events, receipts, traces, evaluations) lives in Node state under exact
Project and Session IDs. Pi JSONL files and Pi Session IDs are never
authoritative.
"All data possible" means all policy-permitted structured evidence, never secrets or indiscriminate content capture. Raw audio and private content stay excluded by default. Every authoritative transition has durable correlation evidence and a live Node projection; secrets, hidden reasoning, raw audio and forbidden content stay unobservable. Clients, Swarm, evaluators and exporters read shared Node projections and cursors, never worker-owned streams (decision).
Networking and process boundaries
- The Node binds to loopback by default; tailnet reachability never replaces authentication, and Funnel is off (decision).
@maiaos/agentbuilds one official-Pi worker process with a bounded, versioned NDJSON protocol, explicit resource loading, in-memory Pi state, idempotent requests and interruption acknowledgement.- Node Sessions run two kinds of runtime: Pi (the official Pi RPC mode on
macOS, that managed worker under hard containment on Linux and Windows), and
vendor executor Sessions (
claude-code,codex-cli) bound to one named account Connection. When the caller names only the executor kind, the Node picks the least-used account (decision). Other legacy Session runtimes fail closed. - Workers run under the Node's process manager and containment tiers (process tiers, Linux host).
Current architecture (audit, 2026-10-31)
Evidence: a read-only import-graph and grep audit of main at commit
612c8ba2 (F0). Counts come from regex parsing of static import,
export from and import() specifiers in production TypeScript/TSX under
workspace src/ (tests and testing/ folders excluded). Edge kinds are named
with each count: type-only, static-value and lazy (import()).
- Client layering holds.
apps/cli,apps/clientandpackages/client-coreimport only@maiaos/sdk, client-core and (CLI only)@maiaos/agent; none imports@maiaos/protocolor the Node, and none callsfetch, WebSocket orHttpClientdirectly. Tauriinvokestays inapps/client/src/platform/desktop. The Desktop Rust bridge keeps an operation allowlist thatscripts/quality/registry-parity.mjschecks against the registry. Package edges are acyclic:
cli -> sdk, client-core, agent client -> client-core, sdk
client-core -> sdk sdk -> protocol
node -> protocol, agent (sdk: a devDependency, used by the bridge)
- The Node areas are tangled. 31 of the 41 top-level areas of
services/node/srcform one strongly connected component (SCC), counting value edges.storageand the domain areas depend on each other: 24 mutual value dependencies, 26 with type edges.runtimeimportsharness,sessionandstorage. File level, the Node has one 11-file static-value SCC (connection/harness). A separate 178-file SCC, which includes type edges, hangs onstorage/database/node-database.tsand the type-only edgeintegration-authorization-operations.tstoNodeDatabaseResource; removing that edge leaves 177 files. - CLI, client and SDK cycles. The CLI has no static-value cycle: its
4-file anchor passes through a type-only import, and its 57-file component
exists only with lazy value edges. The SDK has 24 cyclic components: 21
Effect/facade pairs, one voice triple and two other pairs; F1 classifies
their kinds, and
node-changesis the one named value cycle. The client has one component whose kinds F1 classifies. Report type, static-value and lazy edges separately. - Node reaches into the agent root.
provider/pool-store.tsandcommand-line/release-command.tsimport home resolution and credential parsers from@maiaos/agent, a wider coupling than the worker protocol adapter. - Repeated primitives. Prefixed UUID helpers (
newId(prefix)and named variants, about a dozen copies) and at least 12 immediate-transaction helpers (examples:agents/preview.ts,hold/preview.ts,routine/preview.ts,run-ledger/journal.ts,voice/storage.ts,terminal/storage/live/*,session/storage/record/transition.ts,orchestration/storage.ts:1827,provider/lease-authority.ts:227, integration-authorizationreplay-store.ts:74,session-credential/store.ts:43). Native-host spawns are documented exceptions. - Unsafe SQL boundary. 14
as unknown asrow assertions on SQLite results (for exampleterminal/storage/live/controller-policy.ts:119) in place of schema decodes. - Unwired configuration.
services/node/src/configuration/resolver.tshas no production importer; only the filesystem helpers are used. (Removed in F7a, 31 October 2026, with its writer, parser andjsonc-parser; the last commit that held them is named in that pull request.) - Size. 169 production TypeScript/TSX files under workspace
src/exceed 400 lines (116 in the Node); the largest,node-database.ts, has 2,273. Native code is outside that count: the Desktop Rust bridge is about 3.8k lines and the Linux process host C about 3.4k. - No cycle rule. Nothing enforces the "keep dependencies acyclic" rule of source organization.
Target architecture
- Every primitive has one owner module with a small documented API. Telemetry, errors, transport and extension admission already have owners; their rules and tests get stronger. No new logging, result, RPC or retry framework.
- Dependency direction is enforced by a graph ratchet in CI that baselines exact edges and membership by kind, so an equal-sized swap fails.
- Package dependencies and adapter ownership are checked against declared
package.jsonentries and allowed subpaths. - Storage contracts belong to their domain, not to
NodeDatabaseResource. - Source files trend under about 400 lines.
- One event core (D-188) is the only way to produce or consume an event.
Decision: D-189.
Foundations sequence
Each PR is independently green, runs pnpm check:changed plus its boundary
tests, and keeps the baseline shrinking. Acceptance for every PR also
preserves telemetry redaction, bounds and flushing; error-to-wire mapping;
transport authentication, deadlines and cancellation; and static registry and
extension admission. The event core keeps its own order, EV0a → EV0b →
{EV0c, EV0d} → EV0e (slices).
| # | Bounded change | Enforcing gate | Evidence |
|---|---|---|---|
| F1 | Dependency graph ratchet | Exact edges inside recorded cycles and membership by kind; new acyclic edges are free; F2 owns package edges | Package-exports resolution, barrel, self-import and equal-size-swap tests |
| F2 | Package and adapter ownership | Declared dependencies and allowed subpaths, bundled SDK use recognized | Rejection fixtures; built-package and bridge behavior |
| F3 | SDK node-changes cycle | F1 baseline shrinks | Stream cancellation and public declarations |
| F4 | Node connection/harness cycle (11 files) | F1 baseline shrinks | Connection and launch failure and cleanup tests |
| F5 | One unsafe SQL boundary (controller-policy first) | Baseline the existing SQL assertions as a shrinking exception list; ban new ones | Malformed-row and error-mapping tests |
| F6 | Immediate-transaction consolidation, in batches | Ratchet transaction entry sites | Commit and rollback failures; time sampled after lock acquisition |
| F7 | Unwired configuration cleanup | Production-entry reachability check | Used filesystem helpers kept; provider behavior; built test paths |
| F8 | Storage contract ownership | F1 baseline shrinks | Integration-authorization contract out of NodeDatabaseResource; authorization and idempotency tests |
| F9 | Client cycles; CLI lazy dependencies apart | Scope-entry rules; F1 baseline | Client startup; CLI help and import cost |
| F10 | Node-local ID consolidation | Ratchet ambient randomUUID calls | Prefixes and entropy kept; deterministic tests |
Imported from Maia OS docs at 3dc3e07