Files
dirigent/crates/dirigent_protocol
2026-05-25 17:51:04 +02:00
..
2026-05-08 01:59:04 +02:00
2026-05-08 01:59:04 +02:00
2026-05-08 01:59:04 +02:00
2026-05-08 01:59:04 +02:00
2026-05-08 01:59:04 +02:00
2026-05-08 01:59:04 +02:00
2026-05-08 01:59:04 +02:00

Dirigent Protocol

Version: 0.2.0

A Rust protocol library for agent-client interactions, aligned with Agent-Client Protocol (ACP) and Model Context Protocol (MCP) standards.

Overview

The Dirigent Protocol provides a structured, streaming-first event model for real-time agent interactions. It's designed to support multi-agent orchestration, tool execution, and rich content streaming while maintaining compatibility with standard protocols.

Features

  • ACP-Style Streaming: Real-time content updates via SessionUpdate events
  • MCP-Compatible Content: Structured ContentBlock representation
  • Tool Lifecycle Management: Complete tool call tracking from initiation to completion
  • Provider Agnostic: Adapter system for integrating different AI providers
  • Type-Safe: Strongly-typed Rust API with comprehensive serde support
  • Extensible: Provider metadata and extensibility hooks

Quick Start

Add to your Cargo.toml:

[dependencies]
dirigent_protocol = "0.2"

Basic Usage

use dirigent_protocol::{Event, SessionUpdate, ContentBlock};

fn handle_event(event: Event) {
    match event {
        Event::SessionUpdate { session_id, update } => {
            match update {
                SessionUpdate::AgentMessageChunk { message_id, content, .. } => {
                    if let ContentBlock::Text { text } = content {
                        println!("Agent says: {}", text);
                    }
                }
                SessionUpdate::ToolCall { tool_call, .. } => {
                    println!("Tool called: {}", tool_call.tool_name);
                }
                _ => {}
            }
        }
        Event::SessionCreated { session } => {
            println!("New session: {}", session.id);
        }
        _ => {}
    }
}

Core Concepts

SessionUpdate Event Model

The protocol uses SessionUpdate for all streaming content:

pub enum SessionUpdate {
    UserMessageChunk { message_id: String, content: ContentBlock, _meta: Option<Meta> },
    AgentMessageChunk { message_id: String, content: ContentBlock, _meta: Option<Meta> },
    AgentThoughtChunk { message_id: String, content: ContentBlock, _meta: Option<Meta> },
    ToolCall { message_id: String, tool_call: ToolCall, _meta: Option<Meta> },
    ToolCallUpdate { message_id: String, tool_call_id: String, tool_call: ToolCall, _meta: Option<Meta> },
}

ContentBlock Types

Structured content representation:

pub enum ContentBlock {
    Text { text: String },
    ResourceLink {
        uri: String,
        name: Option<String>,
        mime_type: Option<String>,
    },
}

Tool Call Lifecycle

Complete tool execution tracking:

pub struct ToolCall {
    pub id: ToolCallId,
    pub tool_name: String,
    pub status: ToolCallStatus,  // Pending → Running → Completed/Error
    pub content: Vec<ContentBlock>,
    pub raw_input: Option<Value>,
    pub raw_output: Option<Value>,
    pub title: Option<String>,
    pub error: Option<String>,
    pub metadata: Option<Value>,
}

Documentation

Examples

Streaming Text

use dirigent_protocol::{Event, SessionUpdate, ContentBlock};

// Agent streaming response
Event::SessionUpdate {
    session_id: "session_123".to_string(),
    update: SessionUpdate::AgentMessageChunk {
        message_id: "msg_1".to_string(),
        content: ContentBlock::Text {
            text: "Hello, world!".to_string(),
        },
        _meta: None,
    },
}

Tool Execution

use dirigent_protocol::{Event, SessionUpdate, ToolCall, ToolCallStatus};

// Tool call initiated
Event::SessionUpdate {
    session_id: "session_123".to_string(),
    update: SessionUpdate::ToolCall {
        message_id: "msg_1".to_string(),
        tool_call: ToolCall {
            id: "call_1".to_string(),
            tool_name: "bash".to_string(),
            status: ToolCallStatus::Pending,
            content: vec![],
            raw_input: Some(json!({"command": "ls"})),
            raw_output: None,
            title: Some("List files".to_string()),
            error: None,
            metadata: None,
        },
        _meta: None,
    },
}

