Skip to main content
The memory crate provides persistent memory storage with semantic search, deduplication, knowledge graphs, session management, and automatic eviction policies.

memory::store

Store a memory entry with SHA-256 content deduplication and optional embedding generation.
agentId
string
required
Agent identifier for memory ownership
content
string
required
Memory content to store
role
string
required
Message role: “user”, “assistant”, or “system”
sessionId
string
Optional session identifier for grouping related memories
id
string
UUID of the created memory entry
stored
boolean
True if memory was stored (false if deduplicated)
deduplicated
boolean
True if content already exists (duplicate detected via SHA-256)
use iii_sdk::iii::III;
use serde_json::json;

let iii = III::new("ws://localhost:49134");

let result = iii.trigger("memory::store", json!({
    "agentId": "agent-123",
    "content": "User asked about pricing plans",
    "role": "user",
    "sessionId": "session-456"
})).await?;

if result["deduplicated"].as_bool().unwrap_or(false) {
    println!("Memory already exists (deduplicated)");
} else {
    println!("Stored with ID: {}", result["id"].as_str().unwrap());
}

Storage Details

  • Deduplication: SHA-256 hash of content prevents duplicates
  • Embeddings: Automatically generated via embedding::generate if available
  • Importance: Auto-calculated based on content length, role, and keywords
  • Confidence: Starts at 1.0, decays over time if unaccessed
  • Session Tracking: Updates session metadata if sessionId provided

Memory Entry Structure

struct MemoryEntry {
    id: String,              // UUID v4
    agent_id: String,
    content: String,
    role: String,           // "user" | "assistant" | "system"
    embedding: Option<Vec<f64>>,  // Vector embedding
    timestamp: u64,         // Unix timestamp (ms)
    session_id: Option<String>,
    importance: f64,        // 0.0-1.0 (auto-calculated)
    hash: String,           // SHA-256 hex
    confidence: f64,        // 0.0-1.0 (decays if unaccessed)
    access_count: u64,
    last_accessed: u64,     // Unix timestamp (ms)
}

memory::recall

Hybrid semantic + keyword + recency search across agent memories.
agentId
string
required
Agent ID to search memories for
query
string
required
Search query text
limit
number
default:10
Maximum number of results to return
memories
array
Array of memory objects sorted by relevance score (highest first)
id
string
Memory entry UUID
role
string
Message role
content
string
Memory content
score
number
Relevance score (higher is better)
timestamp
number
Creation timestamp (ms)
let results = iii.trigger("memory::recall", json!({
    "agentId": "agent-123",
    "query": "What did the user ask about pricing?",
    "limit": 5
})).await?;

for memory in results.as_array().unwrap() {
    println!("[{:.2}] {} ({})",
        memory["score"].as_f64().unwrap(),
        memory["content"].as_str().unwrap(),
        memory["role"].as_str().unwrap()
    );
}

Scoring Algorithm

The recall function uses a weighted hybrid scoring system:
  1. Semantic Similarity (50%): Cosine similarity between query and memory embeddings
  2. Keyword Match (25%): Proportion of query keywords found in content (case-insensitive)
  3. Recency (10%): Exponential decay with 1-week half-life
  4. Importance (10%): Stored importance score
  5. Confidence (5%): Current confidence value

Side Effects

  • Increments accessCount for returned memories
  • Updates lastAccessed timestamp
  • Prevents confidence decay for recently accessed memories

memory::evict

Evict stale, low-importance, or low-confidence memories across all agents.
maxAge
number
default:2592000000
Maximum age in milliseconds (default: 30 days)
minImportance
number
Minimum importance threshold (0.0-1.0)
cap
number
default:10000
Maximum memories per agent (evicts lowest importance if exceeded)
evicted
number
Total number of memories evicted
Evict Stale Memories
let result = iii.trigger("memory::evict", json!({
    "maxAge": 15 * 86_400_000,  // 15 days
    "minImportance": 0.3,
    "cap": 5000
})).await?;

println!("Evicted {} memories", result["evicted"].as_u64().unwrap());

Eviction Criteria

A memory is evicted if:
  • (age > maxAge AND importance < minImportance) OR
  • confidence < 0.1 OR
  • Memory count exceeds cap (evicts lowest importance)
This function is automatically triggered daily at 3 AM via cron expression 0 3 * * *.

memory::consolidate

Decay confidence on unaccessed memories (runs every 6 hours via cron).
decayRate
number
Confidence decay rate (0.0-1.0)
memoriesDecayed
number
Number of memories with decayed confidence
memoriesMerged
number
Number of duplicate memories merged (future feature, currently 0)
durationMs
number
Execution time in milliseconds
Consolidate Memories
let result = iii.trigger("memory::consolidate", json!({
    "decayRate": 0.05
})).await?;

println!("Decayed {} memories in {}ms",
    result["memoriesDecayed"].as_u64().unwrap(),
    result["durationMs"].as_u64().unwrap()
);

Consolidation Logic

  • Applies to memories not accessed in 7+ days
  • New confidence = confidence * (1 - decayRate), minimum 0.1
  • Skips memories already at minimum confidence (0.1)
Automatic consolidation runs every 6 hours via cron expression 0 */6 * * *.

memory::kg::add

Add or update knowledge graph entity with bidirectional relation tracking.
agentId
string
required
Agent ID for knowledge graph ownership
entity
object
required
Entity object to store
id
string
required
Unique entity identifier
type
string
Entity type (e.g., “person”, “project”, “concept”)
properties
object
Entity attributes as key-value pairs
relations
array
Array of relation objects
target
string
Target entity ID
type
string
Relation type (e.g., “works_on”, “reports_to”)
stored
boolean
True if entity was stored successfully
id
string
Entity ID
let result = iii.trigger("memory::kg::add", json!({
    "agentId": "agent-789",
    "entity": {
        "id": "person:alice",
        "type": "person",
        "properties": {
            "name": "Alice Johnson",
            "role": "Senior Engineer",
            "department": "Platform"
        },
        "relations": [
            { "target": "project:agentos", "type": "works_on" },
            { "target": "person:bob", "type": "reports_to" }
        ]
    }
})).await?;

println!("Stored entity: {}", result["id"].as_str().unwrap());

Bidirectional Relations

When adding relations, the function automatically creates inverse relations:
  • If A → B with type “works_on”, creates B → A with type “inverse:works_on”
  • Prevents duplicate inverse relations
  • Maintains graph consistency across updates

memory::kg::query

Traverse knowledge graph from a starting entity up to specified depth.
agentId
string
required
Agent ID for knowledge graph
entityId
string
required
Starting entity ID for traversal
depth
number
default:2
Maximum traversal depth (default: 2)
entities
array
Array of entity objects encountered during traversal
Query Knowledge Graph
let results = iii.trigger("memory::kg::query", json!({
    "agentId": "agent-789",
    "entityId": "person:alice",
    "depth": 3
})).await?;

for entity in results.as_array().unwrap() {
    println!("Entity: {} ({})",
        entity["id"].as_str().unwrap(),
        entity["type"].as_str().unwrap_or("unknown")
    );
}

Traversal Algorithm

  • Breadth-first search from starting entity
  • Tracks visited nodes to prevent cycles
  • Stops at specified depth or when all paths exhausted
  • Returns entities in order discovered

memory::session::list

List all sessions for a specific agent.
agentId
string
required
Agent ID to list sessions for
sessions
array
Array of session metadata objects
List Sessions
let sessions = iii.trigger("memory::session::list", json!({
    "agentId": "agent-123"
})).await?;

for session in sessions.as_array().unwrap() {
    println!("Session: {}", session["id"].as_str().unwrap());
}

memory::session::compact

Compact long sessions by summarizing old messages via LLM.
agentId
string
required
Agent ID
sessionId
string
required
Session ID to compact
threshold
number
default:30
Minimum messages before compaction
keepRecent
number
default:10
Number of recent messages to preserve
compacted
boolean
True if compaction was performed
summarized
number
Number of messages summarized
kept
number
Number of recent messages kept
summaryId
string
Memory ID of the generated summary
Compact Session
let result = iii.trigger("memory::session::compact", json!({
    "agentId": "agent-123",
    "sessionId": "session-456",
    "threshold": 50,
    "keepRecent": 15
})).await?;

if result["compacted"].as_bool().unwrap() {
    println!("Compacted {} messages into summary {}",
        result["summarized"].as_u64().unwrap(),
        result["summaryId"].as_str().unwrap()
    );
}

Compaction Process

  1. Check if session has ≥ threshold messages
  2. Split into: messages to summarize and recent messages to keep
  3. Fetch full message content from memory
  4. Chunk conversation text (max 80K chars per chunk)
  5. Generate summaries via llm::complete with claude-haiku
  6. Store combined summary as system message with importance 0.9
  7. Update session with summary + recent messages

memory::session::repair

7-phase validation and repair for corrupted session data.
agentId
string
required
Agent ID
sessionId
string
required
Session ID to repair
repaired
boolean
True if any repairs were performed
totalFixes
number
Total number of fixes applied
stats
object
Breakdown of repair statistics by type
Repair Session
let result = iii.trigger("memory::session::repair", json!({
    "agentId": "agent-123",
    "sessionId": "session-corrupted"
})).await?;

if result["repaired"].as_bool().unwrap() {
    println!("Applied {} fixes:", result["totalFixes"].as_u64().unwrap());
    let stats = &result["stats"];
    println!("  - Empty removed: {}", stats["emptyRemoved"].as_u64().unwrap());
    println!("  - Duplicates removed: {}", stats["duplicatesRemoved"].as_u64().unwrap());
    println!("  - Consecutive merged: {}", stats["consecutiveMerged"].as_u64().unwrap());
    println!("  - Orphaned refs: {}", stats["orphanedRefs"].as_u64().unwrap());
    println!("  - Reordered: {}", stats["reordered"].as_u64().unwrap());
    println!("  - Oversized detected: {}", stats["oversizedDetected"].as_u64().unwrap());
}

7 Repair Phases

  1. Remove Empty Messages: Filter messages with missing or empty IDs
  2. Deduplicate: Remove duplicate message IDs
  3. Merge Consecutive: Merge consecutive messages with same role (except system)
  4. Detect Orphans: Count message references without corresponding memory entries
  5. Fix Ordering: Repair out-of-order timestamps
  6. Detect Oversized: Flag messages with content > 500KB
  7. Update Session: Write repaired session with stats
Repair is idempotent - running multiple times produces the same result.

Cron Automation

The memory worker automatically runs maintenance tasks:
  • Consolidation: Every 6 hours (0 */6 * * *)
  • Eviction: Daily at 3 AM (0 3 * * *)
These can be manually triggered or configured via environment variables.

Build docs developers (and LLMs) love