प्रबंधित एजेंट रनटाइम

स्थिरता: प्रयोगात्मक — यह सुविधा अतिरिक्त है और managed-runtime के पीछे फीचर-गेटेड है। सुविधा अक्षम होने पर यह मौजूदा Runner/LlmAgent APIs को प्रभावित नहीं करती। भविष्य के रिलीज़ में API सतह बदल सकती है।

अवलोकन

प्रबंधित एजेंट रनटाइम (adk-managed) एक प्रदाता-निरपेक्ष, टिकाऊ और पुनःआरंभ योग्य एजेंट निष्पादन इंजन है। यह एक घोषणात्मक ManagedAgentDef लेता है, चलाने योग्य एजेंट बनाता है, और उसे चेकपॉइंट से पुनःआरंभ होने योग्य, इवेंट-स्ट्रीमिंग वाली पृष्ठभूमि सत्र के रूप में संचालित करता है।

रनटाइम एक लाइब्रेरी है, सेवा नहीं। प्लेटफ़ॉर्म इसे होस्ट करता है। इसका अर्थ है:

  • अलग से परीक्षण योग्य: शून्य HTTP/प्रमाणीकरण/बिलिंग निर्भरताएँ
  • एम्बेड करने योग्य: स्व-होस्ट किए गए परिनियोजन सीधे उसी रनटाइम trait का उपयोग करते हैं
  • बदला जा सकने वाला प्लेटफ़ॉर्म: अलग-अलग प्लेटफ़ॉर्म उसी रनटाइम को होस्ट कर सकते हैं
  • प्रदाता-निरपेक्ष: मॉडल प्रदाता की परवाह किए बिना समान इवेंट अनुक्रम

त्वरित शुरुआत

अपने Cargo.toml में सुविधा जोड़ें:

[dependencies]
adk-rust = { version = "2.1.0", features = ["managed-runtime"] }

या सीधे adk-managed क्रेट का उपयोग करें:

[dependencies]
adk-managed = "2.1.0"
adk-session = "2.1.0"

न्यूनतम उदाहरण (ScriptedLlm — API कुंजी के बिना)

use std::sync::Arc;
use adk_managed::{
    DefaultManagedAgentRuntime, ManagedAgentRuntime, ModelResolver,
    ScriptedLlm, ScriptedTurn,
    resolver::ResolverResult,
    types::{ContentBlock, ManagedAgentDef, ModelRef, UserEvent},
};
use adk_session::InMemorySessionService;
use async_trait::async_trait;
use futures::StreamExt;

// A resolver that returns our scripted LLM
struct MockResolver { llm: Arc<dyn adk_core::Llm> }

