Example: Code agent
An agent whose model emits executable shell snippets that a LocalCodeExecutor runs in a subprocess, using a scripted MockModel so no API key is needed.
code_agent wires a CodeExecutor into an LlmAgent: the model emits an ExecutableCode part, the framework runs it in a local subprocess, and the execution result is fed back to the model for a summary. The model is a pre-scripted MockModel, so the demo runs offline with no API key.
What it demonstrates
- Attaching a code executor with
LlmAgent::builder(...).code_executor(...). - Configuring
LocalCodeExecutoraway from itspython3default to/bin/sh -sviawith_interpreter/with_args. - Scripting deterministic model turns with
adk_rs::core::testing::MockModel(push_response,push_text) — see Testing. - The code-execution event shapes:
Part::ExecutableCodein,Part::CodeExecutionResultout. - Pattern-matching on individual
Partvariants instead of usingtext_concat.
Run it
cargo run --example code_agent --features "code-exec,testing"No environment variables are required: the testing feature exposes MockModel outside the crate’s own tests, and code-exec provides LocalCodeExecutor. The executor here spawns /bin/sh, so the demo assumes a Unix-like host.
Full source
use std::sync::Arc;
use adk_rs::agents::LlmAgent;
use adk_rs::code_exec::local::LocalCodeExecutor;
use adk_rs::core::testing::MockModel;
use adk_rs::core::{LlmResponse, Model, SessionService};
use adk_rs::genai_types::part::ExecutableCode;
use adk_rs::genai_types::{Content, Part, Role};
use adk_rs::runner::Runner;
use adk_rs::services::mem::InMemorySessionService;
use futures::StreamExt;
#[tokio::main]
async fn main() -> adk_rs::Result<()> {
// Pre-script a model that emits a single ExecutableCode part.
let model = Arc::new(MockModel::new("mock-code"));
model.push_response(LlmResponse {
content: Some(Content {
role: Role::Model,
parts: vec![Part::ExecutableCode(ExecutableCode {
language: "shell".into(),
code: "echo hello && echo 'computed in container'".into(),
})],
}),
..LlmResponse::default()
});
// Second turn: model summarises the result.
model.push_text("I ran the script and got 'hello' plus a summary line.");
let executor = Arc::new(
LocalCodeExecutor::new()
.with_interpreter("/bin/sh")
.with_args(vec!["-s".into()]),
);
let agent = Arc::new(
LlmAgent::builder("coder")
.model(model as Arc<dyn Model>)
.instruction("Solve problems by emitting shell snippets and explaining the output.")
.code_executor(executor)
.build()?,
);
let svc: Arc<dyn SessionService> = Arc::new(InMemorySessionService::new());
let runner = Runner::builder()
.app_name("code-demo")
.agent(agent)
.session_service(svc)
.build()?;
let mut stream = runner.run("user", None, "Print a greeting.").await?;
while let Some(ev) = stream.next().await {
let ev = ev?;
if let Some(c) = &ev.response.content {
for p in &c.parts {
match p {
Part::Text(t) => println!("[{}] {t}", ev.author),
Part::ExecutableCode(ec) => {
println!("[{}] code ({})\n{}", ev.author, ec.language, ec.code);
}
Part::CodeExecutionResult(r) => {
println!(
"[{}] result outcome={:?} output={:?}",
ev.author, r.outcome, r.output
);
}
_ => {}
}
}
}
}
Ok(())
}Setup: a scripted model
MockModel returns pre-queued LlmResponses in order, which makes the demo deterministic and key-free. The first queued response contains a single Part::ExecutableCode with language: "shell"; the second, queued via the push_text shorthand, is the plain-text summary the "model" gives after seeing the execution result. A real agent would use a live provider here — the rest of the program is unchanged.
The executor
LocalCodeExecutor::new() defaults to python3 - with a 30-second timeout and 2 retries; the example overrides it to /bin/sh -s, so the shell reads the snippet from stdin. This executor is subprocess isolation only — not a security boundary. For untrusted code, use ContainerCodeExecutor behind the code-exec-docker feature, which is locked down by default (no network, read-only rootfs, memory/CPU/pids caps, non-root). See Code execution.
The execution roundtrip
With .code_executor(...) set, the agent watches model output for ExecutableCode parts. When one appears, the executor runs it and the framework appends a CodeExecutionResult part (with an Outcome such as OutcomeOk or OutcomeFailed, plus captured output) before invoking the model again — mirroring the function-tool roundtrip in weather_agent, but for code instead of declared functions.
Expected output
[coder] code (shell)
echo hello && echo 'computed in container'
[coder] result outcome=OutcomeOk output="hello\ncomputed in container\n"
[coder] I ran the script and got 'hello' plus a summary line.Three phases, three event groups: the emitted code, the execution result with outcome=OutcomeOk and the captured stdout, and the scripted summary text.
Related pages
- Code execution —
CodeExecutor,LocalCodeExecutor, and the Docker executor’s hardening flags. - Testing —
MockModeland thetestingfeature. - Events —
Partvariants and how they appear on the stream. - Security — why local execution is not a sandbox.