MACP

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.

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

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'Requesting' | 'Voting' | 'Committed'Current phase

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']

RFC Validation Rules

  1. At most one ApprovalRequest per session (base v1)
  2. requiredApprovals must be > 0 and must not exceed participant count
  3. Each participant may cast at most one ballot (Approve, Reject, or Abstain) — latest replaces earlier
  4. Session is eligible for Commitment when:
    • Approvals reach the threshold, OR
    • Remaining possible approvals cannot reach the threshold
  5. Only an authorized coordinator may emit Commitment
OutcomeAction
Threshold reachedquorum.approved
Threshold unreachablequorum.rejected

Example

See examples/quorum-smoke.ts.