#[async_trait]
impl ModelResolver for MockResolver {
    async fn resolve(&self, _: &ModelRef) -> ResolverResult<Arc<dyn adk_core::Llm>> {
        Ok(self.llm.clone())
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Create a scripted LLM (deterministic, offline, $0)
    let llm = Arc::new(ScriptedLlm::new("test-model", vec![
        ScriptedTurn { text: Some("Hello!".into()), tool_calls: vec![] },
    ]));

    // 2. Build the runtime
    let runtime = DefaultManagedAgentRuntime::new(
        Arc::new(MockResolver { llm }),
        Arc::new(InMemorySessionService::new()),
    );

    // 3. Create an agent
    let def = ManagedAgentDef::new("my-agent", ModelRef::Shorthand("test-model".into()))
        .with_system("You are helpful.");
    let agent = runtime.create(def).await?;

    // 4. Start a session
    let session = runtime.start_session(&agent, None).await?;

    // 5. Subscribe to events and send a message
    let mut stream = runtime.stream_events(&session, None).await?;
    runtime.send_event(&session, UserEvent::Message {
        content: vec![ContentBlock::Text { text: "Hi!".into() }],
    }).await?;

    // 6. Collect events
    while let Some(event) = stream.next().await {
        println!("{event:?}");
    }
    Ok(())
}

आर्किटेक्चर

┌─────────────────────────────────────────────────────────────┐
│                Platform Layer (ep-* crates)                  │
│    HTTP Routes │ Auth │ Billing │ Multi-tenancy              │
└──────────────────────────┬──────────────────────────────────┘
                           │ Rust trait calls (in-process)
                           ▼
┌─────────────────────────────────────────────────────────────┐
│            Runtime Layer (adk-managed)                       │
│                                                             │
│  ManagedAgentRuntime trait + DefaultManagedAgentRuntime      │
│  ───────────────────────────────────────────────────        │
│  • Builds runnable agents from ManagedAgentDef              │
│  • Runs supervised session loop (durable, resumable)        │
│  • Emits provider-neutral SessionEvent stream               │
│  • Manages custom tool parking, checkpoints, interrupts     │
│  • Resolves ModelRef → Arc<dyn Llm>                         │
│                                                             │
│  Composes existing crates:                                  │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐    │
│  │adk-runner│ │adk-session│ │adk-model │ │adk-tool    │    │
│  └──────────┘ └──────────┘ └──────────┘ └────────────┘    │
└─────────────────────────────────────────────────────────────┘

मुख्य प्रकार

ManagedAgentRuntime trait

एजेंट के संपूर्ण जीवनचक्र को परिभाषित करने वाला केंद्रीय async trait:

विधिविवरण
create(def)एजेंट परिभाषा पंजीकृत करता है, AgentHandle लौटाता है
start_session(agent, env?)नया सत्र शुरू करता है, प्रारंभिक स्थिति Queued
send_event(session, event)सत्र को एक UserEvent भेजें
stream_events(session, from_seq?)SessionEvent स्ट्रीम की सदस्यता लें
interrupt(session)अगले सीमा-बिंदु पर रुकें, status.idle उत्सर्जित करें
pause(session)चेकपॉइंट बनाएं और प्रोसेसिंग रोकें
resume(session)विराम से फिर शुरू करें या पुनः प्रारंभ करें
status(session)वर्तमान SessionStatus की क्वेरी करें
archive(session)टर्मिनल स्थिति, डेटा सुरक्षित रखा गया
delete_session(session)सत्र डेटा हटाएँ

ManagedAgentDef

बिल्डर API के साथ घोषणात्मक एजेंट परिभाषा:

let def = ManagedAgentDef::new("my-agent", ModelRef::Shorthand("gemini-3.7-flash".into()))
    .with_system("You are a helpful assistant.")
    .with_description("Research agent with web search")
    .with_tools(vec![ToolConfig::BuiltIn(ManagedBuiltinTool::WebSearch)]);

SessionEvent

एकसमान क्रम संख्याओं वाली प्रदाता-निरपेक्ष इवेंट स्ट्रीम:

  • agent.message — सहायक टेक्स्ट सामग्री
  • agent.tool_use — अंतर्निहित टूल आह्वान
  • agent.custom_tool_use — क्लाइंट द्वारा निष्पादित कस्टम टूल (लूप रुकता है)
  • agent.mcp_tool_use — MCP टूल आह्वान
  • status.running — टर्न शुरू हुआ
  • status.idle — टर्न पूरा हुआ (stop_reason के साथ)
  • error — निष्पादन त्रुटि

UserEvent

क्लाइंट-से-एजेंट इवेंट:

  • user.message — एजेंट को सामग्री भेजें
  • user.interrupt — वर्तमान टर्न रोकें
  • user.tool_confirmation — टूल निष्पादन की अनुमति दें/अस्वीकार करें
  • user.custom_tool_result — कस्टम टूल परिणाम लौटाएँ
  • user.tool_result — अंतर्निहित टूल परिणाम (केवल स्वयं-होस्टेड)
  • user.define_outcome — सफलता मानदंड निर्धारित करें

ModelRef

सभी प्रदाताओं का समर्थन करने वाला प्रदाता-निरपेक्ष मॉडल संदर्भ:

// Shorthand (provider inferred from name)
ModelRef::Shorthand("gemini-3.7-flash".into())
ModelRef::Shorthand("gpt-5.6-terra".into())
ModelRef::Shorthand("claude-sonnet-5".into())

// Structured (explicit provider)
ModelRef::Structured {
    provider: Provider::OpenaiCompatible,
    model: ModelConfig::Compatible {
        model: "my-model".into(),
        base_url: "https://my-endpoint.com/v1".into(),
        api_key: "sk-...".into(),
    },
    speed: None,
}

मुख्य विशेषताएँ

टिकाऊ सत्र

प्रत्येक इवेंट को परमाणु रूप से चेकपॉइंट किया जाता है। प्रक्रिया क्रैश होने पर, resume() बिना किसी इवेंट हानि के अंतिम सुसंगत चेकपॉइंट से पुनर्स्थापित होता है:

// Before crash: events 0..5 committed
// After restart:
runtime.resume(&session).await?;
// Continues from seq=5, no gap, no duplicate

कस्टम टूल पार्किंग

जब एजेंट agent.custom_tool_use उत्सर्जित करता है, तो लूप तब तक रुकता है जब तक क्लाइंट परिणाम नहीं लौटाता या कॉन्फ़िगर किया गया टाइमआउट समाप्त नहीं हो जाता:

// Agent emits: agent.custom_tool_use { custom_tool_use_id: "ct_1", name: "deploy" }
// Client executes the tool, then:
runtime.send_event(&session, UserEvent::CustomToolResult {
    custom_tool_use_id: "ct_1".into(),
    content: vec![ContentBlock::Text { text: "Deployed successfully".into() }],
}).await?;

इवेंट रीप्ले

क्रम-आधारित रीप्ले के माध्यम से SSE Last-Event-ID पुनः कनेक्शन का समर्थन:

// Reconnect from seq 42 — replays events 43, 44, ... then live tail
let stream = runtime.stream_events(&session, Some(42)).await?;

प्रदाता समानता

समान ManagedAgentDef, Gemini, OpenAI, Anthropic, Ollama, और OpenAI-संगत प्रदाताओं में बाइट-समरूप इवेंट प्रकार अनुक्रम उत्पन्न करता है (फिक्सचर F-8)।

ScriptedLlm के साथ परीक्षण

ScriptedLlm एक नियतात्मक LLM डबल है, जो पूर्ण रनटाइम पाइपलाइन का परीक्षण करता है। केवल प्रदाता API कॉल को प्रतिस्थापित किया जाता है:

use adk_managed::testing::{ScriptedLlm, ScriptedTurn, ScriptedToolCall};
use serde_json::json;

let llm = ScriptedLlm::new("test", vec![
    ScriptedTurn {
        text: Some("I'll search for that.".into()),
        tool_calls: vec![ScriptedToolCall {
            name: "web_search".into(),
            input: json!({"query": "rust agents"}),
            id: Some("tc_1".into()),
        }],
    },
    ScriptedTurn {
        text: Some("Here are the results...".into()),
        tool_calls: vec![],
    },
]);

API संदर्भ

पूर्ण API दस्तावेज़ docs.rs पर उपलब्ध है:

स्मोक टेस्ट उदाहरण

प्लेटफ़ॉर्म टीमों के लिए एक स्वतंत्र उदाहरण क्रेट प्रदान किया गया है:

cargo run --manifest-path examples/managed_runtime_hello/Cargo.toml

यह ScriptedLlm के साथ फ़िक्स्चर F-1 को आरंभ से अंत तक चलाता है (किसी API कुंजी की आवश्यकता नहीं है)।

प्रबंधित स्थिति की स्थायित्व

प्रबंधित सत्र स्थिति — इवेंट लॉग, अनुक्रम स्थिति, पार्क किए गए टूल कॉल और जीवनचक्र स्थिति — एक ManagedStateStore में रहती है। स्टोर अपनी गारंटी स्वयं बताता है:

स्थायित्वअर्थ
ProcessLocalप्रक्रिया के चलते रहने तक पुनः चलाना और कार्य जारी रखना संभव है। क्रैश होने पर स्थिति खो जाती है, और कोई अन्य प्रक्रिया सत्र को फिर से जारी नहीं रख सकती।
CrashDurableलेखन की पुष्टि होने से पहले स्थिति को बैकिंग स्टोर में लिख दिया जाता है, इसलिए कोई अन्य प्रक्रिया सत्र का पुनर्निर्माण कर सकती है।

केवल InMemoryManagedStateStore ही उपलब्ध कराया जाता है, और यह ProcessLocal है। गारंटी की जाँच करें, checkpointing की मौजूदगी से उसका अनुमान न लगाएँ:

use adk_managed::{Durability, InMemoryManagedStateStore, ManagedStateStore};

let store = InMemoryManagedStateStore::new();
assert_eq!(store.durability(), Durability::ProcessLocal);
assert!(!store.durability().survives_process_loss());

Checkpointing बनाम flushing

CheckpointManager::checkpoint किसी event और नई run state को एक साथ रिकॉर्ड करता है, इसलिए replay में उनमें से केवल एक कभी दिखाई नहीं देता। यह manager के अपने fields में किया गया लेखन है। flush snapshot को configured store में लिखता है, और restore उससे manager का पुनर्निर्माण करता है:

use adk_managed::{CheckpointManager, InMemoryManagedStateStore, ManagedStateStore};
use std::sync::Arc;

# async fn example() -> Result<(), adk_managed::types::RuntimeError> {
let store: Arc<dyn ManagedStateStore> = Arc::new(InMemoryManagedStateStore::new());
let manager = CheckpointManager::new("session-1".to_string()).with_store(Arc::clone(&store));
manager.flush().await?;

let restored = CheckpointManager::restore("session-1".to_string(), store).await?;
assert_eq!(restored.session_id(), "session-1");
# Ok(())
# }

स्थिति रिपोर्टिंग

ManagedAgentRuntime::status उसी handle को पढ़ता है जिस पर session loop लिखता है, इसलिए केवल control-plane transitions ही नहीं, बल्कि सामान्य transitions भी दिखाई देते हैं:

संक्रमणकारण
QueuedRunningएक टर्न शुरू होता है
RunningIdleटर्न पूरा होता है और उपयोग दर्ज किया जाता है
कोई भी → Pausedpause
कोई भी → Archivedarchive या delete_session

नोट: पहले यह एक साझा हैंडल था, status पूरे सत्र के जीवनकाल के लिए Queued रिपोर्ट करता था, जिसमें उसके टर्न निष्पादित होने के दौरान का समय भी शामिल था। कंट्रोल-प्लेन संक्रमण (रोकना, फिर से शुरू करना, संग्रहित करना) दिखाई देते थे क्योंकि वे सीधे हैंडल में लिखे जाते थे।

विलोपन का अर्थ

delete_session दोनों प्लेन हटाता है:

