This file provides guidance to coding agents collaborating on this repository.
Zodd is a small, embeddable Datalog engine written in pure Zig. It evaluates recursive rules over sets of tuples using semi-naive iteration, merge joins, and indexed extension primitives. Zodd is designed to be embedded in Zig projects as a library. Priorities, in order:
- Correctness of relations, variables, joins, extensions, and fixed-point iteration.
- Minimal public API for use as a library from other Zig projects.
- Small dependency footprint and maintainable, well-tested code.
- Cross-platform support (Linux, macOS, and Windows).
- Use English for code, comments, docs, and tests.
- Prefer small, focused changes over large refactoring.
- Add comments only when they clarify non-obvious behavior.
- Do not add features, error handling, or abstractions beyond what is needed for the current task.
- Keep the dependency set small: do not add new Zig packages or C libraries without prior discussion.
- Use Oxford commas in inline lists: "a, b, and c" not "a, b, c".
- Do not use em dashes. Restructure the sentence, or use a colon or semicolon instead.
- Avoid colorful adjectives and adverbs. Write "Datalog engine" not "blazing-fast Datalog engine", "merge join" not "efficient merge join".
- Use noun phrases for checklist items, not imperative verbs. Write "redundant index detection" not "detect redundant indexes".
- Headings in Markdown files must be in the title case: "Build from Source" not "Build from source". Minor words (a, an, the, and, but, or, for, in, on, at, to, by, of, is, are, was, were, be) stay lowercase unless they are the first word.
src/lib.zig: Public API entry point. Re-exportsRelation,Variable,Iteration,Database, join helpers, and extend primitives.src/zodd/relation.zig: ImmutableRelationtype (sorted, deduplicated tuples).src/zodd/variable.zig: MutableVariabletype for fixed-point iteration, plus thegallopsearch helper.src/zodd/iteration.zig:Iterationdriver for semi-naive evaluation.src/zodd/join.zig: Merge-join algorithms (joinHelper,joinInto,joinAnti).src/zodd/extend.zig: Leaper-based extension primitives (ExtendWith,FilterAnti,ExtendAnti,extendInto).src/zodd/secondary_index.zig: Indexes for keyed lookups.src/zodd/aggregate.zig: Group-by and aggregation operations.src/zodd/frontend/: Datalog frontend.program.zigis the publicDatabaseAPI;token.zigandparser.zigparse textual Datalog;ast.zigandbuilder.zighold the shared IR and the programmatic builder;analyze.zigchecks safety and stratification;dyntuple.zig,plan.zig,join_runtime.zig, andevaluator.zigcompile and run rules on the engine core;explain.zigrenders rule plans and provenance proof trees;magic.zigbuilds the demand-transformed (magic sets) program behindDatabase.queryDemand.src/cli/main.zig: ThezoddCLI executable (run,query,plan,explain, andreplsubcommands), built viazig build cli.tests/: Non-unit tests (integration_tests.zig,regression_tests.zig,property_tests.zig,incremental_tests.zig,frontend_tests.zig).tests/differential/difftest.py: Differential testing against Clingo; random stratified programs evaluated by both engines must agree. Run viamake diff-test(needsuv; the Clingo dependency is declared in the rootpyproject.tomland pinned byuv.lock).web/: Web frontend.zodd_wasm.zigis the Wasm wrapper built byzig build wasm;index.html,main.js, andstyle.cssare the UI;smoke_test.mjsis the Node.js smoke test run bymake web-test.examples/: Self-contained example programs (e1_network_reachability.zigthroughe8_comparison_filters.zig) built as executables viabuild.zig..github/workflows/: CI workflows (tests.ymlfor unit and integration tests plus the Wasm smoke test,docs.ymlfor deploying the website: the web frontend at the site root and API docs under/api, andrelease.ymlfor publishing the web frontend Docker image to GHCR).Dockerfile/.dockerignore: Docker image for the web frontend (nginx serving the playground at the root and API docs under/api), built and published byrelease.yml.build.zig/build.zig.zon: Zig build configuration and package metadata.Makefile: GNU Make wrapper aroundzig buildtargets.docs/: Generated API docs land indocs/api/(produced bymake docs).
A Datalog program flows through: base data is loaded into a Relation (relation.zig). Derived predicates use a Variable (variable.zig) driven
by an Iteration (iteration.zig) loop that calls changed() until a fixed point. Each iteration extends tuples via join (join.zig) or extend
(extend.zig), optionally using indexes (index.zig) or aggregates (aggregate.zig). Every primitive takes a std.mem.Allocator directly; there is
no wrapper context type.
relation.zigis the immutable, sorted, deduplicated tuple container used for base facts and finalized results.variable.zigis the mutable counterpart used inside fixed-point loops; it tracks stable, recent, and to-add tuple sets for semi-naive evaluation.- New join shapes go in
join.zig. New leaper-style extensions go inextend.zig.
index.zig provides keyed lookups used by the extend primitives. aggregate.zig provides group-by reductions.
When adding a new join or extension shape, consider whether it needs an index variant and add it alongside the existing ones.
Everything re-exported from src/lib.zig is part of the public API.
Changes to names or signatures there are breaking.
The rest of src/zodd/ is internal and may be refactored freely as long as the public surface and its behavior are preserved.
Zodd depends on three sibling Zig packages declared in build.zig.zon:
ordered: sorted container primitives, linked into thezoddmodule for all builds.minish: property-testing framework, used only bytests/property_tests.zigand lazy-loaded inbuild.zig.chilli: CLI framework, used only by thezoddCLI executable (src/cli/main.zig).
Please do not add further dependencies without prior discussion.
- Zig version: 0.16.0 (as declared in
build.zig.zonand the Makefile'sZIG_LOCALpath). CI pins the version declared inbuild.zig.zon. - Formatting is enforced by
zig fmt. Runmake formatbefore committing. - Naming follows Zig standard-library conventions:
camelCasefor functions (e.g.joinInto,extendInto,fromSlice),snake_casefor local variables and struct fields,PascalCasefor types and structs (e.g.Relation,Variable,Database), andSCREAMING_SNAKE_CASEfor top-level compile-time constants.
Run the relevant targets for any change:
| Target | Command | What It Runs |
|---|---|---|
| Unit tests | make test |
Inline test blocks in src/ plus every file under tests/ |
| Lint | make lint |
Checks Zig formatting with zig fmt --check over src/ and tests/ |
| Examples | make example |
Builds and runs every example under examples/ |
| Single example | make example EXAMPLE=e1_network_reachability |
Runs one example program |
| Docs | make docs |
Generates API docs into docs/api |
| Differential | make diff-test |
Compares Zodd against Clingo on random programs (needs uv) |
| Everything | make all |
Runs build, test, lint, and docs |
- Read the relevant module under
src/zodd/(oftenrelation.zig,variable.zig,join.zig, orextend.zig). - Implement the smallest change that covers the requirement.
- Add or update inline
testblocks in the changed Zig module, or extend a test file undertests/, to cover the new behavior. - Run
make testandmake lint. - If public behavior changed, also run
make exampleto ensure no example regresses.
Good first tasks:
- Add a new join or extension shape in
src/zodd/join.zigorsrc/zodd/extend.zig(with an inlinetestblock and, if appropriate, an integration test undertests/). - Improve an existing index strategy in
src/zodd/index.zig. - Add a new aggregate operation in
src/zodd/aggregate.zig. - Add a new example under
examples/demonstrating a Datalog pattern, and list it inexamples/README.md.
- Unit tests live as inline
testblocks in the module they cover (src/lib.zigandsrc/zodd/*.zig). They are discovered automatically viastd.testing.refAllDecls(@This())insrc/lib.zig. - Non-unit tests live under
tests/(integration_tests.zig,regression_tests.zig,property_tests.zig,incremental_tests.zig) and are auto-discovered bybuild.zig. - Property tests use the
minishdependency and should use fixed seeds so failures are reproducible in CI. - Every new relation, variable operation, join, extension, index, or aggregate must ship with at least one
testblock that exercises it. - No public API change is complete without a test covering the new or changed behavior.
Before coding:
- Identify which module(s) the change touches (
relation,variable,iteration,join,extend,index,aggregate, ordatabase). - Consider whether a new join or extension needs a matching index or anti-variant.
- Check whether the change is public-API-visible (like re-exported from
src/lib.zig); if so, treat it as a breaking or additive API change deliberately. - Check cross-platform implications, especially for anything that touches the filesystem, timing, or OS-specific types.
Before submitting:
make testpasses.make lintpasses.make examplestill succeeds when touching relations, variables, joins, extensions, or iteration.- Docs updated (
make docs) if the public API surface changed, andROADMAP.mdticked/updated if a listed item was implemented.
- Keep commits scoped to one logical change.
- PR descriptions should include:
- Behavioral change summary.
- Tests added or updated.
- Whether examples were run locally (yes/no), and on which OS.