MACP

Architecture

Two-Layer Design

The SDK is organized into two layers:

┌─────────────────────────────────────────────┐
│         High-Level Session Helpers           │
│  DecisionSession · ProposalSession · ...     │
│  ┌─────────────────────────────────────┐     │
│  │       Projections (local state)     │     │
│  │  DecisionProjection · TaskProjection│     │
│  └─────────────────────────────────────┘     │
├─────────────────────────────────────────────┤
│          Low-Level Transport                 │
│  MacpClient · MacpStream · ProtoRegistry     │
├─────────────────────────────────────────────┤
│          gRPC / Protobuf                     │
│  @grpc/grpc-js · protobufjs                  │
└─────────────────────────────────────────────┘


     ┌─────────────────┐
     │   MACP Runtime   │
     │   (Rust/gRPC)    │
     └─────────────────┘

Layer 1: MacpClient (Transport)

MacpClient provides typed wrappers around the gRPC MACPRuntimeService:

RPCMethodReturns
Initializeclient.initialize()InitializeResult
Sendclient.send(envelope)Ack
StreamSessionclient.openStream()MacpStream
GetSessionclient.getSession(id)SessionMetadata
CancelSessionclient.cancelSession(id, reason)Ack
GetManifestclient.getManifest(agentId?)AgentManifest
ListModesclient.listModes()ModeDescriptor[]
ListRootsclient.listRoots()Root[]
ListExtModesclient.listExtModes()ModeDescriptor[]
RegisterExtModeclient.registerExtMode(desc){ ok, error? }
UnregisterExtModeclient.unregisterExtMode(mode){ ok, error? }
PromoteModeclient.promoteMode(mode, name?){ ok, error?, mode? }
WatchModeRegistryvia ModeRegistryWatcherasync iterator
WatchRootsvia RootsWatcherasync iterator

The client dynamically loads protobuf definitions from the proto/ directory at construction time.

Layer 2: Session Helpers

Each coordination mode has a session class that:

  1. Holds a sessionId, version strings, and optional auth
  2. Provides typed methods for each mode-specific message type
  3. Builds envelopes via buildEnvelope() + ProtoRegistry.encodeKnownPayload()
  4. Sends via MacpClient.send() with automatic Ack checking
  5. Applies accepted envelopes to a local projection
// Internal pattern (same for all session classes):
private async sendAndTrack(envelope: Envelope, auth?: AuthConfig): Promise<Ack> {
  const ack = await this.client.send(envelope, { auth: auth ?? this.auth });
  if (ack.ok) this.projection.applyEnvelope(envelope, this.client.protoRegistry);
  return ack;
}

Projections

Projections are pure state machines that track session state client-side. They receive envelopes and maintain typed collections:

Envelope → applyEnvelope() → updates internal state

                               ├── Maps (proposals, tasks, handoffs, etc.)
                               ├── Arrays (evaluations, updates, etc.)
                               ├── Phase tracking
                               └── Query helpers (voteTotals, hasQuorum, etc.)

Projections only track state for envelopes that were successfully accepted (Ack ok: true). Rejected messages are never applied.

Key property: Projections are deterministic. Given the same sequence of envelopes, they always produce the same state. This makes them easy to test without a running runtime.

ProtoRegistry

The ProtoRegistry is the bridge between TypeScript objects and protobuf wire format:

TypeScript Object → encodeKnownPayload(mode, messageType, value) → Buffer
Buffer → decodeKnownPayload(mode, messageType, payload) → TypeScript Object

It maintains two lookup maps:

  • MODE_MAP: Maps (mode, messageType) to protobuf type names for mode-specific messages
  • CORE_MAP: Maps messageType to protobuf type names for core messages (SessionStart, Commitment, Signal, Progress)

For extension modes using JSON encoding (like ext.multi_round.v1), it falls back to JSON serialization.

Runtime Boundary

The runtime is the authoritative source of truth. The SDK provides convenience but does not enforce:

ConcernHandled By
Session state (OPEN/RESOLVED/EXPIRED)Runtime
Message ordering (acceptance order)Runtime
Message deduplication (message_id)Runtime
TTL enforcementRuntime
Mode-specific validationRuntime
Participant authorizationRuntime
Envelope building + encodingSDK
Local state projectionSDK
Typed method signaturesSDK

If the runtime rejects a message (Ack ok: false), the SDK throws MacpAckError and does not apply the envelope to the projection.

File Organization

src/
├── index.ts              # Barrel export
├── client.ts             # MacpClient + MacpStream
├── auth.ts               # Auth factory + gRPC metadata
├── constants.ts          # MACP_VERSION, mode identifiers
├── envelope.ts           # Envelope + payload builders
├── errors.ts             # Error class hierarchy
├── proto-registry.ts     # Protobuf encode/decode registry
├── types.ts              # TypeScript interfaces
├── watchers.ts           # ModeRegistryWatcher, RootsWatcher
├── decision.ts           # DecisionSession
├── proposal.ts           # ProposalSession
├── task.ts               # TaskSession
├── handoff.ts            # HandoffSession
├── quorum.ts             # QuorumSession
├── projections.ts        # Barrel re-export for projections/
└── projections/
    ├── decision.ts       # DecisionProjection
    ├── proposal.ts       # ProposalProjection
    ├── task.ts           # TaskProjection
    ├── handoff.ts        # HandoffProjection
    └── quorum.ts         # QuorumProjection