Skip to main content

aios_protocol/
ports.rs

1//! Canonical runtime ports for Agent OS integrations.
2//!
3//! These traits define the only allowed runtime boundary between the kernel
4//! engine and external implementations (event stores, model providers, tool
5//! harnesses, policy engines, approval systems, and memory backends).
6//!
7//! Object-safety note:
8//! - Traits use `async-trait` for async dyn-dispatch.
9//! - Streaming uses boxed trait objects (`EventRecordStream`).
10
11use crate::error::KernelResult;
12use crate::event::{EventRecord, TokenUsage};
13use crate::ids::{ApprovalId, BranchId, RunId, SessionId, ToolRunId};
14use crate::policy::Capability;
15use crate::tool::{ToolCall, ToolOutcome};
16use async_trait::async_trait;
17use chrono::{DateTime, Utc};
18use futures_util::stream::BoxStream;
19use serde::{Deserialize, Serialize};
20
21pub type EventRecordStream = BoxStream<'static, KernelResult<EventRecord>>;
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct ModelCompletionRequest {
25    pub session_id: SessionId,
26    pub branch_id: BranchId,
27    pub run_id: RunId,
28    pub step_index: u32,
29    pub objective: String,
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub proposed_tool: Option<ToolCall>,
32    /// Optional system prompt to prepend to the conversation.
33    /// Used for skill catalogs, persona blocks, and context compiler output.
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub system_prompt: Option<String>,
36    /// Tool whitelist from active skill. When set, only these tools are sent to the LLM.
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub allowed_tools: Option<Vec<String>>,
39    /// Conversation history from prior turns in this session.
40    /// Built by the runtime from the event journal before each provider call.
41    #[serde(default, skip_serializing_if = "Vec::is_empty")]
42    pub conversation_history: Vec<ConversationTurn>,
43}
44
45/// A single turn in the conversation history (user message + assistant response).
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct ConversationTurn {
48    pub role: String,
49    pub content: String,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53#[serde(tag = "type", rename_all = "snake_case")]
54pub enum ModelDirective {
55    TextDelta {
56        delta: String,
57        #[serde(default, skip_serializing_if = "Option::is_none")]
58        index: Option<u32>,
59    },
60    Message {
61        role: String,
62        content: String,
63    },
64    ToolCall {
65        call: ToolCall,
66    },
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70#[serde(rename_all = "snake_case")]
71pub enum ModelStopReason {
72    Completed,
73    ToolCall,
74    MaxIterations,
75    Cancelled,
76    Error,
77    Other(String),
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ModelCompletion {
82    pub provider: String,
83    pub model: String,
84    #[serde(default)]
85    pub directives: Vec<ModelDirective>,
86    pub stop_reason: ModelStopReason,
87    #[serde(default, skip_serializing_if = "Option::is_none")]
88    pub usage: Option<TokenUsage>,
89    #[serde(default, skip_serializing_if = "Option::is_none")]
90    pub final_answer: Option<String>,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct ToolExecutionRequest {
95    pub session_id: SessionId,
96    pub workspace_root: String,
97    pub call: ToolCall,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ToolExecutionReport {
102    pub tool_run_id: ToolRunId,
103    pub call_id: String,
104    pub tool_name: String,
105    pub exit_status: i32,
106    pub duration_ms: u64,
107    pub outcome: ToolOutcome,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct PolicyGateDecision {
112    #[serde(default)]
113    pub allowed: Vec<Capability>,
114    #[serde(default)]
115    pub requires_approval: Vec<Capability>,
116    #[serde(default)]
117    pub denied: Vec<Capability>,
118}
119
120impl PolicyGateDecision {
121    pub fn is_allowed_now(&self) -> bool {
122        self.denied.is_empty() && self.requires_approval.is_empty()
123    }
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct ApprovalRequest {
128    pub session_id: SessionId,
129    pub call_id: String,
130    pub tool_name: String,
131    pub capability: Capability,
132    pub reason: String,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct ApprovalTicket {
137    pub approval_id: ApprovalId,
138    pub session_id: SessionId,
139    pub call_id: String,
140    pub tool_name: String,
141    pub capability: Capability,
142    pub reason: String,
143    pub created_at: DateTime<Utc>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct ApprovalResolution {
148    pub approval_id: ApprovalId,
149    pub approved: bool,
150    pub actor: String,
151    pub resolved_at: DateTime<Utc>,
152}
153
154#[async_trait]
155pub trait EventStorePort: Send + Sync {
156    async fn append(&self, event: EventRecord) -> KernelResult<EventRecord>;
157    async fn read(
158        &self,
159        session_id: SessionId,
160        branch_id: BranchId,
161        from_sequence: u64,
162        limit: usize,
163    ) -> KernelResult<Vec<EventRecord>>;
164    async fn head(&self, session_id: SessionId, branch_id: BranchId) -> KernelResult<u64>;
165    async fn subscribe(
166        &self,
167        session_id: SessionId,
168        branch_id: BranchId,
169        after_sequence: u64,
170    ) -> KernelResult<EventRecordStream>;
171}
172
173#[async_trait]
174pub trait ModelProviderPort: Send + Sync {
175    async fn complete(&self, request: ModelCompletionRequest) -> KernelResult<ModelCompletion>;
176}
177
178#[async_trait]
179pub trait ToolHarnessPort: Send + Sync {
180    async fn execute(&self, request: ToolExecutionRequest) -> KernelResult<ToolExecutionReport>;
181}
182
183#[async_trait]
184pub trait PolicyGatePort: Send + Sync {
185    async fn evaluate(
186        &self,
187        session_id: SessionId,
188        requested: Vec<Capability>,
189    ) -> KernelResult<PolicyGateDecision>;
190
191    async fn set_policy(
192        &self,
193        _session_id: SessionId,
194        _policy: crate::policy::PolicySet,
195    ) -> KernelResult<()> {
196        Ok(())
197    }
198}
199
200#[async_trait]
201pub trait ApprovalPort: Send + Sync {
202    async fn enqueue(&self, request: ApprovalRequest) -> KernelResult<ApprovalTicket>;
203    async fn list_pending(&self, session_id: SessionId) -> KernelResult<Vec<ApprovalTicket>>;
204    async fn resolve(
205        &self,
206        approval_id: ApprovalId,
207        approved: bool,
208        actor: String,
209    ) -> KernelResult<ApprovalResolution>;
210}