Spaces:
Running
agentmemory-python β Agent Instructions
What this project is
A Python REST + WebSocket + MCP memory server backed by SQLite. No Node.js, no iii-engine, no Dolt. Agents use it to store observations, memories, lessons, and slots, and to retrieve context at session start.
Project layout
src/
βββ app.py Flask server β all endpoints, WebSocket broadcaster
βββ db.py SQLite StateKV β WAL mode, audit_log table
βββ functions.py Core business logic (observe, remember, search, context)
βββ search.py BM25 index + Gemini vector index + HybridSearch (RRF)
βββ viewer/
βββ index.html Single-file HTML dashboard
sync.py HuggingFace dataset backup/restore
Dockerfile HF Space container definition
start.sh Boot: restore DB β start server β start sync loop
requirements.txt flask, flask-sock, requests, websockets, python-dateutil, huggingface_hub
Running
pip install -r requirements.txt
python src/app.py
# Server on http://localhost:3111
# Viewer at http://localhost:3111/viewer
No build step. No external database. SQLite file lives at ~/.agentmemory/agentmemory.db.
Architecture
Storage β src/db.py
StateKV wraps a single SQLite file with two tables:
kv_store(scope TEXT, key TEXT, value TEXT, PRIMARY KEY(scope, key))β all data as JSON, namespaced by scopeaudit_log(id, ts, agent_id, message)β write audit trail (replaces Dolt versioning)
Key scopes (defined in functions.py KV class):
| Scope | Content |
|---|---|
mem:sessions |
Session objects |
mem:obs:{session_id} |
Observations for a session |
mem:memories |
Long-term memories |
mem:lessons |
Lessons with confidence scores |
mem:slots |
Pinned memory slots |
mem:relations |
Knowledge graph edges |
mem:actions |
Work items / actions |
Business logic β src/functions.py
Global state:
_bm25_index/_vector_indexβ in-memory search indexes, rebuilt from DB on startup if empty_hybrid_searchβ combines BM25 + vector; only initialized when Gemini key is set_stream_broadcasterβ WebSocket broadcast callback injected byapp.py
Observation pipeline:
raw payload β strip_private_data() β build_synthetic_compression()
β stored in kv_store β BM25-indexed β vector-indexed (if key set)
β audit_log entry β WebSocket broadcast
Memory versioning: remember() checks Jaccard similarity against existing memories. If > 0.7 match found, new memory supersedes old (isLatest=False on old, parentId set on new).
Context compilation (context()): assembles pinned slots β project profile β lessons (scored by confidence Γ project match) β past session summaries, capped at TOKEN_BUDGET tokens (estimated at len/3).
Lessons: fingerprinted by SHA-256 of content. Duplicate saves strengthen confidence (+0.1 Γ (1 - conf)). Weekly decay reduces confidence by decayRate Γ weeks; soft-deleted at β€ 0.1 confidence with 0 reinforcements.
Search β src/search.py
SearchIndex: BM25 with custom Porter stemmer. Persisted tokv_storein sharded 2MB chunks viaIndexPersistence.VectorIndex: cosine similarity over Gemini 768-dim embeddings stored as base64-encoded float32 arrays.HybridSearch: fuses BM25 + vector scores with RRF (k=60, reciprocal rank fusion).
Server β src/app.py
Boot order:
- Initialize
StateKV(SQLite) - Initialize embedding provider (Gemini if key set)
- Initialize
IndexPersistence - Rebuild BM25/vector index if empty (background thread)
- Start Flask on
III_REST_PORT(default 3111)
Auth: all endpoints check AGENTMEMORY_SECRET via timing-safe hmac.compare_digest Bearer token comparison if the env var is set. /livez is always open.
WebSocket at /stream/mem-live/viewer broadcasts raw + compressed observations to connected viewers.
MCP Tools
The server exposes 31 MCP tools via GET /agentmemory/mcp/tools (schema) and POST /agentmemory/mcp/tools (execution).
| Tool | Description | Status |
|---|---|---|
memory_recall |
Search past session observations | Working |
memory_save |
Save long-term memory (concepts/files as string or array) | Working |
memory_sessions |
List recent sessions | Working |
memory_sessions_list |
Retrieve all memory sessions | Working |
memory_smart_search |
Hybrid semantic+keyword search | Working |
memory_timeline |
Chronological observations | Working |
memory_observations |
Get observations for session | Working |
memory_profile |
User/project profile | Working |
memory_lessons |
List saved lessons | Working |
memory_lesson_save |
Save lesson from session | Working |
memory_lesson_recall |
Search lessons by query | Working |
memory_lesson_search |
Search lessons (keywords) | Working |
memory_consolidate |
Summarize sessions, extract memory | Working |
memory_reflect |
Reflect on session, update context | Working |
memory_diagnose |
Health check subsystems | Working |
memory_forget |
Delete memory/session/observations | Working |
memory_export |
Export all data as JSON | Working |
agent_observe |
Log agent execution observation | Working |
agent_remember |
Save agent memory to long-term | Working |
memory_antigravity_sync |
Sync Antigravity transcripts | Working |
memory_antigravity_sync_all |
Master sync: transcript + crystallize + reflect | Working |
memory_slot_list |
List all pinned memory slots | Working |
memory_slot_get |
Retrieve a specific pinned slot | Working |
memory_slot_create |
Create/overwrite pinned slot | Working |
memory_slot_append |
Append text content to slot | Working |
memory_slot_replace |
Replace slot content | Working |
memory_slot_delete |
Delete pinned memory slot | Working |
memory_action_create |
Create a new work item / action | Working |
memory_action_update |
Update fields of existing action | Working |
memory_frontier |
Get active/pending actions | Working |
memory_crystallize |
Summarize session observations | Working |
MCP stdio wrapper: src/mcp_stdio.py reads AGENTMEMORY_URL and AGENTMEMORY_SECRET from environment variables dynamically.
Consistency rules
When adding a REST endpoint:
- Add the route in
src/app.py - Update
API Referencesection inREADME.md - Add the MCP tool in
src/app.pyMCP dispatch if it should be agent-callable
When adding an MCP tool:
- Add the schema to the
GET /mcp/toolsresponse insrc/app.py - Add the handler case to the
POST /mcp/toolsdispatch insrc/app.py - Update the tool table in
README.md - Update
AGENTS.mdtool list
When changing data scopes:
- Update the
KVclass insrc/functions.py - Update the scope table in this file
Code patterns
Adding a new KV scope
class KV:
your_scope = "mem:your-scope"
@staticmethod
def your_dynamic_scope(id: str) -> str:
return f"mem:your-scope:{id}"
Adding a REST endpoint
@app.route('/agentmemory/your-path', methods=['POST'])
def your_endpoint():
if AGENTMEMORY_SECRET:
auth = request.headers.get('Authorization', '')
if not hmac.compare_digest(auth, f'Bearer {AGENTMEMORY_SECRET}'):
return jsonify({'error': 'Unauthorized'}), 401
body = request.get_json(silent=True) or {}
# validate fields explicitly β never pass raw body to functions
result = your_function(kv, body.get('field'))
return jsonify(result), 200
Adding an MCP tool schema
In the GET /mcp/tools handler, add to the tools list:
{
"name": "memory_your_tool",
"description": "What it does",
"inputSchema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "..."}
},
"required": ["query"]
}
}
In the POST /mcp/tools handler, add a case:
elif tool_name == 'memory_your_tool':
query = args.get('query', '')
result = your_function(kv, query)
return jsonify({'content': [{'type': 'text', 'text': json.dumps(result)}]})
Environment variables
| Variable | Default | Purpose |
|---|---|---|
III_REST_PORT / PORT |
3111 |
Server port |
GEMINI_API_KEY / GOOGLE_API_KEY |
β | Enables vector search + Gemini LLM |
AGENTMEMORY_SECRET |
β | Bearer token auth |
AGENT_ID |
β | Default agent ID |
AGENTMEMORY_AGENT_SCOPE=isolated |
β | Filter data to current agent |
MAX_OBS_PER_SESSION |
500 |
Observations hard cap |
TOKEN_BUDGET |
2000 |
Context compilation cap |
GRAPH_EXTRACTION_ENABLED |
false |
Graph extraction (needs LLM) |
CONSOLIDATION_ENABLED |
false |
Consolidation (needs LLM) |
AGENTMEMORY_AUTO_COMPRESS |
false |
LLM compression |
HF_TOKEN |
β | HuggingFace sync |
AGENTMEMORY_DATASET_REPO |
β | HF dataset repo for backup |
HuggingFace Space deployment
Data flow: agentmemory.db (SQLite) β sync.py β HF dataset repo.
sync.py uses mtime fingerprinting (_quick_hash) to detect changes before uploading. Backup only runs when the DB actually changed. Restore uses hf_hub_download for targeted file fetches rather than full snapshot_download.
start.sh sequence:
- Restore
agentmemory.dbfrom dataset repo - Start
python src/app.pyin background - Run
sync.pyin a loop (backup every ~60s if changed)
Viewer β src/viewer/index.html
Single-file HTML dashboard, served directly by Flask at /viewer. No build step, no bundler.
Tabs: Dashboard, Sessions, Memories, Graph, Timeline, Lessons, Slots, Replay.
Graph tab (loadGraph()): fetches sessions + memories, groups by project path into folder nodes. Edges connect folders that share concepts or parent path segments. Each folder node gets a unique color via folderColor(id) β a hash-to-hex function that converts the folder path string into a distinct HSL color. The simulation uses force-directed physics with per-node-count repulsion tuning.
No tests yet
No test runner is configured. When adding tests, use pytest β it's the standard Python choice and requires no extra config for basic test discovery (test_*.py files).