Skip to content

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 WorkerProcessSupervisor and 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 ownsNode does not own
durable Agent Session aggregate and revisionsAgent definition semantics or prompt text
activation/attempt/effect ledgerprovider model behavior
authenticated local and remote operation transportSwarm scheduling priority
process and worker supervisionexternal executor state truth
Connection public records, encrypted bindings, and secret resolutionprovider-specific credential policy
ordered events, command receipts, trace outbox, JSONL evidenceclient caches as authority
evaluation runs and maintenance proposalsautomatic semantic config rewrites
A2A ingress normalizationA2A 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.show projection has three exact kinds, client, session and machine, each with its own filter that forbids the others' fields. The machine kind 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 or all) and revocation revision, all resolved server-side, never trusted from request claims. No transport mints it yet: the loopback listener refuses x-maia-principal: machine and Authorization: Maia-Machine, and a machine credential is mTLS (MN1). Its allowedActions stay 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

  1. Node listens only on 127.0.0.1 with an ephemeral port and writes an owner-private, versioned connection descriptor and bearer credential to its profile runtime directory.
  2. CLI and SDK discovery read those files with bounded no-follow reads, then authenticate a node.status identity probe.
  3. Operations use POST /api/v1/operations. Observations use /api/v1/observations with the maia.node.observations.v1 WebSocket subprotocol.
  4. 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.
  5. A Node restart replaces the credential and invalidates prior requests and signed observation cursors.
  6. Without more evidence the caller is the fixed local client principal (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 the x-maia-invocation-credential header. Node resolves it to a session principal bound to one Session and Workspace, a list of allowed actions and an expiry (used by context show --attached and session inbox ... --attached).
  7. A vendor executor Session (Codex, Claude Code) authenticates with its own credential instead of the bearer: Authorization: Maia-Session, from the MAIA_SESSION_CREDENTIAL its launch received. Node resolves it to a session principal with a worker or orchestrator role 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_terminal tier 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 idle after an activation;
  • waiting releases the worker lease while waiting for exact input;
  • blocked names a missing authority or external-state condition;
  • outcome-unknown always enters reconciliation before any retry;
  • archived is 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:

  1. transactionally persist authorization, normalized input digest, effectId, expected revisions, idempotency key, reservation and outbox event;
  2. dispatch only after a fenced worker assignment is valid;
  3. persist the receipt before returning the result to the model;
  4. on restart, replay a stored result when the receipt proves completion;
  5. execute an un-applied intent only when the operation contract proves it safe;
  6. 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 classPre-dispatch witnessCompletion witnessSafe replay predicateUnknown outcomeReconciler and terminal receipt
file create/replace/editintent, canonical path, prior digest/absence, result digestobserved file digest equals result digestcurrent digest still equals declared prior digest or result digestpath state matches neither digestworkspace reconciler, 30 s; completed, not-applied, or outcome-unknown
shell executionintent, profile digest, argv digest, worker epochdurable child exit plus captured output digestonly when an operation-specific external idempotency witness is declared; never by defaultdispatch may have occurred without durable child/exit evidenceprocess supervisor, 30 s; completed or outcome-unknown
background process startintent, process ID, profile, argv digest, epochsupervised OS identity and start markersame process ID is absent and supervisor proves dispatch never occurredPID/child identity cannot be tied to the intentprocess supervisor, 30 s; running, not-applied, or outcome-unknown
process input/stop/clearintent, process revision, input/output digestnew process revision and acknowledged actioncurrent revision equals prior revision and supervisor proves no acknowledgementprocess disappeared or acknowledgement is lostprocess supervisor, 15 s; completed, not-applied, or outcome-unknown
MCP mutationintent, server/tool/schema digest, upstream idempotency keytrusted upstream operation ID/result digest or verified postconditionadapter declares idempotency-key support or a deterministic postconditionupstream has neither lookup nor postcondition evidenceMCP adapter, adapter timeout capped at 120 s; completed or outcome-unknown
external Agent dispatch/message/stopintent, Connection, remote identity/idempotency keyfresh provider object/message/action identityadapter proves idempotent create/send/stop for the exact keyprovider accepted request but identity cannot be recoveredexecutor adapter, 5 min bounded reconciliation; accepted, completed, or outcome-unknown
provider model/realtime requestrequest and turn IDs, Connection lease, input digestprovider response/turn ID and terminal eventprovider documents exact request replay for that identifierstream disconnects after dispatch without terminal evidenceprovider 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_ledger revision and zero or more tasks. Task IDs are unique within the Session; subjects need not be. Lifecycle is pending -> in-progress -> blocked -> in-progress -> completed, with cancellation from any nonterminal state and archival only from completed or cancelled. 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, then done or abandoned. rewrite creates 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, respond and cancel are human-only operations on an exact Project and Session, with lifecycle pending -> answered | cancelled | expired. respond requires 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 always record-only with no activation. Target: activation-bound questions; a structured json answer allowed only when the interaction stores an exact protocol schema digest; acceptance atomically recording responder, answer digest, answered time and wake disposition (resume-waiting-session or record-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:

ClassProcess toolsEligible callersRemote/Routine/peer/voice
managed-read-onlyabsentmanaged local or Execution Node Sessionallowed if separately granted
local-operator-workspaceunsandboxedforeground local OS user on an attached CLI/TUIforbidden

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>;
}
  • publishCommitted references only an already committed event or outbox row; it cannot create a canonical event or bypass the owning aggregate transaction.
  • ProjectionRunner commits 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 admitted ObservationProcessorDescriptorV1 and 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 sets truncated.
  • The scheduler reports turns; the adapter reports items. A turn's end closes its open items (interrupted or incomplete), 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; a tool_use starts an item and its tool_result completes it, as command for Bash, file_change for Edit, Write and NotebookEdit (the diff from the tool's structured patch, or a new file's content) and tool_call otherwise, declined when Maia's guard or Claude refused it. The prompt Maia sends becomes the user message, and each result adds a usage item (input counts cache reads and writes, as Codex does) and an error item unless Maia interrupted the turn; API retries are error items with retrying. An error item 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 a reasoning item reading Thinking… 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), with permissionMode: "dontAsk" Claude asks nothing, so no approval_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).
  • synchronized carries 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 sets truncated; 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 resync once 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 with session turn send and session 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.

