Draft RFC

Agent Consensus Protocol (ACP) - Draft RFC v1

A minimal, transport-agnostic protocol for weighted multi-agent consensus. Designed for AI-native systems where agents hold heterogeneous beliefs, operate with different domain expertise, and must converge on actions without a central orchestrator.

Status: Draft Version: 1.0 License: CC-BY-4.0 Author: Prakul Sharma

All views expressed here are my own and do not represent the views of my employer. This is independent research on open protocol design, not affiliated with any organization or product.


Table of Contents

1. Introduction

2. Terminology

3. Protocol Overview

4. Proposal Lifecycle

5. Response Semantics

6. Weighted Voting Formula

7. Quorum Policies

8. Timeout and Expiration

9. Domain-Scoped Resolution

10. Human Escalation

11. Decision Events

12. Wire Format (JSON)

13. Transport

14. Security Considerations

15. Failure Modes

16. Comparison with Other Protocols

17. Implementation Guide

18. Extensibility

- Appendix B: Full JSON Schema Definitions

- Appendix C: Example Scenarios


1. Introduction

1.1 Problem

Multi-agent systems that must reach collective decisions face a fundamental coordination problem: agents hold heterogeneous beliefs, operate with different domain expertise and trust levels, and must converge on actions without a central orchestrator that becomes a bottleneck or single point of failure.

Existing distributed consensus algorithms (Raft, Paxos, PBFT) solve total-order state-machine replication - they are concerned with which log entry comes next. ACP solves a different problem: should this proposed action be taken, given that participating agents have weighted opinions, domain authority, and confidence levels that differ.

ACP is designed for AI-native systems where agents perceive event streams, reason about proposals, and express graded beliefs (confidence) - not just binary votes.

1.2 Goals

Define a minimal, transport-agnostic protocol for multi-agent weighted consensus

Support heterogeneous agent trust and domain expertise via weight factors

Enable human escalation as a first-class protocol primitive

Be implementable in any language without dependencies on a specific framework

Compose naturally with event-driven architectures

1.3 Non-Goals

Total-order log replication (use Raft/Paxos for that)

Agent discovery and capability negotiation (see Google A2A)

LLM inference or agent reasoning internals

Distributed transaction semantics (ACID)

Prescribing transport mechanisms

1.4 Design Principles

1. No central orchestrator - resolution is computed from responses already posted to a shared workspace; no coordinator node is required

2. Weighted beliefs, not binary votes - agents express confidence and domain authority, not just yes/no

3. Immutable proposals - once posted, proposal content never changes; responses accumulate

4. Human escalation is first-class - tied decisions and policy triggers route to humans before taking action

5. Graceful degradation - timeout-based resolution ensures liveness even if some agents are unavailable


2. Terminology

Proposal

A candidate action or decision posted to the shared workspace by an agent. A proposal has a lifecycle from PENDING through terminal states.

Endorsement

A response indicating agreement with a proposal, carrying a confidence level and weight factors.

Challenge

A response indicating disagreement with a proposal, carrying a confidence level and weight factors.

Quorum

The minimum number of responses required to trigger resolution by vote count (rather than waiting for timeout).

Resolution

The final determination of a proposal: its outcome (approved, rejected, escalated, or expired), the trigger that caused it, and the aggregated vote weights.

Domain

A named namespace scoping a proposal. Resolution is domain-scoped: an agent's authority within a domain is expressed as domain_authority.

Participant

Any agent authorized to respond to proposals within a given domain.

Weight

A scalar in [0, 1] derived from a participant's domain authority, confidence, and trust score. Weights determine how much a response influences the outcome.

Domain Authority

A scalar in [0, 1] measuring how expert a participant is within a specific domain. Domain authority is domain-specific: the same agent may have high authority in risk.credit and low authority in risk.market.

Confidence

A scalar in [0, 1] expressing how certain a participant is in their own response. A response with confidence 0.3 contributes less weight than one with confidence 0.9.

Trust Score

