Pure Go, no CGO
Open a file or :memory: and run Cypher from Go. Storage is SQLite through a pure-Go driver, so there is nothing to install and cross-compiling just works.
Open source · Apache 2.0 · v0.3.0
graingraph is an embedded property graph database for Go. It stores data in SQLite, speaks Cypher, and needs no CGO and no server. It also ships graingraph-query, an HTTP server with the Neo4j Query API, a compact output format and a built-in skill, so an agent can learn to use it on its own.
One module, two ways to use it: import it as a Go package, or run the server and talk to it over HTTP.
Open a file or :memory: and run Cypher from Go. Storage is SQLite through a pure-Go driver, so there is nothing to install and cross-compiling just works.
Pattern matching, MERGE, subqueries, shortest paths, indexes and constraints, temporal types and vector search. It passes the openCypher TCK scenarios that apply to Cypher 25.
graingraph-query speaks the Neo4j Query API v2 and has been compared side by side with Neo4j 2026.07, so clients written for Neo4j work unchanged and moving to Neo4j later stays straightforward.
Ask for text/toon and get a flat, compact table. Failures are real HTTP error statuses, and a skill document at /skills teaches any agent how to query it.
Pick the way that suits you. Both run the same engine.
As a Go library
go get github.com/graingraph/graingraph
import "github.com/graingraph/graingraph"
db, err := graingraph.Open(":memory:")
if err != nil { log.Fatal(err) }
defer db.Close(ctx)
_, err = db.RunQuery(ctx,
`CREATE (:Person {name: 'Ada'})-[:KNOWS]->(:Person {name: 'Grace'})`, nil)
result, err := db.RunQuery(ctx,
`MATCH (a:Person)-[:KNOWS]->(b:Person)
RETURN a.name AS from, b.name AS to`, nil)
for result.Next(ctx) {
fmt.Println(result.Record().Values()) // [Ada Grace]
}As a server
go install \
github.com/graingraph/graingraph/cmd/graingraph-query@latest
export GRAINGRAPH_QUERY_PASSWORD=change-me
graingraph-query --username admin --db graingraph.db
curl -u admin:change-me \
-H 'Content-Type: application/json' \
-H 'Accept: text/toon' \
localhost:7474/db/neo4j/query/v2 \
-d '{"statement":"MATCH (p:Person) RETURN p.name AS name, p.born AS born ORDER BY born"}'
# rows[2]{name,born}:
# Ada,1815
# Grace,1906Prebuilt binaries for Linux, macOS and Windows are on the releases page.
A statement that returns two nodes and the relationship between them, on the Neo4j movies graph. Real output from the server, on the left as the Neo4j-compatible JSON and on the right as TOON.
JSON 147 tokens
{
"data": {
"fields": ["p", "r", "m"],
"values": [
[
{
"elementId": "57",
"labels": ["Person"],
"properties": {
"born": 1956,
"name": "Tom Hanks"
}
},
{
"elementId": "97",
"startNodeElementId": "57",
"endNodeElementId": "165",
"type": "ACTED_IN",
"properties": {
"roles": ["Chuck Noland"]
}
},
{
"elementId": "165",
"labels": ["Movie"],
"properties": {
"released": 2000,
"tagline": "At the edge of the world, his journey begins.",
"title": "Cast Away"
}
}
]
]
},
"bookmarks": ["FB:AAAAAAAAAA8vsraGnMfqiQ=="],
"queryType": "r",
"resultAvailableAfter": 1,
"resultConsumedAfter": 1
}TOON, Accept: text/toon 96 tokens
rows[1]{p._id,p._labels,p.born,p.name,r._id,r._type,r._start,r._end,r.roles,m._id,m._labels,m.released,m.tagline,m.title}:
"57",Person,1956,Tom Hanks,"97",ACTED_IN,"57","165","[\"Chuck Noland\"]","165",Movie,2000,"At the edge of the world, his journey begins.",Cast AwayEach node and relationship is spread over columns, the header is written once, and bookmarks and timings are left out. The JSON is reformatted here for reading; the server sends it on one line. Token counts are for this one row with a GPT tokenizer, so the saving grows with the number of rows.
An agent that has never seen graingraph can find out how to use it from the server itself.
GET /skills lists the skills a server offers. It needs no credentials.GET /skills/graingraph-query returns a SKILL.md in the Agent Skills format, with that server's URL filled into the examples. The binary can print the same document with no server running.Accept: text/toon. Parameters go in the request, results come back as a compact table, and every failure is an error status.# any agent with HTTP access
curl https://your-server/skills
curl https://your-server/skills/graingraph-query
# or from the binary, with no server running
graingraph-query skill --url https://your-server > SKILL.mdThis site also has an llms.txt, and the repository has an AGENTS.md for agents that work on the code.
We compared the compact format against JSON and the server against a real Neo4j.
60%
of the tokens of plain JSON, across 49 queries on the Neo4j movies graph. About half for nodes and relationships.
98.4% vs 98.4%
of questions answered correctly from JSON and from TOON results, by Haiku and Sonnet, 192 questions each on the movies graph.
72 / 72
side-by-side compatibility checks against Neo4j 2026.07 pass. The differences that remain are documented.
These are small experiments on one dataset, with two models and one run per question. The accuracy result means no loss was measurable, not that TOON is better. The README has the method, the limits and the known differences from Neo4j.
graingraph is at v0.3.0 and young. The API may still change before 1.0. The server is single-user by design: one credential, and one transaction open at a time. If that fits what you are building, take a look at the code, and open an issue if something is missing.