Composition: S, C, T, P, M, G, E, A
Namespace modules for composing agent configuration: State, Context, Tools, Prompt, Middleware, Guards, Evaluation, and Artifacts.
Two operators, one meaning each
Section titled “Two operators, one meaning each”a >> bmeans “then”. Order matters:bapplies aftera, to whataproduced. Used for state transforms, context rewrites, middleware layers and agent pipelines.a + bmeans “together”. Both apply, and neither sees the other’s result. Used for prompt sections, tools, guards (every guard must pass), evaluation criteria and artifact declarations.
Agents also take | (run in parallel), * (loop) and / (fallback).
| Namespace | Operator | Purpose | Consumed by | Key methods |
|---|---|---|---|---|
S:: |
>> |
State transforms | a pipeline step (agent >> S::pick(..) >> agent), loop and branch predicates |
pick, rename, merge, flatten, set, defaults, drop, map |
C:: |
>> |
Context rewrites, applied in order | AgentBuilder::context |
window, user_only, model_only, head, truncate, filter, from_state |
M:: |
>> |
Middleware layers, outermost first | .middleware(..) |
log, latency, timeout, retry, audit, circuit_breaker |
P:: |
+ |
Prompt sections | .instruction(..) |
role, task, constraint, format, example, persona, guidelines |
T:: |
+ |
Tools | .tools(..) |
simple, contextual, typed, google_search, code_execution, mcp |
G:: |
+ |
Output guards, all must pass | AgentBuilder::guard |
pii, length, json, regex, toxicity |
E:: |
+ |
Evaluation criteria | E::suite().criteria(..) |
response_match, contains_match, trajectory, safety |
A:: |
+ |
Artifact declarations | AgentBuilder::artifacts, check_contracts |
json_output, json_input, text_output, text_input |
Before 3.0, C chained with +, M and T with |, and G and E
with | (which read as “either” but meant “all”). See the
migration guide.
S – State Transforms
Section titled “S – State Transforms”State transforms mutate a serde_json::Value representing agent state. Chain them with >> for sequential application.
A transform chain is also a pipeline step: between two agents it reshapes the session state, and the pipeline’s text passes through unchanged.
let workflow = researcher >> (S::pick(&["findings", "input"]) >> S::rename(&[("findings", "notes")])) >> writer;Methods
Section titled “Methods”| Method | What it does |
|---|---|
S::pick(&["a", "b"]) |
Keep only the listed keys, drop everything else |
S::drop(&["x"]) |
Remove the listed keys |
S::rename(&[("old", "new")]) |
Rename keys according to mappings |
S::merge(&["x", "y"], "combined") |
Merge listed keys into a single nested object |
S::flatten("nested") |
Flatten a nested object into the top level |
S::set("key", json!(42)) |
Set a key to a fixed value |
S::defaults(json!({"k": "v"})) |
Set default values for missing keys |
S::map(fn) |
Apply a custom transformation function |
State Predicates
Section titled “State Predicates”S also provides predicates for phase transition guards and .when() modifiers:
| Predicate | What it checks |
|---|---|
S::is_true("key") |
Key holds a truthy boolean |
S::eq("key", "value") |
Key equals a specific string |
S::one_of("key", &["a", "b"]) |
Key matches any of the listed strings |
Example
Section titled “Example”use gemini_adk_fluent_rs::compose::S;
// Chain transforms: pick keys, then renamelet transform = S::pick(&["name", "age"]) >> S::rename(&[("name", "customer_name")]);
let mut state = json!({"name": "Alice", "age": 30, "internal_id": "x123"});transform.apply(&mut state);// Result: {"customer_name": "Alice", "age": 30}
// Predicates for phase transitionsLive::builder() .phase("verify") .instruction("Verify the customer's identity") .transition("main", S::is_true("verified")) .done() .phase("main") .instruction("Handle the request") .transition("billing", S::eq("issue_type", "billing")) .transition("tech", S::one_of("issue_type", &["technical", "setup"])) .done()C – Context Engineering
Section titled “C – Context Engineering”Context policies filter and transform conversation history. Chain them with >>: each rewrites the history the one before produced, so C::prepend(..) >> C::window(10) and C::window(10) >> C::prepend(..) differ.
Methods
Section titled “Methods”| Method | What it does |
|---|---|
C::window(n) |
Keep only the last n messages |
C::head(n) |
Keep only the first n messages |
C::user_only() |
Keep only user messages |
C::model_only() |
Keep only model messages |
C::text_only() |
Keep only messages containing text parts |
C::exclude_tools() |
Remove messages with function call/response parts |
C::sample(n) |
Keep every n-th message |
C::truncate(max_chars) |
Truncate to approximately max_chars total text (keeps most recent) |
C::prepend(content) |
Add a message at the start of context |
C::append(content) |
Add a message at the end of context |
C::from_state(&["key1", "key2"]) |
Inject state values as a context preamble |
C::dedup() |
Remove adjacent duplicate messages |
C::empty() |
Return empty context (for isolated agents) |
C::filter(fn) |
Filter messages by a custom predicate on Content |
C::map(fn) |
Transform each message with a custom function |
C::custom(fn) |
Full custom filter over the entire history |
Example
Section titled “Example”use gemini_adk_fluent_rs::compose::C;
// Keep recent context, no tool noise, inject statelet policy = C::window(20) >> C::exclude_tools() >> C::from_state(&["user:name", "app:balance"]);
// For isolated sub-agents that should not see conversation historylet isolated = C::empty();
// Character-budget context for cost controllet budget = C::truncate(4000) >> C::dedup();T – Tool Composition
Section titled “T – Tool Composition”Combine tools with +. Mix runtime function tools with built-in Gemini tools.
Methods
Section titled “Methods”| Method | What it does |
|---|---|
a #[tool] fn’s value |
A documented async fn; converts into a composite directly |
T::typed(name, desc, fn) |
A closure tool whose argument type derives JsonSchema |
T::simple(name, desc, fn) |
A closure tool that takes no parameters |
T::function(arc_fn) |
Register an existing Arc<dyn ToolFunction> |
T::google_search() |
Add built-in Google Search |
T::url_context() |
Add built-in URL context fetching |
T::code_execution() |
Add built-in code execution |
T::toolset(vec) |
Combine multiple tool functions into one composite |
Example
Section titled “Example”use gemini_adk_fluent_rs::compose::T;
#[derive(serde::Deserialize, schemars::JsonSchema)]struct City { /// The city to report on. city: String,}
// Combine custom tools with built-inslet tools = T::typed("get_weather", "Get weather for a city", |args: City| async move { Ok(json!({"temp": 22, "city": args.city})) }) + T::google_search() + T::code_execution();
assert_eq!(tools.len(), 3);
// Use in a Live session builderLive::builder() .tools(tools)P – Prompt Composition
Section titled “P – Prompt Composition”Compose structured prompt sections with +. Each section has a semantic kind that determines its rendering format.
Methods
Section titled “Methods”| Method | Renders as | Kind |
|---|---|---|
P::role("analyst") |
"You are analyst." |
Role |
P::task("analyze data") |
"Your task: analyze data" |
Task |
P::constraint("be concise") |
"Constraint: be concise" |
Constraint |
P::format("JSON") |
"Output format: JSON" |
Format |
P::example("input", "output") |
"Example:\nInput: ...\nOutput: ..." |
Example |
P::context("background info") |
"Context: background info" |
Context |
P::persona("friendly, direct") |
"Persona: friendly, direct" |
Persona |
P::guidelines(&["be clear", ...]) |
"Guidelines:\n- be clear\n- ..." |
Guidelines |
P::text("free-form text") |
The text as-is | Text |
PromptSection Kinds
Section titled “PromptSection Kinds”The PromptSectionKind enum provides semantic categories:
| Kind | Purpose |
|---|---|
Role |
Agent role definition |
Task |
Task description |
Constraint |
Behavioral constraint |
Format |
Output format specification |
Example |
Input/output example |
Context |
Background context |
Persona |
Personality description |
Guidelines |
Bulleted guideline list |
Text |
Free-form text |
Instruction Modifiers
Section titled “Instruction Modifiers”P also provides instruction modifier factories that bridge the prompt module to the live phase system:
| Method | What it does |
|---|---|
P::show_state(&["key1", "key2"]) |
Append selected state keys to the instruction |
P::when(predicate, text) |
Conditionally append text based on state |
P::context_fn(fn) |
Append dynamic text from a formatting function |
Example
Section titled “Example”use gemini_adk_fluent_rs::compose::P;
// Build a structured promptlet prompt = P::role("a senior financial analyst") + P::task("Review the quarterly earnings report and identify trends") + P::constraint("Use only data from the provided report") + P::constraint("Flag any numbers that seem inconsistent") + P::format("Markdown with headers for each section") + P::guidelines(&[ "Start with an executive summary", "Include specific numbers when citing trends", "End with a risk assessment", ]);
// Render to a single instruction stringlet instruction: String = prompt.into();// "You are a senior financial analyst.\n\nYour task: Review the quarterly...\n\n..."
// Instruction modifiers for phasesLive::builder() .phase("negotiation") .instruction("Negotiate a payment arrangement") .modifiers(vec![ P::show_state(&["emotional_state", "willingness_to_pay"]), P::when( |s| s.get::<String>("risk").unwrap_or_default() == "high", "IMPORTANT: Show extra empathy and offer flexible options.", ), P::context_fn(|s| { let name = s.get::<String>("user:name").unwrap_or_default(); format!("Customer: {name}") }), ]) .done()M – Middleware Composition
Section titled “M – Middleware Composition”Stack middleware layers with >>, outermost first. Middleware intercepts agent events, tool calls, and errors.
Methods
Section titled “Methods”| Method | What it does |
|---|---|
M::log() |
Log all agent events |
M::latency() |
Track execution latency |
M::timeout(duration) |
Enforce a time limit |
M::retry(max) |
Retry on failure up to max times |
M::cost() |
Track tool call counts as a cost proxy |
M::rate_limit(rps) |
Enforce max requests per second |
M::circuit_breaker(threshold) |
Open circuit after consecutive failures |
M::trace() |
Create distributed tracing spans |
M::audit() |
Record all tool calls for review |
M::tap(fn) |
Custom event observer |
M::before_tool(fn) |
Custom filter before each tool invocation |
M::validate(fn) |
Validate tool input arguments |
Example
Section titled “Example”use gemini_adk_fluent_rs::compose::M;use std::time::Duration;
// Production middleware stacklet middleware = M::log() >> M::latency() >> M::timeout(Duration::from_secs(30)) >> M::retry(3) >> M::circuit_breaker(5) >> M::audit();
assert_eq!(middleware.len(), 6);
// Custom validationlet validated = M::validate(|call| { if call.name == "delete_account" && call.args.get("confirm").is_none() { return Err("delete_account requires 'confirm' argument".into()); } Ok(())});A – Artifact Schemas
Section titled “A – Artifact Schemas”Declare input/output artifact schemas with +, and attach them to an agent with .artifacts(..). Each input counts as a read and each output as a write of artifact:{name}, so check_contracts reports an artifact input no agent produces.
Methods
Section titled “Methods”| Method | What it does |
|---|---|
A::output(name, mime, desc) |
Declare an output artifact |
A::input(name, mime, desc) |
Declare an input artifact |
A::json_output(name, desc) |
Shorthand for application/json output |
A::json_input(name, desc) |
Shorthand for application/json input |
A::text_output(name, desc) |
Shorthand for text/plain output |
A::text_input(name, desc) |
Shorthand for text/plain input |
Example
Section titled “Example”use gemini_adk_fluent_rs::compose::A;
// Declare what an analysis agent produces and consumeslet artifacts = A::text_input("source_document", "The document to analyze") + A::json_output("analysis_report", "Structured analysis results") + A::json_output("risk_assessment", "Risk scores and flags");
assert_eq!(artifacts.all_inputs().len(), 1);assert_eq!(artifacts.all_outputs().len(), 2);Composable Operators
Section titled “Composable Operators”Beyond the six namespace modules, the operators module provides structural composition via Rust operators on AgentBuilder:
| Operator | Type | Meaning |
|---|---|---|
>> |
Shr |
Sequential pipeline |
| |
BitOr |
Parallel fan-out |
* |
Mul<u32> |
Fixed-count loop |
* |
Mul<LoopPredicate> |
Conditional loop |
/ |
Div |
Fallback chain |
These produce Composable nodes that form a tree. Call .compile(llm) to turn the tree into an executable TextAgent.
use gemini_adk_fluent_rs::prelude::*;
// Build a treelet workflow = AgentBuilder::new("research").instruction("Research the topic") >> (AgentBuilder::new("tech").instruction("Technical review") | AgentBuilder::new("biz").instruction("Business review")) >> AgentBuilder::new("merge").instruction("Merge perspectives");
// Compile and executelet agent = workflow.compile(llm)?;let result = agent.run(&state).await?;The tree auto-flattens: a >> b >> c produces a single Pipeline with 3 steps, not nested pipelines.
Combining Operators into Full Agent Configuration
Section titled “Combining Operators into Full Agent Configuration”The six namespaces compose orthogonally. Each configures a separate dimension:
use gemini_adk_fluent_rs::prelude::*;
// S: transform state before agent sees itlet state_prep = S::pick(&["customer", "order"]) >> S::defaults(json!({"priority": "normal"}));
// C: control what context the agent seeslet context = C::window(10) >> C::exclude_tools();
// T: equip the agent with toolslet tools = order_status() // a #[tool] fn + T::google_search();
// P: compose the instructionlet prompt = P::role("a customer support specialist") + P::task("Help the customer with their order inquiry") + P::constraint("Never reveal internal order IDs") + P::format("Conversational, friendly tone");
// A: declare I/O artifactslet artifacts = A::json_output("resolution", "How the issue was resolved");
// M: add operational middlewarelet middleware = M::log() >> M::latency() >> M::audit();Builder Integration
Section titled “Builder Integration”AgentBuilder is the entry point for compiling these into executable agents:
use gemini_adk_fluent_rs::builder::AgentBuilder;
let agent = AgentBuilder::new("support") .model(ModelId::FLASH_LATEST) .instruction("Help the customer") // or use P:: composition .temperature(0.5) .google_search() // or use T:: composition .thinking(2048) .writes("resolution") .reads("customer_name") .build(llm)?;
let result = agent.run(&state).await?;Copy-on-write semantics mean every setter returns a new builder. Use builders as templates:
let base = AgentBuilder::new("analyst") .instruction("You are a data analyst") .temperature(0.3);
// Variants share the base configurationlet conservative = base.clone().temperature(0.1);let creative = base.clone().temperature(0.9);Pre-Built Patterns
Section titled “Pre-Built Patterns”The patterns module provides common multi-agent workflows built from these operators:
use gemini_adk_fluent_rs::patterns::*;
// Review loop: worker -> reviewer -> repeat until quality targetlet reviewed = review_loop(writer, reviewer, "quality", "good", 3);
// Cascade: try each agent until one succeedslet robust = cascade(vec![primary, secondary, fallback]);
// Fan-out merge: run all in parallel, merge resultslet multi = fan_out_merge(vec![analyst_a, analyst_b, analyst_c]);
// Supervised: worker -> supervisor -> repeat until approvallet approved = supervised(drafter, supervisor, "approved", 5);
// Map-over: apply agent to every item of the JSON array at state["items"]// (a Composable node — compiles to MapOverTextAgent, composes with >>)let batch = map_over(item_processor, "items");See also
Section titled “See also”- Text Agent Combinators — the full combinator reference (
SequentialTextAgent,ParallelTextAgent, etc.) - Tools — defining tools to equip your agents with
- cookbook 07 — state transforms
- cookbook 08 — prompt composition
- cookbook 09 — tool composition
- cookbook 21 — full algebra