Skip to main content

adk_core/
agent.rs

1use crate::{InvocationContext, Result, RunConfig, event::Event};
2use async_trait::async_trait;
3use futures::stream::Stream;
4use serde::{Deserialize, Serialize};
5use std::pin::Pin;
6use std::sync::Arc;
7
8/// A pinned, boxed stream of [`Event`] results emitted by an agent during execution.
9pub type EventStream = Pin<Box<dyn Stream<Item = Result<Event>> + Send>>;
10
11/// Runtime composition features an [`Agent`] can consume or enforce.
12///
13/// The declaration is intentionally separate from an agent's domain-specific
14/// skills. It lets portable composition validate that a coordinator can accept
15/// invocation-scoped tools, emit governed transfers, and participate in safe
16/// resume before execution starts.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
18#[serde(rename_all = "camelCase")]
19pub struct AgentCapabilities {
20    /// The agent consumes [`RunConfig::runtime_toolsets`].
21    pub runtime_tools: bool,
22    /// The agent can emit agent-to-agent transfer actions.
23    pub handoff: bool,
24    /// Runtime-injected tools can use exact-call confirmation policy.
25    pub relationship_confirmation: bool,
26    /// The agent can safely resume an unresolved operation from a durable checkpoint.
27    pub checkpoint_resume: bool,
28    /// The agent observes invocation shared state when it is supplied.
29    pub shared_state: bool,
30    /// The agent forwards cancellation and authenticated request metadata.
31    pub invocation_metadata: bool,
32}
33
34/// Primary interaction pattern exposed by an [`Agent`].
35///
36/// This describes how a runtime should present the agent, not how the agent is
37/// composed. Teams and workflows therefore retain their existing agent
38/// primitives while a realtime implementation can advertise audio-oriented
39/// interaction to generic servers and user interfaces.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
41#[serde(rename_all = "camelCase")]
42pub enum AgentInteractionMode {
43    /// A bounded request produces a bounded stream of response events.
44    #[default]
45    RequestResponse,
46    /// A long-lived bidirectional session may emit audio and transcript events.
47    Realtime,
48}
49
50/// Portable description of a member in an agent composition.
51#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(rename_all = "camelCase")]
53pub struct AgentTopologyMember {
54    /// Stable member name used by the runtime.
55    pub name: String,
56    /// Human-readable member purpose.
57    pub description: String,
58    /// Whether this member receives the initial request.
59    pub coordinator: bool,
60    /// Runtime capabilities declared by the bound member.
61    pub capabilities: AgentCapabilities,
62}
63
64/// Control-flow semantics for one portable composition relationship.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66#[serde(rename_all = "camelCase")]
67pub enum AgentRelationshipKind {
68    /// Deterministic workflow control flows from the source node to the target node.
69    Flow,
70    /// Invoke the target and return its result to the caller.
71    Delegate,
72    /// Transfer active control to the target.
73    Handoff,
74}
75
76/// One exact directed relationship in a portable agent composition.
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
78#[serde(rename_all = "camelCase")]
79pub struct AgentTopologyRelationship {
80    /// Calling or transferring member.
81    pub from: String,
82    /// Exact target member.
83    pub to: String,
84    /// Relationship execution semantics.
85    pub kind: AgentRelationshipKind,
86}
87
88/// Portable topology metadata for a composed agent root.
89///
90/// The metadata is deliberately execution-provider neutral. Runtimes and user
91/// interfaces can inspect a composition without depending on a concrete team,
92/// graph, workflow, or model implementation.
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct AgentTopology {
96    /// Stable executable root name.
97    pub root: String,
98    /// Member that receives the initial request.
99    pub coordinator: String,
100    /// Members bound to the composition.
101    pub members: Vec<AgentTopologyMember>,
102    /// Exact directed relationships between members.
103    pub relationships: Vec<AgentTopologyRelationship>,
104}
105
106/// One proposed agent-to-agent transfer presented to a composite root.
107#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct AgentTransferRequest {
110    /// Root invocation that owns the transfer chain.
111    pub invocation_id: String,
112    /// Member requesting the transfer.
113    pub from: String,
114    /// Proposed target member.
115    pub to: String,
116    /// One-based transfer depth if the transfer is accepted.
117    pub depth: u32,
118}
119
120/// Result of asynchronous transfer governance.
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase", tag = "decision")]
123pub enum AgentTransferDecision {
124    /// Accept the exact proposed target.
125    Allow,
126    /// Reject the transfer with an auditable reason.
127    Deny {
128        /// Human-readable policy reason.
129        reason: String,
130    },
131}
132
133/// The fundamental trait for all ADK agents.
134///
135/// Every agent — whether a simple LLM wrapper, a multi-step workflow, or a
136/// composite orchestrator — implements this trait. The runtime invokes
137/// [`run`](Self::run) with an [`InvocationContext`] and consumes the returned
138/// [`EventStream`].
139#[async_trait]
140pub trait Agent: Send + Sync {
141    /// Returns the unique name of this agent.
142    fn name(&self) -> &str;
143    /// Returns a human-readable description of this agent's purpose.
144    fn description(&self) -> &str;
145    /// Returns the child agents managed by this agent.
146    fn sub_agents(&self) -> &[Arc<dyn Agent>];
147
148    /// Returns the agent's primary interaction pattern.
149    ///
150    /// Existing agents remain request/response by default. Realtime agents
151    /// override this without introducing a new atomic agent kind or changing
152    /// composition semantics.
153    fn interaction_mode(&self) -> AgentInteractionMode {
154        AgentInteractionMode::RequestResponse
155    }
156
157    /// Whether this agent participates in LLM-driven agent transfer and may be
158    /// resumed directly across conversation turns.
159    ///
160    /// When a session persists across turns, the runner inspects history to
161    /// decide which agent should handle the next user message. LLM-based and
162    /// custom agents return the default `true`, so the runner can hand a new
163    /// turn back to whichever agent responded last.
164    ///
165    /// Deterministic workflow agents (sequential, parallel, loop, conditional)
166    /// override this to return `false`. Their sub-agents must not be resumed
167    /// individually: doing so would skip the workflow's other sub-agents on
168    /// subsequent turns. Returning `false` makes the runner resume the workflow
169    /// root instead, so every sub-agent runs again on each turn.
170    fn supports_agent_transfer(&self) -> bool {
171        true
172    }
173
174    /// Declares the execution-plane capabilities this agent supports.
175    ///
176    /// The default remains compatible with existing custom agents: transfer,
177    /// shared-state, cancellation, and request metadata follow the established
178    /// [`Agent`] and [`InvocationContext`] contracts. Runtime tool injection and
179    /// exact-call relationship confirmation are opt-in because an implementation
180    /// must actively consume those facilities.
181    fn capabilities(&self) -> AgentCapabilities {
182        AgentCapabilities {
183            runtime_tools: false,
184            handoff: self.supports_agent_transfer(),
185            relationship_confirmation: false,
186            checkpoint_resume: false,
187            shared_state: true,
188            invocation_metadata: true,
189        }
190    }
191
192    /// Returns portable composition metadata when this agent owns an explicit topology.
193    ///
194    /// Leaf agents and legacy composites return `None`. The default keeps this
195    /// additive API backward compatible for existing [`Agent`] implementations.
196    fn topology(&self) -> Option<AgentTopology> {
197        None
198    }
199
200    /// Applies agent-composition policy to a run before the runtime creates the
201    /// invocation context for `agent_name`.
202    ///
203    /// Composite agents can use this hook to inject invocation-scoped tools,
204    /// constrain transfer targets, or tighten depth and concurrency limits.
205    /// The default is a no-op, preserving the behavior of existing agents.
206    fn configure_run(&self, _agent_name: &str, _config: &mut RunConfig) {}
207
208    /// Returns the exact handoff targets allowed for `agent_name`, when this
209    /// agent owns an explicit transfer topology.
210    ///
211    /// `None` asks the runtime to retain its legacy parent/peer discovery.
212    /// `Some(vec![])` explicitly forbids handoff from the named agent.
213    fn transfer_targets_for(&self, _agent_name: &str) -> Option<Vec<String>> {
214        None
215    }
216
217    /// Whether transfer-policy violations should fail the run.
218    ///
219    /// Legacy agent trees return `false`, so missing targets and exceeded depth
220    /// retain their historical warn-and-stop behavior. Validated composites
221    /// return `true` to make topology violations observable errors.
222    fn strict_transfer_policy(&self) -> bool {
223        false
224    }
225
226    /// Applies asynchronous policy to an otherwise valid transfer.
227    ///
228    /// Runner calls this after exact target validation and before control moves
229    /// to the target. Ordinary agents allow transfers, preserving legacy
230    /// behavior; validated composites can attach authorization, lifecycle hooks,
231    /// audit logging, or other policy without teaching Runner their schema.
232    async fn govern_transfer(
233        &self,
234        _request: &AgentTransferRequest,
235    ) -> Result<AgentTransferDecision> {
236        Ok(AgentTransferDecision::Allow)
237    }
238
239    /// Executes the agent and returns a stream of events.
240    async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<EventStream>;
241}
242
243/// A validated context containing engineered instructions and resolved tool instances.
244///
245/// This structure serves as the "Atomic Unit of Capability" for an agent. It guarantees
246/// that the agent's cognitive frame (the instructions telling it what it can do) is
247/// perfectly aligned with its physical capabilities (the binary tool instances bound
248/// to the session).
249///
250/// By using `ResolvedContext`, the framework eliminates "Phantom Tool" hallucinations,
251/// where an agent tries to call a tool that was mentioned in its prompt but never
252/// actually registered in the runtime.
253#[derive(Clone)]
254pub struct ResolvedContext {
255    /// The engineered system instruction.
256    pub system_instruction: String,
257    /// The resolved, executable tools.
258    pub active_tools: Vec<Arc<dyn crate::Tool>>,
259}
260
261impl std::fmt::Debug for ResolvedContext {
262    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263        f.debug_struct("ResolvedContext")
264            .field("system_instruction_len", &self.system_instruction.len())
265            .field("active_tools_count", &self.active_tools.len())
266            .finish()
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273    use crate::{Content, ReadonlyContext, RunConfig};
274    use async_stream::stream;
275
276    struct TestAgent {
277        name: String,
278    }
279
280    use crate::{CallbackContext, Session, State};
281    use std::collections::HashMap;
282
283    struct MockState;
284    impl State for MockState {
285        fn get(&self, _key: &str) -> Option<serde_json::Value> {
286            None
287        }
288        fn set(&mut self, _key: String, _value: serde_json::Value) {}
289        fn all(&self) -> HashMap<String, serde_json::Value> {
290            HashMap::new()
291        }
292    }
293
294    struct MockSession;
295    impl Session for MockSession {
296        fn id(&self) -> &str {
297            "session"
298        }
299        fn app_name(&self) -> &str {
300            "app"
301        }
302        fn user_id(&self) -> &str {
303            "user"
304        }
305        fn state(&self) -> &dyn State {
306            &MockState
307        }
308        fn conversation_history(&self) -> Vec<Content> {
309            Vec::new()
310        }
311    }
312
313    #[allow(dead_code)]
314    struct TestContext {
315        content: Content,
316        config: RunConfig,
317        session: MockSession,
318    }
319
320    #[allow(dead_code)]
321    impl TestContext {
322        fn new() -> Self {
323            Self {
324                content: Content::new("user"),
325                config: RunConfig::default(),
326                session: MockSession,
327            }
328        }
329    }
330
331    #[async_trait]
332    impl ReadonlyContext for TestContext {
333        fn invocation_id(&self) -> &str {
334            "test"
335        }
336        fn agent_name(&self) -> &str {
337            "test"
338        }
339        fn user_id(&self) -> &str {
340            "user"
341        }
342        fn app_name(&self) -> &str {
343            "app"
344        }
345        fn session_id(&self) -> &str {
346            "session"
347        }
348        fn branch(&self) -> &str {
349            ""
350        }
351        fn user_content(&self) -> &Content {
352            &self.content
353        }
354    }
355
356    #[async_trait]
357    impl CallbackContext for TestContext {
358        fn artifacts(&self) -> Option<Arc<dyn crate::Artifacts>> {
359            None
360        }
361    }
362
363    #[async_trait]
364    impl InvocationContext for TestContext {
365        fn agent(&self) -> Arc<dyn Agent> {
366            unimplemented!()
367        }
368        fn memory(&self) -> Option<Arc<dyn crate::Memory>> {
369            None
370        }
371        fn session(&self) -> &dyn Session {
372            &self.session
373        }
374        fn run_config(&self) -> &RunConfig {
375            &self.config
376        }
377        fn end_invocation(&self) {}
378        fn ended(&self) -> bool {
379            false
380        }
381    }
382
383    #[async_trait]
384    impl Agent for TestAgent {
385        fn name(&self) -> &str {
386            &self.name
387        }
388
389        fn description(&self) -> &str {
390            "test agent"
391        }
392
393        fn sub_agents(&self) -> &[Arc<dyn Agent>] {
394            &[]
395        }
396
397        async fn run(&self, _ctx: Arc<dyn InvocationContext>) -> Result<EventStream> {
398            let s = stream! {
399                yield Ok(Event::new("test"));
400            };
401            Ok(Box::pin(s))
402        }
403    }
404
405    #[test]
406    fn test_agent_trait() {
407        let agent = TestAgent { name: "test".to_string() };
408        assert_eq!(agent.name(), "test");
409        assert_eq!(agent.description(), "test agent");
410        assert!(agent.capabilities().handoff);
411        assert!(!agent.capabilities().runtime_tools);
412        assert_eq!(agent.interaction_mode(), AgentInteractionMode::RequestResponse);
413        assert_eq!(agent.topology(), None);
414    }
415
416    #[tokio::test]
417    async fn default_transfer_governance_is_backward_compatible() {
418        let agent = TestAgent { name: "test".to_string() };
419        let request = AgentTransferRequest {
420            invocation_id: "inv-1".to_string(),
421            from: "test".to_string(),
422            to: "peer".to_string(),
423            depth: 1,
424        };
425        assert_eq!(agent.govern_transfer(&request).await.unwrap(), AgentTransferDecision::Allow);
426    }
427}