Files
Luna/Documentation/References/ZeroClaw/Agent Loop & Tools.md
T
darman 9929941748 Add project documentation and reference materials
Include Luna AI Assistant design docs covering channels, configuration,
core architecture, memory, scheduler, and skills. Add reference docs
from OpenClaw and ZeroClaw projects, plus Mistral and OpenAI API specs.
2026-04-04 04:14:06 +02:00

5.5 KiB

Agent Loop & Tools

The ZeroClaw agent architecture is built around a recursive execution loop that allows an LLM to invoke external capabilities, observe their results, and refine its response until a final answer is reached.


Tool Trait

The Tool trait in tools/traits.rs defines the interface for any capability exposed to the agent. It provides both the metadata needed for LLM registration and the execution logic for the tool itself.

/// Result of a tool execution
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResult {
    pub success: bool,
    pub output: String,
    pub error: Option<String>,
}

/// Description of a tool for the LLM
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolSpec {
    pub name: String,
    pub description: String,
    pub parameters: serde_json::Value,
}

/// Core tool trait — implement for any capability
#[async_trait]
pub trait Tool: Send + Sync {
    /// Tool name (used in LLM function calling)
    fn name(&self) -> &str;

    /// Human-readable description
    fn description(&self) -> &str;

    /// JSON schema for parameters
    fn parameters_schema(&self) -> serde_json::Value;

    /// Execute the tool with given arguments
    async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult>;

    /// Get the full spec for LLM registration
    fn spec(&self) -> ToolSpec {
        ToolSpec {
            name: self.name().to_string(),
            description: self.description().to_string(),
            parameters: self.parameters_schema(),
        }
    }
}

Agent Loop

The core logic resides in agent/loop_.rs within run_tool_call_loop. This function orchestrates the interaction between the provider and the tool registry.

The process follows a repeating cycle:

  1. Send the current conversation history and tool definitions to the LLM.
  2. Receive a response.
  3. Parse the response for tool calls (native or prompt-guided).
  4. If tool calls exist, execute them and append the results to the history, then repeat.
  5. If no tool calls exist, return the final text response.

Iteration Safety

To prevent infinite loops or runaway execution, the system enforces a strict iteration cap:

  • DEFAULT_MAX_TOOL_ITERATIONS = 10

Streaming and Progress

Streaming responses are handled by accumulating chunks before relaying them to the draft channel to minimize noise:

  • STREAM_CHUNK_MIN_CHARS = 80
  • PROGRESS_MIN_INTERVAL_MS = 500 (minimum time between progress updates)
  • DRAFT_CLEAR_SENTINEL = \x00CLEAR\x00 (used to clear progress lines before showing the final answer)

Auto-Compaction

Conversation history is managed through an auto-compaction mechanism to stay within context window limits while preserving essential information.

  • Threshold: Triggered when non-system message count exceeds 50 (DEFAULT_MAX_HISTORY_MESSAGES).
  • Retention: Keeps the 20 most recent messages (COMPACTION_KEEP_RECENT_MESSAGES).
  • Summarization: Older messages are summarized by the LLM.
  • Caps: Summarization source is capped at 12,000 characters; the resulting summary is capped at 2,000 characters.

Security & Pattern Matching

Credential Scrubbing

ZeroClaw proactively redacts sensitive information from tool outputs before they are fed back into the LLM history.

static SENSITIVE_KV_REGEX: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r#"(?i)(token|api[_-]?key|password|secret|user[_-]?key|bearer|credential)["']?\s*[:=]\s*(?:"([^"]{8,})"|'([^']{8,})'|([a-zA-Z0-9_\-\.]{8,}))"#).unwrap()
});

The scrubbing logic preserves the first 4 characters for context then appends *[REDACTED].

Autosave

Meaningful exchanges are automatically persisted to memory based on message length:

  • AUTOSAVE_MIN_MESSAGE_CHARS = 20

Tool Serialization & Formats

Tools are converted into the appropriate format for the specific LLM provider.

tools_to_openai_format()

This function serializes the tool registry into the standard OpenAI function-calling schema used by many providers.

fn tools_to_openai_format(tools_registry: &[Box<dyn Tool>]) -> Vec<serde_json::Value> {
    tools_registry
        .iter()
        .map(|tool| {
            serde_json::json!({
                "type": "function",
                "function": {
                    "name": tool.name(),
                    "description": tool.description(),
                    "parameters": tool.parameters_schema()
                }
            })
        })
        .collect()
}

Parsing Priority

The system handles multiple response formats to support different providers:

  1. Native structured JSON (OpenAI style)
  2. XML tags (<tool_call>, <invoke>)
  3. Markdown code blocks with tool_call identifiers
  4. Provider-specific shortened formats (GLM, etc.)

Relevance to Luna

Luna currently operates as a pure chat relay, streaming LLM responses without any capability to interact with the system or external APIs. This lack of a tool execution layer is the primary bottleneck preventing Luna from becoming an autonomous assistant.

Bridging the Gap

Implementing the agent loop pattern in Luna would involve:

  • Defining an ITool interface in C# that mirrors the Rust Tool trait.
  • Transitioning from a single request-response model to a recursive loop in the Core engine.
  • Integrating the tool loop with Skills to allow dynamic capability discovery.

The implementation of this protocol will turn Luna from a passive interface into an active participant capable of executing commands and managing its own Memory.