Open source · Apache 2.0 · v0.3.0

A graph database you can embed, query with Cypher, and hand to an AI agent.

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.

What you get

One module, two ways to use it: import it as a Go package, or run the server and talk to it over HTTP.

Embedded

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.

Cypher

Real Cypher 25

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.

Server

Neo4j when you need it

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.

Agents

Made to be used by agents

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.

Quick start

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,1906

Prebuilt binaries for Linux, macOS and Windows are on the releases page.

The same query, in two formats

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 Away

Each 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.

For AI agents

An agent that has never seen graingraph can find out how to use it from the server itself.

  1. Discover. GET /skills lists the skills a server offers. It needs no credentials.
  2. Learn. 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.
  3. Query. Send Cypher with 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.md

This site also has an llms.txt, and the repository has an AGENTS.md for agents that work on the code.

Measured, with the caveats

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.

Status

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.