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}