//! bridge_keys_demo —— bridge_keys 过滤 + 子↔子共享 namespace 示例。 //! Required features: cargo run --example bridge_keys_demo --features "engine" //! //! 演示: //! 1. 父 session 设置 SessionMemory(key: "project_goal", "constraints", "noise") //! 2. dispatch + bridge_keys = ["project_goal", "constraints"] → 只继承这两个 //! 3. 验证子 session 读到的 session_memory 与过滤一致 //! 4. 演示子↔子共享 namespace:dispatch 时设 `shared_namespace`, //! 子 A 写入 `shared:{parent_id}:fact_x`,子 B 通过约定 key 读取 //! //! 运行:`cargo run --example bridge_keys_demo` use std::sync::Arc; use agcore::agent::{Agent, AgentBuilder}; use agcore::engine::{DispatchConfig, SessionManager}; use agcore::llm::hooks::HookExecutor; use agcore::llm::mock::MockProvider; use agcore::llm::types::Usage; use agcore::llm::types::message::{ContentBlock, Message}; use agcore::llm::types::response_v2::{MessageResponse, StopReason}; use agcore::memory::store::InMemoryStore; use agcore::tools::ToolRegistry; struct WorkerAgent; impl Agent for WorkerAgent { fn name(&self) -> &str { "worker" } fn system_prompt(&self) -> Option<&str> { Some("You are a worker.") } } fn assistant_text(text: &str) -> MessageResponse { MessageResponse { id: String::new(), model: String::new(), message: Message::Assistant { content: vec![ContentBlock::Text { text: text.into() }], }, usage: Usage::from_input_output(8, 4), stop_reason: StopReason::Stop, extra: Default::default(), } } #[tokio::main] async fn main() { println!("=== Bridge Keys Demo ===\n"); let store: Arc = Arc::new(InMemoryStore::new()); // 3 个 dispatch 调用需要 3 个 mock response let provider = Arc::new(MockProvider::new(vec![ assistant_text("Worker 1: done"), assistant_text("Worker 2: done"), assistant_text("Worker 3: done"), ])); let bundle = Arc::new( AgentBuilder::new() .provider(provider) .tool_registry(Arc::new(ToolRegistry::new())) .hook_executor(Arc::new(HookExecutor::new())) .session_memory_backend(store.clone()) .build() .expect("RuntimeBundle"), ); let worker: Arc = Arc::new(WorkerAgent); let sm = Arc::new(SessionManager::new(store)); let parent_id = sm .create(worker.clone(), bundle.clone()) .await .expect("create"); println!("[1] parent session created: {parent_id}"); // 父 session_memory 写入 3 个 key { let session = sm.get(&parent_id).await.unwrap(); let mut guard = session.lock().await; guard .set_session_data("project_goal", "Build a fast compiler") .await .unwrap(); guard .set_session_data("constraints", "Rust, no unsafe") .await .unwrap(); guard .set_session_data("noise", "should NOT be inherited") .await .unwrap(); } println!("[2] parent set 3 keys: project_goal, constraints, noise"); // dispatch + bridge_keys 过滤 let config = DispatchConfig { bridge_keys: Some(vec!["project_goal".to_string(), "constraints".to_string()]), ..Default::default() }; let result = sm .dispatch(&parent_id, worker.clone(), "do work", config) .await .expect("dispatch"); println!( "[3] dispatched sub-agent (child_id={})\n", &result.child_id[..20] ); // 验证过滤效果 let child_session = sm.get(&result.child_id).await.unwrap(); let child_guard = child_session.lock().await; let inherited_goal = child_guard .session_memory() .get("project_goal") .await .unwrap(); let inherited_constraint = child_guard .session_memory() .get("constraints") .await .unwrap(); let filtered_noise = child_guard.session_memory().get("noise").await.unwrap(); drop(child_guard); println!("[verify] inherited keys in child session:"); println!(" - project_goal: {:?}", inherited_goal); println!(" - constraints: {:?}", inherited_constraint); println!(" - noise: {:?} (should be None)", filtered_noise); assert_eq!(inherited_goal, Some("Build a fast compiler".to_string())); assert_eq!(inherited_constraint, Some("Rust, no unsafe".to_string())); assert_eq!(filtered_noise, None, "noise should be filtered out"); println!("\n✓ bridge_keys filtering works correctly"); // =============== 第二部分:子↔子共享 namespace(convention)=============== println!("\n=== Part 2: Child↔Child Shared Namespace (convention) ===\n"); // 关键点:`SessionMemory::get`/`set` 通过 session 自身 namespace 隔离 // (每个 session 一个独立 namespace),所以"子↔子共享"不能直接通过 SessionMemory。 // 真正的子↔子共享需要直接操作底层 MemoryStore,或由上层应用维护一个 // 跨 session 的"共享通道"(例如独立的 namespace + 所有子 session 知道 key 前缀)。 // // 本 demo 演示通过 DispatchConfig.shared_namespace(convention-based): // - `shared_namespace: Some(prefix)` 作为约定标记,告知子 agent // "你的数据共享 namespace 是 shared:{prefix}:*" // - 子 agent 自行通过 `sm.store()` 直接操作 MemoryStore(绕过 SessionMemory 的 namespace 隔离) // // 演示 2 个子 agent 通过约定 namespace prefix 共享数据。 let shared_ns_config = DispatchConfig { bridge_keys: Some(vec![]), shared_namespace: Some("parent-123".to_string()), ..Default::default() }; // dispatch 第一个子 agent let _researcher_result = sm .dispatch( &parent_id, worker.clone(), "research task", shared_ns_config.clone(), ) .await .expect("dispatch researcher"); // 子 A 通过 `sm.store()` 直接写入共享 namespace key // (约定 prefix: "shared:parent-123:") let shared_key = "shared:parent-123:fact_architecture"; sm.store() .save(agcore::memory::types::MemoryItem { id: shared_key.to_string(), content: "Microservices with event sourcing".to_string(), metadata: serde_json::json!({}), created_at: time::OffsetDateTime::now_utc(), }) .await .expect("save shared fact"); println!("[4] researcher wrote {shared_key}"); // dispatch 第二个子 agent let _writer_result = sm .dispatch(&parent_id, worker.clone(), "writing task", shared_ns_config) .await .expect("dispatch writer"); // 子 B 通过 `sm.store()` 直接读取共享 namespace key let read_item = sm.store().get(shared_key).await.expect("get"); let read_fact = read_item.map(|i| i.content); println!("[5] writer reads {shared_key} = {read_fact:?}"); assert_eq!( read_fact, Some("Microservices with event sourcing".to_string()), "writer should read researcher's shared fact" ); println!("\n✓ child↔child shared namespace works correctly (via MemoryStore convention)"); println!("\n=== All Bridge Keys Demo checks passed ==="); }