// Tool call completed
Event::SessionUpdate {
    session_id: "session_123".to_string(),
    update: SessionUpdate::ToolCallUpdate {
        message_id: "msg_1".to_string(),
        tool_call_id: "call_1".to_string(),
        tool_call: ToolCall {
            id: "call_1".to_string(),
            tool_name: "bash".to_string(),
            status: ToolCallStatus::Completed,
            content: vec![
                ContentBlock::Text {
                    text: "file1.txt\nfile2.txt".to_string(),
                },
            ],
            raw_input: Some(json!({"command": "ls"})),
            raw_output: Some(json!({"exit_code": 0})),
            title: Some("List files".to_string()),
            error: None,
            metadata: None,
        },
        _meta: None,
    },
}

Agent Thinking

use dirigent_protocol::{Event, SessionUpdate, ContentBlock};

// Agent internal reasoning
Event::SessionUpdate {
    session_id: "session_123".to_string(),
    update: SessionUpdate::AgentThoughtChunk {
        message_id: "msg_1".to_string(),
        content: ContentBlock::Text {
            text: "Analyzing the user's request...".to_string(),
        },
        _meta: None,
    },
}

Adapters

The protocol includes adapter modules for translating provider-specific events:

  • OpenCode Adapter: Converts OpenCode.ai events to Dirigent Protocol
  • REST Adapter: Translates REST API responses

Using an Adapter

use dirigent_protocol::adapters::opencode::OpenCodeAdapter;

let adapter = OpenCodeAdapter::new();
let dirigent_events = adapter.translate_event(opencode_event);

Version History

0.2.0 (Current)

Breaking Changes:

  • Removed Event::MessagePartAdded (replaced with SessionUpdate)

New Features:

  • ACP-style SessionUpdate event system
  • MCP-compatible ContentBlock types
  • Structured ToolCall with lifecycle tracking
  • Provider metadata via _meta field

See CHANGELOG.md for details.

0.1.0

Initial release with basic event types and adapters.

Migration from 0.1.x

If you're upgrading from version 0.1.x, see the Migration Guide for detailed instructions and examples.

Quick Summary:

  • Replace Event::MessagePartAdded with Event::SessionUpdate
  • Use SessionUpdate variants instead of MessagePart
  • Handle tool lifecycle with ToolCall and ToolCallUpdate
  • Access ContentBlock::Text { text } instead of MessagePart::Text { content }

Architecture

dirigent_protocol/
├── src/
│   ├── types/           # Core types
│   │   ├── content.rs   # ContentBlock definitions
│   │   ├── updates.rs   # SessionUpdate variants
│   │   ├── tool.rs      # Tool call types
│   │   └── meta.rs      # Provider metadata
│   ├── session.rs       # Session types
│   ├── conversation.rs  # Message types
│   ├── events.rs        # Event enum
│   └── adapters/        # Provider adapters
│       ├── opencode.rs  # OpenCode adapter
│       └── rest.rs      # REST adapter
├── docs/                # Documentation
├── examples/            # Code examples
└── tests/               # Integration tests

Event Types

The protocol defines several event categories:

Session Events

  • SessionCreated - New session started
  • SessionUpdated - Session metadata changed
  • SessionDeleted - Session removed
  • SessionsListed - Available sessions returned

Streaming Events

  • SessionUpdate - Real-time content/tool updates (see SessionUpdate variants above)

Message Events

  • MessageStarted - Message creation initiated
  • MessageCompleted - Message finalized
  • MessageDeleted - Message removed

System Events

  • ConnectorStateChanged - Connector status changed
  • Error - Error occurred

Best Practices

For Consumers

  1. Use SessionUpdate for streaming: The new event model provides granular updates
  2. Track tools by ID: Maintain a HashMap of tool calls keyed by tool_call_id
  3. Replace tool state on update: ToolCallUpdate sends complete state, not deltas
  4. Distinguish thoughts from messages: Render AgentThoughtChunk differently (e.g., collapsible sections)
  5. Handle optional fields: Provider metadata (_meta) may not always be present

For Adapters

  1. Preserve provider info: Store original IDs in _meta.provider for debugging
  2. Send complete tool state: Include all tool call fields in updates
  3. Use appropriate chunk types: Choose User/Agent/Thought for correct semantics
  4. Keep metadata minimal: Avoid large payloads in _meta (use excerpts)

Testing

Run the test suite:

cargo test

Run with output:

cargo test -- --nocapture

Contributing

Contributions welcome! Please ensure:

  • All tests pass
  • New features include tests
  • Documentation is updated
  • Code follows Rust conventions

License

[License information here]

  • dirigent_core - Multi-agent orchestration runtime
  • opencode_client - OpenCode.ai HTTP client library
  • dirigent_archive - Session persistence

Support

For questions or issues:

Standards Alignment

This protocol is designed to align with:

  • Agent-Client Protocol (ACP) - Streaming model and event types
  • Model Context Protocol (MCP) - Content block structure

Differences from standards are documented for compatibility and future convergence.