| name | context-graph-compressor |
| version | 2 |
| description | Compress any conversation into a portable JSON context graph for handoff to a new chat or LLM. Trigger on: "compress this chat", "save context", "new chat", "hand off", "transfer context", "too many tokens", "start fresh", "export conversation", "context graph", "continue in new session". Also trigger proactively when the user mentions starting over in a long session.
|
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
rel for 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
supersedes relationship, keep both nodes, note the switch in the newer node's summary
- Re-ID: renumber final nodes as
n1, n2...
- Drop all
l importance nodes
- Write one unified
handoff
- Add
"chunks": X to 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
supersedes relationship
{
"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
X nodes
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
C node 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.