Agent Development Kit (RUST)
An open-source, code-first Rust framework for building, evaluating, and deploying sophisticated AI agents with flexibility and control.
Agent Development Kit (ADK) is a flexible, modular framework that applies software-engineering discipline to AI-agent construction. adk-rs is a Rust port of the Google's ADK Python implementation, aimed at teams that want low overhead, predictable latency, and the safety guarantees of the Rust toolchain. Like its Python counterpart, ADK is model-agnostic, deployment-agnostic, and integrates cleanly alongside other frameworks.
✨ Key Features
- First-class providers — Gemini (REST + SSE) with server-side built-ins (
google_search,url_context,built_in_code_execution), Anthropic Claude (Messages API + SSE), and an OpenAI-compatible client that also serves Azure OpenAI, Ollama, and Groq via base-URL override. - Composable agent primitives —
LlmAgent,SequentialAgent,ParallelAgent, andLoopAgent, all driven by a unified event stream overtokioand a cooperativeCancellationToken. - Ergonomic tools — annotate any async function with
#[adk_rs::tool]; the macro derives the JSON schema, theFunctionDeclaration, and aToolimpl. Manual implementations remain available as an escape hatch. - Pluggable services — session, memory, artifact, and credential traits with in-memory, filesystem, SQLite, and PostgreSQL backends out of the box.
- MCP toolset — connect to any Model Context Protocol server over stdio or streamable HTTP (with
Mcp-Session-Idecho and SSE-response support). - A2A protocol — wire-compatible Google Agent-to-Agent JSON-RPC client + server bridge:
message/send,message/stream,tasks/get/cancel/resubscribe,tasks/pushNotificationConfig/*webhook delivery, and/.well-known/agent.jsondiscovery. Talk to Pythongoogle-adkagents and vice versa. - Authenticated tools — full OAuth 2.0 (auth-code + PKCE, client-credentials, refresh), Service Account JWTs, API keys, and HTTP basic/bearer with interactive-consent suspend/resume.
- OpenAPI generator — point at a 3.x spec; get one tool per operation with security schemes mapped to
AuthConfig. - Sandboxed code execution — local subprocess or locked-down Docker container (cap-drop, no-new-privileges, memory / CPU / pids caps, non-root user, no network, read-only rootfs).
- Production telemetry —
tracingintegration with optional OpenTelemetry OTLP export. - Evaluation framework — replay JSON eval sets (compatible with the Python ADK format) and score with trajectory and response-match metrics.
- Dev server + CLI scaffolding — an
axum-based HTTP/SSE server (with bearer-token auth + loopback-default) and a library-style CLI that you embed in your own binary. - Secure by default — refuses to send API keys / OAuth tokens over plaintext HTTP, refuses non-loopback binds without auth, and sanitises filesystem artifact paths against
..traversal.
🚀 Installation
adk-rs ships as a single crate with cargo features. Opt in to the providers, storage backends, and subsystems you need:
[]
= { = "0.1", = ["gemini", "macros"] }
= { = "1", = ["macros", "rt-multi-thread"] }
= "0.3"
Available features
| Feature | Pulls in |
|---|---|
gemini / anthropic / openai |
the matching LLM provider |
fs |
filesystem artifact service |
sqlite / postgres |
SQL session backend |
mcp |
Model Context Protocol — stdio + streamable HTTP transports |
telemetry |
tracing-subscriber setup (add otel for OpenTelemetry OTLP export) |
eval |
evaluation framework |
server |
axum dev server with SSE + bearer auth + loopback guard |
cli |
embeddable clap-based CLI scaffolding |
macros |
the #[tool] proc-macro |
auth |
OAuth2 / ServiceAccount / API-key / HTTP credential flow |
openapi |
generate tools from an OpenAPI 3.x spec |
code-exec |
local-subprocess code executor |
code-exec-docker |
extra: ephemeral Docker container per call (docker on $PATH) |
a2a |
Agent-to-Agent JSON-RPC client + server bridge (spec-compliant) |
full |
enables all of the above |
Requires Rust 1.85+ (edition 2024).
Provider credentials
Each provider reads its API key from the environment:
| Provider | Variable |
|---|---|
| Gemini | GOOGLE_API_KEY |
| Anthropic | ANTHROPIC_API_KEY |
| OpenAI-compatible | OPENAI_API_KEY (plus optional OPENAI_BASE_URL) |
🏁 Quick start
Define a single agent and stream its events to stdout:
use LlmAgent;
use Gemini;
use Runner;
use InMemorySessionService;
use StreamExt;
use Arc;
async
🤝 Multi-agent composition
Agents nest via the same BaseAgent trait. A coordinator can delegate to specialised children, with the runner choosing between them based on each agent's description:
use LlmAgent;
use Arc;
let greeter = new;
let task_executor = new;
let coordinator = builder
.model
.description
.sub_agent
.sub_agent
.build?;
SequentialAgent, ParallelAgent, and LoopAgent provide explicit orchestration when LLM-driven delegation is not appropriate.
🔐 Authenticated tools (feature = "auth")
adk-rs ships full Python ADK parity for the credential lifecycle: OAuth 2.0 (authorization-code + PKCE, client-credentials, refresh-token), Service Account JWTs (Google-style RS256), API keys, and HTTP basic/bearer. When a tool declares auth_config(), the runner resolves the credential via CredentialManager before dispatch and injects it into ToolContext::auth_credential. If the underlying flow requires interactive consent (authorization-code), the agent emits a synthetic adk_request_credential function-call response and pauses; the caller resubmits the exchanged credential on the next turn.
use ;
let cfg = new.with_raw;
🌐 OpenAPI tool generator (feature = "openapi")
Point OpenAPIToolset at an OpenAPI 3.x spec and get one tool per operation. Security schemes from the spec map to AuthConfig automatically:
use AuthCredential;
use OpenAPIToolset;
let tools = from_path?
.with_credential
.into_tools;
let agent = builder
.model
.tools
.build?;
🐍 Code execution (feature = "code-exec")
Attach a CodeExecutor to an LlmAgent and the agent will run any ExecutableCode parts the model emits, feeding CodeExecutionResult back on the next turn.
use LocalCodeExecutor;
use Arc;
let agent = builder
.model
.code_executor // python3 on $PATH
.build?;
Two executors ship in the box:
LocalCodeExecutor— spawns a child interpreter viatokio::processwith a configurable timeout. Subprocess isolation only; not a security boundary.ContainerCodeExecutor(feature = "code-exec-docker") — fresh ephemeral container per call, locked down by default:--network=none,--read-onlyrootfs,--memory=256m,--cpus=1.0,--pids-limit=128,--cap-drop=ALL,--security-opt=no-new-privileges, and--user=65534:65534(non-root). Every cap is exposed as a typedwith_*builder method; relaxing the defaults is a deliberate act. Requires thedockerCLI.
🔗 MCP — stdio and streamable HTTP (feature = "mcp")
Connect to a local MCP server over stdio:
use ;
let tools = stdio
.await?;
Or to a remote MCP server over the streamable-HTTP transport (single POST endpoint, response is application/json or SSE; Mcp-Session-Id is echoed automatically):
use ;
use HashMap;
let mut headers = new;
headers.insert;
let tools = http
.await?;
If you pass a credential-bearing header against a non-loopback http:// URL, construction refuses — see Secure by default below.
🤖 A2A — Agent-to-Agent JSON-RPC (feature = "a2a")
adk-rs ships a spec-compliant A2A surface so Rust agents can talk to other ADK agents (Python or Rust) over JSON-RPC. Both halves work:
Expose a local Runner as an A2A endpoint:
use ;
use Arc;
let card = AgentCard ;
let state = new;
serve.await?;
The router mounts:
GET /.well-known/agent.json— discovery (theAgentCard).POST /— JSON-RPC:message/send,message/stream(SSE),tasks/get,tasks/cancel,tasks/resubscribe,tasks/pushNotificationConfig/{set,get,list,delete}.
Push-notification webhooks fan out via PushNotifier — bodies match the message/stream envelope so the same receiver can consume both. Webhook URLs must be HTTPS or loopback.
Call a remote A2A agent as a local BaseAgent:
use ;
use Duration;
let remote = connect
.await?;
Plug remote into any agent tree exactly like a local LlmAgent — sub_agent(remote), AgentTool::new(remote), etc.
⏹ Cancel and resume
Every invocation carries a cooperative CancellationToken. Agents check it between LLM calls and between sub-agents; when set, the stream ends with a CANCELLED event.
let handle = runner
.start
.await?;
let inv_id = handle.invocation_id.clone;
// From anywhere:
runner.cancel;
// Or via A2A: `tasks/cancel` on the corresponding task id routes the
// cancel back through to the underlying runner invocation.
Resuming an interrupted conversation needs no special API — sessions are append-only event logs, so calling runner.run(..., session_id, ...) against an existing session replays the conversation. Auth-pending tool calls are resumed via the adk_request_credential synthetic flow.
🔒 Secure by default
A handful of guards trip when behaviour would be unsafe:
- HTTPS-only credentials. Provider clients (
Gemini,Anthropic,OpenAi),RestApiTool, MCP HTTP, and the A2A client all refuse to send API keys / bearer tokens / cookies over plaintext HTTP. Loopback hosts are allowed for local mocks. - Loopback-only dev servers. Both the
serverand A2Aserverefuse non-loopback binds unless an auth token is configured (AppState::with_bearer_token(...)) orServeOptions::dangerously_allow_unauthenticated_remoteis opted in. CLI:--auth-token/--dangerously-allow-unauthenticated-remote. - Filesystem artifact paths.
FileArtifactService::sanitizecollapses dot-only components so an attacker-controlledapp_name/user_id/session_id/filenamecan't escape the artifact root via..segments. - Container code execution. See the
ContainerCodeExecutordefaults above — locked-down memory / CPU / pids caps, capability drops, non-root user.
🛠 Defining a tool
Add #[tool] to any async function. The macro derives a JSON schema from the arguments struct and returns a constructor for an Arc<dyn Tool> that can be handed to LlmAgent::builder().tool(...).
use tool;
use JsonSchema;
use ;
/// Look up the current weather in `args.city`.
async
Attach it with .tool(get_weather()) on the agent builder.
💻 Embedding the CLI
Unlike the Python CLI, Rust agents are statically linked. Build your own binary on top of adk_rs::cli::App:
use Arc;
This gives you four subcommands out of the box:
🌐 Dev web server
adk-rs::server exposes the runner over HTTP and Server-Sent Events for local testing and integration with web frontends. The web subcommand above starts it.
- Binds to
127.0.0.1by default. Non-loopback addresses are refused unless you set a bearer token (--auth-token/AppState::with_bearer_token) or opt in via--dangerously-allow-unauthenticated-remote. - When a token is set, every request must carry
Authorization: Bearer <token>; comparisons are constant-time.
📊 Evaluating agents
The eval framework loads eval-set JSON files compatible with the Python ADK format, replays them through any BaseAgent, and scores with trajectory and response-match metrics. From the CLI:
Programmatically:
let bytes = read.await?;
let set: EvalSet = from_slice?;
let runner = new;
let report = runner.run_set.await?;
📦 Module layout
adk-rs is a single crate organised by responsibility. Heavy dependencies (sqlx, axum, reqwest, OpenTelemetry, etc.) sit behind cargo features.
| Module | Feature gate | Responsibility |
|---|---|---|
error |
always on | Error / Result and error codes. |
transport_security |
always on | require_secure_url — HTTPS-or-loopback guard shared by every credential-bearing client. |
genai_types |
always on | Wire-neutral data: Content, Part, Schema, FunctionCall, GenerateContentConfig, Tool (including Gemini server-side googleSearch / urlContext / codeExecution). |
core |
always on | Domain primitives: Event, Session, State, LlmRequest/Response, InvocationContext, CancellationToken, service traits. |
services::mem |
always on | In-memory session, memory, artifact, and credential services. |
services::fs |
fs |
Filesystem artifact service (path-traversal hardened). |
services::sql |
sqlite / postgres |
SQL SessionService over sqlx. |
providers::gemini |
gemini |
Gemini REST + SSE provider; HTTPS-only base URL. |
providers::anthropic |
anthropic |
Anthropic Messages API + SSE provider; HTTPS-only base URL. |
providers::openai |
openai |
OpenAI-compatible provider (Azure / Ollama / Groq via base-URL); HTTPS-only base URL. |
tools |
always on | Tool trait, FunctionTool, built-ins (transfer_to_agent, exit_loop, google_search, url_context, built_in_code_execution, load_artifacts, load_memory, get_user_choice, agent_tool, LongRunningFunctionTool). |
tools::openapi |
openapi |
OpenAPIToolset — generate RestApiTools from an OpenAPI 3.x spec. |
agents |
always on | BaseAgent, LlmAgent, SequentialAgent, ParallelAgent, LoopAgent. All observe InvocationContext::cancellation. |
auth |
types always on, flow gated on auth |
AuthCredential, AuthScheme, AuthConfig, CredentialService, CredentialManager, OAuth2 AuthHandler, AuthPreprocessor. |
code_exec |
code-exec (+ code-exec-docker) |
CodeExecutor trait; LocalCodeExecutor, locked-down ContainerCodeExecutor. |
runner |
always on | Orchestration: Runner::start returns a RunningInvocation handle; Runner::cancel(invocation_id) halts in-flight agents. |
mcp |
mcp |
MCP stdio + streamable HTTP transports, McpClient, McpToolset. |
a2a |
a2a |
Spec-compliant A2A JSON-RPC: types, TaskService + InMemoryTaskService, PushNotifier, RemoteA2aAgent client, axum server bridge, agent-card discovery. |
telemetry |
telemetry (+ otel) |
tracing-subscriber setup with optional OTLP export. |
eval |
eval |
Eval-set IO and metrics. |
server |
server |
axum dev server with SSE, bearer-token auth, and loopback-default bind guard. |
cli |
cli |
Embeddable CLI scaffolding. |
The #[tool] proc-macro lives in a sibling crate, adk-rs-macros, which is required by the Rust compiler to be its own crate. Enable it via the macros feature.
🧩 Examples
Runnable demos live under examples/:
gemini_chat— minimal single-agent loop.weather_agent—#[tool]-defined function tool.three_providers— the same prompt against Gemini, Claude, and OpenAI.code_agent— agent that emits shell snippets, runner executes them viaLocalCodeExecutor.
🤝 Contributing
Bug reports, feature requests, and pull requests are welcome. Before submitting:
Please open an issue before starting on substantial changes.
📄 License
Licensed under the Apache License, Version 2.0 — see LICENSE.