Hello everyone! As the ZeroClaw project progresses, I want to share the design considerations and solutions encountered while building a high-performance Rust agent runtime and a multi-agent system. Recent security incidents, such as the ‘Remote Access Trojan (RAT) exploiting Obsidian plugins’, serve as a stark reminder of how crucial Security and Trust are in plugin and agent systems.
Today, I’ll introduce the secure and scalable inter-agent communication protocol designed by analyzing ZeroClaw’s codebase, along with concrete Rust code examples.
1. Introduction: The Need for Security-Conscious Agent Communication
During the development of previous MCP (Model Context Protocol) and blog automation systems, we realized that beyond simple data transfer, Authentication and Authorization between agents are essential. In a multi-agent environment where multiple agents collaborate to generate files or call external APIs, a mechanism to prevent malicious command injection is indispensable.
To address this, ZeroClaw adopts an architecture that leverages Rust’s Type Safety and Ownership system to catch many bugs at compile time and minimize runtime overhead.
2. Communication Protocol Design: IPC and Message Queues
ZeroClaw’s communication platform is broadly divided into Local IPC (Inter-Process Communication) and an Asynchronous Message Queue. Based on our experience improving the logging system, all communication is handled asynchronously (async), ensuring the independence of each agent.
Key Design Principles
- Zero-Copy Serialization: Minimizes data serialization overhead using
serdeandbincode. - Message Validation: A layer validates all incoming messages to prevent tampered data from reaching the system core.
- Capability-Based Security: Goes beyond simple token-based authentication by restricting the capabilities (actions) an agent can perform.
3. Implementing Secure Messaging Handlers with Rust
Let’s explore how this is implemented through actual code. This example is based on tokio and demonstrates the basic structure for secure message delivery.
3.1 Message Definition and Validation
First, we define the data structures for inter-agent communication and use the validator crate for input validation.
// message.rs
use serde::{Deserialize, Serialize};
use validator::Validate;
/// Agent communication message type
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AgentMessage {
TaskRequest { id: String, payload: TaskPayload },
TaskResponse { id: String, result: String },
Heartbeat,
}
/// Task payload (includes validation logic)
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct TaskPayload {
#[validate(length(min = 1, max = 100))]
pub command: String,
// Accepts JSON-formatted arguments but must be parsed structurally safely
pub args: serde_json::Value,
}
impl AgentMessage {
/// Safely deserializes a message from a byte array.
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
// 1. Basic serialization validation
let msg: AgentMessage = bincode::deserialize(bytes)?;
// 2. Business logic validation (e.g., prevent Command Injection)
if let AgentMessage::TaskRequest { payload, .. } = &msg {
payload.validate()?; // Using the validator crate
// Security: Example of filtering disallowed shell commands
if payload.command.contains("rm ") || payload.command.contains("sudo") {
return Err("Blocked potentially dangerous command".into());
}
}
Ok(msg)
}
}
3.2 Actor Model-Based Agent Structure
Each agent follows an Actor pattern, maintaining its own state and processing messages. Rust’s tokio::sync::mpsc channel is used to implement the mailbox.
// agent.rs
use tokio::sync::mpsc;
use super::message::AgentMessage;
pub struct Agent {
id: String,
receiver: mpsc::Receiver<AgentMessage>,
// The agent's state is encapsulated here and not directly accessible from outside
}
impl Agent {
pub fn new(id: String, receiver: mpsc::Receiver<AgentMessage>) -> Self {
Self { id, receiver }
}
/// The agent's main loop
pub async fn run(mut self) {
println!("[{}] Agent started", self.id);
while let Some(msg) = self.receiver.recv().await {
if let Err(e) = self.handle_message(msg).await {
eprintln!("[{}] Error handling message: {:?}", self.id, e);
}
}
}
async fn handle_message(&self, msg: AgentMessage) -> Result<(), Box<dyn std::error::Error>> {
match msg {
AgentMessage::TaskRequest { id, payload } => {
println!("[{}] Executing task {}: {}", self.id, id, payload.command);
// Actual task execution logic goes here (e.g., LLM calls, file writes, etc.)
// Safeguards: Execute in a sandboxed environment or restrict permissions.
},
AgentMessage::Heartbeat => {
// Handle heartbeat signal
},
_ => return Err("Unknown message type".into()),
}
Ok(())
}
}
3.3 Runtime and Message Routing
Finally, here’s a simple runtime system for creating multiple agents and routing messages.
// runtime.rs
use tokio::sync::mpsc;
use std::collections::HashMap;
use super::agent::Agent;
use super::message::AgentMessage;
pub struct Runtime {
agents: HashMap<String, mpsc::Sender<AgentMessage>>,
}
impl Runtime {
pub fn new() -> Self {
Self { agents: HashMap::new() }
}
pub fn spawn_agent(&mut self, id: &str) {
let (tx, rx) = mpsc::channel(32);
let agent = Agent::new(id.to_string(), rx);
// Run the agent in a separate task
tokio::spawn(agent.run());
self.agents.insert(id.to_string(), tx);
println!("Runtime: Agent '{}' spawned", id);
}
pub async fn broadcast(&self, msg: AgentMessage) {
for (id, tx) in self.agents.iter() {
if let Err(e) = tx.send(msg.clone()).await {
eprintln!("Failed to send to {}: {:?}", id, e);
}
}
}
}
4. Conclusion and Next Steps
The code implemented above demonstrates the core of the ZeroClaw multi-agent system: secure communication and isolated state management. Systems that execute external inputs or scripts, like the Obsidian plugin incident, are always potential targets for attacks.
ZeroClaw uses Rust’s powerful type system to block most of these risks at compile time and defends against them at runtime with additional validation layers. In the next post, we will discuss how this agent system actually integrates with external gateways like Discord or MCP, and how it is controlled through LLM configurations.
So far, I’ve shared the current status of ZeroClaw’s architectural design. I hope this is helpful for those looking to build high-performance yet secure agent systems!
Note: The code above is a Proof of Concept (POC) level example. For actual production environments, more robust error handling and logging systems (referencing the logging setup improved in the previous post) must be integrated.