Maia Node architecture
Status: source candidate today for the local Node foundation (the loopback
HTTP/WebSocket operation API, the maia.sqlite journal, observe.events,
session.observe with its attention value, the S19 Run ledger, R1a
interactions, Session-bound Invocation attachments and
the Session drivers for Codex,
Claude Code and Pi). Everything else is planned, for R1, R3, R4, R10a/R10b,
S12, S17/S18, MN0–MN5 and A2A ingress after S17; statements marked "today" are
verified against the source.
Outcome
Maia Node is the durable control plane and execution environment for Maia Agent Sessions. It alone persists and enforces state, identity, authorization, worker processes, effect reconciliation, events, receipts, traces, evaluations and recovery. It does not own the model loop, multi-Project scheduling decisions or provider semantics.
CLI / SDK / Swarm / A2A
|
authenticated operation transport
|
Maia Node
protocol registry | SQLite | receipts | JSONL evidence
|
process supervisor + secret resolver
|
out-of-process Maia Agent worker
Neighbors:
- Maia Agent supplies the Pi SDK kernel and local CLI process.
- Maia Swarm decides admission, Session trees, queues, budgets, placement and voice routing, then calls Node operations to persist and enforce those decisions.
- Providers and Login owns adapter semantics and credential policy; Node stores safe Connection records and resolves secret handles at the execution boundary.
- Clients consume Node-owned SDK projections and never become an authority or streaming server.
- The user and repository
.maia/trees form the shared Maia configuration; Node persists admitted snapshots and exposes bounded resolved views, not the prose itself. - Contracts own cross-domain schemas, versions, IDs, events, receipts, worker framing and artifacts. Operations owns deployment, paths, SQLite, backup, observability, SLOs, retention and incidents.
Deployment
One Control Node is the only durable writer: it runs Node and Swarm services in one process over local SQLite, and Execution Nodes are disposable fenced capacity with no competing Project or Session truth (decision, topology).
Execution-only composition target
Planned (MN0–MN5, in roadmap order); no remote runtime or startup flag exists today.
- Reuse the Maia Node executable with an explicit execution-only composition; no new package and no Control Node copy.
- It owns one local
WorkerProcessSupervisorand the same out-of-process Maia Agent adapter. It never opens Control SQLite, runs Swarm planning, accepts product-client mutations or takes business authority from cached assignments. - It connects outbound to one pinned Control Node over the worker transport that D-150 leaves to an owner decision (authenticated HTTP/WebSocket is the proposal) and serves no inbound worker-control API. Registry-owned machine operations carry assignment reads, acknowledgements, renewals, control instructions and evidence; notifications only trigger authoritative reads.
- After reconnect or restart it reconciles the exact assignment and worker incarnation before launch. A finite, locally enforced lease bounds disconnected execution; rejecting stale events alone is not containment.
- Local spool and checkpoint state is bounded recovery evidence, not a second ledger.
- MN0 freezes wire schemas, TTL bounds, the clock error budget and storage limits with protocol and SDK tests before MN1. The startup flags move to the slice that adds the execution-only mode (MN1 or MN2).
The process layout owns its lifecycle and security boundaries.
Node ownership
| Node owns | Node does not own |
|---|---|
| durable Agent Session aggregate and revisions | Agent definition semantics or prompt text |
| activation/attempt/effect ledger | provider model behavior |
| authenticated local and remote operation transport | Swarm scheduling priority |
| process and worker supervision | external executor state truth |
| Connection public records, encrypted bindings, and secret resolution | provider-specific credential policy |
| ordered events, command receipts, trace outbox, JSONL evidence | client caches as authority |
| evaluation runs and maintenance proposals | automatic semantic config rewrites |
| A2A ingress normalization | A2A peer authority over internal leases |
packages/protocol is the sole public operation registry. Node cannot expose
an unregistered mutation. The exact CLI, model-tool and JSON contracts are in
the CLI and tool reference.
Identity, authentication, and tenancy
Maia's direction is to be the coordination hub where any agent or harness reaches others through a Node (decision, team Workspaces). This identity model keeps that path open.
Target: every request resolves a server-created context before operation decoding reaches a domain service:
interface RequestContextV1 {
readonly requestId: string;
readonly actorId: string;
readonly principalId: string;
readonly transport: "loopback-http" | "remote-https";
readonly clientSessionId: string;
readonly deviceId: string | null;
readonly operator: boolean;
readonly projectGrants: ReadonlyArray<{
readonly projectId: string;
readonly capabilities: ReadonlyArray<string>;
}>;
readonly authenticatedAt: string;
readonly expiresAt: string;
}
The caller cannot supply actorId, operator state or Project grants as command
input. Authorization is default-deny. Filtering happens before query,
pagination, aggregation, sampling or export. Non-operators cannot omit Project
scope as an implicit "all". Receipts, trace exports, evaluations, voice
sessions and external dispatches bind actor and Project.
Principals
The interface above sketches the client principal. Principal kinds stay separate, and none inherits another's rights:
- Human client: a person on an authenticated device and client Session. Only humans answer questions and issue confirmations.
- Machine (MN0, Source candidate): an enrolled Execution Node. The
protocol-owned
context.showprojection has three exact kinds,client,sessionandmachine, each with its own filter that forbids the others' fields. Themachinekind binds the Control Node audience, Execution Node identity (the principal ID), certificate fingerprint, registration incarnation, exact Project/capability grants (at most 16 Projects with 8 capabilities each, never a wildcard orall) and revocation revision, all resolved server-side, never trusted from request claims. No transport mints it yet: the loopback listener refusesx-maia-principal: machineandAuthorization: Maia-Machine, and a machine credential is mTLS (MN1). ItsallowedActionsstay empty until a machine operation exists. It accepts only machine operations for its exact assigned scope; it cannot gain user or operator rights, issue human confirmation or call arbitrary product operations. A machine device record is not User Device membership, and certificate authentication is not the user's Keychain credential lifecycle. - Session (S18): a worker or coordinator Session acting through its Session-bound credential (decision); coordinators also follow owner isolation.
- External peer (after S17): see A2A ingress.
Enrollment requires the verified local owner and an authenticated, explicitly confirmed administrator operation; the local profile bearer alone is not enrollment authority. First-version exception (D-150): enrollment is the D-136 terminal ceremony, and revoke and narrow use the local operator bearer, each attributed to its actor, until the managed administrator credential replaces both. Team invitations and cross-Control-Node clients may come later, but owner bootstrap, managed admin authorization, machine grants, revocation and transport boundaries cannot be skipped. Pairing, certificates and rotation are in operations.
Local transport today
- Node listens only on
127.0.0.1with an ephemeral port and writes an owner-private, versioned connection descriptor and bearer credential to its profile runtime directory. - CLI and SDK discovery read those files with bounded no-follow reads, then
authenticate a
node.statusidentity probe. - Operations use
POST /api/v1/operations. Observations use/api/v1/observationswith themaia.node.observations.v1WebSocket subprotocol. - Node checks the exact Host authority, rejects browser Origin and ambient credential headers, authenticates before reading a bounded body and authorizes through the registry-backed executor.
- A Node restart replaces the credential and invalidates prior requests and signed observation cursors.
- Without more evidence the caller is the fixed local
clientprincipal (actor_local_user, a legacy transport actor: D-138). A process Node launches for a Session can instead carry an Invocation attachment: a 32-byte credential read from an inherited pipe and sent as thex-maia-invocation-credentialheader. Node resolves it to asessionprincipal bound to one Session and Workspace, a list of allowed actions and an expiry (used bycontext show --attachedandsession inbox ... --attached). - A vendor executor Session (Codex, Claude Code) authenticates with its own
credential instead of the bearer:
Authorization: Maia-Session, from theMAIA_SESSION_CREDENTIALits launch received. Node resolves it to asessionprincipal with aworkerororchestratorrole and admits that role's operations alone, scoped to the Session (worker credential).
Managed and remote transport target
The managed-client target replaces the profile bearer with a short-lived credential scoped to actor, device, client Session, endpoint audience and capabilities, stored in the OS Keychain. Revocation, client exit, Node restart policy or expiry invalidates it.
Loopback is a network boundary, not an authorization grant. Remote transport
(remote-https) is off by default. The tailnet profile uses one-time
local-admin pairing, Node-generated keys, Control-Node-issued mTLS
certificates, explicit Project/capability grants, short expiry, rotation and
immediate revocation. Tailscale identity is enrollment evidence only, never
Maia authentication, and Funnel is not enabled
(decision).
A2A ingress
Planned after S17 (decision). An A2A peer is an external principal, never a worker or operator. Node authenticates the peer, normalizes the request and records:
a2a_peers: the admitted peer identity (Agent Card digest, grants, revocation);a2a_tasks: one internal task per accepted external task, unique by peer and external task identity, linked to its Session and Project.
Each accepted A2A task maps to one durable Maia task and keeps both identities; retries return the same mapping. Peers never receive worker leases, Connection secrets or mutation authority, and peer content is data, never consent. The Swarm A2A boundary owns admission, the v1 HTTP profile and status mapping.
Coordinator owner isolation
The Swarm ownership contract sets discovery ceilings: a Project sees no sibling Projects beyond the peer directory of reachable Sessions (D-166); a Workspace sees only its own Project directory, not other Workspaces; Maia sees its authorized Workspace directory and permitted summaries. Human portfolio and internal system-service permissions are separate principals, never ambient grants to a coordinator. Each coordinator has its own durable context and activation ownership while Node stays the only persistence authority (decision).
Node enforces the ceiling below every handler:
- Resolve the exact principal, owner kind and identity, Session, policy revision and operation grant before query, search, pagination, counting, aggregation, context assembly or provider export, including reflective catalogs, observations, errors and audit projections. Caller input, parent IDs, role names and UI focus cannot replace that binding.
- Knowing a child does not grant its private history. Cross-owner requests use admitted parent-mediated delegation, not a direct peer bridge. Full lineage stays in protected Node records; recipient-visible receipts, references and summaries never disclose inaccessible sibling identifiers.
- Partition histories, memory and indexes, prompts, tool projections, provider sessions, evaluation records and caches by exact owner and effective policy. Cache reuse also requires current source grants and matching input, rubric, model and source revisions; no cross-owner deduplication may expose existence or return another owner's result.
- Recheck revocation at dispatch, result read, context load and export. Revoking one owner leaves siblings' identities and resources unchanged. A foreign known ID and an absent ID produce indistinguishable lookup details. Text that mentions another scope grants no discovery or access; already disclosed context cannot be erased retroactively.
- Runtime isolation needs private state and working directories, no inherited provider credentials or ambient home access, scoped IPC and resource supervision. Code-capable activations need the qualified OS containment profile; separate prompts, processes, Pi Sessions or Git worktrees are not a sandbox. Until denial, escape and revocation tests pass, report logical scoping or a blocked profile, not full isolation.
- A person's live Terminal is not a Session. It runs as the Node account's own
user, with the person's authority, as an SSH login does, in the
local_user_interactive_terminaltier on macOS and Linux. Sessions, workers and execution commands keep hard containment. D-175: a person's terminal "runs as the Node's account with the person's authority, as an SSH login does", and "Linux needs an interactive terminal tier like macOS's". Only a person in a trusted client creates one or drives it; no agent, coordinator, voice or Session tool reaches it (Linux tier).
The Project-oriented sketches in this document are not schemas for global or Workspace owners. R3 adds the closed owner variants, uniqueness and migration/refusal behavior (contract) through protocol, SDK, Node, CLI and Agent. Existing Project identities and receipts are preserved; no Project becomes an all-powerful coordinator and live state is never silently migrated.
Durable native Agent Session
An Agent Session is one stable Maia aggregate. Pi worker process IDs, Pi in-memory Session IDs and provider request IDs are implementation references only.
Today Sessions are Project-owned and use the states idle, starting,
running, waiting-approval, interrupted, recovering, stopping,
stopped and failed. The lifecycle below is the target; adopting it needs a
versioned migration that preserves Session IDs, history, events and receipts.
R3 extends this aggregate to Workspace-owned ordinary Sessions: an empty Workspace can own several ordinary Sessions without a Project, repository or Execution Environment. Owner and ordinary/coordinator designation are separate, and home-Session uniqueness applies only to designated home Sessions. Creating or reopening a Session never activates a worker or restores an archived Session, and no shell, file or process tool follows from a conversation without execution grants.
Target fields:
- Agent Session, exact owner kind and identity, Agent definition snapshot and optional same-owner parent/root IDs; Project-owned Sessions require their Project ID;
- home/local/external mode and normalized lifecycle;
- admitted config/context/Agent digest references;
- revision and last event sequence;
- current activation/assignment/lease references;
- Connection/model/executor selection evidence;
- budget and deadline projections;
- timestamps and archive/tombstone metadata.
Target lifecycle:
created -> queued -> starting -> running
running -> idle|waiting|blocked|stopping
idle|waiting|blocked -> queued
starting|running|stopping -> outcome-unknown -> reconciling
reconciling -> running|idle|completed|failed|cancelled|blocked
stopping -> idle|completed|cancelled|failed
created|queued|starting -> cancelled|failed
running|idle|waiting|blocked -> completed|failed|cancelled
completed|failed|cancelled -> archived
Rules:
- ordinary Sessions terminate; a Swarm home Session returns to
idleafter an activation; waitingreleases the worker lease while waiting for exact input;blockednames a missing authority or external-state condition;outcome-unknownalways enters reconciliation before any retry;archivedis terminal and immutable in v1; v1 has no Session restore operation;- timeouts and process death are observations, not proof of semantic failure or cancellation;
- every edge validates an expected aggregate revision and emits one ordered event.
Activations, attempts, and effects
Each prompt or wake creates one semantic activationId. Infrastructure retries
increment attemptNumber; they do not create a new Session or silently replay
model work.
admitted -> assigned -> starting -> running -> succeeded|failed|cancelled
assigned|starting|running -> outcome-unknown -> reconciling
reconciling -> running|succeeded|failed|cancelled|blocked
An activation freezes Session/config/context/Agent/instruction digests, attempt number, assignment revision, lease epoch, provider/model, budget reservation, deadline and idempotency key. Every worker event echoes activation, attempt and epoch.
Consequential tool effects follow:
- transactionally persist authorization, normalized input digest,
effectId, expected revisions, idempotency key, reservation and outbox event; - dispatch only after a fenced worker assignment is valid;
- persist the receipt before returning the result to the model;
- on restart, replay a stored result when the receipt proves completion;
- execute an un-applied intent only when the operation contract proves it safe;
- block an unknown effect until adapter- or operator-specific reconciliation.
effectId is stable for (activationId, modelToolCallId). Repeating a
different input with the same identity is a conflict. Node never replays an
effect merely because the worker process disappeared.
Effect-class recovery contract
The protocol registry assigns every consequential operation one recovery class. "Exactly once" is claimed only when durable evidence proves it; an unknowable external outcome is a first-class terminal-blocking result, never a retry hint.
| Effect class | Pre-dispatch witness | Completion witness | Safe replay predicate | Unknown outcome | Reconciler and terminal receipt |
|---|---|---|---|---|---|
| file create/replace/edit | intent, canonical path, prior digest/absence, result digest | observed file digest equals result digest | current digest still equals declared prior digest or result digest | path state matches neither digest | workspace reconciler, 30 s; completed, not-applied, or outcome-unknown |
| shell execution | intent, profile digest, argv digest, worker epoch | durable child exit plus captured output digest | only when an operation-specific external idempotency witness is declared; never by default | dispatch may have occurred without durable child/exit evidence | process supervisor, 30 s; completed or outcome-unknown |
| background process start | intent, process ID, profile, argv digest, epoch | supervised OS identity and start marker | same process ID is absent and supervisor proves dispatch never occurred | PID/child identity cannot be tied to the intent | process supervisor, 30 s; running, not-applied, or outcome-unknown |
| process input/stop/clear | intent, process revision, input/output digest | new process revision and acknowledged action | current revision equals prior revision and supervisor proves no acknowledgement | process disappeared or acknowledgement is lost | process supervisor, 15 s; completed, not-applied, or outcome-unknown |
| MCP mutation | intent, server/tool/schema digest, upstream idempotency key | trusted upstream operation ID/result digest or verified postcondition | adapter declares idempotency-key support or a deterministic postcondition | upstream has neither lookup nor postcondition evidence | MCP adapter, adapter timeout capped at 120 s; completed or outcome-unknown |
| external Agent dispatch/message/stop | intent, Connection, remote identity/idempotency key | fresh provider object/message/action identity | adapter proves idempotent create/send/stop for the exact key | provider accepted request but identity cannot be recovered | executor adapter, 5 min bounded reconciliation; accepted, completed, or outcome-unknown |
| provider model/realtime request | request and turn IDs, Connection lease, input digest | provider response/turn ID and terminal event | provider documents exact request replay for that identifier | stream disconnects after dispatch without terminal evidence | provider adapter, 60 s; completed, cancelled, or outcome-unknown |
Each row becomes a closed RecoveryPolicyV1 record referenced by the operation
registry. An adapter cannot mark an effect replay-safe dynamically. Timeout
leaves the ledger reserved and blocks a new effect with the same semantic
identity until reconciliation or an authorized operator resolution records
evidence.
Session support aggregates
Tasks, goals, interactions and processes are durable Node-owned aggregates, even though the model reaches them through Agent tools.
- Tasks. A Session has one
session_task_ledgerrevision and zero or more tasks. Task IDs are unique within the Session; subjects need not be. Lifecycle ispending -> in-progress -> blocked -> in-progress -> completed, with cancellation from any nonterminal state and archival only fromcompletedorcancelled. V1 has no dependencies or task graph. - Goals. A Session has at most one current goal and immutable prior goal
revisions. Lifecycle is
active <-> paused, thendoneorabandoned.rewritecreates a goal when none exists or replaces the current one by CAS; a terminal goal needs an explicit rewrite to start a new one. Planned S69 goals add a person-set check and budget (Session goals). - Interactions. Today (R1a source candidate)
interaction.create,list,show,respondandcancelare human-only operations on an exact Project and Session, with lifecyclepending -> answered | cancelled | expired.respondrequires the expected revision and a command ID, replays return the prior result, and another answer is a conflict. Answers are text up to 16 KiB or one to eight frozen option IDs; results are alwaysrecord-onlywith no activation. Target: activation-bound questions; a structuredjsonanswer allowed only when the interaction stores an exact protocol schema digest; acceptance atomically recording responder, answer digest, answered time and wake disposition (resume-waiting-sessionorrecord-only) with an inbox/event row; and expiry or cancellation waking the waiting Session with a typed non-answer event, never fabricated model input. - Processes. A Session process is bound to the activation, execution
profile, supervisor identity and lease epoch. Lifecycle is
starting -> running -> exited | stopped | failed | outcome-unknown; output chunks have monotonic per-process sequence and byte offsets. A process cannot outlive Session shutdown unless a separate operator-owned service operation adopts it.
Preview and confirmation persistence
The mutation contract defines previews, confirmations and receipts. Node persists them as follows.
operation_previews stores preview ID, operation ID, normalized input digest,
actor, device/client Session, exact target IDs and revisions, risk, policy
revision, creation/expiry and invalidation reason. A preview contains no
reusable secret.
confirmation_consumptions stores a one-way token digest, preview ID, actor,
device/client Session, optional voice Session/turn, consumed time, command ID
and resulting receipt ID. Unique token digest and unique preview ID enforce
one-time use. confirmation.consume transactionally verifies identity, expiry
and unchanged operation/input/targets/revisions/policy, consumes the proof and
admits the exact command. Any drift, reconnect identity change, competing
mutation, logout, Project hold or policy revision invalidates the preview. A
replay with the same command ID returns its receipt; no proof is ever returned
to a model.
Worker and process supervision
Pi SDK runs out of process behind a versioned NDJSON worker protocol (framing and limits). The worker receives only:
- exact Maia Session and activation IDs;
- working directory/workspace grant;
- resolved Agent/context snapshot references or bounded content;
- allowed tool manifest and execution profile;
- selected model and one bounded credential handle/material;
- redacted durable history required for the activation;
- request size, output size, timeout and interruption limits.
The worker uses in-memory Pi state; Pi session JSONL is not authoritative. Startup, prompt, interrupt, heartbeat and shutdown requests are idempotent and bounded. Malformed frames, oversize payloads, wrong model/Session identity, stale epoch and request-ID reuse with different input are typed failures.
Execution profiles
The model-facing run_command and process tools are absent unless the activation
resolves an enforceable profile:
interface ExecutionProfileV1 {
readonly profileId: string;
readonly workspaceRoots: ReadonlyArray<{
readonly path: string;
readonly access: "read" | "read-write";
}>;
readonly environmentAllowlist: ReadonlyArray<string>;
readonly secretBindings: ReadonlyArray<string>;
readonly networkPolicy: "none" | "allowlisted";
readonly allowedHosts: ReadonlyArray<string>;
readonly executablePolicyDigest: string;
readonly maxProcesses: number;
readonly maxRssBytes: number;
readonly maxOutputBytes: number;
readonly maxRuntimeMs: number;
readonly sandbox: { readonly kind: string; readonly identity: string } | null;
}
Project files cannot broaden this profile. If requested guarantees cannot be enforced, activation fails. A worktree is not a sandbox, and Pi does not provide one. V1 admits exactly two execution-profile classes:
| Class | Process tools | Eligible callers | Remote/Routine/peer/voice |
|---|---|---|---|
managed-read-only | absent | managed local or Execution Node Session | allowed if separately granted |
local-operator-workspace | unsandboxed | foreground local OS user on an attached CLI/TUI | forbidden |
local-operator-workspace is a development escape hatch, not a sandbox claim.
It needs an explicit per-activation preview and confirmation, prints an
unsandboxed warning, limits roots, environment and network, and ends when the
foreground client exits or detaches. No Agent, Project, Routine, child
Session, remote device, A2A peer, voice client or external executor can select
it.
The first remote pool slice is limited to separately granted
managed-read-only behavior, which still needs multi-host transport and
containment qualification. Registration may have no prepared environments;
placement binds an exact admitted Execution Environment and keeps its existing
workspace_* identity through the
naming migration.
Neither renaming nor host enrollment grants environment access, and
environment preparation is a separate trusted operation that grants no worker
write authority. Report read-only qualification separately from remote coding.
Managed run_command and process tools stay unavailable until a concrete OS containment
adapter passes filesystem, process, network, resource, secret and escape tests
on the target platform. Adding one needs a decision entry, a new capability ID,
clean-host evidence and no weakening fallback.
Session-owned process children stop on Session shutdown. Reboot-durable
services need separate Node job/service operations with supervisor identity,
recovery policy, idempotency, receipts and operator authorization.
Assignment and fencing
interface SessionAssignmentV1 {
readonly agentSessionId: string;
readonly activationId: string;
readonly nodeId: string;
readonly workerId: string;
readonly assignmentRevision: number;
readonly leaseEpoch: number;
readonly expiresAt: string;
}
Acquire, renew, drain, release, expiry and handoff are CAS operations. Every
event/effect writer carries the current epoch; stale epochs fail. uncertain
assignment state preserves exclusivity until positive cleanup or expiry
reconciliation. Swarm plans placement; the Control Node persists and fences
assignments, and the selected host's Node supervisor enforces local process
scope and lease expiry. Missing connectivity does not prove cleanup or undo
external effects.
MN0 extends this schema with the exact Control Node/registration incarnation,
Project, Execution Environment with its revision and content digest, capability
revision, worker incarnation, and
request/reconciliation identifiers (MachineAssignment, with revisions as
decimal strings;
bounds, lease, spool and identity generations). Existing local assignment rows bind to
local Control Node capacity and are not reinterpreted as enrolled remote
Nodes; additive migrations preserve Session IDs, attempts, events and receipts.
Remote start, steer and cancel use the same ownership and idempotency rules;
an uncertain acknowledgement is reconciled before any duplicate delivery could
create another worker.
Ordered events, receipts, and projections
Event order is monotonic per streamOwnerId, normally an Agent Session or
Routine tick; there is no global causal sequence. Each accepted mutation writes
aggregate state, command receipt, canonical event, budget/reservation change
and trace-outbox row in one SQLite transaction. Observation partition offsets
serve replay and fan-out only; they never replace owner sequence or imply
cross-owner causality (event contract).
Receipts prove the accepted or rejected operation outcome, not downstream completion, unless the operation contract says it completes synchronously. Retries reuse the same idempotency key and reconcile the prior receipt first.
Client, SDK, Swarm, UI and transcript views are replaceable projections. A
stale projection carries observedAt and cannot overwrite a newer server
revision or invent health or lifecycle state.
Observation ingestion and streaming server
Planned (R1, S12). Agent, Swarm, Node, provider, voice, A2A, security, storage
and scheduler records enter one normalized ObservationRecordV1 plane.
Workers never serve clients, and viewers never multiply worker output: one
emitter per worker or adapter submits bounded batches to ObservationIngress,
and Node normalizes identity, schema, classification, redaction, partition and
correlation once before fan-out. Operations owns the
lanes,
partitioning, fan-out and processors,
limits, coverage and health; this section owns the Effect ports.
workers / Node services / provider adapters / Swarm
-> bounded batch emitters
-> ObservationIngress + redaction
-> authoritative lane (SQLite event/outbox) | diagnostic lane (spool)
-> logical partitions -> projections + processor checkpoints
-> StreamBroker -> CLI/TUI, Desktop, mobile, Swarm processors
interface ObservationIngress {
readonly publishCommitted: (
records: ReadonlyArray<CommittedEventRefV1>,
) => Effect<void>;
readonly offerDiagnostic: (
records: ReadonlyArray<DiagnosticObservationInputV1>,
) => Effect<ObservationOfferResultV1>;
}
interface ObservationStore {
readonly snapshot: (
query: AuthorizedObservationQueryV1,
) => Effect<ObservationSnapshotV1>;
readonly readBatch: (
query: AuthorizedObservationQueryV1,
cursor: ObservationCursorV1,
) => Effect<ObservationBatchV1>;
}
interface ObservationProcessor {
readonly descriptor: ObservationProcessorDescriptorV1;
readonly process: (
batch: ObservationBatchV1,
checkpoint: ObservationProcessorCheckpointV1,
) => Effect<ObservationProjectionCommitV1>;
}
publishCommittedreferences only an already committed event or outbox row; it cannot create a canonical event or bypass the owning aggregate transaction.ProjectionRunnercommits a processor batch, its projection and one checkpoint together. Swarm scheduling projections are admission dependencies; optional search, evaluation and export projections may lag without stopping an Agent.- Extension has three levels: a new producer adds a protocol-owned
domain/event schema and emits through
ObservationIngress; a first-party view adds an admittedObservationProcessorDescriptorV1and a deterministic processor Layer; an application or analytics service reads the public SDK feed with its own grant and cursor. Processors are reviewed code, not public plugins, and Project code cannot load one in process. These services start inside the Control Node and move out of process only behind the same contracts and replay tests.
Live Session item stream
Source candidate for Codex (S12b, decision) and
Claude Code (S12c, decision), with the Desktop
Session timeline (S12d) as a source candidate; the Pi adapters are planned,
so Pi Sessions stream turn boundaries only. observe.events
still pushes change signals without content; full item streams go through
session.attach. observe.events replays committed journal rows from SQLite,
which stays the source of cursors and replay (S12a). An idle subscriber wakes
as soon as a journal append commits: a TEMP trigger on domain_events signals
every append on the Node's connection, and a burst coalesces into one wake. The
five-second heartbeat proves liveness and publishes filtered progress.
services/node/src/session/stream owns the writer and both lanes, and the
observation socket serves the attach operation. The measured budgets are in
completed slices
and the observation suite of the benchmarks.
One vocabulary for every harness. packages/protocol defines Session →
Turn → Item. Item kinds: message, reasoning, tool_call, command,
file_change, approval_request, error, usage. Durable events are
turn.started, item.started, item.completed and turn.completed;
item.delta (appended text or tool output) is ephemeral. Every record carries
the Session, item, owner sequence and the Node epoch. Harness-specific data
goes in a namespaced extension field that older clients decode as unknown.
Adapters live in the Node, harnesses stay unmodified. One adapter per
harness reads that harness's public interface (Codex app-server notifications,
Claude Agent SDK partial messages and tool events, Pi RPC message_update and
tool events) and emits the vocabulary above. Adding a harness means adding an
adapter with recorded fixtures: real vendor streams replayed into the adapter
must produce the expected records, and one shared test checks ordering
(started → delta → completed) for every adapter. Maia's own managed Pi
worker may emit the vocabulary directly.
One writer, two lanes. A per-Session ingest assigns the owner sequence. The live lane offers each attachment its own bounded queue, never a shared PubSub, coalescing deltas every 33 ms and keeping each open item's current text, so a late viewer sees a partial answer at once. The durable lane commits finished items and checkpoints about every 400 ms in batched transactions, never one row per token. Live delivery does not wait for the durable commit. Planned (S12t, D-168): the durable lane keeps a trace without content (kind, IDs, sizes, digest, times and a native reference into the harness's own session), and a replay or snapshot reads finished items' text through the harness's reader.
Attach order. Subscribe to the live lane first, then replay from the
client's cursor, or send a snapshot when the gap exceeds 1,000 records or
8 MiB, then send synchronized, then live records; the client drops
duplicates by sequence. A changed epoch means the Node restarted and the
client resynchronizes. An open approval request is an open item, so
synchronized lists it on every attach; coding Sessions produce them
(approvals). A slow subscriber gets resync instead of slowing the producer or
other viewers. One controller holds input and approvals; any number of
viewers observe. sessions.watch carries only state and activity for
dashboards; full item streams go to attached Sessions only.
Clients. The SDK exposes attachSession() as an async iterable over the
same frames on every transport (the local WebSocket; an SSH stdio bridge to a
remote Node is planned), and attachSessionEffect
as an Effect stream after discovery. One pure reducer,
reduceSessionAttach, turns frames into view state for the CLI
(maia session attach), Desktop and mobile alike. It lives in @maiaos/sdk
because the public CLI cannot depend on the private client-core, which
re-exports it unchanged.
As built (S12b). These choices fill in the design above:
- The owner sequence is a positive integer per Session, and the epoch is the Node instance ID. A Session's sequence continues across runtime restarts in the same epoch; durable records keep theirs across epochs.
- Item IDs are
item_<sequence of item.started>, so an adapter's own IDs never reach a client. Each text field is bounded (32 KiB for messages and summaries, 16 KiB for output, 8 open items per Session) and stripped of terminal control sequences; a cut setstruncated. - The scheduler reports turns; the adapter reports items. A turn's end
closes its open items (
interruptedorincomplete), and Node startup closes what a crashed epoch left open, so every durable turn ends once. - The Codex adapter keeps the visible reasoning summary and never the raw reasoning text or encrypted content; account, rate-limit, thread and environment notifications never become items. Recorded app-server fixtures (Codex 0.160.0 against a scripted Responses model on loopback) prove the mapping, and one shared ordering check applies to every adapter.
- The Claude Code adapter (S12c) reads the Agent SDK's messages with partial
messages on: text deltas stream into the assistant
message; atool_usestarts an item and itstool_resultcompletes it, ascommandfor Bash,file_changefor Edit, Write and NotebookEdit (the diff from the tool's structured patch, or a new file's content) andtool_callotherwise,declinedwhen Maia's guard or Claude refused it. The prompt Maia sends becomes the usermessage, and each result adds ausageitem (input counts cache reads and writes, as Codex does) and anerroritem unless Maia interrupted the turn; API retries areerroritems withretrying. Anerroritem names the API error class, HTTP status or result subtype in a fixed sentence and never keeps Claude's text, which can echo a key. Claude may return its full thinking, so a thinking block becomes areasoningitem readingThinking…with the text's length in its extension, live and durable; its text, signatures and redacted thinking never enter a record. Subagent frames are skipped (workers have no agent tool), withpermissionMode: "dontAsk"Claude asks nothing, so noapproval_request, and a frame the adapter cannot read is dropped without touching the turn. Synthetic fixtures shaped after Agent SDK 0.3.285 prove the mapping (decision). synchronizedcarries each open item's text so far (its newest 96 KiB across all open items), so a late viewer starts from the partial answer and appends live deltas. A cut tail setstruncated; the CLI prints it after an ellipsis.- A durable batch that fails commits again per Session, then record by record: a record SQLite refuses (a constraint) is dropped and logged, so one Session cannot block the others; any other failure keeps the records pending, retries back off up to 12.8 s, and each Session keeps at most 16,384 records or 32 MiB pending.
- Records keep commands, arguments, output, diffs and absolute paths as the harness reported them, without redaction, and nothing purges them yet (decision); S12t keeps only the trace (D-168).
- A full attachment queue (256 records or 2 MiB) fails that queue: the viewer
gets
resynconce its socket drains, or the socket closes with 4429 if it stays stalled past the write deadline. Either way it reattaches after its cursor. Attach is view-only; input and approvals stay withsession turn sendandsession approval.
Latency budget (a target to measure). Local, harness output to rendered client: p50 30–40 ms, p95 under 100 ms (coalescing window, loopback, one frame of rendering). Remote over SSH: add one-way network latency. A CI benchmark with a synthetic harness (1 and 100 Sessions) timestamps ingest and render and fails above the local p95 budget.
Trace and content classification
Planned (S12t, D-168). The Node keeps the trace, holds content only while its delivery is open, and never stores the rest. The live lane still carries content to attached viewers in memory.
| Data | Trace (kept) | Content (in transit only) | Never stored |
|---|---|---|---|
| Turn identity and lifecycle | sequence, turn, Session, command and runtime turn IDs, state, revisions, times | ||
| Prompt | digest, size, native reference, delivery state | the prompt until its delivery closes | a settled copy |
| Response | digest, size, native reference, completion | live buffers | settled text in turns, receipts or stream records |
| Failure | reason, code, a fixed sentence Maia wrote | vendor or provider error text | |
| Item | item and turn IDs, epoch, sequence, schema version, kind, status, times, sizes, truncated, digest, native reference | message text, reasoning summary, tool arguments and output, command text, diffs, paths, approval summaries | settled copies |
| Tool, command, file change | admitted tool name, exit code, change kind and count | as Item | |
| Extension | allowlisted typed fields with namespace and version | every other field | extension.data kept whole |
| Receipt and event | command, receipt, actor and target IDs, method, revisions, cursors, times, request digest, authorization, outcome | content embedded while its delivery is open | a settled turn or interaction snapshot |
| Interrupt receipt | nativeRequired, persistPhase, runtimeTurnId, turn identity | as Receipt | |
| Usage | input, cached input, output, reasoning and total tokens; model calls; model, cost and duration with their source | invented zeros | |
| Typed decision | option ID or verdict, in the consuming operation's record | an answer until its operation accepts it | |
| Peer message | message and delivery IDs, sender and recipient Session IDs, actors, lifecycle, times, sizes, digests, native references | subject and body, under D-168's transit rule | |
| Question and approval | interaction and approval IDs, actors, kind, lifecycle, times, digests, the typed outcome | question text, options and free-text answers, under D-168's transit rule | |
| Excluded | credentials, auth headers, environment dumps, hidden reasoning, raw audio |
- A field this table does not name, a new extension field included, is content until a reviewed change classifies it.
- Each content kind (prompt, response, each item kind, message, answer) has its own digest domain, and a digest covers the full content, not the truncated record.
- The Session title, the first line of the first prompt cut to 80 characters, is the one prompt excerpt kept.
Executor qualification
An executor's stored content is cut over to the trace only after its reader passes four checks: a stable reference for each item, bounded page reads, reconstruction that matches the trace's digests, and a list of the item kinds it cannot return. Until then its records keep today's durable lane under D-128.
| Executor | Reader | Durable native source | Status |
|---|---|---|---|
| Codex | codex app-server thread reads (D-078) | its own rollout files | to qualify, S12t |
| Claude Code | Claude Agent SDK session reads (D-078) | its own session files | to qualify, S12t |
| Pi and Maia agent | Pi's session reader | none for the managed worker, which keeps its Pi session in memory today; AG3a adds one | after S23 and AG3a |
| OpenCode, Cursor and ACP | each server's or protocol's reads | per harness | with S31, S33, S34 |
Ephemeral client attachments
A client attachment connects one authenticated renderer to one durable Agent
Session. It is not part of the Session aggregate and owns no model execution,
worker lease, inbox state or lifecycle. It differs from today's Invocation
attachment, which is a Session-bound credential for a launched process (see
local transport). S12 delivers it; S12b names the attach
operation session.attach, so the operation names below must be reconciled
with that slice when the registry freezes them.
interface ClientAttachmentV1 {
readonly schemaVersion: "maia.client-attachment.v1";
readonly attachmentId: string;
readonly agentSessionId: string;
readonly clientSessionId: string;
readonly actorId: string;
readonly deviceId: string;
readonly rendererKind: "pi-tui" | "desktop" | "mobile" | "sdk";
readonly rendererVersion: string;
readonly modes: ReadonlyArray<"view" | "send">;
readonly snapshotWatermark: string;
readonly acknowledgedEventCursor: string | null;
readonly heartbeatIntervalMs: 5000;
readonly maxMissedHeartbeats: 3;
readonly maxBufferedEvents: 256;
readonly maxBufferedBytes: 2097152;
readonly connectedAt: string;
readonly expiresAt: string;
}
- Node authorizes the exact Session before creating a snapshot or
subscription. Many viewers are allowed; every send still executes
session.sendwith its own actor, expected revision, idempotency key and receipt. - Clean detach, timeout, transport loss or Node restart closes the attachment
only. Reconnect uses a new
attachmentIdand an authorized cursor; cursor gaps trigger normal bounded resynchronization. - Attachments live in an in-memory registry with a non-authoritative presence projection. They are absent from durable Session tables and from backup and restore. Redacted open, heartbeat-gap, close and revoke observations are required evidence, but no attachment event implies that the Session stopped.
- Limits: 8 attachments per Session, 32 per actor and 256 per Node; authorization may narrow them.
- Operations:
session.attachment.open,session.attachment.heartbeat,session.attachment.closeand operator-onlysession.attachment.revoke. Open accepts one exactagentSessionId, an optional event cursor, requested modes (authorization may narrow them), renderer identity and terminal capabilities. Heartbeat acknowledges the highest fully rendered cursor. Close accepts only an attachment owned by the authenticated client and is idempotent. Revoke requires the attachment ID, reason, preview, confirmation and receipt. Open, heartbeat and close have closed schemas and typed authorization/cursor errors but no Session CAS, because they cannot mutate the Session. Model activations never get any attachment operation.
Canonical Node state
Operations owns today's
paths: maia.sqlite in the profile state directory; the connection
descriptor, credential and lock in the profile runtime directory. RL3 moves
them into one home.
- SQLite is authoritative for aggregates, revisions, identity, idempotency, receipts and indexes. Segmented JSONL is immutable detailed evidence and an evaluation input.
- Target: credentials and client/Node private keys live in the OS secret
store (macOS Keychain first, then Linux
systemd-creds; both opt-in source candidates, D-155). Today model-provider credentials are owner-only plain JSON in$MAIA_HOME/auth.json, and worker-account vendor configuration lives under the profile state'saccounts/directory (secrets). - Canonical logs never live beside versioned Agent or Routine definitions: that would dirty repositories, mix policy with sensitive runtime state and lose truth when a worktree moves.
- There is one redacted event ledger per Session. A transcript is a rebuildable projection whose rows cite exact source event IDs; it accepts no independent writes. Under D-168 the ledger holds the trace, and a transcript view reads its content through the harness's reader.
JSONL commit and recovery protocol
Planned; decided, but no roadmap slice schedules it yet. Under D-169, receipts, events and traces share its one journal integrity contract, and these files export that journal; they are never a separately written audit history. Evidence lives in the profile state:
evidence/<project-id>/<owner-type>/<owner-id>/
<first-sequence>-<last-sequence>.jsonl
<first-sequence>-<last-sequence>.manifest.json
artifacts/<project-id>/<digest-prefix>/<digest>
- The aggregate transaction inserts the canonical event and one outbox row
keyed by
(streamOwnerId, eventSequence). - The projector claims a contiguous range per owner, writes and fsyncs a segment, and records segment path, offset, record digest, projector epoch and prior digest.
- A second transaction marks the range projected only when the exact bytes are durable.
- Restart truncates an incomplete final line, validates the segment chain and replays unprojected rows idempotently.
- Same owner/sequence/digest is a replay; same owner/sequence with another digest is corruption and blocks the stream.
Segments use bounded line and segment sizes, a previous-segment digest chain and a terminal checksum record. Segment writes, manifests and startup repair are in operations. Disk-full or corruption fails consequential mutations closed after a bounded outbox threshold; evidence is never silently dropped.
Trace records are eventType-discriminated safe DTOs. Classification and
redaction happen before the aggregate transaction. Secrets, credentials, auth
headers, environment dumps, hidden reasoning, raw audio and unrestricted tool
output are never persisted; Session conversation content stays only in
transit (D-168), and other large policy-allowed
content becomes an encrypted artifact reference. Retention expires content and artifacts while keeping
minimum receipt/event integrity metadata, and deletion writes a tombstone
receipt. Encryption keys are Node/operator-owned, identified by key ID and
rotated by rewriting to a new segment, never in place.
Trace authorization and exports
trace.view, trace.export and eval.run are distinct grants. Authorization
and Project filtering happen before search, sampling, pagination or
aggregation.
An export records actor, Project, evidence-manifest/query digest, redaction
policy and digest, content classification, retention expiry, destination digest
and sourceAuthority: false. Default exports use owner-only Node state. An
explicit Project destination uses canonical path resolution, symlink
containment, create-new semantics and confirmation for overwrite. The optional
ignored .maia/state/exports/<export-id>/ path is a redacted copy only and is
never read back as authority.
Shared semantic evaluation
Planned (R10a). One Node-owned Effect service gives bounded semantic judgments on permitted subjects. Internal Node programs call its service port, authorized clients and external coordinators call public SDK operations, and granted coordinators and bots use admitted projections; execution workers get no direct access. All paths share one validation, principal/owner binding, policy, limits and receipt semantics. There is no separate classifier backend, model loop, router registry or plugin system.
| Primitive | Question meaning | Preserved answer semantics |
|---|---|---|
| Choice | Select one of the supplied options; include no-match when needed | Selected option; distribution when supplied by the provider |
| Score | Position on at least two ordered rubric levels | Fractional score in [0, levels.length - 1]; level distribution when supplied |
| Boolean / Noul | Probability that the stated condition is true | Probability in [0, 1], not a separate confidence or degree-of-intensity score |
Jev is the intended evaluator, through AI SDK experimental_evaluate and an
explicitly admitted Gateway/model instance. The installed AI SDK closure has no
evaluation export, so pin and verify the experimental closure first;
availability, pricing, latency and quality are not established. Dependency
changes, credentials and paid calls need separate approval. Never substitute a
model with different probability semantics or rely on global SDK defaults.
Freeze the operation contract before implementation:
- Input. Strict bounded JSON state and a nonempty named question map, inline or referencing an exact admitted rubric revision. State is untrusted material or an authorized source projection, never proof of the caller's facts.
- Binding. Node binds actor, owner and source-access/export policy, plus input, rubric, candidate and source digests and an optional exact snapshot reference. A caller cannot name a broader actor or fetch arbitrary state through the evaluator.
- Limits. Registry-owned size, question and option limits, deadline, concurrency and per-call/per-owner spend reservations. Provider export and spending are effects, not a free read. Configuration admission grants bounded repeated use; widening it needs trusted preview and confirmation, never model self-approval.
- Results. Complete typed answers with raw values, available distributions, provider-specific confidence, model identity, usage (unknown stays unknown), timing, source coverage and advisory status. Provider confidence is not a probability of correctness and never proves completion.
- Errors. Exact errors for invalid input or output, unsupported capability, unavailable provider, denied export, exhausted budget, timeout and cancellation. Never fill missing answers, invent distributions or return partial results as success.
- Identity. Stable invocation identity bound to normalized input and owner, with effect/receipt reconciliation. A changed input under the same key conflicts; a lost provider response may leave cost or outcome unknown, and a paid request is never repeated blindly under a fresh identity.
- Retention. Minimal provenance receipts; no raw input, private transcript,
credentials or provider bodies in default logs. Durable sensitive payloads
need an explicit retention policy, and no runtime data goes into versioned
.maiaintent.
Provide live, disabled and deterministic test Layers. Propagate Effect interruption to the SDK abort signal, disable SDK automatic retries unless the frozen policy accounts for them, and keep cancellation distinct from unknown external outcomes. A disabled service reads no evaluator credentials and makes no provider calls. Failures leave manual controls, observations and receipts available; automated readiness follows the LLM/Jev gate, never a hidden deterministic or alternate-model fallback.
Agent use and bounded composition
- Granted coordinators and bots may ask their own permitted questions; central
routing, context and model setup use the service on behalf of LLM-only
workers. Authority comes from the frozen projection, not a role, Skill, model
confidence or provider key. A question never loads
EVAL.mdor grants maintenance or evaluator write authority. - Batch independent questions over one shared state; questions in one call do not see each other's answers. Make a later request only when an answer is needed to build new state. An array is one shared state, not an implicit batch of unrelated subjects. Multi-label classification uses several Booleans, ranking uses comparable Scores, and composition and thresholds stay ordinary code.
- Bound total calls, depth, candidates, time and cost per request. The first routing pilot allows one parallel assessment and at most one evidence-dependent follow-up; more needs measured benefit and an admitted budget. Reuse the existing Agent loop and task owners, not a workflow DSL. A reasoning model proposes plans; Jev selects and scores supplied alternatives.
- Node filters candidates for eligibility before Jev judges relevance; a coordinator may request another bounded authorized catalog search. Exact manual controls need no classifier, and an exact destination does not remove AI mode's remaining context, setup and readiness gates. Scores grant no permission, correlated judgments are not independent evidence, and self-assessment never replaces required verification. Before enabling each adaptive consumer, measure quality, abstention, missed constraints, latency and cost against a labeled deterministic baseline.
This online primitive is distinct from the evaluation runs below. No operation ID, CLI command or agent tool is advertised until protocol, SDK, Node and CLI parity exists.
Evaluations and maintenance proposals
Planned; scheduled by S62 in the roadmap. The EVAL.md format is
owned by Maia Agent.
- Operational Sessions never load
EVAL.md. An evaluation run freezes the subject revision and snapshot digest, theEVAL.mddigest, the trace query and itsasOfSequenceor time cutoff, the deterministic sampling seed and ordered evidence digests, the redaction policy, the evaluator model and prompt digests, the budget and the result digest. - Trace, rubric and evidence content is untrusted. Evaluation activations get only the evaluator model call and proposal creation: no filesystem, process, peer, arbitrary network or mutation tools. Model scores are auditable inputs, not deterministic truth; missing or redacted evidence is never scored as success.
- Results (
maia.evaluation-result.v1) are comparable only when dataset, rubric, evaluator, redaction and normalization digests match; otherwiseeval.comparereturns a non-comparable exploratory report. - Maintenance proposals never rewrite config automatically. The protocol-owned
auto-saferegistry may act only on derived Node state, such as rebuilding an index or clearing an expired cache, never on prompts, Skills, Agents, Routines,EVAL.md, Connections, secrets, authority, retention or evaluator configuration. Every safe apply still uses exact digests, independent deterministic validation, CAS, idempotency and a receipt.
Authoritative graph snapshots and semantic views
Planned (R1, R10b). The state graph is built from existing Node records and committed relationships: Workspace membership, Project/Session ownership, work dependencies, assignments, artifacts and receipts. It is an observation read projection, not a graph database, a new scheduling product or an LLM-generated source of truth. Only supported recorded entities and edges are included; missing data stays unknown.
- Bind each bounded snapshot or page to principal, owner scope/policy, source revisions, per-owner watermarks, observation time and complete/paginated/partial coverage. There is no global atomic watermark across sources.
- "Entire Maia" means an explicitly authorized human/system read, not an unbounded global coordinator prompt. Project and Workspace consumers keep their discovery ceilings: filter before traversal, aggregates, pages and export. Cursors expire or reset under policy or source drift; inaccessible nodes never leak through edge endpoints.
- Jev may score a bounded candidate subgraph, select an eligible focus or presentation, or assess proposed semantic relationships. It cannot create authoritative nodes, ownership, dependency edges, health or completion. Model annotations carry inference status, source/rubric/model identity, freshness and uncertainty, separate from recorded facts. Revalidate grants and revisions before using results; required blockers, approvals and failures stay visible regardless of ranking.
The client presentation contract owns focus changes and rendering of admitted components.
Session context graphs and linked journal views
Planned (R1, R4, R10). Each Session has a scoped context-graph projection over its admitted history, requests, tasks/activations, artifacts, constraints, decisions, feedback and provenance. Node owns the records, revisions and access rules; a context graph is not an unlimited transcript copy or an independent graph store. Recorded relationships are facts; Jev-selected relevance and semantic links are labeled, expiring, rebuildable annotations, never ownership or verified evidence. Missing or redacted sources stay missing. A Session does not inherit the human client's sidebar or another coordinator's graph; cross-scope results follow parent-mediated delegation and permitted summaries.
Context preparation has one central path:
- Bind the exact Session/owner, activation, source and policy revisions; filter authorized graph sources and pinned mandatory constraints before evaluation.
- Retrieve a bounded candidate set deterministically. Jev selects and reweights permitted context against the current request; the reasoning LLM may propose missing questions, not new access. Invalid, ambiguous or stale answers abstain or block affected AI preparation; deterministic manual reads stay independent.
- Compile the final model context in code with selected and omitted source references, reasons, token budget, inference provenance and policy/rubric versions. Mandatory scope, unresolved approvals, blockers and shared constraints survive ranking. Freeze the manifest with the centrally admitted model/effort setup before each model call.
- Revalidate source access, freshness and scope-sensitive caches at use and export. Revocation fences late reads and writes without claiming provider-side erasure. Graph facts update from committed events; semantic annotations are retained only under explicit policy. Learning into durable knowledge or Skills follows the learning contract, not automatic graph promotion.
A client focus snapshot is a separate, ephemeral, device-bound input that never changes grants, command targets or Session facts, and never carries another client's focus or a broad human view into a narrower coordinator context.
One journal, linked input and execution views
Reuse the authoritative Node journal and existing observations/outbox. The input view shows accepted human requests and their routing/admission decisions; the execution view shows internal task requests, handoffs, worker/operation events, results and feedback. They are linked projections, not separate databases, transcript copies or authorities. Preserve actor/origin lane, owner/Session, request/message, branch, parent/causation/correlation, assignment/activation, operation/idempotency/receipt, relevant revisions and source sequence references. Only admitted fields become schema; these names reserve no public resource IDs. An internal follow-up is never reclassified as a new human instruction.
- The work queue derives pending execution and dependencies; the feedback queue derives pending user attention. Both checkpoint committed events and reconcile with owning records after gaps or restart.
- Journal every admitted branch transition, semantic decision provenance and feedback disposition, not raw tokens or audio. Keep ordered source cursors, finite retention and pagination, bounded queue bytes/items, visibility/retry/dead-letter policy and fair per-scope and per-client quotas.
- Reconcile unknown handoffs and effects before replacement work. Delivered feedback is not implicitly seen, read or acknowledged. A visibility timeout is not permission to repeat an external effect. Revalidation after restart covers exact identity, expiry, policy, target revisions and current source access.
- Persist only permitted payloads or minimal references/digests. Submitted text is held only in transit, then lives in the harness's transcript (D-168); voice transcripts stay opt-in and raw audio is not retained by default. Graphs, journals and evaluation caches must not become transcript-retention backdoors, and expired ephemeral speech context is not restored from hidden model logs. Retention and deletion apply to projections and cache reuse as well as source records.
The Swarm contract owns what to dispatch and show; Node records what actually happened.
Live observability ownership
Node is the only source of the causal Session timeline and system health
projection. TUI, CLI, Desktop, mobile and voice consume
observe.events (and the planned observe.snapshot); none reads worker stdout, JSONL files or
SQLite directly. The coverage invariant, record classes, queries, metrics,
retention, SLOs and incident behavior are in
operations.
For each turn a live observer can resolve, without content privileges:
message -> Session revision -> activation -> attempt -> assignment/worker
-> model/provider attempt -> tool/effect -> receipt -> terminal state
-> event watermark -> JSONL projection watermark
If a required edge is missing, health is blocked, the timeline includes a
stable diagnostic and consequential admission pauses. Best-effort renderer or
process logs can fail without hiding the authoritative transition because they
are not part of this chain.
Durable data model
Target tables, grouped by area, with the keys that carry a contract (every
table also has its primary key and lookup indexes). Today's schema is defined
by the migrations: some names
already exist (projects, interactions, command_receipts), and other parts
are built under different names (sessions, session_turns, domain_events,
interaction_resolutions).
| Area | Tables and contract keys |
|---|---|
| Identity and grants | actors; devices (unique public-key fingerprint; actor and revocation indexes); authorization_grants (actor, scope, capability, revision and expiry indexes: server-side authorization truth); capability_enablements; project_memberships (Project+actor; role and grant revision) |
| Configuration | config_snapshots (unique digest); agent_definition_snapshots (unique namespace/Agent/revision/digest); projects (unique canonical root); project_inventory_snapshots (unique registry revision/source digest) |
| Sessions and activations | agent_sessions (partial unique active home); agent_session_relations (unique child, acyclic lineage); session_activations (unique Session+sequence); activation_attempts (unique assignment/epoch); effect_ledger (unique activation+tool-call) |
| Session support | session_task_ledgers (CAS boundary); session_tasks; session_goals (partial unique current goal); interactions; interaction_responses (unique idempotency key); session_processes (unique supervisor identity); process_output_chunks (unique byte range) |
| Confirmation and receipts | operation_previews; confirmation_consumptions (unique token digest, unique preview); command_receipts |
| Messaging | agent_messages (unique sender idempotency key); message_deliveries (claim, visibility, retry, dead-letter) |
| Scheduling and placement | routines / routine_ticks (unique Routine+revision+scheduledAt); admission_queue (unique activation); budget_reservations; placement_plans; session_assignments (one active worker); node_registrations (revision, epoch, heartbeat); handoffs |
| Connections and providers | connections_public (unique name); connection_grants (Connection plus actor or Project; capability, revision and expiry indexes: exact credential-use authority); credential_leases; provider_attempts; provider_usage_ledger (unique provider request); provider_circuit_state; external_bindings (unique adapter+remote identity) |
| A2A and voice | a2a_peers (unique identity/card digest; grant and revocation indexes); a2a_tasks (keyed by internal task; unique peer+external task identity: A2A identity and update mapping); voice_sessions; voice_turns |
| Events and evidence | ordered_events (owner+sequence, unique event ID); trace_outbox (owner+sequence); trace_segments (unique owner+range); trace_exports; artifacts (unique digest+Project); retention_jobs; backups (unique manifest digest) |
| Observation | observation_partitions (next/committed/evidence watermarks); observation_spool_segments; observation_processors (processor+version); observation_checkpoints (processor+partition); observation_saved_filters |
| Evaluation | evaluations (unique result digest); maintenance_proposals |
Foreign keys preserve Project/Session/activation ownership. Normal domain operations archive or tombstone instead of cascading deletion. Every aggregate has a positive CAS revision and created/updated timestamps.
Target tables arrive through additive, versioned migrations. Existing rows, identities, events and receipts stay readable and are never silently reinterpreted (for example, existing Run/Task/Dispatch rows are not public Session-local tasks). A cutover takes a verified backup marker, backfills immutable snapshots and exact legacy-ID aliases, runs bounded dual-read validation of aggregate, event and receipt projections, switches one protocol → SDK → CLI → Node → worker slice behind a rollback reader marker, and emits machine-readable replacement diagnostics for legacy commands and configuration during a bounded compatibility window. No migration rewrites user source config, deletes durable state or adds a dependency without an explicit operation, preview, exact diff, backup marker, CAS and receipt.
Effect service map
RequestAuthentication
OperationAuthorization
ProjectSnapshotRepository
ProjectInventoryRepository
AgentSessionRepository
ClientAttachmentRegistry
ActivationSupervisor
WorkerProcessSupervisor
EffectLedger
ReceiptStore
OrderedEventStore
TraceOutboxProjector
ObservationIngress
ObservationPartitioner
ObservationStore
ProjectionRunner
StreamBroker
ArtifactStore
EvaluationRunner
MaintenanceProposalStore
SecretResolver
ConnectionLease
MonotonicClock
A2AIngress
Every capability is an Effect service with explicit Layer, typed errors,
interruption, Scope and deterministic test implementations for clock, IDs,
filesystem, process, provider, secret store and transport.
MonotonicClock caller contract
MonotonicClock.read returns the host uptime, which counts suspend, with an
epoch. A caller keeps the epoch beside each deadline. It fails closed on every
error: no reading, no grant, no extension.
- A later epoch means this Node's clock lost continuity and no deadline it
measured before then holds. An execution Node treats its own lease as expired
and starts the bounded stop. A Control Node never infers that a remote
worker's lease ended: it moves every horizon to
now + maximum TTLand raises a placement barrier until a fresh authenticatedrunningreport confirms a grant issued after the discontinuity. The lease semantics (items 4, 6 and 8) own both rules. - A sample's three brackets, on the timer, the wall clock and the uptime, must
each finish within the sampling window, an eighth of the clock error budget,
and the wall clock must not run back. Otherwise
readfails asMonotonicClockUnreadable(sampling_window) and keeps its state. The brackets overlap and do not nest: a delay before the opening read and another before the closing capture each pass, so a whole sample can take two windows. CPU throttling, event-loop stalls and filesystem queueing can repeat that failure and starve grants and renewals. The rate on Linux is unmeasured. - The tripwire sits two sampling windows and the sources' rounding under the budget. A suspend longer than a window between a sample's two uptime reads fails that sample, whatever the wall clock does. With at most one wall step inside a sample, a wall skew past the budget never hides in the sampling error.
- A reading counts the suspended time its opening uptime read saw. The closing
uptime read ends the sample, and both uptime reads are synchronous reads of
/proc/uptime, so no wait on the event loop follows the closing capture. A suspend after the opening read shows on the next read: the reading can trail the closing capture by one window plus the uptime's rounding. Like any clock read, it can be stale by the time a caller acts, so a caller re-reads before it acts on an expiry. - Early expiry is conservative. A skew under the tripwire can still expire a lease early, most of all when samples run slow.
- Any other platform than Linux fails Layer construction with
MonotonicClockUnsupported.
Admission settings
Source candidate. The Node reads its admission settings through Effect
Config at startup. MAIA_NODE_MAX_ACTIVE_SESSIONS defaults to 8 and controls
both durable queued/launching/recovering/ready activations and live runtime
slots. MAIA_NODE_DEFAULT_ACCOUNT_SESSIONS defaults to 2 and supplies new
Connections, sign-ins and setup previews when no explicit limit is given.
Both accept integers in 1..64; invalid values fail config load with
configuration_invalid. Existing account records keep their limits;
connection capacity set changes an account explicitly, up to 64.
node status --json reports the effective admissionCaps.
These settings require a Node restart. A lower cap never stops existing work;
it blocks new admission until usage drops below it. Live preview/confirmation
of the Node cap remains planned in FV6.
The host headroom gate is also Source candidate. Before a new Session
create, start or resume (including child creates and Routine scheduled starts),
the Node samples the host through the HostCapacity Effect service:
| Node environment setting | Default | Admission condition |
|---|---|---|
MAIA_NODE_HEADROOM_ENABLED | true | Set false to disable host sampling and this gate. |
MAIA_NODE_MAX_LOAD_PER_CPU | 1.0 | One-minute load divided by logical CPU count is at or below this value. |
MAIA_NODE_MIN_MEMORY_AVAILABLE_PERCENT | 10 | Available memory is at or above this percentage of physical memory. |
MAIA_NODE_MIN_DISK_FREE_PERCENT | 10 | Blocks available to the Node user on its state volume are at or above this percentage. |
Thresholds must be finite and nonnegative; percentages cannot exceed 100.
Invalid settings refuse startup with configuration_invalid, including when
the gate is disabled. Restart the Node to change them. Linux memory uses
/proc/meminfo's MemAvailable, including reclaimable memory. On macOS it uses
Node's process.availableMemory() (memory available to the process) divided by
physical memory; this does not promise Linux's reclaimable-memory semantics.
Other platforms or unavailable, invalid measurements fail closed. The disk
probe uses the state directory, never the current repository's volume.
Equality passes. A refusal is retryable saturated and names cpu, memory
or disk, its measured value and the required threshold. An unavailable
reading says measured unavailable and names the requirement. The Node logs
maia.node.session_admission_refused with those fields. Like existing capacity
refusals it writes no command receipt or domain event and leaves the command
available for retry. Successful receipt replay bypasses fresh admission checks.
Routine launches retain their existing saturation wait and settlement behavior.
The check samples current headroom; it does not reserve host resources or
preempt running work. Native macOS qualification remains pending.
Acceptance gates
Each capability ships unit, contract, migration, crash/fault, security, end-to-end and performance tests. Release tests use deterministic fake layers; live provider tests are opt-in conformance tests unless a release environment explicitly supplies credentials.
| Capability | Required failure and security cases | Performance |
|---|---|---|
| auth | wrong credential, actor, Project and replay; Node restart and expiry; credential bootstrap migration | request p95 |
| Session | worker or Node kill at each activation/effect edge; stale epoch; unauthorized tool | activation and event p95 |
| process | path, network and environment escape; process-tree loss | RSS and output limits |
| observation | burst, slow client, replay, gaps, policy revocation, cross-Project or content cursor replay | ingest, fan-out and lag |
| traces | partial write, disk full, tamper, ACL and redaction | append and query p95 |
| evaluation | poisoning, self-modification, evaluator disconnect | bounded sample |
- No unauthenticated, wrong-Project, expired or replayed request reaches a domain service.
- A worker or Node crash at every activation/effect edge either proves safe
replay/completion or yields
outcome-unknown; no consequential effect is repeated by inference. - Stale assignment epochs cannot append events, receipts or trace rows.
- Session identity and receipts survive disposable worker loss and Node restart.
- Required trace evidence is either durable or the mutation fails closed.
- Any authorized Session is streamable without worker-side viewer state; adding subscribers does not increase worker emission or provider-stream latency.
- Slow clients and optional processors are isolated; authoritative records are complete, diagnostic loss produces an explicit gap, and required processors recover from durable checkpoints before admission resumes.
- Public DTOs contain no credentials, secret handles, raw provider bodies, hidden reasoning, environment dumps or raw audio.
- Process tools are absent without an enforceable execution profile.
- Evaluation and maintenance inputs cannot broaden their own authority or modify source configuration.
- Protocol, SDK, CLI, Node, worker, event, JSONL and documentation contracts agree mechanically.
Imported from Maia OS docs at 3dc3e07