echo_agent 0.2.0

Production-grade AI Agent framework for Rust — ReAct engine, multi-agent, memory, streaming, MCP, IM channels, workflows
Documentation
//! Memory Write tool — lets the agent persist information across sessions.
//!
//! The agent can write key-value memories scoped by lifetime (session/project/user).

use async_trait::async_trait;
use echo_core::error::Result;
use echo_core::tools::{Tool, ToolParameters, ToolResult};
use futures::future::BoxFuture;
use std::collections::HashMap;
use std::sync::Mutex;

static MEMORY: std::sync::LazyLock<Mutex<HashMap<String, String>>> =
    std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));

pub struct MemoryWriteTool;

#[async_trait]
impl Tool for MemoryWriteTool {
    fn name(&self) -> &str {
        "memory_write"
    }
    fn description(&self) -> &str {
        "Write a key-value memory that persists across the session. \
         Use this to remember important information the user shares, \
         project conventions, or decisions made. \
         Parameters: key (memory name), value (content), scope ('session'|'project'|'user')"
    }
    fn parameters(&self) -> serde_json::Value {
        serde_json::json!({
            "type": "object",
            "properties": {
                "key": {"type": "string", "description": "Memory key/name"},
                "value": {"type": "string", "description": "Content to remember"},
                "scope": {"type": "string", "enum": ["session", "project", "user"], "default": "session"}
            },
            "required": ["key", "value"]
        })
    }
    fn execute<'a>(&'a self, params: ToolParameters) -> BoxFuture<'a, Result<ToolResult>> {
        Box::pin(async move {
            let key = params.get("key").and_then(|v| v.as_str()).unwrap_or("");
            let value = params.get("value").and_then(|v| v.as_str()).unwrap_or("");
            let scope = params
                .get("scope")
                .and_then(|v| v.as_str())
                .unwrap_or("session");
            if key.is_empty() {
                return Ok(ToolResult::error("key is required"));
            }
            let full_key = format!("{scope}:{key}");
            MEMORY
                .lock()
                .unwrap()
                .insert(full_key.clone(), value.to_string());
            Ok(ToolResult::success(format!("Stored: {full_key}")))
        })
    }
    fn risk_level(&self) -> echo_core::tools::ToolRiskLevel {
        echo_core::tools::ToolRiskLevel::ReadOnly
    }
}