# Memory Graph — User Guide

The Memory graph lets you store durable engineering knowledge alongside the code graph. Unlike code
nodes (which are auto-generated by the ingester), Memory nodes are authored by you or by AI agents
and survive re-ingestion. They answer the question: *"What do we know about this codebase that
isn't obvious from the source?"*

> **Why bother?** Without memory, your AI agent rediscovers the same facts every session — reading
> the same files, re-learning the same constraints, re-hitting the same bugs. Memory makes every
> session smarter by default.

---

## Node types and allowed properties

**Only the properties in this table may be set on Memory nodes. No extra properties are permitted.**

| Label       | Key props                      | All allowed properties                                                                          |
|-------------|--------------------------------|-------------------------------------------------------------------------------------------------|
| `:Memory`   | `project`                      | `project`                                                                                       |
| `:Decision` | `id`, `project`                | `id`, `project`, `title`, `topic`, `status`, `rationale`, `consequences`, `createdAt`, `updatedAt` |
| `:ADR`      | `id`, `project`                | `id`, `project`, `number`, `title`, `status`, `context`, `decision`, `consequences`, `createdAt`, `updatedAt` |
| `:Rule`     | `id`, `project`                | `id`, `project`, `title`, `topic`, `severity`, `description`, `createdAt`, `updatedAt`          |
| `:Context`  | `id`, `project`                | `id`, `project`, `title`, `topic`, `content`, `source`, `createdAt`, `updatedAt`                |
| `:Finding`  | `id`, `project`                | `id`, `project`, `title`, `topic`, `type`, `status`, `summary`, `evidence`, `createdAt`, `updatedAt` |
| `:Task`     | `id`, `project`                | `id`, `project`, `title`, `status`, `priority`, `description`, `createdAt`, `updatedAt`         |
| `:Risk`     | `id`, `project`                | `id`, `project`, `title`, `topic`, `severity`, `status`, `mitigation`, `createdAt`, `updatedAt` |
| `:Question` | `id`, `project`                | `id`, `project`, `title`, `status`, `answer`, `createdAt`, `updatedAt`                          |
| `:Idea`     | `id`, `project`                | `id`, `project`, `title`, `topic`, `status`, `notes`, `createdAt`, `updatedAt`                  |
| `:CodeRef`  | `project`, `targetType`, `key` | `project`, `targetType`, `key`                                                                  |

### Controlled values

| Node        | Property    | Allowed values                                         |
|-------------|-------------|--------------------------------------------------------|
| `:Decision` | `status`    | `proposed` · `accepted` · `rejected` · `superseded`   |
| `:ADR`      | `status`    | `draft` · `accepted` · `rejected` · `superseded`       |
| `:Rule`     | `severity`  | `hard` · `soft` · `recommendation`                     |
| `:Finding`  | `type`      | `bug` · `perf` · `constraint` · `security`             |
| `:Finding`  | `status`    | `open` · `resolved` · `obsolete`                       |
| `:Task`     | `status`    | `todo` · `doing` · `done` · `blocked` · `cancelled`    |
| `:Risk`     | `severity`  | `low` · `medium` · `high` · `critical`                 |
| `:Risk`     | `status`    | `open` · `mitigated` · `accepted` · `obsolete`         |
| `:Question` | `status`    | `open` · `answered` · `obsolete`                       |
| `:Idea`     | `status`    | `proposed` · `accepted` · `rejected` · `obsolete`      |

---

## The three most important types: Finding, Rule, Context

These three are the backbone of day-to-day agent guidance. They're queried automatically at the
start of every task before any code is touched.

### 🔴 Rule — constraints the agent must follow

Use `:Rule` for anything that restricts how code may be written or changed. An agent that reads
rules up front can't accidentally violate them.

**When to create one:**
- A pattern must always or never be used (e.g. "always use constructor injection")
- A dependency is forbidden
- A naming convention is enforced by a tool
- A security or compliance requirement applies

**Severity:**
- `hard` — the agent must never violate this, no exceptions
- `soft` — follow unless there's a strong reason not to; document the deviation
- `recommendation` — best practice; deviations are acceptable

