I’ve been learning Omnigraph, and the shift that made everything click was to stop thinking of it as a database. It is closer to the shared long-term memory of a team of software agents.

Omnigraph describes itself as a lakehouse graph database for context assembly and multi-agent coordination: an operational state layer where fleets of agents read, write, and branch a shared graph. So the fastest way for me to build intuition was not to load example people and friendships. It was to model the one thing agents actually need to coordinate on.

The goal isn’t to store chats. It’s to store the state of software development.

The mental model

Imagine you have five agents:

Planner
    │
Architect
    │
Coder
    │
Reviewer
    │
Tester

Each agent reads from and writes to the graph. The graph becomes the project’s “brain.”

Instead of files…

Instead of this:

README.md
TODO.md
notes.txt
design.md
bugs.md

Everything becomes entities. For example:

Project
Feature
Task
File
Function
Class
API
Decision
Bug
Test
Commit
PullRequest
Knowledge
Question

First exercise

Let’s pretend we’re building a Todo API. Create this mentally.

Project
Todo API

Then:

Feature
Authentication
CRUD Todos
Search
Labels

Edges:

Project
contains
Feature

Graph:

Todo API
│
├── Authentication
├── CRUD
├── Search
└── Labels

Second layer

Every feature creates Tasks.

Task
Implement JWT

Task
Add login endpoint

Task
Refresh token

Task
Write tests

Edges:

Feature
owns
Task

Graph:

Authentication
│
├── JWT
├── Login
├── Refresh
└── Tests

Third layer

Tasks touch files.

Task
↓
implements
↓
File

Example:

Login endpoint
↓
auth.rs

Now you can ask:

Which tasks modify auth.rs?

Fourth layer

Files contain code.

File
↓
contains
↓
Function

Example:

auth.rs
↓
login()
↓
refresh()
↓
validate_token()

Fifth layer

Now create Decisions. Agents constantly make decisions.

Decision
Use JWT

Reason
Stateless

Alternative
Sessions

Edges:

Decision
affects
Feature

Later another agent can retrieve:

Why are we using JWT?

without rereading conversations.

Sixth layer

Store Bugs.

Bug
Login returns 500

Severity
High

Status
Open

Edges:

Bug
caused_by
Function
login()

Seventh layer

Store Tests.

Test
test_login_success()

covers
login()

Now ask:

Which functions have no tests?

Eighth layer

Store Knowledge. Imagine the Architect discovers:

OAuth tokens expire after 15 minutes.

Store:

Knowledge
OAuth timeout
15 min

source
RFC

Now every future coding agent can retrieve it.

Ninth layer

Store conversations. Instead of raw chat, store:

Question
Should we cache Redis?
↓
Answer
Yes
↓
Reason
Latency reduction

Later:

Decision
depends_on
Question

Tenth layer

Store plans. Planner writes:

Plan
Authentication
↓
Task
↓
Task
↓
Task

Coder consumes Tasks. Tester consumes completed Tasks. Reviewer consumes Pull Requests. Nobody has to reread prompts.

Example of an agent loop

Imagine you’re the coding agent. First query:

MATCH
Open Tasks
assigned_to = Coder
priority DESC

Result:

Task
Implement JWT

Now retrieve context.

Task
↓
belongs_to
↓
Feature

Retrieve:

Feature
↓
Project

Retrieve:

Feature
↓
Decision

Retrieve:

Decision
↓
Knowledge

Now the coder has:

  • project
  • feature
  • architecture
  • previous decisions
  • affected files

before generating one line of code.

After coding

Instead of returning text, the agent writes:

Commit
hash
abc123

Edges:

Commit
implements
Task

and:

Commit
changes
File

Reviewer agent

Reviewer queries:

Commits
without Review

Gets:

Commit
↓
Files
↓
Functions
↓
Tests

It knows exactly what changed.

Tester agent

Tester queries:

Functions
without Tests

or:

Tasks
status = Done
test = Missing

Project manager

Manager asks:

Show unfinished work.

Graph traversal:

Project
↓
Feature
↓
Task
status != Done

Why a graph is better than vector memory

A vector store answers:

“Find things similar to JWT.”

A graph answers structural questions:

Which bugs
were introduced
by commits
that implemented
authentication
and still have no tests?

That’s a 5-hop traversal that vectors don’t model naturally.

The schema I’d build for an autonomous software team

Project
 ├── Feature
 │     ├── Task
 │     ├── Decision
 │     ├── Requirement
 │     └── Milestone
 ├── File
 │     ├── Class
 │     ├── Function
 │     └── Test
 ├── Bug
 ├── PullRequest
 ├── Commit
 ├── Knowledge
 ├── Question
 ├── Answer
 ├── DesignDoc
 ├── Dependency
 └── ExternalResource

with relationships like:

Project -> contains -> Feature
Feature -> owns -> Task
Task -> modifies -> File
File -> contains -> Function
Function -> calls -> Function
Function -> covered_by -> Test
Task -> implements -> Requirement
Decision -> affects -> Feature
Commit -> implements -> Task
Bug -> caused_by -> Commit
Bug -> affects -> Function
Knowledge -> supports -> Decision
Question -> answered_by -> Answer
DesignDoc -> describes -> Feature

