Files
Hy-Memory/docs/pro_graph_design.md
T
2026-10-16 16:26:27 +08:00

269 lines
8.5 KiB
Markdown

# Graph Memory Architecture and Operations
This document describes the Graph features implemented by the current SDK.
It is intended for operators enabling Graph and maintainers working on
`pipelines/_graph`, `reader_base.py`, or a GraphStore backend.
Graph is an optional, fail-open enhancement. Vector retrieval remains the
source of truth: if Graph is unavailable, incomplete, or disabled, normal
memory search must continue to work.
## 1. Enabling Graph
Graph availability depends on the processing mode:
| Mode | Graph behavior |
|---|---|
| `lite` | Disabled |
| `pro` | Controlled by `MEMORY_ENABLE_GRAPH`, default `false` |
| `ultra` | Enabled |
The default backend is embedded Kuzu. Neo4j is available through the `graph`
optional dependency.
```bash
# Embedded, single-process deployment
MEMORY_MODE=pro
MEMORY_ENABLE_GRAPH=true
MEMORY_GRAPH_PROVIDER=kuzu
MEMORY_GRAPH_DB_PATH=/data/hy-memory/kuzu
# Multi-process or remote deployment
MEMORY_GRAPH_PROVIDER=neo4j
NEO4J_URL=bolt://localhost:7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=secret
NEO4J_DATABASE=neo4j
```
Kuzu permits only one process to open a database path. Use Neo4j when several
workers must share the same Graph.
## 2. Write-side projection
When Graph and `MEMORY_GRAPH_WRITE` are enabled, persisted L2 facts are
projected into Graph asynchronously. Projection failures are logged and do not
roll back the VDB write.
### 2.1 Node model
An L2 fact is represented as `Memory(layer="l2_fact")`, not as `VdbRef`.
This allows the node to participate in Topic, Entity, relation, and temporal
edges. `VdbRef` remains the lightweight target used by L6 `DERIVED_FROM`
evidence links.
Graph projection does not replace the VDB. Content, vectors, event time, and
normal retrieval remain VDB-owned.
### 2.2 Edge sources
Current relation sources are:
| Relation | Source | Additional LLM call |
|---|---|---|
| `co_occurs` | L2 facts created in one write batch | No |
| `semantic_neighbor` | Reconcile candidates above `MEMORY_GRAPH_EDGE_MIN_SCORE` | No |
| `causes` | Reconcile `related` output | No additional call |
| `elaborates` | Reconcile `related` output | No additional call |
| `contrasts` | Reconcile `related` output | No additional call |
| `same_series` | Reconcile `related` output | No additional call |
| `TAGGED_WITH` | Memory tags projected to Topic nodes | No new memory LLM call |
| `MENTIONS` | NER entities projected to Entity nodes | No |
| `TEMPORAL_NEXT` | Optional event-time ordering | No |
`co_occurs` is bounded by `MEMORY_GRAPH_CO_OCCURS_MAX_BATCH` to prevent an
O(n²) edge explosion. `semantic_neighbor` is inexpensive but mostly repeats
information already present in vector similarity; typed and co-occurrence
relations usually carry more structural information.
### 2.3 Lifecycle synchronization
Graph state follows memory lifecycle changes:
- ADD projects the new active fact and its edges.
- UPDATE refreshes the projected node.
- SUPERSEDE and SHADOW retire the old projection from active retrieval.
- Search filters inactive or shadowed nodes even if stale edges remain.
Projection code lives in `hy_memory/pipelines/_graph/projector.py` and is
scheduled from `hy_memory/pipelines/writer.py`.
## 3. Read-side Graph usage
`legacy` and `hybrid` readers share the same Graph post-processing. The readers
differ only in candidate recall: `legacy` uses dense vectors; `hybrid` adds the
keyword/BM25 channel.
Enable Graph post-processing with:
```bash
MEMORY_GRAPH_RERANK_ENABLED=true
```
All Graph operations are fail-open. Exceptions return the pre-Graph candidate
order unless a test explicitly requests strict behavior.
### 3.1 A — Support propagation
Support propagation reranks candidates already present in the expanded recall
pool. It never adds a new candidate.
For candidate `v`, the implementation computes bounded support from recalled
neighbors and applies a small score adjustment:
```text
support(v) = sum(edge_prior(u,v) * score(u)) / sqrt(pool_degree(v))
score'(v) = score(v) + beta * min(support(v), support_cap)
```
Relevant settings:
```bash
MEMORY_GRAPH_PROPAGATE_BETA=0.15
MEMORY_GRAPH_PROPAGATE_ITERS=1
MEMORY_GRAPH_SUPPORT_CAP=1.0
```
### 3.2 B — Bounded relation expansion
Relation expansion may nominate nodes outside the initial pool. Admission is
bounded by all of the following:
- traversal hop limit;
- seed score floor;
- prefilter and expansion limits;
- relation-type quota;
- semantic hard gate `sim(candidate) >= sim(seed) - delta`;
- final multi-channel scoring.
Admitted items are merged into a seed by default, so they do not consume an
extra top-k slot.
Core settings and current defaults:
```bash
MEMORY_GRAPH_RELAX_GAMMA=0.3
MEMORY_GRAPH_SEED_FLOOR=0.5
MEMORY_GRAPH_MAX_EXPAND=20
MEMORY_GRAPH_EXPAND_HOPS=2
MEMORY_GRAPH_EXPAND_PREFILTER_TOP_N=50
MEMORY_GRAPH_SEED_SIM_DELTA=0.2
MEMORY_GRAPH_EXPAND_MERGE_SEED=true
```
Set `MEMORY_GRAPH_RELAX_GAMMA=0` to disable relation expansion while retaining
support propagation and relation paths.
### 3.3 C — Relation paths
Relation paths preserve connection semantics that would be lost in a flat
memory list. The shared reader stores them independently from normal memories
in its diagnostic `ReadResponse.extra["related_paths"]` field:
```python
read_response.extra["related_paths"]
```
Each path contains the ordered nodes and relation types. Temporal edges also
include direction (`earlier -> later`).
```bash
MEMORY_GRAPH_PATH_ENABLED=true
MEMORY_GRAPH_PATH_HIGH_FLOOR=0.5
```
### 3.4 Relation-type priors
The reader weights an edge by both its stored weight and a relation-type prior.
Current priors are defined in `pipelines/_graph/rerank.py`:
| Relation | Prior |
|---|---:|
| `causes` | 0.90 |
| `elaborates` | 0.90 |
| `same_series` | 0.85 |
| `contrasts` | 0.70 |
| `temporal_next` | 0.70 |
| `co_occurs` | 0.55 |
| `episode` | 0.55 |
| untyped `related` | 0.50 |
| `semantic_neighbor` | 0.35 |
Higher priors are reserved for relations that provide information not already
encoded by vector similarity. The degree normalization and support cap prevent
high-degree nodes from dominating ranking.
## 4. Temporal edges and episodes
`MEMORY_GRAPH_TEMPORAL_SPINE=true` creates directed `TEMPORAL_NEXT` edges using
VDB `memory_at` values. Graph nodes do not own canonical event time; projection
resolves timestamps from VDB before building the spine.
Episodes are separate from Graph. `episode_id` groups memories belonging to one
discussion and can provide bounded candidate support even when Graph is off.
```bash
MEMORY_EPISODE_RECALL_ENABLED=false
MEMORY_EPISODE_RECALL_WEIGHT=0.7
MEMORY_EPISODE_SUMMARY_ENABLED=false
```
Detailed Episode and summary settings are listed in `docs/env_reference.md`.
## 5. Backfill and rebuild
Existing VDB facts can be projected with:
```python
client.build_memory_graph(
user_id="u1",
agent_id=None, # None means all agents for the user
clear_first=False,
)
```
Backfill can reconstruct:
- L2 Memory nodes;
- Topic and Entity projections;
- `semantic_neighbor` edges from stored vectors;
- optional `TEMPORAL_NEXT` edges.
Backfill cannot reconstruct batch `co_occurs` edges or LLM typed relations,
because the original write-batch and reconcile decisions are not recoverable.
Use `clear_first=True` after changing projection rules. It clears the selected
L2 projection and orphaned Topic/Entity nodes without deleting System 2 L6
schemas.
## 6. Observability
The shared reader records internal diagnostics in:
```python
read_response.extra["graph_rerank"] # edge, propagation, and admission stats
read_response.extra["related_paths"] # decorated relation paths
```
`HyMemoryClient.search()` does not currently forward these two internal fields.
For public operational visibility, use pipeline traces: read-side Graph work is
recorded as `READ_GRAPH_RERANK`, while write projection reports `GRAPH_PROJECT`
statistics and errors.
## 7. Implementation map
| Responsibility | Module |
|---|---|
| Projection and lifecycle synchronization | `hy_memory/pipelines/_graph/projector.py` |
| Support, expansion, paths, priors | `hy_memory/pipelines/_graph/rerank.py` |
| Reader integration | `hy_memory/pipelines/reader_base.py` |
| Write scheduling | `hy_memory/pipelines/writer.py` |
| Relation output parsing | `hy_memory/agent/reconciler.py` |
| GraphStore interface | `hy_memory/data/graph_store_base.py` |
| Kuzu backend | `hy_memory/data/graph_store_kuzu.py` |
| Neo4j backend | `hy_memory/data/graph_store_neo4j.py` |
| Graph backfill API | `HyMemoryClient.build_memory_graph()` |
Tests covering these contracts live primarily in `tests/test_memory_graph.py`,
`tests/test_graph_edge_idempotency.py`, and `tests/test_profile_evidence.py`.