Architecture
How aide-memory works internally. For users this is optional reading; the docs you actually need are Concepts, Reference, and Configuration.
Editor hooks fire ┌──────────────────┐ ┌────────────┐
──────────▶ │ .aide/memories/ │───▶│ SQLite │
│ (JSON files, │ │ (cache) │
│ one per memory) │ └────────────┘
└──────────────────┘ │
│ ▼
git push / pull ┌────────────┐
│ │ MCP server │
▼ │ (7 tools) │
teammates get └────────────┘
the same │
memories ▼
agent gets
nudged on
matching file
reads + editsStorage
Each memory is stored as a single JSON file under .aide/memories/:
.aide/memories/
preferences/
personal/ ← gitignored, per-developer
<uuid>.json
shared/ ← committed, team-shared
<uuid>.json
technical/
<uuid>.json
area_context/
<uuid>.json
guidelines/
<uuid>.jsonJSON files are the source of truth. SQLite is a rebuildable cache that provides indexing, FTS5 search, and per-machine recall statistics.
Why file-per-memory?
- Git is the sync mechanism. Each file can be merged, diffed, and reviewed in PRs.
- Deletion is simple: remove the file. No status field, no soft deletes.
- Personal preferences (gitignored) stay local. Shared memories travel with the repo.
- SQLite cache can be rebuilt from files at any time (
aide-memory sync import).
JSON file format (MemoryFile)
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"layer": "area_context",
"what": "Dashboard uses skeleton loading, not spinners",
"why": "Decided in sprint 3 planning",
"scope": "src/components/dashboard/**",
"context_label": "dashboard loading",
"contributor": "ahmed",
"tags": ["ui", "loading"],
"source": "conversation",
"shared": true,
"generated_by": { "tool": "claude-code", "model": null, "author_type": "ai" },
"derived_from": null,
"created_at": "2026-04-06T12:00:00.000Z",
"updated_at": "2026-04-06T12:00:00.000Z"
}SQLite cache fields (per-machine, not in JSON)
The SQLite table adds three fields that only make sense per machine, so they’re kept out of the JSON files:
| Field | Description |
|---|---|
id | Auto-increment row ID (not stable across cache rebuilds) |
recalled_count | How many times this memory has been recalled on this machine |
last_recalled_at | Timestamp of last recall on this machine |
Recall stats stay local: your teammate’s machine has its own count for the same memory, and neither count is committed to git.
SQLite location
~/.aide/projects/<hash>/memory.dbThe <hash> is the first 12 characters of the SHA-1 hash of the absolute project path. WAL journal mode is enabled for concurrent read access.
Recall flow
When the agent needs context for a code area:
1. PreToolUse hook fires
→ "5 memories exist for src/auth/middleware.ts"
2. Agent decides to call aide_recall
→ paths: ["src/auth/middleware.ts"]
3. Recall engine runs:
a. Load all memories from store (no status filter, file exists = active)
b. Filter by scope matching against provided paths (with parent inheritance, so querying `src/` returns memories scoped under `src/`)
c. If query provided, score by keyword relevance
d. Sort by **scope match first** (scoped beats project-wide), then **layer priority** (`area_context` > `technical` > `preferences` > `guidelines`), then **scope specificity** (deeper scopes rank higher) and keyword relevance
e. Cap at `recall.limit` (default 20)
f. **Layer-diversity rebalance**: when total results are below `recall.layerDiversityMinLimit` (default 5), under-represented layers swap up so each enabled layer is represented
g. Record recall for analytics
4. Results returned grouped by layerScope matching rules
Given a memory with scope and a query path:
| Scope | Query path | Match? | Why |
|---|---|---|---|
null or "project" | anything | yes | Project-wide |
src/auth/** | src/auth/middleware.ts | yes | Path under scope |
src/auth/** | src/auth/ | yes | Exact directory |
src/components/** | src/ | yes | Scope is within query (parent inheritance) |
src/auth/** | src/db/store.ts | no | Different subtree |
Parent inheritance means querying a broad path (like src/) returns memories scoped to any subdirectory under it.
Layer priority
Results are sorted by layer in this order:
area_context, most specific (decisions for a code area), highest prioritytechnical, facts about the stackpreferences, how someone likes to workguidelines, team-wide principles
Within the same layer, memories are ranked by:
- Scope match (scoped memories beat project-wide)
- Keyword relevance (if a query was provided)
- Scope specificity (deeper scopes rank higher)
Search pipeline
Search uses a three-tier fallback:
1. FTS5 (BM25 ranking)
↓ if unavailable or 0 results
2. LIKE matching (case-insensitive substring on what/why)
↓ if < 3 results and embeddings available
3. Semantic embedding search (cosine similarity)FTS5: Full-text search using SQLite’s FTS5 extension with BM25 ranking. Queries are escaped and tokenized. Index covers what and why fields.
LIKE fallback: Simple WHERE what LIKE '%keyword%' OR why LIKE '%keyword%'. Always available, no extensions needed.
Semantic search (optional): If an EmbeddingService is attached and fewer than 3 keyword results are found, the system generates an embedding for the query and finds similar memories by cosine similarity. Results below score 0.3 are discarded. Two backends are supported: a local @huggingface/transformers install (in optionalDependencies, downloads the model on first use) or a local Ollama server.
Sync
Git handles distribution of the JSON memory files; the local SQLite cache is rebuilt automatically from them at the right moments. There is no separate sync service.
At init:
aide-memory initcreates the.aide/memories/directory structure- Personal preferences (
.aide/memories/preferences/personal/) are gitignored - The SQLite cache (
.aide/cache/) is gitignored - A
post-checkoutgit hook is installed that rebuilds the cache from JSON files after everygit pullor branch switch
During a working session:
- The agent calls
aide_remember,aide_update, oraide_forget - The MCP server writes the JSON file under
.aide/memories/<layer>/ - The same call writes a row to the local SQLite cache (so subsequent
aide_recall/aide_searchis fast and indexed) - You commit the JSON file with the rest of your changes
When a teammate pulls:
git pullbrings in the new JSON files- The
post-checkoutgit hook fires and reconciles JSON to SQLite (newer-wins byupdated_at) - The teammate’s next file read in the relevant area triggers the PreToolUse hook, which surfaces “N memories exist for this path” and prompts
aide_recall - The agent calls
aide_recall, gets the newly-pulled memories, and proceeds
Sync operations
| Command | Direction | Description |
|---|---|---|
aide-memory sync import | JSON → SQLite | Full rebuild. Read all JSON files, reconcile with cache. |
aide-memory sync export | SQLite → JSON | Fill gaps. Create JSON files for any SQLite-only memories. |
| post-checkout hook | JSON → SQLite | Incremental. Runs after git pull or branch switch. |
Conflict resolution
When JSON files and SQLite disagree, the system compares updated_at timestamps. The newer version always wins. Conflicts are logged but never block operations.
No status field
The original design included an active/archived status field. This was removed. The rule is simpler:
- File exists = active. The memory is live and will be recalled.
- File deleted = gone. The memory no longer exists.
aide_forget deletes the JSON file and removes the SQLite row. There is no archive, no soft delete, no status transitions.
Memory layers
| Layer | Purpose | Scope | Committed |
|---|---|---|---|
preferences | How someone likes to work | Per-contributor | shared/ yes, personal/ no |
technical | Facts about the stack | Project-wide or scoped | yes |
area_context | Decisions for specific code areas | Always scoped | yes |
guidelines | Team principles | Project-wide | yes |
The preferences layer has a special directory split: shared/ (team-visible, committed) and personal/ (gitignored, local-only). The shared boolean on the memory controls which directory the file goes to. Other layers ignore this flag and live in their layer folder regardless.
Analytics
The analytics SQLite table tracks events (recalls, stores, searches) with timestamps. The aide-memory stats command aggregates:
- Total memory count
- Count by layer
- Top 5 most-recalled memories
- Breakdown by source (conversation, hook, agent_discovery, import)
- Stale count (0 recalls, created >30 days ago)
Analytics data lives only in the SQLite cache and is not exported to JSON files. Anonymized event tallies (event type, hashed machine id, platform, Node version) ship to PostHog by default; disable any time with AIDE_TELEMETRY=off. Memory content, code, file paths, and queries never leave your machine.
Cross-tool sync
Install in Claude Code and your memories are also available in Cursor. The same .aide/ directory is read by both tools. Add or edit a memory in one, the other tool’s agent reads the new content on the next recall.
Team sync via git
Memories committed to git are automatically available to team members:
# Your session captures a memory about test patterns
git add .aide/memories/
git commit -m "Store test pattern preferences"
git push
# Your teammate pulls
git pull
# Their agent sees the nudge immediately
# "3 memories exist for tests/**. Call aide_recall if relevant."No Docker, no sync service, no authentication. Just git.