adk_managed/runtime.rs
1//! Core trait and handle types for the managed agent runtime.
2//!
3//! The [`ManagedAgentRuntime`] trait defines the full lifecycle interface for
4//! managed agents: create agents from declarative definitions, start sessions,
5//! send/receive events, pause/resume/interrupt, and archive.
6//!
7//! # Architecture
8//!
9//! The runtime is a **library**, not a service. The platform hosts it.
10//! This trait is provider-agnostic — it behaves identically for Gemini, OpenAI,
11//! Anthropic, Ollama, and OpenAI-compatible providers.
12//!
13//! # Example
14//!
15//! ```rust,ignore
16//! use adk_managed::runtime::{ManagedAgentRuntime, AgentHandle, SessionHandle};
17//! use adk_managed::types::ManagedAgentDef;
18//!
19//! async fn example(runtime: &dyn ManagedAgentRuntime) {
20//! let def = ManagedAgentDef::default();
21//! let agent = runtime.create(def).await.unwrap();
22//! let session = runtime.start_session(&agent, None).await.unwrap();
23//! let status = runtime.status(&session).await.unwrap();
24//! println!("Session status: {status:?}");
25//! }
26//! ```
27
28use std::collections::HashMap;
29
30use async_trait::async_trait;
31use futures::stream::BoxStream;
32use serde::{Deserialize, Serialize};
33
34use crate::types::{ManagedAgentDef, RuntimeError, SessionEvent, SessionStatus, UserEvent};
35
36// ─── Handle Types ────────────────────────────────────────────────────────────
37
38/// Opaque agent handle.
39///
40/// The platform assigns the user-facing `agt_` prefixed ID; the runtime uses
41/// this internal handle for lookups. The inner string is an implementation detail.
42///
43/// # Example
44///
45/// ```
46/// use adk_managed::runtime::AgentHandle;
47///
48/// let handle = AgentHandle("agent_abc123".to_string());
49/// assert_eq!(handle.0, "agent_abc123");
50/// ```
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52pub struct AgentHandle(pub String);
53
54/// Opaque session handle.
55///
56/// Identifies an active or archived session within the runtime. The inner string
57/// is an implementation detail assigned by the runtime on session creation.
58///
59/// # Example
60///
61/// ```
62/// use adk_managed::runtime::SessionHandle;
63///
64/// let handle = SessionHandle("session_xyz789".to_string());
65/// assert_eq!(handle.0, "session_xyz789");
66/// ```
67#[derive(Debug, Clone, PartialEq, Eq, Hash)]
68pub struct SessionHandle(pub String);
69
70// ─── EnvironmentConfig ───────────────────────────────────────────────────────
71
72/// Optional environment configuration for a session.
73///
74/// Provides environment variables and working directory context that the agent
75/// session can use during execution (e.g., for sandbox or tool execution).
76///
77/// # Example
78///
79/// ```
80/// use adk_managed::runtime::EnvironmentConfig;
81///
82/// let env = EnvironmentConfig {
83/// env_vars: [("API_KEY".to_string(), "secret".to_string())].into(),
84/// working_dir: Some("/workspace".to_string()),
85/// };
86/// assert_eq!(env.env_vars.get("API_KEY").unwrap(), "secret");
87/// ```
88#[derive(Debug, Clone, Default, Serialize, Deserialize)]
89pub struct EnvironmentConfig {
90 /// Environment variables available to the agent.
91 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
92 pub env_vars: HashMap<String, String>,
93
94 /// Optional working directory for the session.
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub working_dir: Option<String>,
97}
98
99// ─── ManagedAgentRuntime Trait ───────────────────────────────────────────────
100
101/// The central async trait defining the managed agent lifecycle.
102///
103/// Implementations of this trait encapsulate the full agent lifecycle:
104/// creating agents from declarative definitions, starting durable sessions,
105/// sending/receiving events, pausing/resuming, interrupting, and archiving.
106///
107/// The trait is provider-agnostic: it takes a [`ManagedAgentDef`] with a
108/// [`ModelRef`](crate::types::ModelRef) and behaves identically regardless
109/// of which LLM provider powers the agent.
110///
111/// # Implementors
112///
113/// - `DefaultManagedAgentRuntime` — the default implementation
114/// composed from `Runner` + pluggable `SessionService` + optional sandbox/memory.
115///
116/// # Design Notes
117///
118/// - All methods return `Result<_, RuntimeError>` for structured error handling.
119/// - `stream_events` returns a `BoxStream` for SSE-compatible event delivery.
120/// - `from_seq` on `stream_events` enables `Last-Event-ID` reconnection.
121/// - The runtime is `Send + Sync` for use across async task boundaries.
122#[async_trait]
123pub trait ManagedAgentRuntime: Send + Sync {
124 /// Create a managed agent from a declarative definition.
125 ///
126 /// Resolves the [`ModelRef`](crate::types::ModelRef), builds a runnable
127 /// agent, and stores it in the internal registry. Returns an opaque handle
128 /// for use with `start_session`.
129 async fn create(&self, def: ManagedAgentDef) -> Result<AgentHandle, RuntimeError>;
130
131 /// Start a new session for the given agent.
132 ///
133 /// Creates a session in `Queued` status, initializes the session loop,
134 /// and returns a handle for event interaction. The optional
135 /// [`EnvironmentConfig`] provides env vars and working directory.
136 async fn start_session(
137 &self,
138 agent: &AgentHandle,
139 env: Option<EnvironmentConfig>,
140 ) -> Result<SessionHandle, RuntimeError>;
141
142 /// Send an event from the client to the agent session.
143 ///
144 /// Dispatches the [`UserEvent`] to the session loop. The event type
145 /// determines behavior:
146 /// - `user.message` — enqueues a message for processing
147 /// - `user.interrupt` — signals the session to stop at next boundary
148 /// - `user.custom_tool_result` — delivers a result to a parked tool call
149 /// - `user.tool_confirmation` — approves or denies a pending tool use
150 async fn send_event(
151 &self,
152 session: &SessionHandle,
153 event: UserEvent,
154 ) -> Result<(), RuntimeError>;
155
156 /// Subscribe to the session's event stream.
157 ///
158 /// Returns a stream of [`SessionEvent`]s. If `from_seq` is provided,
159 /// replays all events with `seq > from_seq` before attaching to the
160 /// live broadcast (enabling SSE `Last-Event-ID` reconnection).
161 async fn stream_events(
162 &self,
163 session: &SessionHandle,
164 from_seq: Option<u64>,
165 ) -> Result<BoxStream<'static, SessionEvent>, RuntimeError>;
166
167 /// Interrupt the session at the next safe boundary.
168 ///
169 /// Signals the session loop's cancellation token. The loop will stop
170 /// processing at the next inter-event boundary and emit `status.idle`.
171 async fn interrupt(&self, session: &SessionHandle) -> Result<(), RuntimeError>;
172
173 /// Pause the session, checkpointing current state.
174 ///
175 /// Stops consuming new input and persists the current run-state.
176 /// The session transitions to `Paused` status.
177 async fn pause(&self, session: &SessionHandle) -> Result<(), RuntimeError>;
178
179 /// Resume a paused session from its last checkpoint.
180 ///
181 /// Clears the pause flag, rehydrates state if needed, and returns
182 /// the session to active processing.
183 async fn resume(&self, session: &SessionHandle) -> Result<(), RuntimeError>;
184
185 /// Query the current status of a session.
186 async fn status(&self, session: &SessionHandle) -> Result<SessionStatus, RuntimeError>;
187
188 /// Archive a session (terminal state).
189 ///
190 /// Sets the session to `Archived` status and stops the session loop.
191 /// Archived sessions retain their event log for read access.
192 async fn archive(&self, session: &SessionHandle) -> Result<(), RuntimeError>;
193
194 /// Delete a session and its associated data.
195 ///
196 /// Archives the session (if not already terminal) and removes all
197 /// persisted data including events and checkpoints.
198 async fn delete_session(&self, session: &SessionHandle) -> Result<(), RuntimeError>;
199}