A scalar in [0, 1] representing system-level trust in a participant, typically derived from historical accuracy or security posture.

Escalation

A state where a proposal is forwarded to a human decision-maker because automated consensus cannot resolve it.

Decision Event

An immutable event emitted on resolution, carrying the outcome and causal lineage to the originating proposal.


3. Protocol Overview

ACP has three phases: Submit, Respond, and Resolve.


Phase 1: Submit
===============
Proposer                Shared Workspace
    |                        |
    |-- POST Proposal -----→ |
    |                        | (status: PENDING)
    |                        |

Phase 2: Respond
================
Participant A            Shared Workspace
    |                        |
    |← notify/poll --------- |
    |                        |
    |-- POST Endorsement --→ |
    |                        |

Participant B            Shared Workspace
    |                        |
    |← notify/poll --------- |
    |                        |
    |-- POST Challenge ----→ |
    |                        |

Phase 3: Resolve
================
                         Shared Workspace
                              |
                              | [quorum reached OR expires_at passed]
                              |
                              | compute: votes_for, votes_against
                              | determine: outcome
                              | emit:     Decision Event
                              |
                              | (status: RESOLVED | ESCALATED | EXPIRED)

3.1 Phase Descriptions

Submit - A proposer creates a Proposal with a unique ID, domain, payload, quorum requirement, and optional expiry. The proposal enters PENDING state on the shared workspace.

Respond - Participants who have authority in the proposal's domain review it and post either an Endorsement or a Challenge. Each participant may respond at most once. Participants cannot respond to their own proposals.

Resolve - Resolution is triggered automatically when the quorum threshold is met, or when the proposal expires. The workspace computes weighted vote totals, determines the outcome, and emits a Decision Event. Resolution is a purely local computation - no inter-agent communication is required.


4. Proposal Lifecycle

4.1 State Machine


                         ┌─────────┐
                         │         │
                         │ PENDING │
                         │         │
                         └────┬────┘
                              │
                ┌─────────────┼─────────────┐
                │             │             │
          quorum/        timeout/         manual
          vote result    vote result      escalate
                │             │             │
                ▼             ▼             ▼
        ┌──────────┐   ┌──────────┐  ┌───────────┐
        │ RESOLVED │   │ RESOLVED │  │ ESCALATED │
        │ (quorum) │   │(timeout) │  │           │
        └──────────┘   └──────────┘  └─────┬─────┘
                                           │
                                     human resolves
                                           │
                                           ▼
                                     ┌──────────┐
                                     │ RESOLVED │
                                     │ (manual) │
                                     └──────────┘

Special case: timeout with no responses:
        ┌─────────┐
        │ PENDING │ ──timeout, 0 responses──→ EXPIRED
        └─────────┘

RESOLVED, ESCALATED, and EXPIRED are terminal states. A proposal in a terminal state MUST NOT accept further responses.

4.2 Entry Types

Not all entries require full consensus. Implementations SHOULD support the following entry types:

TypeConsensus RequiredDefault Behavior
`OBSERVATION`NoAuto-resolves on post (APPROVED, QUORUM trigger)
`HYPOTHESIS`NoAuto-resolves on post (APPROVED, QUORUM trigger)
`PROPOSAL`YesFull consensus via Submit → Respond → Resolve

4.3 Proposal Fields

A Proposal MUST include the following fields:

FieldTypeDescription
`proposal_id`ULID (string)Unique identifier, sortable by creation time
`type`stringOne of: `observation`, `hypothesis`, `proposal`
`domain`stringNamespace scoping this proposal (e.g., `"risk.credit"`)
`author`stringParticipant ID of the proposer
`payload`objectArbitrary JSON payload; semantics are application-defined
`status`stringCurrent lifecycle state
`responses`arrayAccumulated endorsements and challenges
`quorum`integer ≥ 1Number of responses required to trigger resolution
`created_at`ISO 8601 datetimeCreation timestamp (UTC)
`expires_at`ISO 8601 datetime \nullOptional expiry; null means no timeout
`resolution`object \nullPopulated on terminal state; null while PENDING

