MACP

Quorum Mode

Mode URI: macp.mode.quorum.v1 Status: provisional RFC: RFC-MACP-0011

Threshold-based approval or rejection. N-of-M participants must approve for the action to pass. Designed for governance, compliance gates, and multi-party authorization.

When to use

Use Quorum mode when an action requires approval from a minimum number of parties:

  • Security policy changes (require 3-of-5 security team members)
  • Production deployments (require 2-of-3 reviewers)
  • Budget approvals (require manager + finance)
  • Any N-of-M voting scenario

Participant model: quorum

Threshold-based, not unanimous. The required_approvals field sets the bar. Each eligible participant casts at most one ballot (approve, reject, or abstain). The session resolves when the threshold is reached or becomes mathematically unreachable.

Determinism: semantic-deterministic

Same accepted envelope sequence → same ballot counts and threshold outcome. The quorum result is fully determined by the message history.

Message flow

SessionStart

ApprovalRequest (defines action, threshold)

Approve / Reject / Abstain (participants cast ballots)

Commitment → RESOLVED

Key semantics

  • At most one ApprovalRequest per session (v1)
  • required_approvals must be > 0 and ≤ participant count
  • Each participant casts at most one ballot — later ballots override earlier ones
  • Commitment is eligible when:
    • Approvals ≥ required_approvals (threshold reached), OR
    • Remaining possible approvals cannot reach threshold (mathematically unreachable)

Authorization rules

MessageWho can send
ApprovalRequestSession initiator (coordinator)
ApproveAny declared participant
RejectAny declared participant
AbstainAny declared participant
CommitmentSession initiator / authorized coordinator

Terminal conditions

A session becomes eligible for Commitment when:

  1. Threshold reached: approval_count >= required_approvals
  2. Threshold unreachable: approval_count + remaining_voters < required_approvals

The orchestrator then commits with the appropriate action (approved or rejected).

Session helper

from macp_sdk import AuthConfig, MacpClient
from macp_sdk.quorum import QuorumSession

client = MacpClient(target="127.0.0.1:50051", secure=False, auth=AuthConfig.for_dev_agent("coordinator"))
session = QuorumSession(client)
session.start(
    intent="approve security policy update",
    participants=["coordinator", "alice", "bob", "carol", "dave", "eve"],
    ttl_ms=86_400_000,  # 24 hours
)

# Coordinator creates the approval request
session.request_approval(
    "r1",
    "security-policy-tls13",
    summary="Enforce TLS 1.3 minimum across all services",
    details=b'{"affected_services": 47, "rollout_plan": "gradual over 2 weeks"}',
    required_approvals=3,
)

# Participants vote over time
session.approve("r1", reason="long overdue improvement", sender="alice")
session.reject("r1", reason="too aggressive timeline", sender="bob")
session.approve("r1", reason="security best practice", sender="carol")
session.abstain("r1", reason="not in my domain", sender="dave")
session.approve("r1", reason="agreed", sender="eve")

# Check and commit
proj = session.quorum_projection
total_eligible = 5  # all participants except coordinator

if proj.is_threshold_reached():
    session.commit(
        action="quorum.approved",
        authority_scope="security-policy",
        reason=f"{proj.approval_count()} of {total_eligible} approved (threshold: 3)",
    )
elif proj.is_threshold_unreachable(total_eligible):
    session.commit(
        action="quorum.rejected",
        authority_scope="security-policy",
        reason=f"Only {proj.approval_count()} approvals possible, need 3",
    )

Projection queries

proj = session.quorum_projection

# Request metadata
proj.request                              # ApprovalRequestRecord or None
proj.request.required_approvals           # 3
proj.request.action                       # "security-policy-tls13"

# Ballots
proj.ballots                              # dict[sender, BallotRecord]
proj.ballots["alice"].choice              # "approve"
proj.ballots["bob"].choice                # "reject"

# Counts
proj.approval_count()                     # 3
proj.rejection_count()                    # 1
proj.abstention_count()                   # 1

# Threshold logic
proj.is_threshold_reached()               # True (3 >= 3)
proj.is_threshold_unreachable(5)          # False
proj.commitment_ready(5)                  # True (threshold reached OR unreachable)

# Lifecycle
proj.phase                                # "Pending" | "Voting" | "Committed"
proj.is_committed                         # True after Commitment

Ballot override

If the same sender votes multiple times, the latest ballot supersedes the previous one:

session.reject("r1", sender="alice")   # alice initially rejects
session.approve("r1", sender="alice")  # alice changes to approve

proj.ballots["alice"].choice  # "approve" (latest wins)
proj.approval_count()         # 1 (not 0)

Orchestrator patterns

Deadline-based auto-commit

import time

session.request_approval("r1", "deploy", required_approvals=2)

deadline = time.time() + 3600  # 1 hour
while time.time() < deadline:
    # ... collect votes asynchronously ...
    if proj.commitment_ready(total_eligible=5):
        break
    time.sleep(10)

if proj.is_threshold_reached():
    session.commit(action="approved", ...)
else:
    session.commit(action="rejected", reason="deadline reached without quorum")

Weighted quorum (orchestrator logic)

The SDK tracks raw ballot counts. For weighted voting (e.g., senior reviewers count double), implement the weighting in your orchestrator:

weights = {"alice": 2, "bob": 1, "carol": 1}
weighted_approvals = sum(
    weights.get(sender, 1)
    for sender, ballot in proj.ballots.items()
    if ballot.choice == "approve"
)
if weighted_approvals >= required_weighted:
    session.commit(...)

Error cases

ErrorWhenHow to handle
FORBIDDEN on Approve/Reject/AbstainSender not a declared participantVerify sender
INVALID_ENVELOPESecond ApprovalRequest in same sessionOnly one per session (v1)
FORBIDDEN on CommitmentSender not the coordinatorOnly initiator can commit

API Reference

::: macp_sdk.quorum.QuorumSession

::: macp_sdk.quorum.QuorumProjection