S.C.T.P.M.A Operator Algebra

Six namespace modules for declaratively composing agent primitives. Each maps to a dimension of agent configuration: State, Context, Tools, Prompt, Middleware, Artifacts. They compose using Rust operators so agent definitions read like algebraic expressions.

The Six Operators

NamespaceOperatorPurposeKey Methods
S::>>State transformspick, rename, merge, flatten, set, defaults, drop, map
C::+Context engineeringwindow, user_only, model_only, head, truncate, filter, from_state
T::|Tool compositionsimple, function, google_search, code_execution, toolset
P::+Prompt compositionrole, task, constraint, format, example, persona, guidelines
M::|Middleware layerslog, latency, timeout, retry, audit, circuit_breaker
A::+Artifact schemasoutput, input, json_output, json_input, text_output, text_input

S -- State Transforms

State transforms mutate a serde_json::Value representing agent state. Chain them with >> for sequential application.

Methods

MethodWhat 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

S also provides predicates for phase transition guards and .when() modifiers:

PredicateWhat 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

use gemini_adk_fluent_rs::compose::S;

// Chain transforms: pick keys, then rename
let 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 transitions
Live::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

Context policies filter and transform conversation history. Compose them with + to combine multiple policies.

Methods

MethodWhat it does
C::window(n)Keep only the last n messages
C::head(n)Keep only the first n messages
C::last(n)Alias for window(n)
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

use gemini_adk_fluent_rs::compose::C;

// Keep recent context, no tool noise, inject state
let policy = C::window(20) + C::exclude_tools() + C::from_state(&["user:name", "app:balance"]);

// For isolated sub-agents that should not see conversation history
let isolated = C::empty();

// Character-budget context for cost control
let budget = C::truncate(4000) + C::dedup();

T -- Tool Composition

Compose tools with |. Mix runtime function tools with built-in Gemini tools.

Methods

MethodWhat it does
T::simple(name, desc, fn)Create a tool from a name, description, and async closure
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

use gemini_adk_fluent_rs::compose::T;

// Combine custom tools with built-ins
let tools = T::simple("get_weather", "Get weather for a city", |args| async move {
        let city = args["city"].as_str().unwrap_or("Unknown");
        Ok(json!({"temp": 22, "city": city}))
    })
    | T::google_search()
    | T::code_execution();

assert_eq!(tools.len(), 3);

// Use in a Live session builder
Live::builder()
    .with_tools(tools)

P -- Prompt Composition

Compose structured prompt sections with +. Each section has a semantic kind that determines its rendering format.

Methods

MethodRenders asKind
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-isText

PromptSection Kinds

The PromptSectionKind enum provides semantic categories:

KindPurpose
RoleAgent role definition
TaskTask description
ConstraintBehavioral constraint
FormatOutput format specification
ExampleInput/output example
ContextBackground context
PersonaPersonality description
GuidelinesBulleted guideline list
TextFree-form text

Instruction Modifiers

P also provides instruction modifier factories that bridge the prompt module to the live phase system:

MethodWhat it does
P::with_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

use gemini_adk_fluent_rs::compose::P;

// Build a structured prompt
let 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 string
let instruction: String = prompt.into();
// "You are a senior financial analyst.\n\nYour task: Review the quarterly...\n\n..."

// Instruction modifiers for phases
Live::builder()
    .phase("negotiation")
        .instruction("Negotiate a payment arrangement")
        .modifiers(vec![
            P::with_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

Compose middleware layers with |. Middleware intercepts agent events, tool calls, and errors.

Methods

MethodWhat 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

use gemini_adk_fluent_rs::compose::M;
use std::time::Duration;

// Production middleware stack
let 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 validation
let 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

Declare input/output artifact schemas with +. Artifacts describe data that flows between agents as typed, named entities.

Methods

MethodWhat 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

use gemini_adk_fluent_rs::compose::A;

// Declare what an analysis agent produces and consumes
let 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

Beyond the six namespace modules, the operators module provides structural composition via Rust operators on AgentBuilder:

OperatorTypeMeaning
>>ShrSequential pipeline
|BitOrParallel fan-out
*Mul<u32>Fixed-count loop
*Mul<LoopPredicate>Conditional loop
/DivFallback 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 tree
let 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 execute
let 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

The six namespaces compose orthogonally. Each configures a separate dimension:

use gemini_adk_fluent_rs::prelude::*;

// S: transform state before agent sees it
let state_prep = S::pick(&["customer", "order"]) >> S::defaults(json!({"priority": "normal"}));

// C: control what context the agent sees
let context = C::window(10) + C::exclude_tools();

// T: equip the agent with tools
let tools = T::simple("lookup", "Look up order status", |args| async move {
        Ok(json!({"status": "shipped"}))
    })
    | T::google_search();

// P: compose the instruction
let 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 artifacts
let artifacts = A::json_output("resolution", "How the issue was resolved");

// M: add operational middleware
let middleware = M::log() | M::latency() | M::audit();

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(GeminiModel::Gemini2_0Flash)
    .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 configuration
let conservative = base.clone().temperature(0.1);
let creative = base.clone().temperature(0.9);

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 target
let reviewed = review_loop(writer, reviewer, "quality", "good", 3);

// Cascade: try each agent until one succeeds
let robust = cascade(vec![primary, secondary, fallback]);

// Fan-out merge: run all in parallel, merge results
let multi = fan_out_merge(vec![analyst_a, analyst_b, analyst_c]);

// Supervised: worker -> supervisor -> repeat until approval
let approved = supervised(drafter, supervisor, "approved", 5);

// Map-over: apply agent to each item with concurrency limit
let batch = map_over(item_processor, 4);