Quorum Mode

Mode identifier: macp.mode.quorum.v1 Participant model: quorum Determinism: semantic-deterministic

Purpose

Threshold-based approval or rejection. An action requires a specified number of approvals before it can be committed.

Canonical references: RFC-MACP-0011 (Quorum Mode) is normative for the state machine, threshold arithmetic, and validation constraints. See also the spec mode summaries and runtime modes guide › Quorum Mode for validation as implemented. This page covers the TypeScript API.

Session Lifecycle

SessionStart → ApprovalRequest → Approve/Reject/Abstain (per participant) → Commitment

API

QuorumSession

import { QuorumSession } from 'macp-sdk-typescript';

const session = new QuorumSession(client);
await session.start({
  intent: 'approve production deploy',
  participants: ['alice', 'bob', 'carol'],
  ttlMs: 60_000,
});

Methods

MethodMessage TypeDescription
requestApproval(input)ApprovalRequestDefine what needs approval
approve(input)ApproveCast an approval vote
reject(input)RejectCast a rejection vote
abstain(input)AbstainAbstain from voting
commit(input)CommitmentFinalize once quorum is reached

Like every mode session, QuorumSession also exposes the shared lifecycle helpers — metadata(), cancel(reason), suspend(reason), resume(reason), and openStream(). suspend() (proto 0.1.3+) is a non-terminal pause: the runtime banks the remaining TTL and rejects messages until resume() restores SESSION_STATE_OPEN and the banked TTL. See Decision Mode → Lifecycle helpers.

Request Approval

await session.requestApproval({
  requestId: 'r1',
  action: 'deploy-v3.0-to-production',
  summary: 'Production deployment of v3.0 with new auth system',
  details: Buffer.from('...'),  // optional
  requiredApprovals: 2,          // must be > 0 and <= participant count
});

Cast Votes

// Approve
await session.approve({
  requestId: 'r1',
  reason: 'all tests pass, staging verified',
  sender: 'alice',
  auth: Auth.devAgent('alice'),
});

// Reject
await session.reject({
  requestId: 'r1',
  reason: 'performance regression detected',
  sender: 'bob',
  auth: Auth.devAgent('bob'),
});

// Abstain
await session.abstain({
  requestId: 'r1',
  reason: 'not familiar with this component',
  sender: 'carol',
  auth: Auth.devAgent('carol'),
});

Vote Override

If the same sender votes again, their new vote replaces the previous one:

// Bob initially rejects
await session.reject({ requestId: 'r1', sender: 'bob', auth: Auth.devAgent('bob') });

// Performance fix deployed, Bob changes to approve
await session.approve({
  requestId: 'r1',
  reason: 'regression fixed',
  sender: 'bob',
  auth: Auth.devAgent('bob'),
});
// Bob's rejection is replaced by approval

QuorumProjection

State

PropertyTypeDescription
requestsMap<string, ApprovalRequestRecord>Approval requests
ballotsMap<string, Map<string, BallotRecord>>requestId → sender → ballot
transcriptEnvelope[]All accepted envelopes
phase'Pending' | 'Voting' | 'Committed'Current phase
commitmentRecord<string, unknown> | undefinedCommitment payload if resolved

BallotRecord

interface BallotRecord {
  requestId: string;
  vote: 'approve' | 'reject' | 'abstain';
  reason?: string;
  sender: string;
}

Query Helpers

// Vote counts
session.projection.approvalCount('r1');          // number of approve votes
session.projection.rejectionCount('r1');         // number of reject votes
session.projection.abstentionCount('r1');        // number of abstain votes

// Threshold checks
session.projection.threshold('r1');              // requiredApprovals value
session.projection.hasQuorum('r1');              // approvalCount >= requiredApprovals
session.projection.remainingVotesNeeded('r1');   // max(0, required - approvalCount)

// Participation
session.projection.votedSenders('r1');           // ['alice', 'bob', 'carol']

// Commitment readiness
session.projection.commitmentReady('r1');        // quorum reached and not yet committed
session.projection.isThresholdUnreachable('r1', 3); // remaining unvoted eligibles can't reach threshold
session.projection.isCommitted;                  // true once a Commitment is applied
session.projection.isPositiveOutcome;            // undefined until committed; then outcomePositive

RFC Validation Rules

The runtime enforces the cross-message rules — at most one ApprovalRequest per session (base v1), requiredApprovals within (0, participant count], one ballot per participant (latest replaces earlier), commitment eligibility only once the threshold is reached or provably unreachable, and coordinator-only Commitment. The normative rule set lives in RFC-MACP-0011 §4; the runtime modes guide › Quorum Mode documents validation as implemented.

OutcomeAction
Threshold reachedquorum.approved
Threshold unreachablequorum.rejected

Example

See examples/quorum-smoke.ts.