**Example prompts to your agent:**

```
Remember: always use constructor injection, never field injection. Store this as a hard rule.
```

```
Add a rule: the GraphWriter class must never be called from outside the ingestion package.
```

```
We've decided the retry logic must always use exponential backoff. Record this as a hard rule 
and link it to the RetryHandler class.
```

**Cypher — write a Rule:**

```cypher
MERGE (m:Memory {project: 'my-project'})
MERGE (r:Rule {id: 'RULE-di-constructor-injection', project: 'my-project'})
SET r.title       = 'No field injection',
    r.topic       = 'dependency-injection',
    r.severity    = 'hard',
    r.description = 'Always use constructor injection. Field injection (@Autowired on fields) is forbidden.',
    r.createdAt   = coalesce(r.createdAt, datetime()),
    r.updatedAt   = datetime()
MERGE (m)-[:HAS_RULE]->(r)
RETURN r.id;
```

**Cypher — read all Rules (run before any code change):**

```cypher
MATCH (m:Memory {project: 'my-project'})-[:HAS_RULE]->(r:Rule)
RETURN r.id, r.severity, r.description
ORDER BY r.severity;
```

---

### 🟡 Finding — discovered bugs, limitations, and performance issues

Use `:Finding` to record anything you or an agent discovered about the codebase that future work
should know. Think of it as a shared bug log that the agent checks before touching any area.

**Types:**
- `bug` — incorrect behavior discovered
- `perf` — performance issue or bottleneck
- `constraint` — a technical limitation (e.g. "JavaParser can't resolve this pattern")
- `security` — a security concern

**Example prompts:**

```
We found that GraphWriter.writeMethod() doesn't handle null return types. 
Record this as a bug finding linked to that method.
```

```
JavaParser fails to resolve CALLS edges when Map.of() is used with mixed types. 
Record this as a constraint finding so we know not to rely on those edges.
```

```
The ParseService is slow on large files because it re-parses on every call instead of caching. 
Store this as a performance finding.
```

**Cypher — write a Finding:**

```cypher
MERGE (m:Memory {project: 'my-project'})
MERGE (f:Finding {id: 'FIND-parser-map-of-resolution', project: 'my-project'})
SET f.title     = 'Map.of() CALLS edges not resolved',
    f.topic     = 'call-graph',
    f.type      = 'constraint',
    f.status    = 'open',
    f.summary   = 'CALLS edges are not created for Map.of() call sites with mixed types due to JavaParser type inference limits.',
    f.evidence  = 'Verified on IngestionOrchestrator — edges absent after two ingestion passes.',
    f.createdAt = coalesce(f.createdAt, datetime()),
    f.updatedAt = datetime()
MERGE (m)-[:HAS_FINDING]->(f)
RETURN f.id;
```

**Cypher — read open Findings:**

```cypher
MATCH (m:Memory {project: 'my-project'})-[:HAS_FINDING]->(f:Finding)
WHERE f.status = 'open'
RETURN f.id, f.type, f.summary
ORDER BY f.type;
```

**Cypher — close a Finding (e.g. when the corresponding Task is resolved):**

```cypher
MATCH (f:Finding {id: 'FIND-<id>', project: 'my-project'})
SET f.status = 'resolved', f.updatedAt = datetime();
```

Use `obsolete` instead of `resolved` when the finding is no longer relevant (e.g. the affected code was deleted).

**Cypher — Findings for a specific code area (via CodeRef):**

```cypher
MATCH (f:Finding {project: 'my-project'})-[:REFERS_TO]->(ref:CodeRef)
      -[:RESOLVES_TO]->(target)
WHERE target.fqn CONTAINS 'GraphWriter'
RETURN f.id, f.type, f.summary, labels(target) AS targetType;
```

---

### 🔵 Context — explanatory notes

Use `:Context` to record *why* something works the way it does. When an agent reads context for a
module, it understands design intent without re-reading all the surrounding code.

