Context Graph Compressor v2
Convert a conversation into a structured, portable JSON state graph — preserving decisions, facts, problems, goals, code, assumptions, and relationships — so any LLM can resume from the most important conversational state at minimal token cost.
Honest scope: This preserves the most important state from a conversation. It is not a lossless transcript. Some nuance and detail will be lost. The goal is continuity, not reproduction.
Compression Modes
Infer from user intent. Default to compact.
| Mode | When | Target tokens |
|---|---|---|
compact |
Handoff to new chat, token saving (DEFAULT) | 400–800 |
readable |
Human summary, sharing with team, archiving | 1000–2000 |
compact triggers: "new chat", "hand off", "transfer", "save context", "too many tokens", "start fresh"
readable triggers: "summarize", "readable", "share", "export", "archive"
Node Types
| Code | Full | Use when |
|---|---|---|
F |
Fact | Explicitly stated technical truth — stack, versions, config, schema |
D |
Decision | A choice was made, or something was ruled out |
P |
Problem | A bug, error, or blocker — with or without resolution |
G |
Goal | What the user is trying to build or achieve |
C |
Code | A specific snippet, method, annotation, or fix — preserve verbatim |
A |
Assumption | Something inferred but never explicitly stated |
X |
Context | Open threads, deferred work, unresolved questions |
A (Assumption) explained: Facts are stated. Assumptions are inferred.
If the user never said "we're deploying to AWS" but every config decision implies it — that's an A node, not an F node. Assumptions matter because a new LLM might contradict them. Making them explicit prevents silent conflicts.
Node Status
Every node gets a st (status) field:
| Value | Use when |
|---|---|
active |
Currently in progress or relevant right now |
open |
Known issue or question, not yet addressed |
resolved |
Completed, fixed, answered |
deferred |
Explicitly postponed for later |
blocked |
Cannot proceed — waiting on something |
abandoned |
Tried and dropped; preserve so the new session doesn't retry it |
Rule: If status is unknown and the node is a fact or decision, omit st. Only set it when it meaningfully changes how a new LLM should treat the node.
Importance
| Code | Meaning |
|---|---|
h |
High — losing this breaks continuity |
m |
Medium — useful context, not critical |
l |
Low — drop in compact mode |
Graph Relationships
Nodes can have explicit relationships via a rel array. Use relationships when a simple parent-child
nesting does not capture the actual connection.
{ "from": "n2", "to": "n5", "type": "depends_on" }
| Type | Use when |
|---|---|
depends_on |
n2 cannot work correctly without n5 being in place |
caused_by |
n2 (a problem) was caused by n5 (a decision or fact) |
resolves |
n2 (a fix) resolves n5 (a problem) |
supersedes |
n2 replaces n5 — n5 is outdated but preserved for traceability |
references |
n2 mentions or relates to n5 without a causal link |
related_to |
Loose connection — same area, no precise relationship type fits |
When to use vs. parent-child nesting:
- Use nesting for "this detail belongs under this topic"
- Use
relfor cross-cutting dependencies that span topics
Relationships go in a top-level "rel" array in the graph, not inside individual nodes.
Confidence (optional)
Only use when inference was required to produce a node — i.e. something was not explicitly stated.
{ "conf": 0.85 }
| Range | Meaning |
|---|---|
| 0.9–1.0 | Explicitly stated in the conversation |
| 0.7–0.9 | Strongly implied by multiple signals |
| < 0.7 | Uncertain — consider whether the node should exist at all |
Rule: Omit conf entirely on nodes that are directly stated. Only include it on A (Assumption)
nodes or nodes where inference was clearly required. If confidence would be below 0.6, drop the node.
Conversation Size & Strategy
Do not rely on exact token estimates — message lengths vary widely. Use these signals instead:
| Signal | Strategy |
|---|---|
| Conversation clearly fits in one read | Single pass |
| Conversation is very long but manageable | Single pass with aggressive pruning |
| Conversation is too large to hold in context | Chunked compression |
| Extremely large — only critical state survives | Chunked + priority-only |
Chunked Compression
When the conversation exceeds what can be processed in one pass:
- Divide into segments at natural breakpoints (topic shifts, time gaps, phase changes)
- Compress each segment independently using the same schema. Prefix node IDs:
A-n1,B-n2 - Merge:
- Deduplicate: same topic across segments → one node, latest version wins
- Conflict: use
supersedesrelationship, keep both nodes, note the switch in the newer node's summary - Re-ID: renumber final nodes as
n1,n2... - Drop all
limportance nodes - Write one unified
handoff
- Add
"chunks": Xto output metadata
Token budget
Compact output: target 400–800 tokens, hard cap 1200.
If over budget: drop l nodes → trim m summaries → merge related siblings → never cut h nodes.
Conflict & Decision History
Do not silently overwrite changed decisions. When a decision was made, then changed:
- Keep both nodes
- The newer node's summary notes the switch: "Switched from X to Y"
- Link with
supersedesrelationship
{
"n": [
{ "id": "n3", "t": "D", "i": "m", "st": "abandoned", "s": "Initially chose PostgreSQL." },
{ "id": "n7", "t": "D", "i": "h", "st": "active", "s": "Switched to MySQL — portability requirement." }
],
"rel": [
{ "from": "n7", "to": "n3", "type": "supersedes" }
]
}
This preserves traceability. A new LLM seeing n3 with abandoned status won't re-suggest PostgreSQL.
Conversation State Preservation
Beyond summarizing history, capture the current state of the project:
Always include if present:
- Active goals (
G,st: active) — what the user is working toward right now - Open problems (
P,st: open) — unresolved bugs or blockers - Deferred work (
X,st: deferred) — explicitly postponed, must not be forgotten - Blocked items (
st: blocked) — what is stuck and why - Pending decisions — choices not yet made, framed as
Xnodes
The handoff field should reflect current state, not just history.
COMPACT Schema
{
"v": 2,
"mode": "compact",
"desc": "One tight sentence: topic and current state.",
"n": [
{
"id": "n1",
"t": "F",
"i": "h",
"s": "Spring Boot 3.2, Java 21, MySQL. Maven. Constructor injection throughout.",
"c": [
{ "id": "n1.1", "t": "A", "i": "m", "conf": 0.82, "s": "Assumed deploying to Linux server — all configs Unix-style paths." }
]
},
{
"id": "n2",
"t": "D",
"i": "h",
"st": "active",
"s": "JWT auth chosen over sessions. JwtAuthFilter extends OncePerRequestFilter."
},
{
"id": "n3",
"t": "P",
"i": "h",
"st": "resolved",
"s": "403 on all endpoints. Fix: .requestMatchers('/auth/**').permitAll() before anyRequest().authenticated()."
},
{
"id": "n4",
"t": "X",
"i": "m",
"st": "deferred",
"s": "Refresh token support explicitly postponed."
},
{
"id": "n5",
"t": "G",
"i": "h",
"st": "active",
"s": "Complete task CRUD endpoints with role-based access control."
}
],
"rel": [
{ "from": "n3", "to": "n2", "type": "caused_by" }
],
"handoff": "Resuming Spring Boot JWT API. Stack: Java 21, MySQL, JPA. Auth working. N+1 fixed with @EntityGraph. Active goal: task CRUD with RBAC. Refresh tokens deferred."
}
After outputting JSON, print one line:
~X tokens | Y nodes | Z relationships | estimated X% compression
READABLE Schema
Full field names, prose summaries, complete handoff prompt.
{
"context_graph": {
"metadata": {
"description": "...",
"mode": "readable",
"version": 2,
"node_count": 6,
"compressed_at": "ISO-8601"
},
"nodes": [
{
"id": "n1",
"title": "Descriptive label",
"type": "fact | decision | problem | goal | code | assumption | context",
"importance": "high | medium | low",
"status": "active | open | resolved | deferred | blocked | abandoned",
"summary": "2–4 sentences. Dense, no filler.",
"confidence": 0.95,
"children": []
}
],
"relationships": [
{ "from": "n2", "to": "n5", "type": "depends_on" }
],
"handoff_prompt": "You are resuming a conversation. Treat this graph as your working memory — prioritize nodes marked high importance and active/open status.\n\nCurrent state:\n- Active goals: ...\n- Open issues: ...\n- Deferred: ...\n\nFull graph: <paste JSON>\n\nAsk the user what they'd like to work on next."
}
}
Universal Compression Rules
| Rule | Detail |
|---|---|
| Code verbatim | Method names, class names, annotations, SQL — never paraphrase |
| Keep negatives | "Decided NOT to use Redis" is active state |
| Versions exact | Java 21 ≠ Java 17. Always preserve |
| Superseded ≠ deleted | Use supersedes + abandoned status instead of deletion |
| Assumptions explicit | If you inferred it, mark it A + add conf |
| Status over recency | A resolved old bug is less important than an open new one |
| No tone | Strip all conversational filler |
| Open threads = nodes | Never discard an unresolved question |
Benchmarks (realistic ranges)
| Conversation | Input tokens | Output tokens | Nodes | Compression |
|---|---|---|---|---|
| Short debug session (10–20 turns) | 3k–8k | 200–400 | 2–4 | ~92–96% |
| Medium dev session (50–100 turns) | 15k–40k | 400–700 | 5–10 | ~97–98% |
| Long project session (200–400 turns) | 60k–150k | 600–1000 | 10–18 | ~99% |
| Chunked large session (400k+ tokens) | 400k+ | 800–1200 | 12–20 | ~99.7% |
These are estimates. Actual results depend on conversation density and topic variety.
How to Use Output in a New Chat
Context from previous session:
<paste compact JSON here>
Resume from this state.
The new session treats the graph as working memory. Nodes marked h importance and active/open
status are the highest priority. Nodes marked abandoned signal things not to re-suggest.
Extensibility
The v field and meta object allow future versions to add node types, relationship types, or
fields without breaking parsers that consume earlier versions.
{ "v": 2, "meta": {} }
New node types and relationship types may be added in future versions. Parsers should treat unknown values gracefully rather than erroring.
Edge Cases
- < 5 turns: 1–2 nodes max. Skip relationships entirely.
- Code-heavy session: One
Cnode per major class/method. Signatures verbatim. - Contradictions without explicit resolution: Create both nodes, link with
related_to, note the conflict in summaries. - Plain text input: Treat as conversation. Same process.
- Multiple unrelated topics: Sibling root nodes. No forced hierarchy.
- Assumptions with low confidence (< 0.6): Drop the node entirely rather than publish unreliable state.