Learning Omnigraph as the Shared Memory of an AI Agent Team
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:

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, andRequirementnodes. - The Architect adds
DecisionandDesignDocnodes linked to those tasks. - The Coder creates
Commit, updatesFileandFunctionrelationships, and marks tasks complete. - The Reviewer adds
Reviewfindings and links them to commits and functions. - The Tester creates
Testnodes, links them to functions, and recordsBugnodes 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.