Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "macp-runtime"
version = "0.1.0"
version = "0.3.0"
edition = "2021"

[dependencies]
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# macp-runtime v0.2
# macp-runtime v0.3

**Minimal Coordination Runtime (MCR)** — an RFC-0001-compliant gRPC server implementing the Multi-Agent Coordination Protocol (MACP).

Expand All @@ -9,13 +9,13 @@ The MACP Runtime provides session-based message coordination between autonomous
- **RFC-0001 Compliant Protocol** — Structured protobuf schema with versioned envelope, typed errors, and capability negotiation
- **Initialize Handshake** — Protocol version negotiation and capability discovery before any session work begins
- **Pluggable Mode System** — Coordination logic is decoupled from runtime physics; ship new modes without touching the kernel
- **Decision Mode (RFC Lifecycle)** — Full Proposal → Evaluation → Objection → Vote → Commitment workflow with phase tracking
- **Multi-Round Convergence Mode** — Participant-based `all_equal` convergence strategy with automatic resolution
- **Decision Mode (RFC Lifecycle)** — Full Proposal → Evaluation → Objection → Vote → Commitment workflow with phase tracking and mode-aware authorization
- **Multi-Round Convergence Mode (Experimental)** — Participant-based `all_equal` convergence strategy with automatic resolution (not advertised via discovery RPCs)
- **Session Cancellation** — Explicit `CancelSession` RPC to terminate sessions with a recorded reason
- **Message Deduplication** — Idempotent message handling via `seen_message_ids` tracking
- **Mode-Aware Authorization** — Sender authorization delegated to modes; Decision Mode allows orchestrator Commitment bypass per RFC
- **Participant Validation** — Sender membership enforcement when a participant list is configured
- **Signal Messages** — Ambient, session-less messages for out-of-band coordination signals
- **Bidirectional Streaming** — `StreamSession` RPC for real-time session event streaming
- **Mode & Manifest Discovery** — `ListModes` and `GetManifest` RPCs for runtime introspection
- **Structured Errors** — `MACPError` with RFC error codes, session/message correlation, and detail payloads
- **Append-Only Audit Log** — Log-before-mutate ordering for every session event
Expand Down Expand Up @@ -107,7 +107,7 @@ The runtime exposes `MACPRuntimeService` on `127.0.0.1:50051` with the following
|-----|-------------|
| `Initialize` | Protocol version negotiation and capability exchange |
| `Send` | Send an Envelope, receive an Ack |
| `StreamSession` | Bidirectional streaming for session events |
| `StreamSession` | Bidirectional streaming for session events (not yet fully implemented) |
| `GetSession` | Query session metadata by ID |
| `CancelSession` | Cancel an active session with a reason |
| `GetManifest` | Retrieve agent manifest and supported modes |
Expand Down
6 changes: 3 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Welcome to the Multi-Agent Coordination Protocol (MACP) Runtime documentation. T

The MACP Runtime — also called the **Minimal Coordination Runtime (MCR)** — is a **gRPC server** that helps multiple AI agents or programs coordinate with each other. Think of it as a traffic controller for structured conversations between autonomous agents: it manages who can speak, tracks the state of each conversation, enforces time limits, and determines when a conversation has reached its conclusion.

Version **0.2** of the runtime implements **RFC-0001**, introducing a formal protocol handshake, structured error reporting, a rich Decision Mode lifecycle, session cancellation, message deduplication, participant validation, and a host of new RPCs for runtime introspection.
Version **0.3** of the runtime implements **RFC-0001**, introducing a formal protocol handshake, structured error reporting, a rich Decision Mode lifecycle, session cancellation, message deduplication, participant validation, mode-aware authorization, and a host of new RPCs for runtime introspection.

### Real-World Analogy

Expand Down Expand Up @@ -107,7 +107,7 @@ Two modes are built in:
| Mode Name | Aliases | Description |
|-----------|---------|-------------|
| `macp.mode.decision.v1` | `decision` | RFC-compliant decision lifecycle: Proposal → Evaluation → Objection → Vote → Commitment |
| `macp.mode.multi_round.v1` | `multi_round` | Participant-based convergence using `all_equal` strategy |
| `macp.mode.multi_round.v1` | `multi_round` | Participant-based convergence using `all_equal` strategy (experimental, not on discovery surfaces) |

An empty `mode` field defaults to `macp.mode.decision.v1` for backward compatibility.

Expand Down Expand Up @@ -188,7 +188,7 @@ cargo run

You should see:
```
macp-runtime v0.2 (RFC-0001) listening on 127.0.0.1:50051
macp-runtime v0.3.0 (RFC-0001) listening on 127.0.0.1:50051
```

**Terminal 2** — Run a test client:
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Architecture

This document explains how the MACP Runtime v0.2 is built internally. It walks through every component, every data structure, every flow, and every design decision in narrative detail. You do not need to know Rust to follow along — the documentation explains concepts in plain language, with code excerpts for precision where it matters.
This document explains how the MACP Runtime v0.3 is built internally. It walks through every component, every data structure, every flow, and every design decision in narrative detail. You do not need to know Rust to follow along — the documentation explains concepts in plain language, with code excerpts for precision where it matters.

