Building High-Precision Cost Monitoring for Multi-Agent Environments in [ZeroClaw]

Building High-Precision Cost Monitoring for Multi-Agent Environments in ZeroClaw

Hello. I am a developer running a tech blog.

When building high-performance multi-agent runtimes like the ZeroClaw project, one of the biggest headaches is ‘unexpected cloud costs.’ When agents calling LLMs run concurrently, it’s common to be surprised by the bill when it arrives.

Especially after seeing the news on Hacker News that Infracost was hiring a Senior Dev Advocate with the goal of “giving agents cloud cost awareness,” I was motivated to implement a feedback loop for cost optimization in my own system.

In this post, I will share the process of building a system within the Rust-based ZeroClaw architecture that tracks agent activities in real-time, calculates estimated costs, and informs users.

Problem Definition: Consumption of Invisible Resources

The existing monitoring dashboard for [blog-api-server] primarily focused on ‘status.’ Was the agent still alive? Was the API responding? However, it couldn’t answer questions like:

  • “What was the actual token cost for generating this team meeting minutes?”
  • “How much resource are the 5 sub-agents currently running consuming per hour?”

Simply logging was not enough. We needed to manage costs as ‘code’ and make agents cost-aware themselves.

Solution Architecture

Architecture Diagram

We decided to add a Cost Tracker module to ZeroClaw’s main loop and intercept token usage at each agent’s communication protocol layer.

  1. Agent Layer: Each agent reports token usage every time it sends or receives messages.
  2. Cost Estimator: Calculates real-time costs based on token counts and model pricing (Input/Output).
  3. Reporter: Sends reports to Slack/Discord, etc., when a certain threshold is exceeded or a task is completed.

Implementing a Token Metric Collector in Rust

Since ZeroClaw is written in Rust, the cost calculation logic must also ensure type safety and high performance. First, let’s create a struct to define the pricing information for each model.

// src/cost/model.rs

use std::collections::HashMap;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelPricing {
    pub input_price_per_1k: f64, // USD
    pub output_price_per_1k: f64, // USD
}

pub struct PricingRegistry {
    prices: HashMap<String, ModelPricing>,
}

impl PricingRegistry {
    pub fn new() -> Self {
        let mut prices = HashMap::new();
        // Example: GPT-4o pricing (as of 2024)
        prices.insert("gpt-4o".to_string(), ModelPricing {
            input_price_per_1k: 2.50,
            output_price_per_1k: 10.00,
        });
        // Example: Claude 3.5 Sonnet pricing
        prices.insert("claude-3-5-sonnet".to_string(), ModelPricing {
            input_price_per_1k: 3.00,
            output_price_per_1k: 15.00,
        });

        Self { prices }
    }

    pub fn get(&self, model: &str) -> Option<&ModelPricing> {
        self.prices.get(model)
    }
}

Now, let’s implement the logic to calculate costs based on the results after an agent actually calls the LLM API. This logic is called by the MCP (Model Context Protocol) bridge or internal communication wrappers.

// src/cost/calculator.rs

use super::model::PricingRegistry;

#[derive(Debug, Clone)]
pub struct UsageStats {
    pub model: String,
    pub input_tokens: u32,
    pub output_tokens: u32,
}

pub struct CostCalculator {
    registry: PricingRegistry,
}

impl CostCalculator {
    pub fn new(registry: PricingRegistry) -> Self {
        Self { registry }
    }

    pub fn calculate(&self, usage: &UsageStats) -> f64 {
        if let Some(pricing) = self.registry.get(&usage.model) {
            let input_cost = (usage.input_tokens as f64 / 1000.0) * pricing.input_price_per_1k;
            let output_cost = (usage.output_tokens as f64 / 1000.0) * pricing.output_price_per_1k;
            input_cost + output_cost
        } else {
            0.0
        }
    }
}

Integration with Agent Communication Protocols

ZeroClaw’s agents communicate via a file-based architecture or IPC. In this process, the message structure needs to be extended to also transfer ‘cost’ information.

Adding a cost field to the existing communication packet is the cleanest approach.

// src/agent/message.rs

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct AgentMessage {
    pub id: String,
    pub content: String,
    pub timestamp: i64,
    // Added cost field
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cost_incurred: Option<f64>, 
}

impl AgentMessage {
    pub fn with_cost(mut self, cost: f64) -> Self {
        self.cost_incurred = Some(cost);
        self
    }
}

Now, when processing messages in the main agent loop, we can accumulate this cost to track the total session cost.

// src/agent/executor.rs

use crate::agent::message::AgentMessage;
use crate::cost::{CostCalculator, UsageStats};
use std::sync::Arc;
use tokio::sync::RwLock;

pub struct AgentRuntime {
    calculator: CostCalculator,
    total_cost: Arc<RwLock<f64>>,
}

impl AgentRuntime {
    pub async fn process_llm_request(&self, model: &str, prompt: &str) -> Result<String, String> {
        // 1. LLM API call (hypothetical function)
        let response = call_llm_api(model, prompt).await?;
        
        // 2. Extract usage metrics (parse from API response headers or body)
        let usage = UsageStats {
            model: model.to_string(),
            input_tokens: response.usage.input_tokens,
            output_tokens: response.usage.output_tokens,
        };

        // 3. Calculate cost
        let cost = self.calculator.calculate(&usage);
        
        // 4. Update total cost
        let mut total = self.total_cost.write().await;
        *total += cost;
        drop(total);

        // 5. Log output (can be sent to monitoring dashboard)
        println!("[Cost Monitor] Model: {}, Input: {}, Output: {} | Cost: ${:.6}", 
            model, usage.input_tokens, usage.output_tokens, cost);

        Ok(response.text)
    }
}

Practical Application and Effects

After applying this system, we were able to see the cost incurred in real-time for decision-making within the [Discord Decision MCP] architecture.

  • Case 1: For long document summarization tasks, we switched the model from GPT-4o to Claude 3.5 Haiku, reducing costs by approximately 70%.
  • Case 2: We detected a rapid increase in costs when a specific agent entered an infinite loop and implemented a safety mechanism that automatically activated a kill_switch.

Conclusion

Just as Infracost inspects infrastructure code, cost awareness at the agent runtime level is essential. By integrating a cost optimization layer directly into the ZeroClaw platform using Rust’s safe type system and concise code, we have enabled the operation of a more efficient multi-agent system.

In the next post, we will cover building a dashboard for visualizing the collected cost data (Grafana + Prometheus).

Thank you.


Reference Links:

Built with Hugo
Theme Stack designed by Jimmy