CodeActAgent (CodeAct)
CodeActAgent LlmAgent का एक समकक्ष है जो एक समय में एक tool call emit करने के बजाय कोड लिखकर और चलाकर कार्य करता है। हर turn में model एक single script बनाता है; tools callable functions के रूप में उपलब्ध कराए जाते हैं जिन्हें script compose कर सकती है; और script अपना result एक tagged value लौटाकर संप्रेषित करती है।
यह CodeAct pattern है: call tool A → observe → call tool B करने के बजाय, model एक script में b(a(x)) लिखता है, इसलिए multi-step work एक ही turn में हो जाता है। यह adk-agent पर codeact feature द्वारा enabled है।
कब उपयोग करें
- ऐसे tasks जो प्रति turn कई tools को chain या combine करते हैं (data wrangling, batch operations, glue logic).
- Code generation के लिए post-trained models.
- ऐसे workflows जहाँ action substrate के रूप में एक वास्तविक interpreter (जैसे Python) उपलब्ध हो।
Native tool-calling के लिए LlmAgent को प्राथमिकता दें। Sandboxed file/shell coding harness के लिए Coding Agent देखें।
Loop कैसे काम करता है
हर turn:
- Model एक fenced code block (एक script) emit करता है।
- Script एक [
CodeRuntime] पर चलती है; tool calls host तक surface होते हैं, जो tool execute करता है और result के साथ script को resume करता है। - Script एक tagged
ScriptOutputलौटाती है:observation— model को वापस fed किया जाता है; loop जारी रहता है।error— message के रूप में वापस fed किया जाता है; loop जारी रहता है।final_result— caller को लौटाया जाता है; loop समाप्त हो जाता है।transfer_to_agent— control दूसरे agent को सौंपता है; loop समाप्त हो जाता है।
Framework language-agnostic है: CodeRuntime trait step-wise interpreter seam है, और यह freeform prompt के माध्यम से model को अपनी language/environment स्वयं रिपोर्ट करता है। Intended production adapter Monty को wrap करता है, जो Rust-native Python interpreter है।
Durability: suspend और resume
CodeActAgent invocations के बीच stateless है — durable state session में रहती है, बिल्कुल LlmAgent की तरह। दो स्थितियाँ run को suspend करती हैं:
- एक confirmation-gated tool जिसके लिए अभी decision नहीं आया है (HITL), और
- एक long-running tool जिसका result out-of-band आता है।
Suspend होने पर, live interpreter continuation को CodeActCheckpoint में serialize किया जाता है और session state में लिखा जाता है; अगला run() इसे वापस पढ़ता है और resume करता है — confirmation decision RunConfig::tool_confirmation_decisions के माध्यम से आता है, और long-running result अगले message में एक FunctionResponse के रूप में आता है। Inline tool calls को write-ahead (SAVE-BEFORE) और SAVE-AFTER checkpoints से bracket किया जाता है: एक बार SAVE-AFTER checkpoint persist हो जाने पर, recovery stored result के साथ resume होती है और tool को कभी दोबारा नहीं चलाती। Tool के side effect के बाद लेकिन उसका SAVE-AFTER checkpoint landing से पहले आने वाला crash recovery पर tool को फिर से चलाएगा, इसलिए जो tools idempotent नहीं हैं उन्हें उससे बचाव करना चाहिए (LlmAgent जैसी same at-least-once boundary)।
इसके लिए ऐसा runtime चाहिए जो paused call का snapshot ले सके। जो runtime ऐसा नहीं कर सकता, वह long-running tools को inline चलाता है और confirmation pauses को अस्वीकार करता है।
एक CodeActAgent बनाना
use adk_agent::codeact::CodeActAgent;
use std::sync::Arc;
// `model` implements `adk_core::Llm`; `runtime` implements `CodeRuntime`.
let agent = CodeActAgent::builder()
.name("analyst")
.model(model)
.runtime(runtime)
.instruction("Prefer concise, composable steps.")
.tool(Arc::new(load_csv_tool))
.output_key("report")
.build()?;
model और runtime आवश्यक हैं; बाकी सबका default है।
LlmAgent के साथ समानता
Builder LlmAgentBuilder की mirror करता है:
- Model:
generate_content_configऔरtemperature/top_p/top_k/max_output_tokensshorthand। - Instructions:
instruction/instruction_provider,global_instruction/global_instruction_provider, साथ में{state.key}template injection; और skills (skillsfeature)। - History:
include_contents। - Tools: static
tools और per-invocationtoolsets;tool_timeout,default_retry_budget/tool_retry_budget,circuit_breaker_threshold, औरon_tool_errorfallbacks। - Authorization:
ToolConfirmationPolicy(require_tool_confirmation/require_tool_confirmation_for_all)। - Transfer:
sub_agents औरdisallow_transfer_to_parent/disallow_transfer_to_peers। - Output:
output_key,output_schema/output_typeके साथ एक correction-retry loop (output_max_retries)। - Callbacks:
before_callback/after_callback,before_model_callback/after_model_callback, औरbefore_tool_callback/after_tool_callback/after_tool_callback_full। After-tool callbacks structured execution metadata कोCallbackContext::tool_outcome()के माध्यम से inspect कर सकते हैं। - Feature-gated: input/output guardrails (
guardrails) औरEnhancedPluginpipeline (enhanced-plugins)।
हर tool call को एक नया ToolContext मिलता है जो interpreter call id carry करता है और artifacts, memory, shared state, user scopes, तथा secrets को live invocation तक delegate करता है — इसलिए tool CodeActAgent या LlmAgent के अंतर्गत identically व्यवहार करता है।
जानबूझकर अंतर
- Code-execution sandboxing
CodeRuntimeकी responsibility है, कोई bolt-on नहीं। - Tool dispatch जानबूझकर sequential है (एक single continuation को एक call boundary पर snapshot किया जाता है), इसलिए कोई parallel
tool_execution_strategyनहीं है। skip_summarizationbuilder option नहीं है — model loop को स्वयंfinal_resultके माध्यम से समाप्त करता है — हालांकि जो tool अपनी actions परskip_summarizationset करता है, वह फिर भी run समाप्त कर देता है।
उदाहरण
एक runnable, dependency-free end-to-end demo — एक self-contained CodeRuntime और एक deterministic model — यहाँ मौजूद है:
examples/codeact_agent:
cargo run --manifest-path examples/codeact_agent/Cargo.toml
एक CodeRuntime लागू करना
एक CodeRuntime एक script को parse और step करता है, और एक समय में एक external call surface करता है:
pub trait CodeRuntime: Send + Sync {
fn start(&self, script: &str, script_name: &str) -> Result<RunStep, RuntimeError>;
fn resume(&self, snapshot: &[u8], with: ResumeWith) -> Result<RunStep, RuntimeError>;
fn capabilities(&self) -> RuntimeCapabilities { /* default */ }
fn render_tools(&self, tools: &[Arc<dyn Tool>]) -> String { /* default */ }
}
RunStepstruct variants का एक सेट है —Call { call, stdout },Complete { value, stdout }, औरRaised { message, stdout }। इन्हेंRunStep::call/RunStep::complete/RunStep::raisedhelpers के साथ बनाएं और captured output को.with_stdout(..)के साथ जोड़ें।RunStep::Callबिल्कुल एक pending call surface करता है; उसे एक value या error के साथ resume करें, या उसकी continuation को suspend करने के लिएdump()करें।stdoutजिसे एक runtime attach करता है, model को वापस surface किया जाता है और checkpoints में persisted रहता है, इसलिए यह suspend/resume के बाद भी बना रहता है।- एक
PendingCallअपने arguments उसी तरह report करता है जैसे interpreter ने उन्हें produce किया —positional_args()औरkeyword_args()अलग-अलग। positional arguments को names में स्वयं map न करें: driver उन्हें centrally tool के parameters पर viaadk_agent::codeact::bind_call_argsbind करता है, इसलिए एक runtime को call boundary पर कोई tool schema की आवश्यकता नहीं होती औरrender_toolstool slice का एक pure function हो सकता है। - Script vs. host errors. जो भी model अलग code लिखकर ठीक कर सकता है — एक syntax/parse error, an uncaught exception, a resource-limit
cancellation — वह एक
RunStep::Raisedहै (एक opaque string जो model को verbatim वापस feed किया जाता है)।RuntimeErrorवास्तविक host failures के लिए reserved है (snapshot (de)serialization, internal interpreter errors) और run को abort करता है। - HITL और
long-running deferral को enable करने के लिए
RuntimeCapabilities::supports_suspensionकाtrueहोना आवश्यक है;promptmodel को language/environment का वर्णन करता है।
पूर्ण, minimal
implementation के लिए examples/codeact_agent/src/runtime.rs देखें जो suspend/resume का support करता है।
Monty के माध्यम से Python
इच्छित production adapter है
adk-codeact-monty,
जो Pydantic Monty द्वारा backed एक CodeRuntime है। यह
model को Python लिखकर act करने देता है, बिना container या
subprocess के in-process चलता है, और एक paused run को bytes में snapshot करता है — बिल्कुल वही जो suspend/resume
को चाहिए। यह एक regular workspace member है — Monty crates.io पर 0.0.19 से मौजूद है
(crates monty, monty-types, और monty-fs) और rustc 1.95+ की आवश्यकता होती है।
use adk_codeact_monty::MontyRuntime;
// Conservative default resource limits (per-advance time + memory caps) make
// `new()` safe for untrusted, LLM-generated code.
let runtime = Arc::new(MontyRuntime::new());
// Tighten or relax with the builder; `unlimited()` removes the caps for
// trusted scripts only.
let runtime = Arc::new(
MontyRuntime::builder()
.max_duration(std::time::Duration::from_secs(2))
.max_memory(64 * 1024 * 1024)
.build(),
);
OS access
एक script द्वारा attempted operating-system effects — filesystem reads/writes,
os.getenv/os.environ, और date.today()/datetime.now() — host-controlled policy के विरुद्ध
in place serviced होते हैं। ये tools नहीं हैं और agent loop को कभी
pause नहीं करते। डिफ़ॉल्ट रूप से एक runtime पूरी तरह sandboxed होता है (कोई filesystem
access नहीं, खाली environment, host clock enabled)। builder के साथ specific access दें:
use adk_codeact_monty::{MontyRuntime, PathAccess};
let runtime = Arc::new(
MontyRuntime::builder()
// Mount host directories at virtual paths; Monty enforces the boundary
// (canonicalization + symlink-escape detection) so a script can never
// escape a mount. Reads/writes outside every mount raise PermissionError.
.allow_path("/data", "/srv/agent/data", PathAccess::ReadOnly)
.allow_path("/out", "/srv/agent/out", PathAccess::ReadWrite)
// Expose an explicit environment map to os.getenv / os.environ. Empty by
// default — the host process environment is never exposed implicitly.
.environ_var("PROJECT", "acme")
// date.today() / datetime.now() read the host clock (enabled by default).
.system_clock(true)
.build(),
);
Network और subprocess access का कोई Monty OS-call surface नहीं है और policy के बावजूद उपलब्ध नहीं रहते। दी गई access model को system prompt में described की जाती है, ताकि उसे पता रहे कि वह किन paths को read या write कर सकता है और कौन-से environment variables मौजूद हैं।
Monty केवल pathlib.Path का एक subset implement करता है, इसलिए जब paths mounted हों तो
prompt exact supported methods list करता है (कोई भी अन्य AttributeError raise करता है):
- Read/query (any mount):
exists(),is_file(),is_dir(),is_symlink(),read_text(),read_bytes(),stat(),iterdir(),resolve(),absolute(),open("r")। - Write (सिर्फ read-write mounts):
write_text(),write_bytes(),append_text(),append_bytes(),mkdir(),unlink(),rmdir(),rename(),open("w")/open("a")। - Pure path ops (कोई I/O नहीं): the
/operator औरjoinpath(),is_absolute(),with_name(),with_stem(),with_suffix(),as_posix(), और.name,.parent,.stem,.suffix,.suffixes,.partsproperties।
Tools को एक single built-in function के माध्यम से invoke किया जाता है,
call_tool("name", {"arg": value, ...}) — tool call करने का यही एकमात्र तरीका है; वे
कभी भी bare callables के रूप में scope में नहीं होते। Tool का नाम एक string literal है और हर
argument एक dict में string-keyed entry होता है, इसलिए वास्तविक name serialized continuation के अंदर travel करता है (host-side name table के बिना suspend/resume में survive करते हुए),
एक tool और हर argument कोई भी name रख सकता है ("fetch-cart" जैसा valid Python identifier नहीं,
एक Python keyword, या यहाँ तक कि "call_tool" भी नहीं), और driver dict की
entries को नाम से बिल्कुल bind करता है — कोई positional inference नहीं। हर tool prompt में एक call_tool("name", {...}) usage line के रूप में
अपने parameters और description के साथ दिखाई देता है। इस एक form के अलावा कुछ भी — एक bare fetch_cart(...), keyword
arguments, non-dict argument, या non-string key — corrective error के साथ refused किया जाता है
ना कि silently dispatched, इसलिए model को सीखने के लिए exactly एक calling form मिलता है।
चलाने योग्य
examples/codeact_monty_agent
पूरी तरह offline real Python के विरुद्ध एक CodeActAgent चलाता है।