  1. सत्र को टर्मिनल स्थिति में सेट करता है और उसके लूप को रद्द करता है।
  2. रनटाइम हैंडल हटाता है।
  3. इंजेक्ट किए गए SessionService के माध्यम से स्थायी रूप से संग्रहीत वार्तालाप को उसी पहचान start_session के अंतर्गत हटाता है, जिससे उसे बनाया गया था।

यदि चरण 3 विफल होता है, तो delete_session उस ऐप, उपयोगकर्ता और सत्र का नाम बताते हुए एक त्रुटि लौटाता है जिनके पास अभी भी डेटा मौजूद है — उस समय तक हैंडल पहले ही हट चुका होता है, इसलिए कॉलर को यह बताना आवश्यक है कि मैन्युअल सफ़ाई में क्या करना है, न कि उसे सफलता मान लेने देना।

runtime.delete_session(&session).await?;
// The handle is gone and the conversation is no longer in the session backend.

महत्वपूर्ण: विलोपन उसके स्वामी के अंतर्गत सत्र के वार्तालाप को हटा देता है। सत्र स्वामित्व देखें।

सत्र स्वामित्व

start_session के लिए एक ManagedOwner आवश्यक है। सत्र उसी पहचान के अंतर्गत स्थायी रूप से संग्रहीत किया जाता है, और सत्र लूप द्वारा की जाने वाली प्रत्येक Runner कॉल इसका उपयोग करती है:

use adk_managed::{ManagedAgentRuntime, ManagedOwner};

# async fn start(runtime: &dyn ManagedAgentRuntime, agent: &adk_managed::AgentHandle)
# -> Result<(), adk_managed::RuntimeError> {
let owner = ManagedOwner::new("support-console", "user-42")?;
let session = runtime.start_session(agent, &owner, None).await?;
# Ok(())
# }

महत्वपूर्ण: checkpoint को "परमाणु रूप से स्थायी रूप से संग्रहीत करना" के रूप में दस्तावेज़ित किया गया था, इस गारंटी के साथ कि "किसी भी क्रैश के बाद पुनःचलाने पर एक सुसंगत दृश्य दिखाई देगा", और लोडिंग को "रीस्टार्ट के बाद सत्र को पुनर्निर्मित करने के लिए आवश्यक सब कुछ" लौटाने वाला बताया गया था। इनमें से कोई भी बात सही नहीं थी: दोनों ही किसी स्थायी स्टोर के विरुद्ध लेन-देन के बिना इन-मेमोरी फ़ील्ड पर काम करते थे। दिए गए स्टोर के साथ, नए प्रोसेस में restore को कुछ भी नहीं मिलता।

दोनों घटक आवश्यक हैं और रिक्त नहीं होने चाहिए। अलग-अलग स्वामियों से संबंधित सत्रों को अलग-अलग संबोधित किया जाता है, इसलिए लुकअप और विलोपन एक ही स्वामी के दायरे में होते हैं और किसी अन्य के डेटा तक नहीं पहुँच सकते।

नोट: प्रत्येक प्रबंधित सत्र को पहले managed / managed_user कॉन्स्टेंट के अंतर्गत स्थायी रूप से संग्रहीत किया जाता था, इसलिए उन सभी ने एक ही तार्किक नेमस्पेस साझा किया: किसी भी चीज़ का दायरा कॉलर तक सीमित नहीं किया जा सकता था और किसी सत्र को किसी एक कॉलर से संबद्ध नहीं किया जा सकता था।

पर्यावरण कॉन्फ़िगरेशन

EnvironmentConfig में env_vars और working_dir शामिल हैं। यह रनटाइम ऐसे कॉन्फ़िगरेशन को अस्वीकार करता है जो इनमें से किसी एक का अनुरोध करता है:

invalid request: EnvironmentConfig cannot be honoured by this runtime: sessions run
in-process, so per-session environment variables and working directories would have to mutate
process-global state shared with other sessions. Pass `None`, or configure a sandboxed runtime.

सत्र प्रक्रिया के भीतर चलते हैं, इसलिए प्रति-सत्र पर्यावरण वेरिएबल या कार्यशील डायरेक्टरी लागू करने से हर दूसरे सत्र के साथ साझा स्थिति बदल जाएगी। अस्वीकार करना ही ईमानदार परिणाम है; एक सैंडबॉक्सयुक्त निष्पादन सीमा ही इस अनुरोध को संतुष्ट करने योग्य बनाएगी।

नोट: आर्ग्युमेंट का नाम पहले _env था और उसे छोड़ दिया जाता था, इसलिए पर्यावरण कॉन्फ़िगरेशन देने वाले कॉलर को ऐसा सत्र मिलता था जो उसे चुपचाप अनदेखा कर देता था।