Install it

Omnigraph is a single CLI. Grab the released binary:

curl -fsSL https://raw.githubusercontent.com/ModernRelay/omnigraph/main/scripts/install.sh | bash

Or with Homebrew:

brew tap ModernRelay/tap
brew install ModernRelay/tap/omnigraph

That installs omnigraph into ~/.local/bin. For a first look you don’t need the server or an object store. A local file-backed graph is enough, so the whole PoC below runs offline.

A 5-minute PoC: the Todo API graph, for real

Everything above was a mental model. Here is the same model as three files and four commands, run against Omnigraph 0.8.0.

1. Declare the schema (schema.pg). Properties go one per line, and the primary key is a body-level @key(...):

node Feature {
  slug: String
  name: String
  @key(slug)
}

node Task {
  slug: String
  title: String
  status: enum(open, done)
  @key(slug)
}

node File {
  path: String
  @key(path)
}

node Function {
  name: String
  @key(name)
}

node Test {
  name: String
  @key(name)
}

edge Owns:     Feature -> Task
edge Modifies: Task    -> File
edge Contains: File    -> Function
edge Covers:   Test    -> Function

2. Load the data (data.jsonl). One record per line. Nodes carry their properties; edges reference nodes by their key value:

{"type":"Feature","data":{"slug":"auth","name":"Authentication"}}
{"type":"Task","data":{"slug":"add-login","title":"Add login endpoint","status":"done"}}
{"type":"Task","data":{"slug":"refresh-token","title":"Refresh token","status":"open"}}
{"type":"File","data":{"path":"auth.rs"}}
{"type":"Function","data":{"name":"login"}}
{"type":"Function","data":{"name":"refresh"}}
{"type":"Function","data":{"name":"validate_token"}}
{"type":"Test","data":{"name":"test_login_success"}}
{"edge":"Owns","from":"auth","to":"add-login"}
{"edge":"Owns","from":"auth","to":"refresh-token"}
{"edge":"Modifies","from":"add-login","to":"auth.rs"}
{"edge":"Modifies","from":"refresh-token","to":"auth.rs"}
{"edge":"Contains","from":"auth.rs","to":"login"}
{"edge":"Contains","from":"auth.rs","to":"refresh"}
{"edge":"Contains","from":"auth.rs","to":"validate_token"}
{"edge":"Covers","from":"test_login_success","to":"login"}

3. Write the two structural questions (queries.gq). These are the queries a vector store can’t answer:

query tasks_touching_file($path: String) {
  match {
    $t: Task
    $f: File { path: $path }
    $t modifies $f
  }
  return { $t.title }
}

query functions_without_tests() {
  match {
    $fn: Function
    not { $t: Test  $t covers $fn }
  }
  return { $fn.name }
}

4. Run it. Initialize the graph, load the data, then ask the questions:

omnigraph init --schema schema.pg graph.omni
omnigraph load --data data.jsonl --mode overwrite graph.omni

omnigraph query tasks_touching_file \
  --query queries.gq --params '{"path":"auth.rs"}' \
  --format table --store graph.omni

omnigraph query functions_without_tests \
  --query queries.gq --format table --store graph.omni

The output:

loaded graph.omni on branch main with overwrite: 8 nodes across 5 node types, 8 edges across 4 edge types

2 rows from branch main via tasks_touching_file
t.title
------------------
Add login endpoint
Refresh token

2 rows from branch main via functions_without_tests
fn.name
--------------
refresh
validate_token

Here is the whole loop start to finish, defining the schema, loading the Todo API graph, and running both structural queries:

Omnigraph PoC: define the schema, load the Todo API graph, and run two structural queries

That second result is the whole point. Without writing any traversal code, the graph told us which functions in auth.rs still have no test coverage: refresh and validate_token. login drops out because a test covers it. Now add a Commit node and a Bug, wire in caused_by, and the same shape answers “which bugs came from commits that touched authentication and still have no tests.” That is the memory an autonomous team actually needs.

Once you outgrow a single file, the same schema and queries move to a deployed cluster backed by object storage, where each agent writes on its own branch that you review and merge. The model doesn’t change: nodes, edges, and the questions you can ask across them.

The most interesting way to learn Omnigraph

Rather than treating it as a graph database, treat it as the persistent working memory for a multi-agent development system. In that model:

  • The Planner creates Feature, Task, and Requirement nodes.
  • The Architect adds Decision and DesignDoc nodes linked to those tasks.
  • The Coder creates Commit, updates File and Function relationships, and marks tasks complete.
  • The Reviewer adds Review findings and links them to commits and functions.
  • The Tester creates Test nodes, links them to functions, and records Bug nodes when tests fail.

If you manually play the role of each agent, creating nodes, traversing relationships, and updating the graph after every step, you’ll develop intuition for the exact kind of structured memory an autonomous software engineering system would need. That’s a much richer exercise than simply loading example people and friendships into a graph.

Omnigraph is open source and built to be driven by coding agents. If you want to try the exercise above for real, start with the project on GitHub.