DataTrace (kept)Content (in transit only)Never stored
Turn identity and lifecyclesequence, turn, Session, command and runtime turn IDs, state, revisions, times
Promptdigest, size, native reference, delivery statethe prompt until its delivery closesa settled copy
Responsedigest, size, native reference, completionlive bufferssettled text in turns, receipts or stream records
Failurereason, code, a fixed sentence Maia wrotevendor or provider error text
Itemitem and turn IDs, epoch, sequence, schema version, kind, status, times, sizes, truncated, digest, native referencemessage text, reasoning summary, tool arguments and output, command text, diffs, paths, approval summariessettled copies
Tool, command, file changeadmitted tool name, exit code, change kind and countas Item
Extensionallowlisted typed fields with namespace and versionevery other fieldextension.data kept whole
Receipt and eventcommand, receipt, actor and target IDs, method, revisions, cursors, times, request digest, authorization, outcomecontent embedded while its delivery is opena settled turn or interaction snapshot
Interrupt receiptnativeRequired, persistPhase, runtimeTurnId, turn identityas Receipt
Usageinput, cached input, output, reasoning and total tokens; model calls; model, cost and duration with their sourceinvented zeros
Typed decisionoption ID or verdict, in the consuming operation's recordan answer until its operation accepts it
Peer messagemessage and delivery IDs, sender and recipient Session IDs, actors, lifecycle, times, sizes, digests, native referencessubject and body, under D-168's transit rule
Question and approvalinteraction and approval IDs, actors, kind, lifecycle, times, digests, the typed outcomequestion text, options and free-text answers, under D-168's transit rule
Excludedcredentials, 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.

ExecutorReaderDurable native sourceStatus
Codexcodex app-server thread reads (D-078)its own rollout filesto qualify, S12t
Claude CodeClaude Agent SDK session reads (D-078)its own session filesto qualify, S12t
Pi and Maia agentPi's session readernone for the managed worker, which keeps its Pi session in memory today; AG3a adds oneafter S23 and AG3a
OpenCode, Cursor and ACPeach server's or protocol's readsper harnesswith 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.send with 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 attachmentId and 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.close and operator-only session.attachment.revoke. Open accepts one exact agentSessionId, 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's accounts/ 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>
  1. The aggregate transaction inserts the canonical event and one outbox row keyed by (streamOwnerId, eventSequence).
  2. 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.
  3. A second transaction marks the range projected only when the exact bytes are durable.
  4. Restart truncates an incomplete final line, validates the segment chain and replays unprojected rows idempotently.
  5. 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.

PrimitiveQuestion meaningPreserved answer semantics
ChoiceSelect one of the supplied options; include no-match when neededSelected option; distribution when supplied by the provider
ScorePosition on at least two ordered rubric levelsFractional score in [0, levels.length - 1]; level distribution when supplied
Boolean / NoulProbability that the stated condition is trueProbability 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 .maia intent.

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.md or 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, the EVAL.md digest, the trace query and its asOfSequence or 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; otherwise eval.compare returns a non-comparable exploratory report.
  • Maintenance proposals never rewrite config automatically. The protocol-owned auto-safe registry 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:

  1. Bind the exact Session/owner, activation, source and policy revisions; filter authorized graph sources and pinned mandatory constraints before evaluation.
  2. 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.
  3. 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.
  4. 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).

AreaTables and contract keys
Identity and grantsactors; 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)
Configurationconfig_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 activationsagent_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 supportsession_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 receiptsoperation_previews; confirmation_consumptions (unique token digest, unique preview); command_receipts
Messagingagent_messages (unique sender idempotency key); message_deliveries (claim, visibility, retry, dead-letter)
Scheduling and placementroutines / 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 providersconnections_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 voicea2a_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 evidenceordered_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)
Observationobservation_partitions (next/committed/evidence watermarks); observation_spool_segments; observation_processors (processor+version); observation_checkpoints (processor+partition); observation_saved_filters
Evaluationevaluations (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 TTL and raises a placement barrier until a fresh authenticated running report 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 read fails as MonotonicClockUnreadable (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 settingDefaultAdmission condition
MAIA_NODE_HEADROOM_ENABLEDtrueSet false to disable host sampling and this gate.
MAIA_NODE_MAX_LOAD_PER_CPU1.0One-minute load divided by logical CPU count is at or below this value.
MAIA_NODE_MIN_MEMORY_AVAILABLE_PERCENT10Available memory is at or above this percentage of physical memory.
MAIA_NODE_MIN_DISK_FREE_PERCENT10Blocks 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.

CapabilityRequired failure and security casesPerformance
authwrong credential, actor, Project and replay; Node restart and expiry; credential bootstrap migrationrequest p95
Sessionworker or Node kill at each activation/effect edge; stale epoch; unauthorized toolactivation and event p95
processpath, network and environment escape; process-tree lossRSS and output limits
observationburst, slow client, replay, gaps, policy revocation, cross-Project or content cursor replayingest, fan-out and lag
tracespartial write, disk full, tamper, ACL and redactionappend and query p95
evaluationpoisoning, self-modification, evaluator disconnectbounded 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