5. Response Semantics

5.1 Response Types

ACP defines exactly two response types:

endorse - The participant agrees with the proposal and supports its execution.

challenge - The participant disagrees with the proposal and opposes its execution.

Implementations MUST NOT introduce additional response types in the base protocol. Extensions may add response types via the extensibility mechanism described in Section 18.

5.2 Response Fields

FieldTypeRequiredDescription
`participant_id`stringYesIdentifier of the responding participant
`response_type`stringYes`"endorse"` or `"challenge"`
`confidence`float [0.0, 1.0]YesParticipant's self-assessed certainty
`domain_authority`float [0.0, 1.0]YesCaller-supplied domain expertise level
`trust_score`float [0.0, 1.0]YesSystem-assigned trust in this participant
`weight`float [0.0, 1.0]Computed`domain_authority × confidence × trust_score`
`reason`string \nullNoHuman-readable rationale
`timestamp`ISO 8601 datetimeYesUTC timestamp when response was recorded

5.3 Constraints

One response per participant: A participant MUST NOT post more than one response to the same proposal. Implementations MUST reject duplicate responses with an error.

No self-response: The author of a proposal MUST NOT respond to it. Implementations MUST reject self-responses.

PENDING-only responses: Responses MUST only be accepted for proposals in PENDING state. Implementations MUST reject responses to proposals in terminal states.


6. Weighted Voting Formula

6.1 Weight Computation

Each response carries a weight that determines its influence on the outcome:


Weight = clamp(domain_authority, 0, 1)
       × clamp(confidence, 0, 1)
       × clamp(trust_score, 0, 1)

Where clamp(x, lo, hi) = max(lo, min(hi, x)).

All three factors are independently clamped to [0, 1] before multiplication. The resulting weight is always in [0, 1].

6.2 Vote Aggregation


votes_for     = Σ weight(r)  for r in responses where r.response_type == "endorse"
votes_against = Σ weight(r)  for r in responses where r.response_type == "challenge"

6.3 Outcome Determination


if votes_for > votes_against:
    outcome = APPROVED

elif votes_against > votes_for:
    outcome = REJECTED

else:  # votes_for == votes_against (including both 0.0)
    outcome = ESCALATED

Note: A proposal with zero responses that reaches quorum (quorum = 0, which is invalid) cannot arise since quorum MUST be ≥ 1. However, if a proposal expires with zero responses, the outcome is EXPIRED (not ESCALATED), as described in Section 8.


7. Quorum Policies

7.1 Count-Based Quorum (Default)

The default quorum policy resolves when the count of responses meets or exceeds a threshold:


is_met = len(responses) >= threshold

threshold MUST be a positive integer (≥ 1). Implementations MUST default to count-based quorum when no other policy is specified.

7.2 Weighted Quorum

An optional extension where quorum is based on accumulated vote weight rather than response count:


total_weight = Σ weight(r) for r in responses
is_met = total_weight >= threshold

threshold is a float in (0, max_possible_weight]. Implementations MAY support weighted quorum as an extension.

7.3 Unanimous Quorum

A special case where all registered participants in a domain must respond:


is_met = len(responses) >= len(domain_participants)

Implementations MAY support unanimous quorum. Note that unanimous quorum requires the implementation to know the set of eligible participants at proposal creation time.

7.4 Quorum Check Ordering

Quorum check MUST run before timeout check. If both conditions are true simultaneously (e.g., quorum is reached at the exact moment of expiry), the trigger MUST be QUORUM.


8. Timeout and Expiration

8.1 Timeout Trigger

A proposal with expires_at set MUST be checked for expiration. A proposal is expired when:


now >= expires_at

Where now is the current UTC time at the moment of the check.

8.2 Expiration Outcomes

ConditionOutcomeTrigger
`now >= expires_at` AND `len(responses) == 0``EXPIRED``TIMEOUT`
`now >= expires_at` AND `len(responses) > 0`Computed by voting formula`TIMEOUT`

8.3 Expiry Sweep

