agentcache / ARCHITECTURE.md
Yash030's picture
feat: migrate agentmemory to agentcache namespace, endpoints, and tools
12a6c9a
|
Raw
History Blame Contribute Delete
5.55 kB

agentcache-python β€” Architecture

Overview

A Python REST + WebSocket + MCP memory server backed by SQLite. Agents store observations scoped by (folderPath, agentId) pairs and retrieve context at session start. No Node.js, no external database, no build step.


Module Responsibilities

src/
β”œβ”€β”€ app.py              Flask application factory (create_app).
β”‚                       Initialises DB, embeddings, blueprints, WebSocket,
β”‚                       CORS hook, and background workers.
β”‚
β”œβ”€β”€ routes/             Flask blueprints β€” one per domain area.
β”‚   β”œβ”€β”€ __init__.py     register_blueprints(app) helper.
β”‚   β”œβ”€β”€ observations.py /observe, /agent/observe, /folders, /folder/observations
β”‚   β”œβ”€β”€ memories.py     /remember, /agent/remember, /memories, /forget
β”‚   β”œβ”€β”€ search.py       /search, /timeline
β”‚   β”œβ”€β”€ graph.py        /graph, /graph/stats, /graph/query, /graph/build
β”‚   β”œβ”€β”€ health.py       /livez, /health, /audit, /config/flags
β”‚   β”œβ”€β”€ mcp.py          GET+POST /mcp/tools
β”‚   └── migration.py    /migrate
β”‚
β”œβ”€β”€ functions.py        Core business logic.
β”‚                       observe(), folder_observe(), remember(), forget(),
β”‚                       folder_search(), folder_timeline(), health_check(),
β”‚                       export_data(), rebuild_index(), auto_forget(),
β”‚                       folder_graph_build(), KV scope registry.
β”‚
β”œβ”€β”€ db.py               StateKV β€” SQLite wrapper (WAL mode, kv_store + audit_log).
β”‚
β”œβ”€β”€ search.py           SearchIndex (BM25 + Porter stemmer + synonyms),
β”‚                       VectorIndex (cosine similarity, base64 float32),
β”‚                       GeminiEmbeddingProvider, HybridSearch (RRF).
β”‚
β”œβ”€β”€ workers.py          Daemon threads: index rebuild, auto-forget sweep,
β”‚                       graceful shutdown (SIGTERM/SIGINT).
β”‚
β”œβ”€β”€ viewer_helpers.py   make_viewer_response() β€” reads viewer/index.html,
β”‚                       injects nonce + version, sets CSP headers.
β”‚
β”œβ”€β”€ mcp_stdio.py        stdio MCP bridge: reads AGENTCACHE_URL and
β”‚                       AGENTCACHE_SECRET, proxies tool calls to the HTTP API.
β”‚
└── viewer/
    └── index.html      Single-file HTML dashboard (no bundler).

KV Scope Layout

All data lives in a single SQLite file (~/.agentcache/agentcache.db) in two tables:

  • kv_store(scope TEXT, key TEXT, value TEXT, PRIMARY KEY(scope, key)) β€” JSON values
  • audit_log(id, ts, agent_id, message) β€” write audit trail
Scope Content
mem:folders Global index of all (folderPath, agentId) pairs
mem:folder:{path}:{agent} Observations for one (folder, agent) pair
mem:foldermeta:{path}:{agent} Metadata for one pair (obsCount, lastUpdated, summary)
mem:memories Long-term global memories
mem:index:bm25 BM25 index shards (manifest + data chunks)
mem:audit Audit log entries (via record_audit())
mem:relations Knowledge graph edges
mem:sessions Legacy session objects (read-only for migration)
mem:obs:{session_id} Legacy session observations (read-only for migration)

Data Flow

Observation Ingestion

POST /agent/observe
  └─► folder_observe(kv, payload)
        1. Validate folderPath, agentId, text, timestamp
        2. normalize_folder_path() + validate_agent_id()
        3. strip_private_data()
        4. Cap text at 4000 chars
        5. Enforce MAX_OBS_PER_FOLDER cap
        6. Generate obs_id (fobs_...)
        7. Write FolderObservation to KV.folder_obs(fp, aid)
        8. Upsert folder metadata (KV.folder_meta)
        9. Upsert global folders index (KV.folders)
       10. Add to BM25 index (_bm25_index.add)
       11. Add to vector index if embedding provider is set
       12. Debounce persistence save (IndexPersistence.schedule_save)
       13. Write audit log entry (kv.commit_version)
       14. Broadcast via WebSocket (/stream/mem-live/viewer)
        └─► return {"observationId": obs_id}

Search

POST /search  or  POST /mcp/tools {name:"memory_recall"}
  └─► folder_search(kv, query, limit, folderPath?, agentId?)
        1. HybridSearch.search() β†’ BM25 + vector RRF fusion
        2. Load all (folder, agent) pairs from KV.folders
        3. Hydrate obs_ids from KV.folder_obs scopes
        4. Apply folderPath/agentId post-filters
        5. Also include matching global memories
        6. Sort by score descending, cap at limit

Memory Versioning

remember() scans existing memories for Jaccard similarity > 0.7. If found, old memory is marked isLatest=False and new memory sets parentId.


Authentication

All endpoints except /livez check AGENTCACHE_SECRET via timing-safe hmac.compare_digest Bearer token comparison. No secret β†’ no auth check.


WebSocket

/stream/mem-live/viewer broadcasts raw JSON payloads to connected viewers. The viewer's "Replay" tab subscribes to this stream for live observation updates.


Embedding Providers

Priority order (auto-selected at startup):

  1. GeminiEmbeddingProvider β€” if GEMINI_API_KEY or GOOGLE_API_KEY is set (768 dims)
  2. OpenAIEmbeddingProvider β€” if OPENAI_API_KEY is set (1536 dims)
  3. SentenceTransformerProvider β€” if AGENTCACHE_LOCAL_EMBEDDING_MODEL is set
  4. BM25-only fallback

Without an embedding provider, HybridSearch falls back to pure BM25.