Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

agent-memory-engine

Architecture banner

An event-sourced long-term memory engine for AI agents, written in Rust. Every observation an agent makes is appended as an immutable event to a JSONL log (episodic memory). A reflect pass compacts recent episodes into higher-level semantic memories (a summary plus extracted facts). Retrieval is hybrid: BM25 keyword scoring blended with embedding cosine similarity, further weighted by time decay and stored importance. It runs fully offline in --mock mode, or against any OpenAI-compatible endpoint (Ollama by default) for real embeddings and LLM-backed reflection.

flowchart LR
    subgraph CLI
        R[remember] --> LOG
        RF[reflect] --> LOG
        RC[recall] --> RANK
        D[demo]
    end
    LOG[("events.jsonl<br/>append-only log<br/>episodes + semantic")] --> RF
    LOG --> RANK
    RF -->|"LLM (chat/completions)<br/>or extractive fallback"| SEM["semantic memories<br/>summary + facts"]
    SEM --> LOG
    E["embedder<br/>OpenAI-compatible /embeddings<br/>or feature-hashing mock"] --> R
    E --> RF
    E --> RC
    RANK["hybrid scorer<br/>alpha·BM25 + (1-alpha)·cosine<br/>+ time decay + importance"] --> OUT["ranked memories<br/>with score breakdown"]
Loading

How it works

  • Episodic log (src/store.rs): each memory is a JSON event {id, ts, kind, text, importance, tags, embedding, sources} appended to memory/events.jsonl. Nothing is ever mutated or deleted; semantic memories reference the episode ids they were folded from.
  • Reflection (src/reflect.rs): finds episodes not yet covered by any semantic memory and compacts them. With a live endpoint it asks a chat model for {"summary", "facts": [...]}; in --mock mode (or on any API failure) it falls back to a deterministic extractive pass: the top-importance episodes become the summary, and episodes phrased as durable statements ("X is/prefers/uses Y") become facts.
  • Embeddings (src/embed.rs): OpenAI-compatible POST /embeddings, or a deterministic 256-dim feature-hashing embedder (unigrams + bigrams, L2-normalized) in mock mode. Embeddings are computed once at write time and stored in the event.
  • Scoring (src/score.rs): total = w_rel · (alpha·bm25 + (1-alpha)·cosine) + w_rec · 0.5^(age/half_life) + w_imp · importance. All weights are CLI flags; recall output shows the per-component breakdown.

Project structure

src/
  main.rs      CLI (clap): remember / reflect / recall / demo
  store.rs     append-only JSONL event log
  embed.rs     API embedder + feature-hashing mock embedder
  score.rs     BM25, cosine, hybrid ranking (+ unit tests)
  reflect.rs   reflection compaction (LLM-backed, extractive fallback)

Usage

cargo build

# End-to-end tour, fully offline: seeds a scenario, reflects, answers 3 queries
cargo run -- demo --mock

# Store an episode
cargo run -- --mock remember "Dana prefers short PRs" --importance 0.7 --tags user,preference

# Compact recent episodes into semantic memories
cargo run -- --mock reflect

# Hybrid retrieval (k results, tunable weights)
cargo run -- --mock recall "what does Dana prefer?" -k 3
cargo run -- --mock recall "staging outage" --alpha 0.8 --w-recency 0.4 --half-life 24

cargo test

Memory lives in ./memory/events.jsonl (override with --dir). The demo command resets that log so it is repeatable.

Using real models (Ollama or any OpenAI-compatible API)

Without --mock, embeddings and reflection call an OpenAI-compatible endpoint:

export OPENAI_BASE_URL=http://localhost:11434/v1   # default; Ollama
export EMBED_MODEL=nomic-embed-text                 # default
export CHAT_MODEL=llama3.2                          # default, used by reflect
# OPENAI_API_KEY is sent as a Bearer token if set (needed for hosted APIs)

ollama pull nomic-embed-text llama3.2
cargo run -- remember "..." && cargo run -- reflect

If the chat call fails, reflect falls back to the extractive compactor rather than aborting.

Notes and limits

  • The mock embedder is a hashing trick, not a learned model: it captures vocabulary overlap, not meaning. It exists so the full loop is testable and demoable offline.
  • Retrieval is a linear scan over the log; fine for personal-agent scale, not a vector database.
  • Reflection is one-shot compaction, not a hierarchy of reflections over reflections.

About

Event-sourced long-term memory for AI agents: reflection compaction, hybrid retrieval, time-decay scoring (Rust)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages