DocumentationArchitecture

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 + edits

Storage

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>.json

JSON 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:

FieldDescription
idAuto-increment row ID (not stable across cache rebuilds)
recalled_countHow many times this memory has been recalled on this machine
last_recalled_atTimestamp 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.db

The <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 layer

Scope matching rules

Given a memory with scope and a query path:

ScopeQuery pathMatch?Why
null or "project"anythingyesProject-wide
src/auth/**src/auth/middleware.tsyesPath under scope
src/auth/**src/auth/yesExact directory
src/components/**src/yesScope is within query (parent inheritance)
src/auth/**src/db/store.tsnoDifferent 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:

  1. area_context, most specific (decisions for a code area), highest priority
  2. technical, facts about the stack
  3. preferences, how someone likes to work
  4. guidelines, team-wide principles

Within the same layer, memories are ranked by:

  1. Scope match (scoped memories beat project-wide)
  2. Keyword relevance (if a query was provided)
  3. 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:

  1. aide-memory init creates the .aide/memories/ directory structure
  2. Personal preferences (.aide/memories/preferences/personal/) are gitignored
  3. The SQLite cache (.aide/cache/) is gitignored
  4. A post-checkout git hook is installed that rebuilds the cache from JSON files after every git pull or branch switch

During a working session:

  1. The agent calls aide_remember, aide_update, or aide_forget
  2. The MCP server writes the JSON file under .aide/memories/<layer>/
  3. The same call writes a row to the local SQLite cache (so subsequent aide_recall / aide_search is fast and indexed)
  4. You commit the JSON file with the rest of your changes

When a teammate pulls:

  1. git pull brings in the new JSON files
  2. The post-checkout git hook fires and reconciles JSON to SQLite (newer-wins by updated_at)
  3. 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
  4. The agent calls aide_recall, gets the newly-pulled memories, and proceeds

Sync operations

CommandDirectionDescription
aide-memory sync importJSON → SQLiteFull rebuild. Read all JSON files, reconcile with cache.
aide-memory sync exportSQLite → JSONFill gaps. Create JSON files for any SQLite-only memories.
post-checkout hookJSON → SQLiteIncremental. 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

LayerPurposeScopeCommitted
preferencesHow someone likes to workPer-contributorshared/ yes, personal/ no
technicalFacts about the stackProject-wide or scopedyes
area_contextDecisions for specific code areasAlways scopedyes
guidelinesTeam principlesProject-wideyes

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.