Implementations MUST run periodic expiry sweeps to detect and resolve expired proposals. The sweep interval is implementation-defined. Proposals MUST NOT remain in PENDING state indefinitely if they have an expires_at that has passed.

8.4 No Expiry

If expires_at is null, the proposal does not expire by timeout. It can only be resolved by quorum or manual escalation.


9. Domain-Scoped Resolution

9.1 Domain Namespaces

Every proposal is scoped to a domain string. Domains partition the proposal namespace and enable domain-specific authority:

Domain strings SHOULD be hierarchical, using . as separator (e.g., "risk.credit", "ops.deploy")

Domains are case-sensitive

Implementations MAY enforce that only participants registered for a domain can respond to proposals in that domain

9.2 Domain Authority

domain_authority in a response is specific to the domain of the proposal being responded to. The same participant may have different domain authority values across domains. Implementations SHOULD store per-domain authority in a participant registry.

9.3 Cross-Domain Proposals

ACP does not define cross-domain proposals. If an action requires consensus across multiple domains, the recommended pattern is a parent proposal that depends on child proposals per domain, with the parent resolved only when all children resolve.


10. Human Escalation

10.1 Escalation Triggers

A proposal MUST be escalated to human review in the following cases:

1. Tied vote - votes_for == votes_against after quorum or timeout (including both-zero)

2. Manual trigger - An authorized participant or system policy explicitly requests escalation

3. Policy-defined - Implementations MAY define policies that escalate before voting (e.g., "escalate all proposals touching production systems")

10.2 Escalation State

ESCALATED is a terminal state in the automated protocol. Once escalated:

No further automated responses are accepted

The proposal awaits human input

Human resolution sets the outcome (APPROVED or REJECTED) with trigger MANUAL

A Decision Event is emitted after human resolution

10.3 Escalation Metadata

Implementations SHOULD record:

The reason for escalation (tie, manual, policy)

The timestamp of escalation

Which human or team is responsible for resolution

10.4 Escalation Channels

ACP does not prescribe how humans are notified. Implementations MAY use any notification channel (email, Slack, ticketing system). The protocol requires only that a human can post a resolution that transitions the proposal from ESCALATED to RESOLVED.


11. Decision Events

11.1 Emitting Decision Events

On resolution (any terminal state reached), an implementation MUST emit a Decision Event. This event serves as the authoritative, immutable record of the outcome.

11.2 Decision Event Fields

FieldTypeDescription
`event_id`ULID (string)Unique event identifier
`event_type`stringAlways `"decision"`
`proposal_id`ULID (string)ID of the resolved proposal
`domain`stringDomain of the resolved proposal
`outcome`string`"approved"`, `"rejected"`, `"escalated"`, or `"expired"`
`trigger`string`"quorum"`, `"timeout"`, or `"manual"`
`votes_for`floatWeighted sum of endorsements
`votes_against`floatWeighted sum of challenges
`decided_at`ISO 8601 datetimeUTC timestamp of resolution
`schema_version`stringAlways `"1.0"` for this version

11.3 Causal Lineage

Decision Events SHOULD include causal lineage: a reference to the proposal that caused them. This allows downstream systems to trace the chain from original observation to action.

Implementations MAY extend Decision Events with caused_by (the proposal_id) and payload_summary fields.


12. Wire Format (JSON)

12.1 General Rules

All IDs are ULID strings (26-character base32, lexicographically sortable by creation time)

All timestamps are ISO 8601 strings with UTC timezone (Z suffix or +00:00)

Float fields use JSON numbers

schema_version MUST be present in all wire objects and MUST be "1.0" for this revision

12.2 Proposal Wire Format


{
  "schema_version": "1.0",
  "proposal_id": "01HXYZ...",
  "type": "proposal",
  "domain": "risk.credit",
  "author": "agent-risk-001",
  "payload": {
    "action": "approve_loan",
    "loan_id": "LOAN-42",
    "amount_usd": 250000
  },
  "status": "pending",
  "responses": [],
  "quorum": 2,
  "created_at": "2025-01-15T10:00:00Z",
  "expires_at": "2025-01-15T10:05:00Z",
  "resolution": null
}

12.3 Endorsement Wire Format


{
  "schema_version": "1.0",
  "participant_id": "agent-credit-002",
  "response_type": "endorse",
  "confidence": 0.92,
  "domain_authority": 0.95,
  "trust_score": 0.88,
  "weight": 0.7697,
  "reason": "Credit score 780, DTI 28% - within policy limits",
  "timestamp": "2025-01-15T10:01:30Z"
}

12.4 Challenge Wire Format


{
  "schema_version": "1.0",
  "participant_id": "agent-risk-003",
  "response_type": "challenge",
  "confidence": 0.75,
  "domain_authority": 0.80,
  "trust_score": 0.90,
  "weight": 0.54,
  "reason": "Property in flood zone, no flood insurance",
  "timestamp": "2025-01-15T10:02:00Z"
}

12.5 Resolution Wire Format


{
  "schema_version": "1.0",
  "outcome": "approved",
  "trigger": "quorum",
  "votes_for": 1.5394,
  "votes_against": 0.54,
  "decided_at": "2025-01-15T10:02:00Z"
}

12.6 Decision Event Wire Format


{
  "schema_version": "1.0",
  "event_id": "01HABC...",
  "event_type": "decision",
  "proposal_id": "01HXYZ...",
  "domain": "risk.credit",
  "outcome": "approved",
  "trigger": "quorum",
  "votes_for": 1.5394,
  "votes_against": 0.54,
  "decided_at": "2025-01-15T10:02:00Z"
}

13. Transport

ACP is transport-agnostic. The protocol defines message semantics and wire formats; it does not prescribe delivery.

13.1 Recommended Transports

TransportUse Case
HTTP POSTSimple request-response, polling
WebSocketReal-time push notifications
Message queue (NATS, Kafka, SQS)High-throughput, persistent delivery
In-process event busSingle-process or testing

13.2 Delivery Guarantees

Implementations SHOULD provide at-least-once delivery for proposal notifications to participants. Duplicate response rejection (Section 5.3) provides idempotency at the protocol level.

13.3 Ordering

ACP does not require total ordering of responses. Resolution is based on accumulated weight, not response order.


14. Security Considerations

14.1 Authentication

Implementations MUST authenticate participants before accepting proposals or responses. Unauthenticated submissions MUST be rejected.

Recommended mechanisms:

Asymmetric key pairs (Ed25519) bound to participant identity

JWT tokens for stateless authentication

mTLS for transport-level identity

14.2 Domain-Scoped Authorization

Access to domains SHOULD be enforced via an authorization policy. Participants MUST be authorized to submit proposals or responses within a given domain. Cross-domain access MUST require explicit grants.

14.3 Payload Signing

Implementations SHOULD sign proposal payloads with the author's private key. Signings allow downstream systems to verify that a decision was based on an unmodified payload.

14.4 Replay Protection

ULID-based proposal IDs provide natural replay protection: each proposal ID is unique and embeds a timestamp. Implementations MUST reject proposals with duplicate proposal_id values.

14.5 Classification Levels

In environments with data classification requirements, implementations SHOULD enforce that participants have the required clearance level before they can read a proposal's payload.

14.6 Audit Logging

All proposal submissions, responses, and resolutions MUST be written to an immutable audit log. The log SHOULD include: actor ID, action type, proposal ID, timestamp, and payload hash.


15. Failure Modes

15.1 LLM / Agent Timeout

If an agent fails to respond before expires_at, the timeout resolution path (Section 8) handles the outcome. The protocol is designed to be liveness-preserving: if some agents are unavailable, resolution still occurs via timeout.

15.2 Partial Consensus

If quorum is set too high (relative to the number of available participants), the proposal may expire via timeout with fewer responses than quorum. This is handled by Section 8.2. Implementations SHOULD set quorum conservatively to avoid spurious expirations.

15.3 Duplicate Responses

