← Back to list

Neo4j Agent Skills: Teaching Your AI Coding Assistant Current Cypher

Why LLM training cutoffs matter for graph developers, and what a single SKILL.md file actually changes.

Konrad Kaliciński · 2026-05-05 19:51 · 1 claps · 3.4 min read
#neo4j #ai-agent #cypher #knowledge-graph #graph-database
Open on Medium ↗
Wiki topics: LLM · Large Language Models AGT · AI Agents EDU · Education & Learning 💻 · Programming

Neo4j Agent Skills: Teaching Your AI Coding Assistant Current Cypher

Why LLM training cutoffs matter for graph developers, and what a single SKILL.md file actually changes.

If you use AI to write Neo4j code, there’s a good chance it’s generating Cypher from two years ago. Not because it’s hallucinating — it’s writing real Cypher, just the old kind. Neo4j just shipped a fix for this: Agent Skills. Here’s what it is and whether it’s worth your time.

The Stale Cypher Problem

AI coding assistants know what was on the internet when they were trained. For most tools, that’s fine. SQL barely changes. But Cypher has moved a lot recently — Neo4j has been catching up with the ISO GQL standard, and that changed patterns developers use every day.

The model doesn’t know this. It learned from Stack Overflow answers and GitHub repos that predate the changes. So it writes code that looks right, compiles, and then breaks in a way that makes you question your Neo4j setup before you question the AI. I’ve been there more times than I’d like to admit. And you have probably been going through the same problem using other languages.

Two patterns that come up a lot:

Counting relationships: the AI defaults to size() , and current Cypher uses COUNT {}

// what the AI writes
MATCH (u:User)
WHERE size((u)-[:FOLLOWS]->()) > 10
RETURN u.name

// what you should write
MATCH (u:User)
WHERE COUNT { (u)-[:FOLLOWS]->() } > 10
RETURN u.name

Checking if a pattern exists: exists() is deprecated; use EXISTS {} instead:

// what the AI writes
MATCH (u:User)
WHERE exists((u)-[:BLOCKED]->(other))
RETURN u.name

// what you should write
MATCH (u:User)
WHERE EXISTS { MATCH (u)-[:BLOCKED]->(other) }
RETURN u.name

Both of these fail in ways that feel like environment issues rather than code issues. That’s what makes them annoying.

What Agent Skills actually are

It’s a SKILL.md file — a knowledge document your coding agent loads as context. The idea is progressive disclosure: it gives the agent a general overview upfront and pulls in deeper content only when the query calls for it. That way it’s not burning your context window every time you ask something simple.

Coverage includes current Cypher syntax, drivers (Python, JavaScript, Java, .NET, Go), data import, vector search, GDS, and a few framework integrations.

It’s MIT-licensed, community-maintained, and lives at neo4j-contrib/neo4j-skills. GitHub Actions watch Neo4j release notes and propose updates when things change — so it doesn’t go stale the moment someone forgets to update the docs.

Works with Claude Code, Cursor, Cline, and others. Auto-detects your agent type on install.

Installing it

npx skills add neo4j-contrib/neo4j-skills

That’s the whole install. No config file, no restart, no prayers to the graph gods.

If you’re on Claude Code and want to confirm it landed

claude skills list

You should see neo4j-contrib/neo4j-skills in the output.

Does it actually help

I tested it on a knowledge graph for plant protection products — products, their regulatory permissions, dosage instructions, and which crops and pests they’re approved for. Asked the agent to find all dosages for a given product, filter out incomplete ones, and return how many dosages that product has in total. Here’s what came back before the skill:

MATCH (p:Product {productId: $productId})-[:HAS_DOSAGE]->(d:Dosage)
WITH p, collect(d) AS dosages
UNWIND dosages AS dosage
WHERE dosage.maxTreatments IS NOT NULL
WITH p, collect(dosage) AS filtered
RETURN p.name, filtered, size((p)-[:HAS_DOSAGE]->()) AS totalDosages

Two problems: WHEREafter UNWINDdoesn’t behave as expected here (it should sit before the WITH), and size() with a pattern is old form.

After the skill:

MATCH (p:Product {productId: $productId})-[:HAS_DOSAGE]->(d:Dosage)
WITH p, d
WHERE d.maxTreatments IS NOT NULL
WITH p, collect(d) AS filtered
RETURN
  p.name,
  filtered,
  COUNT { (p)-[:HAS_DOSAGE]->() } AS totalDosages

On the Python driver side, the skill nudged the agent away from the old session.run() pattern toward driver.execute_query(), which has been the recommended approach since v5. Small thing, but it's the kind of default that quietly follows you into production.

Why this approach makes sense

You could paste Neo4j docs into your system prompt. But that eats context on every query, including the simple ones. You could fine-tune a model, but that’s expensive and breaks again when the next Neo4j version ships.

A community-maintained SKILL.md that only loads depth when needed, tracked against release notes automatically, is a cleaner solution. It’s also not locked to a vendor — anyone can contribute, and it won’t disappear because a product team changed direction.

What this probably means going forward

Other libraries are going to do this. Any tool that changes faster than the LLM training cycle has the same problem. Neo4j is just early.

Whether SKILL.md scales long-term is an open question — agents with better memory and live doc access might not need it. But right now, it’s the most practical way to make sure your coding assistant isn’t writing Cypher like it’s 2022.

Resources

neo4j-contrib/neo4j-skills on GitHub Neo4j announcement post skills.sh listing


메타데이터
post_id
3ce3d51ac63f
slug
neo4j-agent-skills-teaching-your-ai-coding-assistant-current-cypher-3ce3d51ac63f
url
https://medium.com/@akkonrad/neo4j-agent-skills-teaching-your-ai-coding-assistant-current-cypher-3ce3d51ac63f
canonical_url
https://medium.com/@akkonrad/neo4j-agent-skills-teaching-your-ai-coding-assistant-current-cypher-3ce3d51ac63f
author_url
https://medium.com/@akkonrad
status
ok
fetched_at
2026-06-17 08:20:12