Master Graph Engineering With Opus 5 (Exact Config)

Every episode you ingest can trigger an extraction call.
If you feed thousands of conversations, documents, events, or code changes into a knowledge graph, you're effectively asking a frontier model to repeatedly perform the same mechanical job:
Read text → extract entities → extract relationships → attach timestamps → write to graph.
Do that naively at full model rates and your memory layer can become more expensive than the application itself.
The trick isn't to stop using a frontier model.
It's to stop wasting frontier-model intelligence on work that doesn't need it.
With Claude Opus 4.8, prompt caching can reduce repeated input costs dramatically, while Batch API can cut non-time-sensitive workloads by 50%. Opus 4.8 is currently priced at $5/M input tokens and $25/M output tokens, while cached reads are $0.50/M and Batch input is $2.50/M.
That changes how you should architect graph ingestion.
Cheap, repeatable extraction.
Expensive, careful reasoning.
Here's the full setup 👇
Why graphs and frontier models fit together
A vector database gives an agent semantic similarity.
A knowledge graph gives it structure.
Instead of storing:
"Ajay worked on project X."
you can represent:
Ajay
└── worked_on
└── Project X
├── started_at → 2026-04
├── involved → Alice
└── depends_on → Service Y
Now the agent can traverse relationships.
It can answer questions like:
And importantly, the memory can survive context compaction, session boundaries, and individual conversations.
But there's a problem.
Graphs are expensive to build.
Every new piece of information needs to be converted into structured knowledge.
That means extraction.
And extraction happens constantly.
If you ingest:
you're repeatedly sending similar instructions to the model.
That's where the architecture matters.
The key idea: separate extraction from reasoning
Graph engineering has two fundamentally different model-facing workloads.
And treating them the same is where people burn money.
High volume.
Low judgment.
You want the model to:
This happens thousands of times.
It should be:
fast + cheap + deterministic + heavily cached.
Low volume.
High judgment.
The model receives a relevant subgraph and has to:
This is where you want your expensive reasoning budget.
The model shouldn't spend frontier-level reasoning tokens deciding whether "Apple Inc." is an organization.
Save that intelligence for questions like:
"Why did the architecture change after the migration in March?"
That's the difference between a cheap graph and an expensive graph.
The extraction config
The most important optimization is simple:
Keep the stable instructions stable.
Your schema, extraction rules, formatting requirements, and normalization instructions should remain identical across requests.
The changing data should come afterward.
Conceptually:
[STABLE SYSTEM / SCHEMA]
↓
[CACHED PREFIX]
↓
[VARIABLE EPISODE]
↓
[EXTRACTION]
For example:
import anthropic
client = anthropic.Anthropic()
EXTRACTION_SYSTEM = """
Extract a knowledge graph from the text.
Return JSON only:
{
"entities": [
{
"name": "...",
"type": "...",
"description": "..."
}
],
"edges": [
{
"source": "...",
"target": "...",
"relation": "...",
"valid_from": "..."
}
]
}
Rules:
- Use canonical entity names.
- Resolve aliases when the identity is unambiguous.
- Extract only relationships supported by the text.
- Add valid_from when the text provides temporal information.
- Never invent relationships.
- Keep descriptions concise.
"""
def extract(episode_text, occurred_at):
return client.messages.create(
model="claude-opus-4-8",
max_tokens=2000,
system=[
{
"type": "text",
"text": EXTRACTION_SYSTEM,
"cache_control": {
"type": "ephemeral"
}
}
],
messages=[
{
"role": "user",
"content": (
f"reference_time: {occurred_at}\n\n"
f"{episode_text}"
)
}
],
)
The important architectural pattern isn't the exact Python syntax.
It's this:
Schema first. Variable data last.
The schema is reused.
The episode changes.
That is exactly the kind of repeated context prompt caching is designed to optimize. Anthropic currently lists Opus 4.8 cache hits at $0.50/M tokens versus $5/M for standard input.
The three things that actually move the bill
1. Cache the stable prefix
If every extraction request contains the same:
don't treat that text like fresh input every time.
Cache it.
A 600-token extraction schema repeated across 5,000 episodes means you're repeatedly sending 3 million tokens of essentially identical context.
Caching turns that repeated context into a much cheaper input category.
2. Don't use maximum reasoning for mechanical extraction
Extraction is mostly structured parsing.
You don't need the model spending its maximum reasoning budget deciding:
"Google" → organization
"John" → person
"worked_at" → relationship
The expensive reasoning budget belongs on the query side.
Your architecture should therefore look more like:
┌─────────────────────┐
│ Raw Episode │
└──────────┬──────────┘
↓
┌───────────────────┐
│ Cheap Extract │
│ + Cached Schema │
└─────────┬─────────┘
↓
┌───────────────────┐
│ Knowledge │
│ Graph │
└─────────┬─────────┘
↓
┌───────────────────┐
│ Retrieve Subgraph │
└─────────┬─────────┘
↓
┌───────────────────┐
│ Deep Reasoning │
│ + Synthesis │
└───────────────────┘
Extraction is a pipeline problem.
Traversal is a reasoning problem.
Don't optimize them the same way.
3. Batch historical ingestion
This is another easy win.
Suppose you're importing:
Nobody needs those results synchronously.
So don't process them like interactive requests.
Use the Batch API.
Anthropic currently gives Batch processing a 50% discount on input and output tokens, and those savings can be combined with prompt caching.
The architecture becomes:
Historical Data
↓
Batch Jobs
↓
Cached Extraction Schema
↓
Structured Episodes
↓
Knowledge Graph
Instead of:
Historical Data
↓
Request
↓
Wait
↓
Request
↓
Wait
↓
Request
↓
...
Backfills are exactly the kind of workload that should be asynchronous.
The traversal side
Extraction is where you optimize for volume.
Traversal is where you optimize for answer quality.
Don't throw the entire graph into the model.
Retrieve the smallest relevant subgraph first.
For example:
User Question
↓
Entity Resolution
↓
Graph Search
↓
Relevant Nodes
↓
Relevant Edges
↓
Temporal Filtering
↓
Context Assembly
↓
Opus Reasoning
↓
Grounded Answer
The model shouldn't have to search through 50,000 nodes just to answer a question about three entities.
Give it the evidence first.
Then make it reason.
Your routing policy should look like this
Graph Routing
### Ingestion
- Extract entities and relationships from every episode.
- Keep extraction instructions stable.
- Cache the reusable extraction prefix.
- Keep variable episode content outside the stable prefix.
- Use asynchronous batch processing for historical backfills.
- Store timestamps whenever the source provides temporal information.
- Validate extracted entities and relationships before writing.
### Traversal
- Resolve the user's entities first.
- Retrieve only the relevant subgraph.
- Apply temporal filters before reasoning.
- Prefer direct graph evidence over model assumptions.
- Use a high reasoning setting for difficult multi-hop questions.
- Cite the graph edges used to construct the answer.
### Never
- Send the entire graph to the model.
- Rebuild the extraction prompt unnecessarily.
- Run massive historical backfills synchronously.
- Ask the model to invent missing relationships.
- Treat a vector similarity result as a verified relationship.
That last point is especially important.
A graph edge is evidence.
A model-generated assumption isn't.
Add temporal reasoning or you're leaving half the graph unused
A normal knowledge graph says:
Alice → works_at → Company X
A temporal graph says:
Alice
├── works_at → Company X
│ └── valid_from → 2024
│ └── valid_until → 2026
│
└── works_at → Company Y
└── valid_from → 2026
Now your agent can answer:
"Where did Alice work when Project X started?"
instead of:
"Where does Alice work?"
That difference is huge.
For agent memory, time is not metadata.
Time is part of the knowledge.
Don't trust extraction blindly
Another improvement I'd add to the basic architecture:
Put validation between the model and the graph.
The pipeline should be:
LLM Extraction
↓
JSON Schema Validation
↓
Entity Normalization
↓
Duplicate Detection
↓
Relationship Validation
↓
Temporal Validation
↓
Graph Write
Why?
Because a knowledge graph has a nasty property:
Bad data compounds.
If one incorrect relationship gets written to a graph, future retrieval can surface it as if it were a fact.
Then the model reasons over the incorrect fact.
Then the incorrect conclusion becomes another piece of memory.
You can end up with:
Bad extraction
↓
Bad graph edge
↓
Bad retrieval
↓
Bad reasoning
↓
Bad memory
↓
More bad retrieval
So ingestion should be treated like a data pipeline, not just an LLM call.
Graph + vector isn't either/or
Another mistake is treating knowledge graphs and vector databases as competitors.
They solve different problems.
Use vectors for:
"Find things that mean something similar."
Use graphs for:
"Show me how these things are connected."
A strong memory architecture can use both:
User Question
↓
┌────────┴────────┐
↓ ↓
Vector Search Graph Search
↓ ↓
Semantic Context Relationships
└────────┬────────┘
↓
Context Fusion
↓
Opus Reasoning
↓
Final Answer
Vector retrieval finds the neighborhood.
Graph traversal explains the structure.
The frontier model synthesizes the answer.
The economics
Let's use a simple example.
Assume:
That's:
4 million variable input tokens
and
3 million reusable prefix tokens.
At current Opus 4.8 standard pricing:
Standard input:
7M × $5/M
= $35
If the reusable prefix is successfully cached:
Episode text:
4M × $5/M
= $20
Cached prefix:
3M × $0.50/M
= $1.50
Total input:
≈ $21.50
For a non-time-sensitive batch backfill, the variable input can also receive the 50% Batch discount:
Episode text:
4M × $2.50/M
= $10
Cached prefix in batch:
potentially lower still, depending on cache usage
Total:
≈ $11.50 + cache-write/other costs
The exact bill depends on cache duration, cache writes, output tokens, request shape, and whether the workload qualifies for the relevant pricing tier, so treat these as architecture examples rather than a guaranteed invoice. Anthropic explicitly notes that Batch and prompt-caching discounts can stack.
The important point isn't the exact dollar amount.
It's the shape of the optimization:
Don't pay frontier prices repeatedly for identical context.
Claude Code + Graphiti
If you're using Claude Code, you can put the graph behind an MCP server and let the coding agent access persistent memory as a tool.
A conceptual MCP configuration might look like:
{
"mcpServers": {
"graphiti": {
"command": "uvx",
"args": ["graphiti-mcp"],
"env": {
"NEO4J_URI": "bolt://localhost:7687",
"NEO4J_PASSWORD": "${NEO4J_PASSWORD}"
}
}
}
}
The important part isn't the specific MCP configuration.
It's the separation of responsibilities:
Claude Code
↓
MCP
↓
Graph Memory
↓
Neo4j / Graph Store
The agent can retrieve relevant memory when needed instead of stuffing the entire history into its context window.
That is a much more scalable pattern.
The production architecture
If I were building this seriously, I'd make the system look like this:
┌─────────────────┐
│ Conversations │
│ Documents │
│ GitHub Events │
│ Tickets │
└────────┬────────┘
↓
┌─────────────────┐
│ Ingestion Queue │
└────────┬────────┘
↓
┌───────────────────────────┐
│ Cached Extraction Prompt │
│ Low-cost / batch capable │
└─────────────┬─────────────┘
↓
┌───────────────────┐
│ Structured Facts │
└─────────┬─────────┘
↓
┌─────────────────────────────┐
│ Validation + Normalization │
└──────────────┬──────────────┘
↓
┌───────────────────┐
│ Knowledge Graph │
└─────────┬─────────┘
↓
┌────────────────────┐
│ Hybrid Retrieval │
│ Vector + Graph │
└─────────┬──────────┘
↓
┌────────────────────┐
│ Opus 4.8 Reasoning │
└─────────┬──────────┘
↓
Grounded Answer
This architecture gives each component one job.
LLM: extract and reason.
Graph: preserve relationships.
Vector store: retrieve semantic context.
Cache: eliminate repeated prompt cost.
Batch API: make backfills cheaper.
Validation: stop bad facts from entering memory.
Common mistakes
You don't need maximum intelligence to extract:
"Sam joined OpenAI in 2025."
That's structured extraction.
Save expensive reasoning for:
"How did Sam's move to OpenAI affect the projects he was previously working on?"
If your extraction schema is identical across thousands of requests, sending it as fresh input every time is throwing money away.
Retrieval exists for a reason.
Find the relevant subgraph first.
Then reason over it.
Relationships change.
People change jobs.
Projects get renamed.
Dependencies disappear.
Use temporal metadata whenever the source supports it.
One hallucinated relationship can contaminate every future answer.
Validate before writing.
If you're backfilling months or years of history, users aren't waiting for those requests.
Batch them.
Vectors are great at similarity.
They aren't a replacement for explicit relationships and temporal state.
For serious agent memory, hybrid retrieval is often the stronger architecture.
The 20-minute prototype
You don't need a massive infrastructure project to test this.
Step 1 — Start Neo4j
Run a local graph database.
Step 2 — Add Graphiti or your graph layer
Expose the graph through MCP if you're using Claude Code.
Step 3 — Define one extraction schema
Keep it stable.
Don't dynamically rewrite the schema for every episode.
Step 4 — Add prompt caching
Cache the reusable extraction instructions.
Step 5 — Ingest a small dataset
Start with 100–500 episodes.
Measure:
Step 6 — Build one multi-hop query
Ask something the graph actually needs to solve.
For example:
"What decisions led to the current architecture, and which earlier projects influenced them?"
Then inspect the retrieved edges.
If the answer can't be traced back to graph evidence, your retrieval layer isn't ready.
The real graph-engineering lesson
The interesting part isn't that a newer model is cheaper.
It's that model economics are becoming an architecture problem.
A naive agent treats every token the same.
A good agent doesn't.
It knows:
Repeated context → cache it
Historical workload → batch it
Mechanical extraction → cheap configuration
Deep reasoning → expensive configuration
Large graph → retrieve first
Time-sensitive facts → preserve timestamps
Model output → validate before storage
That's graph engineering.
Not:
"Put everything in Neo4j and call Claude."
The winning architecture is the one that spends intelligence exactly where intelligence is needed.
Cheap ingestion.
Structured memory.
Selective retrieval.
Deep reasoning only at the edge.
That's how you make graph memory economically viable at scale.
Thanks for reading.