**When to create one:**
- After a complex refactor — explain what changed and why
- When a non-obvious design choice was made for external reasons
- When a file or module has surprising behavior worth flagging
- After any significant change to config, schema, Cypher queries, or templates

**Example prompts:**

```
The IngestionOrchestrator uses a fixed thread pool and not a virtual thread executor because 
JavaParser is not thread-safe. Record this as context linked to IngestionOrchestrator.
```

```
We updated the upsert-method.cypher query to handle synthetic records in Java 25. 
Record this as context so future agents know why the query looks unusual.
```

```
The schema uses composite keys (fqn + project) instead of internal IDs because nodes must 
survive wipes and re-ingestion. Store this as context.
```

**Cypher — write Context:**

```cypher
MERGE (m:Memory {project: 'my-project'})
MERGE (c:Context {id: 'CTX-orchestrator-threadpool', project: 'my-project'})
SET c.title     = 'Why IngestionOrchestrator uses a fixed thread pool',
    c.topic     = 'concurrency',
    c.content   = 'IngestionOrchestrator uses a fixed thread pool (not virtual threads) because JavaParser is not thread-safe across instances. Each thread owns its own parser instance.',
    c.source    = 'design-decision-2025',
    c.createdAt = coalesce(c.createdAt, datetime()),
    c.updatedAt = datetime()
MERGE (m)-[:HAS_CONTEXT]->(c)
RETURN c.id;
```

**Cypher — read all Context:**

```cypher
MATCH (m:Memory {project: 'my-project'})-[:HAS_CONTEXT]->(c:Context)
RETURN c.id, c.content, c.source
ORDER BY c.id;
```

---

## Other node types

### Decision

Records a design or implementation choice. Agents follow `accepted` decisions and do not revive
`rejected` ones.

```
We decided to use MERGE instead of CREATE for all writes so re-ingestion is idempotent. 
Record this as an accepted decision.
```

```cypher
MERGE (m:Memory {project: 'my-project'})
MERGE (d:Decision {id: 'DEC-writes-merge-idempotent', project: 'my-project'})
SET d.title        = 'Use MERGE for all writes',
    d.topic        = 'ingestion',
    d.status       = 'accepted',
    d.rationale    = 'Ensures re-ingestion is idempotent — no duplicates, no manual cleanup needed.',
    d.consequences = 'Slightly slower than CREATE but eliminates duplicate-node bugs.',
    d.createdAt    = coalesce(d.createdAt, datetime()),
    d.updatedAt    = datetime()
MERGE (m)-[:HAS_DECISION]->(d)
RETURN d.id;
```

### ADR

For formal, numbered architecture decisions with broader scope.

```
Record ADR-1: we chose Memgraph over Neo4j because Memgraph is in-memory-first and 
ships with a free community edition.
```

### Task

Tracks unfinished work. Agents check open tasks at the end of a session.

```
There's a TODO to add retry logic to GraphWriter. 
Store this as an open task linked to GraphWriter.writeMethod().
```

**Task lifecycle — agents must follow this:**

```cypher
// Before starting work on a task
MATCH (t:Task {id: 'TASK-<id>', project: 'my-project'})
SET t.status = 'doing', t.updatedAt = datetime();

// After completing the task
MATCH (t:Task {id: 'TASK-<id>', project: 'my-project'})
SET t.status = 'done', t.updatedAt = datetime();
```

### Risk

Documents a known risk so it's surfaced before related changes are made.

```
There's a risk that incremental mode will miss deletions since it only checks lastModified. 
Store this as an open risk.
```

### Question

Captures open questions so they're not forgotten between sessions.

```
We're not sure whether --incremental mode handles file renames correctly. 
Record this as an open question.
```

### Idea

Holds proposed but not-yet-decided approaches.

```
Someone suggested switching to virtual threads when JavaParser adds thread safety. 
Record this as an open idea linked to IngestionOrchestrator.
```

---

## Linking memory to code

Every memory node should be linked to the code it's about via a `:CodeRef`. This survives
re-ingestion because the ingester refreshes `RESOLVES_TO` edges after each run.

