diff --git a/README.md b/README.md index 3804abf..51cfbf7 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@

Core libraries for the Dirigent agent orchestration platform.

+> [!CAUTION] > **Alpha software.** Dirigent is in early active development and not fully battle-tested. Most crates are experimental — APIs will change without notice. There is nothing to install from this repository yet. The standalone tools listed below have their own repositories and maturity levels. --- @@ -79,6 +80,7 @@ dirigent_core = { git = "https://git.g4b.org/dirigence/dirigent", path = "cr Replace the crate name and path with the one you need. All crates follow the same pattern. +> [!CAUTION] > **Expect breakage.** These are internal library crates under active development. Pin to a specific commit if you depend on stability. --- diff --git a/crates/dirigent_acp_api/CLAUDE.md b/crates/dirigent_acp_api/CLAUDE.md deleted file mode 100644 index a57b19b..0000000 --- a/crates/dirigent_acp_api/CLAUDE.md +++ /dev/null @@ -1,124 +0,0 @@ -# Package: dirigent_acp_api - -ACP Server implementation for accepting incoming ACP connections from external agents. - -## Quick Facts -- **Type**: Library -- **Main Entry**: src/lib.rs -- **Dependencies**: axum, tokio, serde, tracing, uuid, async-trait, dirigent_protocol -- **Status**: Core structure complete, integration with CoreRuntime pending - -## Overview - -The `dirigent_acp_api` package implements an ACP (Agent-Client Protocol) server that allows Dirigent to accept incoming connections from external ACP clients like Claude Code or custom agents. This enables session sharing, remote orchestration, and multi-client collaboration. - -## Architecture - -### Core Components - -- **config.rs** - Server configuration types (`AcpServerConfig`) -- **error.rs** - Error types (`AcpServerError`, `JsonRpcErrorObject`) -- **jsonrpc.rs** - JSON-RPC 2.0 types and parsing -- **rpc.rs** - RPC handler and method dispatch -- **session_manager.rs** - Session/client tracking (TODO) -- **sse.rs** - SSE notification system (TODO) -- **event_bridge.rs** - Event translation (TODO) - -### Key Types - -```rust -pub struct AcpServerConfig { - pub enabled: bool, // Enable/disable server - pub port: u16, // Listen port (default: 3001) - pub allowed_origins: Option>, // CORS origins - pub max_connections: usize, // Connection limit (default: 100) -} -``` - -### ConnectorOperations Trait - -The RPC handler uses a trait abstraction to avoid circular dependencies with dirigent_core: - -```rust -#[async_trait] -pub trait ConnectorOperations: Send + Sync { - async fn create_session(&self, connector_id: &str) -> Result; - async fn load_session(&self, connector_id: &str, session_id: &str) -> Result; - async fn send_prompt(&self, connector_id: &str, session_id: &str, prompt: &str) -> Result; - // ... more methods -} -``` - -## API Endpoints - -### POST `/rpc` - -JSON-RPC 2.0 endpoint supporting: -- `initialize` - Client handshake -- `session/new` - Create session -- `session/load` - Load existing session -- `session/prompt` - Send prompt -- `session/cancel` - Cancel generation -- `session/close` - Close session - -### GET `/events` - -Server-Sent Events for streaming notifications: -- `acp/messageChunk` - Streaming content -- `acp/messageComplete` - Generation complete -- `acp/sessionIdle` - Ready for input - -### GET `/health` - -Health check endpoint. - -## Configuration UI - -The ACP Server is configured via the web UI at **Configuration > ACP Server**: - -- Enable/disable toggle -- Port configuration -- Max connections limit -- Allowed origins (CORS) -- Default connector selection -- Connected clients management - -Server functions in `crates/api/src/acp_server.rs` bridge the UI and this package. - -## Implementation Status - -**Completed:** -- Configuration types (`AcpServerConfig`) -- Error types (`AcpServerError`) -- JSON-RPC types and parsing -- RPC handler structure with ConnectorOperations trait - -**Pending:** -- Session Manager implementation -- SSE Notifier implementation -- Event Bridge implementation -- Axum router integration -- Web server integration - -## Key Files - -| File | Description | -|------|-------------| -| `src/lib.rs` | Module exports and router creation | -| `src/config.rs` | AcpServerConfig with validation | -| `src/error.rs` | Error types and codes | -| `src/jsonrpc.rs` | JSON-RPC 2.0 implementation | -| `src/rpc.rs` | RPC handler and method dispatch | - -## Related Packages - -- **dirigent_core** - Provides CoreHandle implementation of ConnectorOperations -- **dirigent_protocol** - Shared event and message types -- **api** - Server functions for UI configuration -- **web** - Configuration UI components - -## Documentation - -- **Architecture**: `docs/architecture/acp_server.md` -- **Configuration**: `docs/configuration/acp-connectors.md` -- **Tasks**: `docs/building/07_acp_serve/02_acp_server_tasks.md` diff --git a/crates/dirigent_config/CLAUDE.md b/crates/dirigent_config/CLAUDE.md deleted file mode 100644 index 4a1958f..0000000 --- a/crates/dirigent_config/CLAUDE.md +++ /dev/null @@ -1,22 +0,0 @@ -# dirigent_config - -Platform-native configuration and data path resolution. - -## Purpose -Provides `DirigentPaths` for resolving config/data directories across Linux, macOS, Windows. -Creates a symlink on Linux/macOS from config_dir/data -> data_dir for discoverability. - -## Key Types -- `DirigentPaths` -- resolved config_dir + data_dir with convenience methods -- `ConfigPathError` -- error enum for path resolution failures - -## Usage -```rust -let paths = DirigentPaths::resolve()?; -paths.ensure_dirs()?; // creates dirs + symlink -let config = paths.config_file(); // ~/.config/dirigent/dirigent.toml -``` - -## Dependencies -- `dirs` -- cross-platform directory resolution -- Zero UI dependency -- used by core, archivist, zed crates diff --git a/crates/dirigent_core/CLAUDE.md b/crates/dirigent_core/CLAUDE.md deleted file mode 100644 index 855b66c..0000000 --- a/crates/dirigent_core/CLAUDE.md +++ /dev/null @@ -1,616 +0,0 @@ -# Package: dirigent_core - -Core orchestration engine for multi-connector agent system management. - -## Quick Facts -- **Type**: Library -- **Main Entry**: src/lib.rs -- **Dependencies**: dirigent_protocol, tokio, axum, serde, uuid - -## Architecture Overview - -The dirigent_core package provides a **runtime-based architecture** for managing long-lived connections to external agent systems (OpenCode.ai, ACP agents, etc.). The core abstraction is the **Connector**, which represents a bidirectional communication channel to an agent system. - -### Core Components - -#### CoreRuntime -The central orchestrator for managing connectors. It maintains: -- Registry of active connectors (keyed by ConnectorId) -- Global event broadcast channel for system-wide events -- User registry for ownership and authorization -- Configuration state (with persistence support) - -#### CoreHandle -Lightweight, cloneable wrapper around CoreRuntime. Uses Arc internally for cheap cloning across async tasks and server functions. - -#### Connector Trait -Defines the interface for connector implementations: -- Command channel (mpsc) for control operations -- Event broadcast channel for publishing events -- State tracking (Initializing, Connecting, Ready, Error, Stopped) -- User ownership for authorization - -#### ConnectorHandle -Concrete implementation of the Connector trait that wraps: -- Metadata (id, kind, owner, title) -- Shared state (protected by RwLock) -- Command and event channels -- Optional task handle for lifecycle management - -### Connector Implementations - -#### OpenCodeConnector -Connector for OpenCode.ai REST + SSE API: -- HTTP client for session/message operations -- SSE event stream for real-time updates -- Background task loop for command processing -- State machine for connection lifecycle -- **TurnComplete emission**: Uses `TurnCompleteTrigger::ExplicitSignal` after `MessageCompleted` events (based on upstream session.idle signals) - -#### AcpConnector (Future) -Connector for Agent-Client Protocol: -- WebSocket or HTTP/2 transport -- ACP message protocol handling -- Tool execution and streaming support - -## Key Files - -### Core Runtime -- `src/runtime.rs` - CoreRuntime and CoreHandle implementation -- `src/types.rs` - Core types (ConnectorId, ConnectorState, User, etc.) -- `src/error.rs` - Error types for the runtime -- `src/config.rs` - Configuration types and template system - -### Connectors -- `src/connectors/mod.rs` - Connector trait and ConnectorHandle -- `src/connectors/opencode/mod.rs` - OpenCode connector implementation -- `src/connectors/opencode/config.rs` - OpenCode-specific configuration -- `src/connectors/acp/mod.rs` - ACP connector implementation (in progress) - -### ACP Protocol Implementation -- `src/acp/protocol/initialize.rs` - Protocol initialization and capability negotiation -- `src/acp/protocol/authenticate.rs` - Optional authentication flow -- `src/acp/protocol/session.rs` - Session lifecycle (new, load, set_mode, cancel) -- `src/acp/protocol/prompt.rs` - Prompt requests with content blocks (Phases 3-7 complete) -- `src/acp/protocol/streaming.rs` - Session update notifications and handlers -- `src/acp/protocol/stop_reason.rs` - Stop reason interpretation and actions -- `src/acp/protocol/cancellation.rs` - Cancellation and disconnect handling -- `src/acp/protocol/error.rs` - Error classification and retry logic -- `src/acp/connector_state.rs` - Connection and session state management -- `src/acp/transport/mod.rs` - Transport abstraction layer -- `src/acp/transport/stdio.rs` - Stdio transport (process spawning) -- `src/acp/transport/http.rs` - HTTP+SSE transport - -### Bidirectional Request Handling Pattern - -The ACP connector implements **true bidirectional communication** where both client and agent can send JSON-RPC requests at any time. This creates an architectural challenge: how to maintain synchronous request/response semantics (async/await) while handling incoming agent requests during outgoing client requests. - -**The Challenge:** - -When the connector sends `session/prompt` to the agent: -1. It calls `send_request()` which awaits the JSON-RPC response -2. The agent may send permission requests (e.g., `tools/write`) before responding -3. These agent requests arrive as `ConnectorCommand::AgentResponse` via the command channel -4. If `send_request()` only polls transport and response channels, the command channel isn't polled -5. **Result**: Deadlock - agent waits for permission → permission stuck in channel → client waits for prompt response - -**The Solution (src/connectors/acp/connector.rs:1253-1523):** - -The `send_request()` method uses `tokio::select!` to poll **three** sources simultaneously: -- **Response channel** (`response_rx`) - Waiting for the correlated JSON-RPC response -- **Transport channel** - Receiving messages/notifications from agent (may trigger response) -- **Command channel** (`cmd_rx`) - Receiving commands from event bridge (e.g., `AgentResponse`) - -When an `AgentResponse` command arrives during `send_request()`: -1. Extract the response payload from the command -2. Send it to the agent via transport immediately -3. Remove from pending requests map -4. Continue waiting for the original prompt response - -This pattern is **idiomatic async Rust** for implementing synchronous abstractions over bidirectional transports. It's similar to: -- gRPC bidirectional streaming with request/response correlation -- WebSocket clients with RPC-style method calls -- HTTP/2 multiplexing with concurrent streams - -**Why Not Separate Tasks?** - -Alternative architectures (separate command processing task, full actor model) introduce: -- Complex synchronization between tasks -- Race conditions on shared state -- Message ordering guarantees across channels -- Significantly more code and cognitive overhead - -The single-task, multi-channel select pattern keeps all state local and eliminates these issues. - -**Key Invariant:** - -Any method that blocks waiting for a response MUST also poll the command channel to process `AgentResponse` commands, otherwise bidirectional flows deadlock. - -## Main Exports - -### Runtime -- `CoreRuntime` - Main orchestrator -- `CoreHandle` - Cloneable runtime handle -- `CoreConfig` - Runtime configuration - -### Types -- `ConnectorId`, `UserId` - Type aliases for IDs -- `ConnectorKind` - Enum of connector types (OpenCode, Acp, Mock) -- `ConnectorState` - Lifecycle state enum -- `ConnectorSummary` - Lightweight connector view -- `User` - User information - -### Connectors -- `Connector` - Trait for connector implementations -- `ConnectorHandle` - Handle to a running connector -- `ConnectorCommand` - Commands sent to connectors -- `OpenCodeConnector` - OpenCode.ai integration -- `OpenCodeConfig` - OpenCode connector configuration - -### Configuration -- `ConnectorConfig` - Configuration for creating connectors -- `apply_template()` - Apply connector templates with patches - -### ACP Protocol Types (Phases 3-7) -- **Prompt Turn**: - - `SessionPromptRequest`, `SessionPromptResponse` - Prompt requests/responses - - `ContentBlock` - Text, Image, Audio, Resource, ResourceLink content - - `EmbeddedResource` - Text or Blob embedded resources - - `StopReason` - EndTurn, MaxTokens, MaxTurnRequests, Refusal, Cancelled - - `PromptError` - Timeout, JsonRpcError, TransportError, Cancelled, ValidationError - -- **Streaming Updates**: - - `SessionUpdate` - All update types (agent_message_chunk, tool_call, plan, etc.) - - `SessionUpdateNotification` - Notification wrapper with session_id - - `ToolCallInfo` - Tool call tracking with status and content - - `ToolKind` - Read, Edit, Search, Execute, Think, Other - - `ToolCallStatus` - Pending, InProgress, Completed, Failed, Cancelled - - `ToolCallContent` - Content, Diff, Terminal output - - `MessageAccumulator` - Message chunk accumulation helper - - `PlanEntry`, `Command` - Plan and command structures - -- **Stop Reason Handling**: - - `StopReasonAction` - Complete, ShowWarning, ShowError, ShowInfo - - `handle_stop_reason()` - Interpret stop reasons - - `is_continuable()`, `is_error()` - Stop reason classification - -- **Cancellation**: - - `handle_cancellation()` - Cancel pending operations - - `cancel_pending_tool_calls()` - Mark tool calls as cancelled - - `handle_disconnect()` - Handle transport disconnect - -- **Error Classification**: - - `ClassifiedError` - Error with class, message, details, retry_after - - `ErrorClass` - Transient, Terminal, User - - `ErrorSeverity` - Info, Warning, Error, Fatal - - `ErrorAction` - Retry, Reconnect, CheckConfig, ContactSupport, Dismiss - - `classify_jsonrpc_error()`, `classify_transport_error()` - Classify errors - - `exponential_backoff()` - Calculate retry delays - -## Usage Examples - -### Creating a Runtime - -```rust -use dirigent_core::{CoreRuntime, CoreConfig, CoreHandle}; - -// Load config from file or use default -let config = CoreConfig::load_config(None)?; - -// Create runtime -let runtime = CoreRuntime::new(config); - -// Wrap in handle for cheap cloning -let handle = CoreHandle::new(runtime); -``` - -### Creating a Connector - -```rust -use dirigent_core::{ConnectorConfig, ConnectorKind, OpenCodeConfig}; -use serde_json::json; - -// Build OpenCode connector config -let config = OpenCodeConfig { - base_url: "http://localhost:12225".to_string(), - title: "My OpenCode".to_string(), - initial_session: None, -}; - -// Serialize to JSON for ConnectorConfig -let params = serde_json::to_value(&config)?; - -let connector_config = ConnectorConfig { - id: None, // Runtime generates ID - kind: ConnectorKind::OpenCode, - owner: Some("user-123".to_string()), - title: Some("My OpenCode".to_string()), - params, -}; - -// Create connector via runtime -let connector_id = handle.create_connector( - "user-123".to_string(), - connector_config -).await?; -``` - -### Using Templates - -```rust -use dirigent_core::{apply_template, ConnectorKind}; -use serde_json::json; - -// Use default template with custom URL -let connector_config = apply_template( - ConnectorKind::OpenCode, - "default", - json!({ - "base_url": "http://localhost:8080", - "title": "Custom OpenCode" - }) -)?; - -let connector_id = handle.create_connector( - "user-123".to_string(), - connector_config -).await?; -``` - -### Managing Connectors - -```rust -// List all connectors -let all_connectors = handle.list_connectors(None).await; - -// List connectors for a specific user -let user_connectors = handle.list_connectors(Some("user-123".to_string())).await; - -// Get a specific connector -let connector = handle.get_connector(&connector_id).await; - -// Stop a connector -handle.stop_connector(&connector_id).await?; - -// Restart a stopped connector -handle.restart_connector(&connector_id).await?; - -// Remove a connector -handle.remove_connector(&connector_id).await?; -``` - -### Connector Lifecycle with Restart - -Connectors can be restarted after being stopped or entering an error state: - -```rust -// Create and start a connector -let connector_id = handle.create_connector( - "user-123".to_string(), - connector_config -).await?; -// Connector is now in Ready state - -// Stop the connector -handle.stop_connector(&connector_id).await?; -// Connector is now in Stopped state - -// Restart the connector (recreates background task with fresh channels) -handle.restart_connector(&connector_id).await?; -// Connector transitions: Stopped → Initializing → Connecting → Ready - -// Restart preserves: -// - Connector ID (same instance) -// - Configuration (base_url, title, etc.) -// - Event broadcast channel (subscribers continue receiving) -// - State Arc (observers see real-time updates) - -// Restart recreates: -// - Command channel (new sender/receiver pair) -// - Connector instance (fresh OpenCodeConnector) -// - Background task (new spawn) -// - Task handle (new JoinHandle) -``` - -### Sending Commands - -```rust -use dirigent_core::connectors::ConnectorCommand; - -// Get connector handle -let connector = handle.get_connector(&connector_id).await.unwrap(); - -// Subscribe to events -let mut events = connector.subscribe(); - -// Send a command -let cmd_tx = connector.command_tx(); -cmd_tx.send(ConnectorCommand::ListSessions).await?; - -// Receive events -while let Ok(event) = events.recv().await { - match event { - Event::SessionsListed { sessions } => { - println!("Got {} sessions", sessions.len()); - break; - } - Event::Error { message } => { - eprintln!("Error: {}", message); - break; - } - _ => {} - } -} -``` - -### Global Event Stream - -```rust -// Subscribe to every event on the SharingBus. Callers can also pick an -// `EventFilter` via `subscribe_filtered()` to receive only the events -// they care about. -let mut bus_rx = handle.sharing_bus().subscribe_all().await; - -tokio::spawn(async move { - while let Some(bus_event) = bus_rx.rx.recv().await { - println!("Bus event: {:?}", bus_event.event); - } -}); -``` - -## Configuration - -### Runtime Configuration (dirigent.toml or dirigent.json) - -```toml -port = 3000 -project_dir = "." -project_name = "my_project" -templates_enabled = true - -[[connectors]] -id = "opencode-1" -kind = "OpenCode" -owner = "user-123" -title = "OpenCode Local" - -[connectors.params] -base_url = "http://localhost:12225" -title = "OpenCode Local" -initial_session = null -``` - -### Templates - -Available templates: -- `opencode/default` - Standard localhost OpenCode connector -- `acp/claude-default` - Claude API connector (stub for future) - -## Connector Event Emission Patterns - -### TurnComplete Event Semantics - -All connectors emit `Event::TurnComplete` to signal that a turn/message is finalized. This is the **primary signal** for: -- Archivist to finalize and write message to disk -- UI cache to lock message state as immutable -- Conductor bridge to flush response to upstream - -**Event ordering guarantee**: -```text -MessageCompleted → TurnComplete → SessionIdle -``` - -### Connector-Specific TurnComplete Strategies - -Different connectors use different strategies to determine when a turn is complete: - -#### OpenCode Connector -- **Trigger**: `TurnCompleteTrigger::ExplicitSignal` -- **Strategy**: Relies on upstream `session.idle` events from OpenCode.ai -- **Implementation**: After translating `MessageCompleted`, emits `TurnComplete` then `SessionIdle` -- **Code location**: `crates/dirigent_core/src/connectors/opencode.rs:550-575` - -#### ACP Connector (stdio transport) -- **Trigger**: `TurnCompleteTrigger::ResponseReceived` -- **Strategy**: JSON-RPC response message is the final message in a turn -- **Implementation**: Emits `TurnComplete` after receiving the JSON-RPC response to `session/prompt` -- **Code location**: `crates/dirigent_core/src/connectors/acp/connector.rs:328` - -#### Gateway Connector -- **Trigger**: `TurnCompleteTrigger::OperationsComplete` -- **Strategy**: Tracks pending tool calls and emits when all operations resolve -- **Implementation**: Monitors tool call status changes and emits when last pending call completes -- **Code location**: `crates/dirigent_core/src/connectors/gateway/mod.rs:464,556,626,678` - -**Important**: Connectors MUST emit `TurnComplete` exactly once per turn. Duplicate emissions can cause archiving issues and UI state corruption. - -### Gateway Session Transfer Mechanics - -The Gateway connector serves as an **entry point** for incoming ACP connections. Sessions can be transferred to real agent connectors (Claude, etc.) via `/select-connector` commands. - -#### Key Principle: New Connector Is Authority - -**After transfer, the target connector becomes the sole authority for session configuration.** - -```text -Before transfer: - Gateway has placeholder modes: "ask", "write", "yolo" - Gateway has placeholder models: "simple", "default", "high" - -After transfer to Claude: - Claude's actual modes/models become authoritative - Gateway's placeholders are irrelevant - Editor receives config_option_update with Claude's real options -``` - -#### Transfer Flow - -1. User sends `/select-connector claude` in Gateway session -2. Gateway emits `SessionTransferRequest` to CoreRuntime -3. CoreRuntime creates/loads session in target connector -4. Target connector emits `SessionCreated` with its modes/models -5. CoreRuntime extracts modes/models from `SessionCreated` event -6. CoreRuntime emits `SessionTransferred` event with modes/models -7. Event bridge sends `config_option_update` to editor with target's modes/models - -#### What Does NOT Happen - -- Gateway does NOT adjust or map its values to the target connector -- Gateway does NOT remain involved after transfer completes -- Target connector does NOT inherit Gateway's mode/model selections -- No "mapping" between Gateway placeholders and real connector values - -#### Code Locations - -- Transfer request handling: `src/runtime.rs:execute_transfer()` -- Gateway commands: `src/connectors/gateway/commands.rs` -- SessionTransferred event: `dirigent_protocol/src/events/mod.rs` -- config_option_update emission: `dirigent_acp_api/src/event_bridge.rs:handle_session_transferred_internal()` - -## Architecture Patterns - -### Request-Response Pattern -For operations that need results (list_sessions, list_messages): -1. Subscribe to connector events -2. Send command via command channel -3. Wait for corresponding response event (with timeout) -4. Return result or error - -### Fire-and-Forget Pattern -For operations that stream results (send_message): -1. Send command via command channel -2. Return immediately -3. Clients subscribe to event stream for updates - -### Lifecycle Management -Connectors progress through states: -1. **Initializing** - Created but not connecting -2. **Connecting** - Attempting to establish connection -3. **Ready** - Connected and operational -4. **Error** - Encountered failure (with error message) -5. **Stopped** - Shutdown or unrecoverable error - -#### Restart Support -Connectors in `Stopped` or `Error` state can be restarted: -- **restart_connector()** recreates the connector's background task with fresh channels -- Preserves connector identity (ID, owner, configuration) -- Preserves event broadcast channel (existing subscribers continue receiving) -- Recreates command channel and background task -- State transitions: `Stopped`/`Error` → `Initializing` → `Connecting` → `Ready` - -### Configuration Persistence -The runtime automatically saves configuration to disk when: -- A connector is created -- A connector is removed -- Configuration is explicitly saved - -This ensures connectors are restored on server restart. - -## Session Tracking Responsibilities - -**IMPORTANT**: CoreRuntime is a **stateless orchestrator** for session operations. It does NOT maintain session state or cache message history. - -### What CoreRuntime Does - -- **Route Commands**: Forward session/message commands to appropriate connectors -- **Broadcast Events**: Relay connector events to global event stream -- **Manage Connectors**: Track which connectors are active and available -- **Persist Configuration**: Save/load connector configuration (not session data) - -### What CoreRuntime Does NOT Do - -- **Cache Sessions**: Does not maintain lists of sessions or session metadata -- **Store Messages**: Does not retain message content or history -- **Buffer Events**: Does not cache events for replay or historical access -- **Track Session State**: Does not know which sessions exist or their current state - -### Stateless Design Rationale - -1. **Multi-Connector Support**: With multiple connectors (OpenCode, ACP, etc.), caching sessions would require complex invalidation -2. **Memory Efficiency**: Long-running server should not accumulate unbounded session history -3. **Single Source of Truth**: External APIs (OpenCode.ai, ACP agents) are authoritative for session state -4. **Scalability**: Stateless design supports future horizontal scaling - -### Data Flow for Session Operations - -**List Sessions**: -``` -Server Function → CoreRuntime.get_connector() → Send Command → Connector queries API - ↓ ↓ - Returns handle Broadcasts SessionsListed - ↓ - Server function returns to UI - (CoreRuntime does NOT cache list) -``` - -**Send Message**: -``` -Server Function → CoreRuntime.get_connector() → Send Command → Connector sends to API - ↓ ↓ - Returns handle Broadcasts MessageSent - ↓ - SSE pushes to UI in real-time - (CoreRuntime does NOT store message) -``` - -All session data passes through CoreRuntime but is **never cached**. See `docs/architecture/session_tracking.md` for the complete three-layer architecture (CoreRuntime, UI Cache, Archivist). - -## Phase 4: `SharingBus` + `StreamRegistry` (2026-04-21) - -Every `Event` emitted by a connector or the runtime is published onto a -single `SharingBus` that owns: - -- A `broadcast::Sender` as the internal multicast. -- A worker task that receives from that broadcast and dispatches per- - subscriber `mpsc::Sender` pipes with filter matching. -- A `HashMap<(connector_id, native_session_id), scroll_id>` cache that - late-binds `routing.scroll_id` for events emitted before their - `SessionRegistered` event arrived. - -### Subscriber model - -`SharingBus::subscribe_all()` and `subscribe_filtered(EventFilter, cap)` -return a `BusReceiver { id, rx, lagged }`. Filters are applied by the -worker — subscribers never allocate a closure for skipped events. - -`EventFilter` variants: `All | ScrollId | ConnectorUid | Kinds | AnyOf -| AllOf`. - -### Stream registry - -The runtime owns a `StreamRegistry` and a `StreamFactoryRegistry`. -Streams are attached via `CoreRuntime::attach_stream(StreamConfig)`: - -- Factory registry resolves the `kind` string → concrete - `Arc`. -- The new stream gets its own `BusReceiver` scoped via `scope_to_filter` - (Session → ScrollId, Connector → ConnectorUid, ArchiveWide → All). -- A worker task pumps events into `stream.on_event(&bus_event).await`. - -Health drift: `record_failure` / `record_success` in `sharing/health.rs` -transitions `HealthStatus` on 5 consecutive failures -(`Healthy → Degraded → Unavailable`). - -### Replay - -`CoreRuntime::replay_session_to_stream(scroll_id, stream_id, opts)` -loads archived messages via the archivist's read API and calls -`stream.on_event` directly, bypassing the bus. Each replayed event has -`origin = EventOrigin::Replay { replay_id }` and -`routing.scroll_id = Some(scroll_id)`. - -### Config - -`[[streams]]` blocks in `dirigent.toml` are parsed into `StreamsConfig` -and applied at boot (best-effort: failures log + continue). - -## Related Packages -- **api** - Server functions that wrap CoreRuntime operations -- **web** - Dioxus UI that calls server functions -- **dirigent_protocol** - Shared event and message types -- **opencode_client** - Low-level OpenCode.ai HTTP client (used by OpenCodeConnector) - -## Documentation -- README: ./README.md -- Architecture: ../../docs/architecture/overview.md -- Migration Guide: ../../docs/migration/singleton_to_runtime.md diff --git a/crates/dirigent_protocol/CLAUDE.md b/crates/dirigent_protocol/CLAUDE.md deleted file mode 100644 index 28ae11a..0000000 --- a/crates/dirigent_protocol/CLAUDE.md +++ /dev/null @@ -1,268 +0,0 @@ -# dirigent_protocol - -**Version:** 0.2.0 -**Status:** Active Development - -ACP/MCP-aligned protocol library for agent-client interactions. - -## Quick Links - -- **Package README**: [README.md](README.md) - Main documentation -- **Streaming Model**: [docs/streaming_model.md](docs/streaming_model.md) - Detailed SessionUpdate guide -- **Migration Guide**: [docs/migration_from_0.1.md](docs/migration_from_0.1.md) - Upgrading from 0.1.x -- **Architecture Doc**: [../../docs/architecture/protocol.md](../../docs/architecture/protocol.md) - System design - -## Purpose - -This package provides the core event protocol for Dirigent, enabling: -- Real-time streaming of agent interactions -- Provider-agnostic event representation -- Tool lifecycle management -- Structured content representation - -## Key Types - -```rust -use dirigent_protocol::{ - Event, // Top-level event enum - SessionUpdate, // Streaming content updates - ContentBlock, // Structured content (Text, ResourceLink) - ToolCall, // Tool execution state - ToolCallStatus, // Pending → Running → Completed/Error - TurnCompleteTrigger, // How turn completion was detected -}; -``` - -## Event Semantics: MessageCompleted vs TurnComplete vs SessionIdle - -Understanding the distinction between these three events is critical for correct system behavior: - -### MessageCompleted - "Metadata is ready" -- **Purpose**: Informational - signals that message metadata exists -- **Timing**: Emitted when message record is created, content may still be streaming -- **Consumer action**: Update UI status indicators ("Assistant is typing" → "Complete") -- **Example**: Show message timestamp, update message count - -### TurnComplete - "All content received" (ACTIONABLE) -- **Purpose**: **Primary finalization signal** - all content for this turn is complete -- **Timing**: Emitted AFTER all content chunks, tool calls, and metadata updates -- **Consumer action**: **Finalize storage, lock state, trigger post-processing** -- **Example**: Write message to disk, mark as immutable, generate summaries - -### SessionIdle - "No recent activity" -- **Purpose**: Informational - indicates session is quiet -- **Timing**: Emitted AFTER TurnComplete as final safety signal -- **Consumer action**: Hide spinners, update activity indicators -- **Example**: Remove "typing" animation, update last activity timestamp - -### Event Ordering Guarantee - -```text -1. MessageStarted (message created) -2. SessionUpdate::*Chunk (content streaming) -3. SessionUpdate::ToolCall* (tool execution) -4. MessageCompleted (metadata ready) ← UI: "Complete" -5. TurnComplete ← FINALIZE HERE! -6. SessionIdle ← UI: hide spinner -``` - -### Consumer Behavior Table - -| Consumer | MessageCompleted | TurnComplete | SessionIdle | -|----------|------------------|--------------|-------------| -| **Archivist** | Ignore | **Finalize and write** | Safety net | -| **UI Cache** | Update status | **Lock state** | Hide spinner | -| **Conductor Bridge** | - | **Flush response** | Fallback flush | - -### TurnCompleteTrigger Variants - -The `TurnCompleteTrigger` enum indicates **how** the system determined completion: - -- **`ExplicitSignal`**: Upstream provider sent explicit completion (e.g., OpenCode session.idle) -- **`ResponseReceived`**: JSON-RPC response received (ACP stdio - response is last message) -- **`OperationsComplete`**: All tracked operations finished (e.g., pending tool calls resolved) -- **`IdleTimeout { duration_ms }`**: Timeout-based detection (fallback mechanism) - -**For most consumers**, treat all triggers the same - the turn is complete. The trigger type is primarily for debugging and observability. - -## Usage Pattern - -```rust -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: {}", text); - } - } - SessionUpdate::ToolCall { tool_call, .. } => { - println!("Tool: {}", tool_call.tool_name); - } - _ => {} - } - } - _ => {} - } -} -``` - -## Architecture - -``` -dirigent_protocol/ -├── src/ -│ ├── types/ # Core types -│ │ ├── content.rs # ContentBlock definitions -│ │ ├── updates.rs # SessionUpdate variants -│ │ ├── tool.rs # ToolCall, ToolCallStatus -│ │ └── meta.rs # Provider metadata -│ ├── session.rs # Session types -│ ├── conversation.rs # Message types -│ ├── events.rs # Event enum -│ └── adapters/ # Provider adapters -│ ├── opencode.rs # OpenCode translation -│ └── rest.rs # REST translation -├── docs/ # Detailed documentation -├── examples/ # Usage examples -└── tests/ # Integration tests -``` - -## Version 0.2.0 Changes - -**Breaking:** -- Removed `Event::MessagePartAdded` - -**New:** -- `SessionUpdate` event system (ACP-style) -- `ContentBlock` types (MCP-compatible) -- `ToolCall` with lifecycle tracking -- Provider metadata via `_meta` - -See [docs/migration_from_0.1.md](docs/migration_from_0.1.md) for migration guide. - -## Development - -### Running Tests -```bash -cargo test --package dirigent_protocol -``` - -### Checking Code -```bash -cargo check --package dirigent_protocol -``` - -### Running Examples -```bash -cargo run --package dirigent_protocol --example session_metadata_demo -``` - -## Integration - -This package is used by: -- **api** package: Server functions consume protocol events -- **web** package: UI renders protocol events -- **dirigent_core** (future): Runtime emits protocol events - -## Adapters - -The adapter system translates provider-specific events to Dirigent Protocol: - -- **OpenCodeAdapter**: Translates OpenCode.ai events -- **RESTAdapter**: Converts REST API responses - -Adapters preserve provider metadata in the `_meta` field for debugging and traceability. - -## Current Scope - -**Phase 1 (Implemented):** -- User/Agent/Thought message streaming -- Tool lifecycle (Pending → Running → Completed/Error) -- Text and ResourceLink content types -- Provider metadata support - -**Deferred to Future Phases:** -- Plans and mode switching -- Permission system -- Embedded resources (full content) -- Rich media (images, audio) -- Multi-agent communication - -See [../../docs/building/03_acp_prep/04_first_order_refactor.md](../../docs/building/03_acp_prep/04_first_order_refactor.md) for the full plan. - -## Standards Alignment - -**Agent-Client Protocol (ACP):** -- Session-centric streaming -- Separate content types (user/agent/thought) -- Tool status tracking - -**Model Context Protocol (MCP):** -- ContentBlock structure -- Resource links -- Extensible content types - -Differences from standards are documented in [../../docs/architecture/protocol.md](../../docs/architecture/protocol.md). - -## Anti-Patterns - -### Timeout-Based Event Waiting (FORBIDDEN) - -**Never use timeout-based waiting to receive events that should be available immediately.** - -```rust -// ❌ BAD - Race condition waiting for event -async fn wait_for_metadata_event( - events: &mut broadcast::Receiver, - timeout: Duration, -) -> Option { - let start = Instant::now(); - while start.elapsed() < timeout { - match tokio::time::timeout(Duration::from_millis(100), events.recv()).await { - Ok(Ok(Event::SessionMetadataReceived { .. })) => return Some(...), - _ => continue, - } - } - None -} -``` - -**Why this is wrong:** -1. **Race condition**: The event may have been emitted before the receiver subscribed -2. **Arbitrary delays**: 500ms waits add latency for no good reason -3. **Silent failures**: Timeout expiring doesn't indicate the real problem -4. **Fragile**: Works "most of the time" but fails under load or timing variations - -**Instead, pass data directly:** -```rust -// ✅ GOOD - Extract data from existing events -async fn create_session_in_connector(...) -> Result<(String, Option, Option), String> { - // The SessionCreated event already contains models/modes - match event { - Event::SessionCreated { session, .. } => { - Ok((session.id, session.models, session.modes)) - } - } -} -``` - -**Rule**: If you find yourself writing `timeout(Duration::from_millis(N), events.recv())` to wait for an event that "should" arrive, the architecture is wrong. Refactor to pass data directly through return values or existing event payloads. - -## Contributing - -When adding features: -1. Update type definitions in `src/types/` -2. Add comprehensive tests -3. Update documentation in `docs/` -4. Add examples if applicable -5. Update CHANGELOG.md - -## See Also - -- Main project: [../../CLAUDE.md](../../CLAUDE.md) -- Architecture docs: [../../docs/architecture/](../../docs/architecture/) -- Building docs: [../../docs/building/](../../docs/building/) diff --git a/crates/dirigent_tools/CLAUDE.md b/crates/dirigent_tools/CLAUDE.md deleted file mode 100644 index 2abd038..0000000 --- a/crates/dirigent_tools/CLAUDE.md +++ /dev/null @@ -1,169 +0,0 @@ -# Package: dirigent_tools - -Tool implementations for ACP client operations with sandboxing and security. - -## Quick Facts -- **Type**: Library -- **Main Entry**: src/lib.rs -- **Dependencies**: tokio, serde, anyhow, thiserror, tracing, regex, globset, similar, dunce -- **Status**: Scaffolding phase - structure complete, implementation pending - -## Purpose - -This package provides the foundational tool operations for the Dirigent ACP client, implementing file operations, terminal execution, and search capabilities with strong security guarantees. It is designed to support ACP-compliant agents by providing safe, sandboxed tool handlers. - -## Module Structure - -### Core Modules -- **error** (`src/error.rs`) - Error types for tool operations -- **config** (`src/config.rs`) - Configuration types (sandbox, permissions, terminal, search, embedding) -- **path** (`src/path.rs`) - Cross-platform path normalization and containment checking - -### Tool Modules -- **fs** (`src/fs.rs`) - File read/write/edit operations -- **search** (`src/search.rs`) - Glob/grep/ls search operations -- **terminal** (`src/terminal.rs`) - Command execution and output capture - -### Security Modules -- **permission** (`src/permission.rs`) - Permission prompt system and decision caching -- **audit** (`src/audit.rs`) - Structured audit logging - -### Integration Modules -- **tool_call** (`src/tool_call.rs`) - ACP tool call metadata and helpers - -## Security Model - -All operations are subject to: - -1. **Sandbox Containment**: Operations restricted to configured allowed roots -2. **Blocklist Enforcement**: Sensitive paths explicitly denied -3. **Permission Prompts**: Write/execute operations require user approval (configurable) -4. **Resource Limits**: Bounded file sizes, search results, terminal output -5. **Audit Logging**: All operations logged with structured context - -## Platform Support - -Windows is a first-class platform with explicit support for: -- Backslash and forward slash separators -- Drive letters (C:\, D:\) -- UNC paths (\\server\share\...) -- Long path prefixes (\\?\...) -- MINGW-style paths (/c/Users/...) -- Junctions and symlinks -- cmd.exe and PowerShell - -All path normalization and containment logic handles these cases. - -## Configuration (Future) - -Configuration types will be implemented in SCAFF-05: - -```rust -pub struct SandboxConfig { - pub allowed_roots: Vec, - pub blocked_paths: Vec, // glob patterns - pub allow_symlink_escape: bool, - pub follow_symlinks_within_roots: bool, - pub read_enabled: bool, - pub write_enabled: bool, - pub max_read_bytes: u64, - pub max_write_bytes: u64, - // ... more fields -} - -pub struct PermissionConfig { - pub mode: PermissionMode, // Ask | Whitelist | Yolo - pub remember_decisions: bool, - pub remember_ttl_secs: u64, - pub scope: DecisionScope, // PerConnector | PerSession - pub whitelist: WhitelistConfig, -} - -pub struct TerminalConfig { - pub enabled: bool, - pub default_cwd: PathBuf, - pub env_allowlist: Vec, - pub command_blocklist: Vec, - pub output_byte_limit: u64, - pub max_runtime_secs: u64, -} - -pub struct SearchConfig { - pub max_results: u32, - pub max_bytes: u64, - pub default_include_globs: Vec, - pub default_exclude_globs: Vec, -} - -pub struct EmbeddingConfig { - pub max_embed_bytes: u64, - pub allow_resource_link: bool, - pub redact_patterns: Vec, // regex patterns -} -``` - -## Implementation Status - -**Current Phase**: SCAFF-01 (Scaffolding) - Complete - -All modules are stubs with `unimplemented!()`. Implementation will proceed in phases: - -### Phase 1: Path and Sandbox (Protocol tasks) -- Path normalization (Windows, Linux, macOS) -- Canonical path resolution (symlinks, junctions) -- Containment checking -- Blocklist matching - -### Phase 2: Tool Operations (Tool tasks) -- File read with line ranges -- File write with atomic operations -- File edit with diff generation -- Glob search -- Grep search with context -- LS directory listing -- Terminal creation and lifecycle -- Terminal output capture with ring buffer - -### Phase 3: Security and Integration (Integration tasks) -- Permission prompt flow -- Decision caching with TTL -- Audit logging with structured fields -- ACP tool call event generation -- Tool title formatting - -## Integration Points - -### dirigent_core -- Uses `dirigent_tools` for ACP client request handlers -- Passes configuration from connector params -- Routes tool operations through sandbox - -### api -- May use `dirigent_tools` for server function implementations -- Exposes configuration endpoints -- Handles permission prompts via UI - -### web -- Displays tool calls with titles, locations, diffs -- Renders permission prompts -- Shows audit logs - -## Testing Strategy - -Test infrastructure will be set up in SCAFF-03: -- Unit tests for each module -- Integration tests with mocker -- Cross-platform tests (Windows, Linux, macOS) -- Golden transcript fixtures -- Test utilities for temp directories, file comparison, sandboxed environments - -## Related Packages -- **dirigent_core** - Uses this package for tool operations -- **api** - May use this package for server functions -- **dirigent_protocol** - Shared event types for tool calls - -## References -- Task file: `docs/building/04_acp_client/04_tasks_00_scaffolding_and_finishing.md` -- Tools research: `docs/building/04_acp_client/03_tools_research.md` -- Sandbox spec: `docs/building/04_acp_client/03_fs_sandboxing_and_permissions_spec.md` -- Roadmap: `docs/building/04_acp_client/roadmap.md` diff --git a/crates/opencode_client/CLAUDE.md b/crates/opencode_client/CLAUDE.md deleted file mode 100644 index 46e859b..0000000 --- a/crates/opencode_client/CLAUDE.md +++ /dev/null @@ -1,30 +0,0 @@ -# Package: opencode_client - -Rust client library for interacting with the OpenCode.ai API. - -## Quick Facts -- **Type**: Library -- **Main Entry**: src/lib.rs -- **Dependencies**: reqwest, serde, serde_json, chrono - -## Key Files -- `src/lib.rs` - Public API exports -- `src/types.rs` - OpenCode API type definitions (Session, Message, Part, etc.) -- `src/client.rs` - HTTP client implementation with optional logging callbacks - -## Main Exports -- `OpenCodeClient` - Main API client with methods: list_sessions, list_messages, send_message -- `Session` - Session metadata and configuration -- `Message` - User or Assistant message (tagged enum) -- `MessageWithParts` - Message info + content parts -- `Part` - Text, Reasoning, or workflow parts (StepStart, StepFinish, Tool) -- `ClientError` - Error types: Http, Request, Serialization -- `LogCallback` - Type alias for logging callbacks - -## Related -- Used by: web, mobile (future), desktop (future) -- Independent: Can be used in any Rust project - -## Documentation -- README: ./README.md -- API spec: ../../docs/api/opencode.md