| name | datalog |
| description | Read and write a Datalog database document (DatalogDoc) by Automerge URL. Use when working with Datalog facts, rules, or constraints — asserting or retracting facts, reading the current database state, running queries, or checking for constraint violations/conflicts. |
Datalog Skill
Read and write a Datalog database document using repo. Also supports rule evaluation (queries) and constraint checking (conflict detection).
Import
const { createDatalog, getDatalog, queryDatalog, checkConflicts } = await importSkillApi("datalog");
Types
Facts, rules, and constraints all support an optional comment field. When present it is serialized as a // line immediately before the statement, and it is re-parsed and associated automatically when reading back text.
{ pred: string, args: (string|number)[], comment?: string }
{ head: StoredAtom, body: StoredAtom[], comment?: string }
{ body: StoredAtom[], comment?: string }
API
createDatalog(repo, title?)
Creates a new, properly initialised DatalogDoc. Returns { handle, url }.
repo.create() is synchronous — this function must NOT be awaited.
const { createDatalog } = await importSkillApi("datalog");
const { handle, url } = createDatalog(repo, "My Power Grid");
getDatalog(repo, url) (async)
Returns a read/write interface for the DatalogDoc at url. Must be awaited.
| Method | Description |
|---|
getFacts(pred?) | Async. Returns base facts as { pred, args }[], optionally filtered by predicate. |
assertFact(pred, args, comment?) | Adds a ground fact if it doesn't exist. Optional comment is stored on the fact. |
retractFact(pred, args) | Removes all facts matching pred and the given args prefix. |
getRules(pred?) | Async. Returns stored rules as { head, body }[], optionally filtered by head pred. |
assertRule(rule) | Adds a rule if it doesn't already exist (compared by key). Pass comment on the rule object. |
retractRule(rule) | Removes all rules matching the given rule (by key equality). |
getConstraints() | Async. Returns all stored constraints as { body }[]. |
assertConstraint(constraint) | Adds a constraint if it doesn't already exist (compared by key). Pass comment on the constraint object. |
retractConstraint(constraint) | Removes all constraints matching the given constraint (by key equality). |
queryDatalog(repo, url, pred?) (async)
Evaluates all stored rules against the stored facts (bottom-up fixpoint) and returns the full derived database. Optionally filter results to a single predicate.
Returns StoredFact[] — each entry is { pred: string, args: (string|number)[] }.
const derived = await queryDatalog(repo, url);
const totals = await queryDatalog(repo, url, "total_flow");
checkConflicts(repo, url) (async)
Runs rule evaluation then checks all stored constraints. Returns an array of violations — empty if the database is consistent.
Each violation has:
constraint — the StoredConstraint that fired ({ body: StoredAtom[] })
witnesses — array of { bindings, steps } traces explaining why it fired
bindings — Record<string, string|number> variable assignments
steps — array of either:
{ kind: 'fact', fact, isBase, derivedBy? } — a ground fact used in the match
{ kind: 'builtin', atom, resolvedArgs } — a built-in comparison/arithmetic
const violations = await checkConflicts(repo, url);
if (violations.length === 0) {
console.log("No conflicts.");
} else {
for (const v of violations) {
console.log("Constraint violated:", v.constraint.body.map((a) => `${a.pred}(${a.args.join(", ")})`).join(", "));
for (const w of v.witnesses) {
console.log(" Bindings:", w.bindings);
}
}
}
Examples
const { createDatalog, getDatalog, queryDatalog, checkConflicts } = await importSkillApi("datalog");
const { url } = createDatalog(repo, "Power Grid");
const db = await getDatalog(repo, url);
const db = await getDatalog(repo, "automerge:abc123");
const flows = await db.getFacts("flow");
console.log(flows);
db.assertFact("node", ["east"]);
db.assertFact("flow", ["north", "east", 300], "eastern inter-node flow");
db.retractFact("flow", ["north", "east", 300]);
db.retractFact("flow", ["north"]);
db.assertRule({
head: { pred: "connected", args: ["X", "Y"] },
body: [{ pred: "flow", args: ["X", "Y", "_"] }],
});
db.assertRule({
head: { pred: "reachable", args: ["X", "Z"] },
body: [
{ pred: "reachable", args: ["X", "Y"] },
{ pred: "connected", args: ["Y", "Z"] },
],
});
db.retractRule({
head: { pred: "connected", args: ["X", "Y"] },
body: [{ pred: "flow", args: ["X", "Y", "_"] }],
});
db.assertConstraint({ body: [{ pred: "flow", args: ["X", "X", "_"] }] });
db.retractConstraint({ body: [{ pred: "flow", args: ["X", "X", "_"] }] });
const allFacts = await queryDatalog(repo, url);
const capacities = await queryDatalog(repo, url, "capacity");
const violations = await checkConflicts(repo, url);
Comments
Each fact, rule, or constraint can carry an optional comment string. When serialized to text the comment appears as a // line immediately before the statement. When text is parsed back, the preceding // or % line is automatically associated with the next statement.
db.assertFact("node", ["north"], "northern generation hub");
db.assertRule({
head: { pred: "connected", args: ["X", "Y"] },
body: [{ pred: "flow", args: ["X", "Y", "_"] }],
comment: "two nodes are connected if there is any flow between them",
});
db.assertConstraint({
body: [{ pred: "flow", args: ["X", "X", "_"] }],
comment: "no self-loops allowed",
});
Parsing round-trips correctly — comments survive parseProgram → serializeFacts/serializeRules/serializeConstraints → parseProgram.
Built-in predicates (available in rules and constraints)
| Predicate | Description |
|---|
eq(A, B) | A equals B |
neq(A, B) | A does not equal B |
lt(A, B) | A < B (numeric) |
lte(A, B) | A ≤ B (numeric) |
gt(A, B) | A > B (numeric) |
gte(A, B) | A ≥ B (numeric) |
add(A, B, C) | C = A + B |
sub(A, B, C) | C = A − B |
mul(A, B, C) | C = A × B |
div(A, B, C) | C = A / B |
sum(V, pattern, C) | C = sum of V over all matches of pattern |
Notes
assertFact / retractFact update the stored facts array directly.
retractFact matches by prefix: retractFact('flow', ['north']) removes all flow(north, ...) facts regardless of remaining args.