---

Expand Down
14 changes: 7 additions & 7 deletions docs/examples.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Examples and Usage

This document provides step-by-step examples of using the MACP Runtime v0.2. It covers the full lifecycle — from protocol handshake to session creation, decision-making, convergence, cancellation, and error handling — with detailed explanations of what happens at each step.
This document provides step-by-step examples of using the MACP Runtime v0.3. It covers the full lifecycle — from protocol handshake to session creation, decision-making, convergence, cancellation, and error handling — with detailed explanations of what happens at each step.

---

Expand Down Expand Up @@ -35,7 +35,7 @@ cargo run

You should see:
```
macp-runtime v0.2 (RFC-0001) listening on 127.0.0.1:50051
macp-runtime v0.3.0 (RFC-0001) listening on 127.0.0.1:50051
```

The server is now ready to accept connections on port 50051.
Expand Down Expand Up @@ -108,7 +108,7 @@ println!(

**Expected output:**
```
ListModes: ["macp.mode.decision.v1", "macp.mode.multi_round.v1"]
ListModes: ["macp.mode.decision.v1"]
```

### Step 4: Create a Session (SessionStart)
Expand Down Expand Up @@ -228,7 +228,7 @@ GetSession: state=2 mode=decision

## Example 2: Full Decision Mode Lifecycle

The Decision Mode in v0.2 supports a rich lifecycle: Proposal, Evaluation, Objection, Vote, and Commitment. Here is how a complete decision process flows:
The Decision Mode in v0.3 supports a rich lifecycle: Proposal, Evaluation, Objection, Vote, and Commitment. Here is how a complete decision process flows:

### Step 1: Create a Session

Expand Down Expand Up @@ -526,8 +526,8 @@ The fuzz client (`src/bin/fuzz_client.rs`) is a comprehensive test that exercise
[authorized_sender] ok=true duplicate=false error=''
[signal] ok=true duplicate=false error=''
[get_session] state=2 mode=decision
[list_modes] count=2 modes=["macp.mode.decision.v1", "macp.mode.multi_round.v1"]
[get_manifest] agent_id=macp-runtime modes=["macp.mode.decision.v1", "macp.mode.multi_round.v1"]
[list_modes] count=1 modes=["macp.mode.decision.v1"]
[get_manifest] agent_id=macp-runtime modes=["macp.mode.decision.v1"]
[list_roots] count=0
```

Expand Down Expand Up @@ -1073,7 +1073,7 @@ let contribute = create_envelope("multi_round", "Contribute", "mr1", "alice",

### Q: What protocol version should I use?

**A:** Use `macp_version: "1.0"`. This is the only supported version in v0.2. Always call `Initialize` first to confirm.
**A:** Use `macp_version: "1.0"`. This is the only supported version in v0.3. Always call `Initialize` first to confirm.

### Q: How do I encode the SessionStart payload?

Expand Down
2 changes: 1 addition & 1 deletion docs/protocol.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MACP Protocol Specification (v1.0 — RFC-0001)

This document is the authoritative specification of the Multi-Agent Coordination Protocol (MACP) as implemented by `macp-runtime` v0.2. It describes every message type, every field, every validation rule, every error code, and every behavioral guarantee in narrative detail. Whether you are building a client, implementing a new mode, or auditing the protocol for correctness, this document is your reference.
This document is the authoritative specification of the Multi-Agent Coordination Protocol (MACP) as implemented by `macp-runtime` v0.3. It describes every message type, every field, every validation rule, every error code, and every behavioral guarantee in narrative detail. Whether you are building a client, implementing a new mode, or auditing the protocol for correctness, this document is your reference.

---

Expand Down
8 changes: 8 additions & 0 deletions proto/macp/v1/core.proto
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ message GetManifestRequest {
string agent_id = 1;
}

message TransportEndpoint {
string transport = 1;
string uri = 2;
repeated string content_types = 3;
map<string, string> metadata = 4;
}

message AgentManifest {
string agent_id = 1;
string title = 2;
Expand All @@ -152,6 +159,7 @@ message AgentManifest {
repeated string input_content_types = 5;
repeated string output_content_types = 6;
map<string, string> metadata = 7;
repeated TransportEndpoint transport_endpoints = 8;
}

message ModeDescriptor {
Expand Down
91 changes: 59 additions & 32 deletions src/bin/client.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use macp_runtime::decision_pb::ProposalPayload;
use macp_runtime::pb::macp_runtime_service_client::MacpRuntimeServiceClient;
use macp_runtime::pb::{
Envelope, GetSessionRequest, InitializeRequest, ListModesRequest, SendRequest,
SessionStartPayload,
};
use prost::Message;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
Expand Down Expand Up @@ -33,16 +36,27 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
modes_resp.modes.iter().map(|m| &m.mode).collect::<Vec<_>>()
);

// 3) SessionStart
// 3) SessionStart with participants (canonical mode)
let start_payload = SessionStartPayload {
intent: "demo canonical lifecycle".into(),
participants: vec!["alice".into(), "bob".into()],
mode_version: String::new(),
configuration_version: String::new(),
policy_version: String::new(),
ttl_ms: 60_000,
context: vec![],
roots: vec![],
};

let start = Envelope {
macp_version: "1.0".into(),
mode: "decision".into(),
mode: "macp.mode.decision.v1".into(),
message_type: "SessionStart".into(),
message_id: "m1".into(),
session_id: "s1".into(),
sender: "ajit".into(),
timestamp_unix_ms: 1_700_000_000_000,
payload: vec![],
sender: "coordinator".into(),
timestamp_unix_ms: chrono::Utc::now().timestamp_millis(),
payload: start_payload.encode_to_vec(),
};

let ack = client
Expand All @@ -59,81 +73,94 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
ack.error.as_ref().map(|e| &e.code)
);

// 4) Normal message
let msg = Envelope {
// 4) Proposal (protobuf-encoded)
let proposal = ProposalPayload {
proposal_id: "p1".into(),
option: "Deploy v2.1 to production".into(),
rationale: "All integration tests pass".into(),
supporting_data: vec![],
};
let proposal_env = Envelope {
macp_version: "1.0".into(),
mode: "decision".into(),
message_type: "Message".into(),
mode: "macp.mode.decision.v1".into(),
message_type: "Proposal".into(),
message_id: "m2".into(),
session_id: "s1".into(),
sender: "ajit".into(),
timestamp_unix_ms: 1_700_000_000_001,
payload: b"hello".to_vec(),
sender: "alice".into(),
timestamp_unix_ms: chrono::Utc::now().timestamp_millis(),
payload: proposal.encode_to_vec(),
};

let ack = client
.send(SendRequest {
envelope: Some(msg),
envelope: Some(proposal_env),
})
.await?
.into_inner()
.ack
.unwrap();
println!(
"Message ack: ok={} error={:?}",
"Proposal ack: ok={} error={:?}",
ack.ok,
ack.error.as_ref().map(|e| &e.code)
);

// 5) Resolve message (DecisionMode resolves when payload == "resolve")
let resolve = Envelope {
// 5) Evaluation (protobuf-encoded)
let eval = macp_runtime::decision_pb::EvaluationPayload {
proposal_id: "p1".into(),
recommendation: "APPROVE".into(),
confidence: 0.95,
reason: "Looks good".into(),
};
let eval_env = Envelope {
macp_version: "1.0".into(),
mode: "decision".into(),
message_type: "Message".into(),
mode: "macp.mode.decision.v1".into(),
message_type: "Evaluation".into(),
message_id: "m3".into(),
session_id: "s1".into(),
sender: "ajit".into(),
timestamp_unix_ms: 1_700_000_000_002,
payload: b"resolve".to_vec(),
sender: "bob".into(),
timestamp_unix_ms: chrono::Utc::now().timestamp_millis(),
payload: eval.encode_to_vec(),
};

let ack = client
.send(SendRequest {
envelope: Some(resolve),
envelope: Some(eval_env),
})
.await?
.into_inner()
.ack
.unwrap();
println!(
"Resolve ack: ok={} error={:?}",
"Evaluation ack: ok={} error={:?}",
ack.ok,
ack.error.as_ref().map(|e| &e.code)
);

// 6) Message after resolve (should be rejected: SessionNotOpen)
let after = Envelope {
// 6) Commitment (votes not required per RFC — orchestrator bypass)
let commitment = Envelope {
macp_version: "1.0".into(),
mode: "decision".into(),
message_type: "Message".into(),
mode: "macp.mode.decision.v1".into(),
message_type: "Commitment".into(),
message_id: "m4".into(),
session_id: "s1".into(),
sender: "ajit".into(),
timestamp_unix_ms: 1_700_000_000_003,
payload: b"should-fail".to_vec(),
sender: "coordinator".into(),
timestamp_unix_ms: chrono::Utc::now().timestamp_millis(),
payload: b"deploy-approved".to_vec(),
};

let ack = client
.send(SendRequest {
envelope: Some(after),
envelope: Some(commitment),
})
.await?
.into_inner()
.ack
.unwrap();
println!(
"After-resolve ack: ok={} error={:?}",
"Commitment ack: ok={} state={} error={:?}",
ack.ok,
ack.session_state,
ack.error.as_ref().map(|e| &e.code)
);

Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let runtime = Arc::new(Runtime::new(registry, log_store));
let svc = MacpServer::new(runtime);

println!("macp-runtime v0.3 (RFC-0001) listening on {}", addr);
println!("macp-runtime v0.3.0 (RFC-0001) listening on {}", addr);

Server::builder()
.add_service(pb::macp_runtime_service_server::MacpRuntimeServiceServer::new(svc))
Expand Down
Loading
Loading