```cypher
// Step 1: write the memory node (see examples above)

// Step 2: create the CodeRef and link it
MERGE (ref:CodeRef {project: 'my-project', targetType: 'Class', key: 'com.example.GraphWriter'})
MERGE (f:Finding {id: 'FIND-graphwriter-null-return', project: 'my-project'})
MERGE (f)-[:REFERS_TO]->(ref)
MERGE (ref)-[:RESOLVES_TO]->(c:Class {fqn: 'com.example.GraphWriter', project: 'my-project'})
RETURN ref;
```

`targetType` must be one of: `Code`, `Package`, `File`, `Class`, `Interface`, `Annotation`,
`Method`, `Field`. `key` is the matching identity (FQN for types/fields, signature for methods).

---

## Reading memory — useful queries

**Everything in memory for a project:**

```cypher
MATCH (m:Memory {project: 'my-project'})-[r]->(n)
RETURN type(r) AS rel, labels(n) AS type, n.id AS id
ORDER BY type(r);
```

**Recently added (last 24 hours):**

```cypher
MATCH (m:Memory {project: 'my-project'})-[r]->(n)
WHERE n.updatedAt >= datetime() - duration('P1D')
RETURN type(r) AS rel, n.id AS id, labels(n) AS type
ORDER BY n.updatedAt DESC;
```

**All memory linked to a class:**

```cypher
MATCH (n {project: 'my-project'})-[:REFERS_TO]->(ref:CodeRef)
      -[:RESOLVES_TO]->(c:Class {project: 'my-project'})
WHERE c.fqn CONTAINS 'GraphWriter'
RETURN labels(n) AS type, n.id AS id, 
       CASE labels(n)[0]
         WHEN 'Finding' THEN n.summary
         WHEN 'Rule'    THEN n.description
         WHEN 'Context' THEN n.content
         ELSE ''
       END AS detail;
```

**All open tasks:**

```cypher
MATCH (m:Memory {project: 'my-project'})-[:HAS_TASK]->(t:Task)
WHERE t.status IN ['todo', 'doing']
RETURN t.id, t.status, t.priority
ORDER BY t.priority;
```

**All hard rules:**

```cypher
MATCH (m:Memory {project: 'my-project'})-[:HAS_RULE]->(r:Rule {severity: 'hard'})
RETURN r.id, r.description;
```

---

## ID naming convention

```
RULE-<topic>-<name>       e.g.  RULE-di-constructor-injection
FIND-<topic>-<name>       e.g.  FIND-parser-map-of-resolution
CTX-<topic>-<name>        e.g.  CTX-orchestrator-threadpool
DEC-<topic>-<name>        e.g.  DEC-writes-merge-idempotent
ADR-<n>-<name>            e.g.  ADR-1-memgraph-over-neo4j
TASK-<topic>-<name>       e.g.  TASK-graphwriter-retry-logic
RISK-<topic>-<name>       e.g.  RISK-incremental-deletions
Q-<topic>-<name>          e.g.  Q-incremental-rename-handling
IDEA-<topic>-<name>       e.g.  IDEA-virtual-threads-parser
```

Keep IDs short, lowercase, hyphenated, and descriptive. They're the stable key — changing an ID
creates a new node.

---

## Tips

- **Agents write memory automatically** — if the agent template is configured correctly, agents
  create memory nodes at the end of every task without you asking. You can also ask explicitly:
  *"Save what we learned today as memory."*

- **Memory survives re-ingestion** — `--wipe-project-code` does NOT delete memory.
  Use `--wipe-project-memories` only when you want a clean slate.

- **One project, many sessions** — memory accumulates across all agent sessions. The longer you
  use the graph, the smarter each new session starts.

- **Verify after writing** — always run this to confirm memory was persisted:

  ```cypher
  MATCH (m:Memory {project: 'my-project'})-[r]->(n)
  WHERE n.updatedAt >= datetime() - duration('PT5M')
  RETURN type(r), n.id, labels(n);
  ```

- **Keep it signal, not noise** — memory is not a log. Store only what improves future decisions.
  Prefer a precise `:Rule` over a vague `:Context`.