If a participant accidentally submits a response twice (e.g., due to at-least-once delivery), the implementation MUST reject the duplicate with a clear error. The first response stands.

15.4 Resolution Race Conditions

In distributed implementations, multiple nodes may attempt to resolve the same proposal concurrently. Implementations MUST use a compare-and-swap or optimistic locking mechanism to ensure exactly one resolution is recorded.

15.5 Clock Skew

Timeout resolution depends on comparing now with expires_at. Implementations SHOULD use a synchronized time source (NTP or equivalent). Clock skew SHOULD be bounded and documented. Implementations MAY add a configurable grace period to expires_at checks to accommodate skew.


16. Comparison with Other Protocols

16.1 Raft and Paxos

Raft and Paxos solve total-order state-machine replication: given a set of proposed log entries, all nodes agree on the same ordered sequence. This is the right tool for replicated databases and distributed coordination metadata.

ACP solves a different problem: should this specific proposed action be taken, given weighted, graded opinions from agents with heterogeneous expertise. ACP does not produce a log; it produces a decision. The two protocols are complementary: ACP decisions can be replicated via Raft for durability.

16.2 Google Agent-to-Agent (A2A) Protocol

Google's A2A protocol focuses on agent discovery, capability negotiation, and task delegation: how agents advertise their skills and how clients delegate tasks to them. A2A is concerned with the dispatch layer.

ACP is concerned with the consensus layer: once agents are known and a task is underway, ACP determines whether a proposed action should proceed. The two protocols are complementary and can be deployed together.

16.3 FIPA Contract Net

The FIPA Contract Net Protocol (FIPA SC00029H) is a bidding-based coordination protocol where a manager issues a call for proposals, agents submit bids, and the manager selects a winner. It is structurally similar to ACP in that proposals are posted and agents respond.

ACP differs in key ways:

Weighted consensus, not selection: ACP aggregates all responses into a collective decision; Contract Net selects a single winning agent

No manager role: ACP has no manager agent; resolution is a pure computation over accumulated responses

Graded confidence: ACP responses carry continuous confidence and weight values; Contract Net proposals are typically discrete bids

Human escalation: ACP defines escalation as a first-class outcome; Contract Net does not


17. Implementation Guide

17.1 Minimal Conformance (Basic)

A Basic conformant implementation MUST:

☐ Accept proposals with required fields (Section 4.3)

☐ Enforce PENDING-only response acceptance (Section 5.3)

☐ Enforce no-self-response constraint (Section 5.3)

☐ Enforce one-response-per-participant (Section 5.3)

☐ Compute weights using the formula in Section 6.1

☐ Implement count-based quorum (Section 7.1)

☐ Implement timeout resolution (Section 8)

☐ Emit a Resolution on all terminal transitions (Section 11)

☐ Support the wire format in Section 12

☐ Reject duplicate proposal_id values (Section 14.4)

17.2 Full Conformance

A Full conformant implementation additionally MUST:

☐ Implement all Basic requirements

☐ Support OBSERVATION and HYPOTHESIS auto-resolution (Section 4.2)

☐ Implement human escalation with manual resolution (Section 10)

☐ Emit Decision Events with causal lineage (Section 11.3)

☐ Implement periodic expiry sweep (Section 8.3)

☐ Enforce domain-scoped authorization (Section 14.2)

☐ Write to an audit log (Section 14.6)

☐ Support schema_version validation in all wire objects

17.3 What Implementors MAY Support

Implementors MAY extend the protocol with:

Weighted quorum (Section 7.2)

Unanimous quorum (Section 7.3)

CRDT-based distributed blackboards for multi-node deployments (Section 18.3)

Custom weight functions (Section 18.2)

Payload signing (Section 14.3)

Push-based participant notification

Implementors MUST NOT change core semantics (weight formula, outcome determination, constraint rules) in a way that would prevent interoperability with compliant implementations.


18. Extensibility

18.1 Custom Quorum Policies

Implementations MAY define custom quorum policies by implementing the QuorumPolicy interface:


class QuorumPolicy(Protocol):
    def is_met(self, proposal: Proposal) -> bool: ...

Custom policies MUST return True when quorum is met and False otherwise. Implementations SHOULD document which policies they support.

18.2 Custom Weight Functions

Implementations MAY replace the default weight formula with a custom function, provided:

The function returns a value in [0, 1]

It is deterministic given the same inputs

The custom function is documented in the implementation's conformance declaration

18.3 CRDT-Based Distributed Blackboard (Informative)

For multi-node deployments where multiple blackboard nodes must converge on the same proposal state, implementations MAY use a Conflict-free Replicated Data Type (CRDT) as the storage backend. A grow-only set (G-Set) CRDT is well-suited for accumulating responses, since responses are append-only and the ordering does not matter for the weight computation.

This extension is informative only - it is not required for conformance.

18.4 Schema Versioning

The schema_version field in wire objects is reserved for future backward-compatible extensions. Implementations MUST reject wire objects with schema_version values they do not support.


Appendix B: Full JSON Schema Definitions

B.1 Proposal Schema


{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://acp-protocol.org/v1/proposal",
  "title": "ACP Proposal",
  "type": "object",
  "required": [
    "schema_version", "proposal_id", "type", "domain", "author",
    "payload", "status", "responses", "quorum", "created_at"
  ],
  "properties": {
    "schema_version": { "type": "string", "const": "1.0" },
    "proposal_id": {
      "type": "string",
      "description": "ULID - 26-character base32 sortable unique identifier"
    },
    "type": {
      "type": "string",
      "enum": ["observation", "hypothesis", "proposal"]
    },
    "domain": { "type": "string", "minLength": 1 },
    "author": { "type": "string", "minLength": 1 },
    "payload": { "type": "object" },
    "status": {
      "type": "string",
      "enum": ["pending", "resolved", "escalated", "expired"]
    },
    "responses": {
      "type": "array",
      "items": { "$ref": "#/$defs/Response" }
    },
    "quorum": { "type": "integer", "minimum": 1 },
    "created_at": { "type": "string", "format": "date-time" },
    "expires_at": {
      "oneOf": [{ "type": "string", "format": "date-time" }, { "type": "null" }]
    },
    "resolution": {
      "oneOf": [{ "$ref": "#/$defs/Resolution" }, { "type": "null" }]
    }
  },
  "$defs": {
    "Response": {
      "type": "object",
      "required": [
        "schema_version", "participant_id", "response_type",
        "confidence", "domain_authority", "trust_score", "weight", "timestamp"
      ],
      "properties": {
        "schema_version": { "type": "string", "const": "1.0" },
        "participant_id": { "type": "string" },
        "response_type": { "type": "string", "enum": ["endorse", "challenge"] },
        "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
        "domain_authority": { "type": "number", "minimum": 0, "maximum": 1 },
        "trust_score": { "type": "number", "minimum": 0, "maximum": 1 },
        "weight": { "type": "number", "minimum": 0, "maximum": 1 },
        "reason": { "oneOf": [{ "type": "string" }, { "type": "null" }] },
        "timestamp": { "type": "string", "format": "date-time" }
      }
    },
    "Resolution": {
      "type": "object",
      "required": [
        "schema_version", "outcome", "trigger",
        "votes_for", "votes_against", "decided_at"
      ],
      "properties": {
        "schema_version": { "type": "string", "const": "1.0" },
        "outcome": {
          "type": "string",
          "enum": ["approved", "rejected", "escalated", "expired"]
        },
        "trigger": {
          "type": "string",
          "enum": ["quorum", "timeout", "manual"]
        },
        "votes_for": { "type": "number", "minimum": 0 },
        "votes_against": { "type": "number", "minimum": 0 },
        "decided_at": { "type": "string", "format": "date-time" }
      }
    }
  }
}

B.2 Decision Event Schema


