-
Notifications
You must be signed in to change notification settings - Fork 2
Cookbook Application Workflows
End-to-end recipes for common Minigraf deployment scenarios. Rust API snippets appear where the native API is required; all other recipes use the Datalog REPL syntax.
← Bitemporal Modeling | Home →
Problem: Implement an agent belief lifecycle — assert, correct, and retract beliefs while preserving the full decision audit trail.
;; Agent records a belief
(transact [[:agent-session-1 :belief/sky-color "blue"]]) ;; tx 1
;; Belief is corrected
(retract [[:agent-session-1 :belief/sky-color "blue"]])
(transact [[:agent-session-1 :belief/sky-color "red"]]) ;; tx 3
;; Current belief
(query [:find ?color
:where [:agent-session-1 :belief/sky-color ?color]])
;; => "red"
;; What did the agent believe before the correction?
(query [:find ?color
:as-of 1
:valid-at :any-valid-time
:where [:agent-session-1 :belief/sky-color ?color]])
;; => "blue"
Notes:
- Assert multiple beliefs in a single transaction:
[[:agent :belief/A "x"] [:agent :belief/B "y"]]— all receive the same tx-count - Store the tx-count alongside decisions:
(transact [[:decision-42 :decision/belief-tx 1]])— use it later as the:as-ofhandle for audits - One
.graphfile per agent instance; each agent's memory is private (see Recipe 9)
Problem: Reconstruct exactly what an agent knew at the moment it made a specific decision.
;; Store the tx-count when a decision was made
(transact [[:decision-42 :decision/made-at-tx 7]
[:decision-42 :decision/choice :route-A]])
;; Retrieve the tx-count for decision-42
(query [:find ?tx
:where [:decision-42 :decision/made-at-tx ?tx]])
;; => 7
;; Reconstruct the agent's belief state at tx 7
(query [:find ?belief
:as-of 7
:valid-at :any-valid-time
:where [:agent :belief/value ?belief]])
Notes:
-
:as-of 7freezes the database to what was known after tx 7 — this is the agent's exact knowledge state at decision time - Combine with
:valid-atto also restrict which real-world facts were considered valid at that moment - The tx-count stored in
:decision/made-at-txis the durable link between a decision record and its knowledge context
Problem: Run the same query shape thousands of times per session with different entity IDs and tx snapshots without re-parsing each time.
use minigraf::{Minigraf, OpenOptions, BindValue};
let db = OpenOptions::new().open("agent.graph")?;
// Prepare once at session startup — parse and plan happen here
let pq = db.prepare(
"(query [:find ?belief
:as-of $tx
:where [$entity :belief/value ?belief]])"
)?;
// Execute thousands of times — query plan is reused, only bind values differ
for (entity_id, tx) in agent_queries {
let results = pq.execute(&[
("tx", BindValue::TxCount(tx)),
("entity", BindValue::Entity(entity_id)),
])?;
// process results...
}Notes:
-
$txand$entityare named bind slots;prepare()parses and plans once;execute()substitutes values and runs -
BindValue::TxCount(u64)for:as-of-style tx bindings;BindValue::Entity(uuid::Uuid)for entity IDs;BindValue::String(String)for string values -
PreparedQueryisSend + Sync; safe to share across threads — eachexecute()call is independent
Problem: Combine a vector store for fuzzy retrieval with Minigraf for structured graph and bitemporal navigation.
The two layers serve complementary roles:
| Layer | Tool | Job |
|---|---|---|
| Fuzzy retrieval | Vector store (Chroma, Qdrant, Pinecone…) | Find entities similar to the query embedding |
| Graph + temporal backbone | Minigraf | Follow relationships, audit facts, rewind to past states |
Pattern:
- Store the Minigraf entity UUID alongside its embedding in the vector store
- On a query, retrieve top-k matching entity UUIDs from the vector store
- Use those UUIDs as entry points into Minigraf for graph traversal and temporal queries
;; Entry-point UUID retrieved from vector store
;; Follow relationships and fetch bitemporal metadata from Minigraf
(query [:find ?title ?approver ?approved-at
:where [#uuid "550e8400-e29b-41d4-a716-446655440000" :doc/title ?title]
[#uuid "550e8400-e29b-41d4-a716-446655440000" :doc/approved-by ?approver-id]
[?approver-id :person/name ?approver]
[#uuid "550e8400-e29b-41d4-a716-446655440000" :doc/approved-at ?approved-at]])
Notes:
- Minigraf has no vector search — the vector store does fuzzy retrieval; Minigraf handles graph traversal and temporal queries
- Entity UUIDs are stable across time; they are the durable link between the two systems
- The vector store answers "which entities are relevant?"; Minigraf answers "what are their relationships and history?"
Problem: Record a fact on a device offline, then retroactively correct it after the user reviews the data on sync.
;; Device records a health measurement on 2025-06-01 (recorded offline)
(transact {:valid-from "2025-06-01"}
[[:user :health/weight-kg 82.5]]) ;; tx 1
;; After sync, user notices a mis-entry and corrects it
(retract [[:user :health/weight-kg 82.5]])
(transact {:valid-from "2025-06-01"}
[[:user :health/weight-kg 80.5]]) ;; tx 3 — corrected, same valid-from
;; Current value: corrected
(query [:find ?w :where [:user :health/weight-kg ?w]])
;; => 80.5
;; What was originally recorded (before correction)?
(query [:find ?w
:as-of 1
:valid-at :any-valid-time
:where [:user :health/weight-kg ?w]])
;; => 82.5
Notes:
- The original measurement is preserved in tx history — the correction is auditable
-
valid-from "2025-06-01"reflects when the measurement was taken, not when the device synced — this is valid-time modeling - This pattern works identically whether the device was online or offline when recording; Minigraf does not distinguish
Problem: Find all facts added after a known sync checkpoint so they can be pushed to a central store.
;; Store the tx-count of the last successful sync
(transact [[:app/state :sync/last-tx-count 12]])
;; Retrieve it before each sync
(query [:find ?last-tx
:where [:app/state :sync/last-tx-count ?last-tx]])
;; => 12
;; Find all weight facts recorded since tx 12
(query [:find ?value ?tx
:valid-at :any-valid-time
:where [:user :health/weight-kg ?value]
[:user :db/tx-count ?tx]
[(> ?tx 12)]])
Notes:
- Store the last-sync tx-count as a fact; update it after each successful sync with
retract+transact - Replace the hard-coded
12with the retrieved?last-txvalue (or use a prepared query with a bind slot — see Recipe 3) - To detect changes across multiple attributes, enumerate them with
or:(or [?e :health/weight-kg ?v] [?e :health/steps ?v])
Problem: Model a project task graph and find all tasks that are transitively blocked by a given task.
;; Task graph: tasks and their blocking relationships
(transact [[:task-a :task/name "Design API"]
[:task-b :task/name "Implement endpoint"]
[:task-c :task/name "Write tests"]
[:task-d :task/name "Deploy"]
[:task-a :task/blocks :task-b]
[:task-b :task/blocks :task-c]
[:task-b :task/blocks :task-d]])
;; Recursive rule: all tasks transitively blocked by a given task
(rule [(blocked-by ?blocker ?blocked)
[?blocker :task/blocks ?blocked]])
(rule [(blocked-by ?blocker ?blocked)
[?blocker :task/blocks ?mid]
(blocked-by ?mid ?blocked)])
;; What does completing :task-a unblock (transitively)?
(query [:find ?name
:where (blocked-by :task-a ?t)
[?t :task/name ?name]])
;; => "Implement endpoint", "Write tests", "Deploy"
Notes:
- Model blocking as
:task/blocks(A blocks B = B cannot start until A completes) - The recursive rule finds all transitive dependents; Minigraf detects fixed points and terminates safely on cycles
- Add a
:task/statusattribute and filter with(not [?t :task/status :complete])to find only currently-blocked tasks
Problem: Reconstruct which tasks were unblocked at a specific point in the project's history.
;; At tx 5, :task-a was marked complete
;; What tasks became unblocked at that point that are not yet done?
(query [:find ?name
:as-of 5
:where [:task-a :task/status :complete]
(blocked-by :task-a ?t)
(not [?t :task/status :complete])
[?t :task/name ?name]])
Notes:
- The
blocked-byrule must be defined in the current REPL session before running this query — see Recipe 7 for the two-clause rule definition -
:as-of 5ensures the query sees the database state after tx 5, including the:completestatus update - The
blocked-byrule is evaluated within the:as-of 5snapshot — only edges that existed at tx 5 are traversed - Use this for retrospective sprint planning: "why was :task-d still blocked at the end of sprint 3?"
Problem: Scale an agent fleet or multi-user application without a shared database.
Pattern: Each agent instance or user session maintains its own .graph file. There is no shared database.
agent-instance-1/ → session-abc.graph
agent-instance-2/ → session-def.graph
agent-instance-3/ → session-ghi.graph
When this is the right model:
- Each agent handles one session, task, or conversation
- Memory is private and does not need to be shared across instances
- The
.graphfile travels with the agent (container volume, device storage, cloud object store)
// Each session gets its own file — no coordination needed
let db = OpenOptions::new().open(&format!("sessions/{session_id}.graph"))?;When to use a different approach:
- Multiple agents need shared state → use a distributed database for that layer; Minigraf remains the per-agent L1 reasoning cache
- See the Worker-local reasoning (L1 cache) pattern in Use-Cases for the fleet architecture
Notes:
- Each
.graphfile is completely self-contained; no server, no coordination, no network calls -
OpenOptions::new().open(path)creates the file if it doesn't exist, opens it otherwise -
MiniGrafDbisArc<Mutex<…>>internally — the handle isSend + Syncbut concurrent calls are serialized
← Bitemporal Modeling | Home →
Reference: Prepared queries, Bi-temporal queries, Use Cases