{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://acp-protocol.org/v1/decision-event",
  "title": "ACP Decision Event",
  "type": "object",
  "required": [
    "schema_version", "event_id", "event_type", "proposal_id",
    "domain", "outcome", "trigger", "votes_for", "votes_against", "decided_at"
  ],
  "properties": {
    "schema_version": { "type": "string", "const": "1.0" },
    "event_id": { "type": "string" },
    "event_type": { "type": "string", "const": "decision" },
    "proposal_id": { "type": "string" },
    "domain": { "type": "string" },
    "outcome": {
      "type": "string",
      "enum": ["approved", "rejected", "escalated", "expired"]
    },
    "trigger": {
      "type": "string",
      "enum": ["quorum", "timeout", "manual"]
    },
    "votes_for": { "type": "number", "minimum": 0 },
    "votes_against": { "type": "number", "minimum": 0 },
    "decided_at": { "type": "string", "format": "date-time" }
  }
}

Appendix C: Example Scenarios

C.1 2-of-3 Endorsement

Three agents review a loan approval proposal. Two endorse, one challenges. Quorum is 2.


Proposal: domain="risk.credit", quorum=2

Agent A (domain_authority=0.9, confidence=0.85, trust_score=0.9):
  → ENDORSE, weight = 0.9 × 0.85 × 0.9 = 0.6885

Agent B (domain_authority=0.8, confidence=0.7, trust_score=0.85):
  → ENDORSE, weight = 0.8 × 0.7 × 0.85 = 0.476

[Quorum reached: 2 responses ≥ 2]

votes_for = 0.6885 + 0.476 = 1.1645
votes_against = 0.0

Outcome: APPROVED (trigger: QUORUM)

Note: The third agent never needed to respond - quorum was met at 2.

C.2 Weighted Domain Experts

A proposal requires sign-off from two domain experts. One senior expert challenges; a junior expert endorses. Despite 1-1 response count, weight determines outcome.


Proposal: domain="ops.deploy", quorum=2

Senior Expert (domain_authority=0.95, confidence=0.90, trust_score=0.95):
  → CHALLENGE, weight = 0.95 × 0.90 × 0.95 = 0.8123

Junior Agent (domain_authority=0.4, confidence=0.6, trust_score=0.7):
  → ENDORSE, weight = 0.4 × 0.6 × 0.7 = 0.168

[Quorum reached: 2 responses ≥ 2]

votes_for = 0.168
votes_against = 0.8123

Outcome: REJECTED (trigger: QUORUM)

C.3 Timeout with Partial Responses

A proposal with quorum=3 expires with only one endorsement.


Proposal: domain="finance.budget", quorum=3, expires_at=T+5min

Agent A (domain_authority=0.8, confidence=0.75, trust_score=0.9):
  → ENDORSE, weight = 0.8 × 0.75 × 0.9 = 0.54

[T+5min: expires_at reached, only 1 response, quorum not met]

votes_for = 0.54
votes_against = 0.0

Outcome: APPROVED (trigger: TIMEOUT)

No other agents responded, but the single endorsement outweighs zero challenges.

C.4 Escalation from Tie

Two high-authority agents submit exactly equal-weight opposing responses.


Proposal: domain="strategy.pricing", quorum=2

Agent A (domain_authority=0.9, confidence=0.8, trust_score=0.9):
  → ENDORSE, weight = 0.9 × 0.8 × 0.9 = 0.648

Agent B (domain_authority=0.9, confidence=0.8, trust_score=0.9):
  → CHALLENGE, weight = 0.9 × 0.8 × 0.9 = 0.648

[Quorum reached: 2 responses ≥ 2]

votes_for = 0.648
votes_against = 0.648

votes_for == votes_against → ESCALATED (trigger: QUORUM)

Human resolves → APPROVED (trigger: MANUAL)

This is a draft specification published for community review. If you have feedback, corrections, or implementation experience to share, reach out at hello@prakulsharma.ai.

All views expressed in this document are solely my own and do not represent or reflect the views, positions, or policies of my employer. This is independent thinking on open protocol standards, not affiliated with any organization or product.

← All Perspectives