Skip to content

Quick Start Guide

Installation

Choose the method that matches your operating system:

๐Ÿง Linux (Arch Linux)

zelph is available in the AUR:

paru -S zelph

๐Ÿง Linux (Debian / Ubuntu)

Download the latest .deb package for your architecture from Releases and install it:

sudo apt install ./zelph_*_amd64.deb

๐Ÿง Linux (Other Distributions)

Download the latest zelph-linux-x64.zip (for arm64: zelph-linux-arm64.zip) from Releases, extract it, and run the binary directly. Alternatively, see Building zelph to compile from source.

๐Ÿ macOS (via Homebrew)

brew tap acrion/zelph
brew install zelph

๐ŸชŸ Windows (via Chocolatey)

choco install zelph

Basic Usage

Once installed, you can run zelph in interactive mode simply by typing zelph in your terminal. (If you downloaded a binary manually without installing, run ./zelph from the extraction directory).

A file of the same lines runs the same way, either as an argument or on standard input:

zelph my-facts.zph          # runs as if the lines had been typed
zelph < my-facts.zph        # the same, read from a pipe

Both are a session, so statements are echoed, derivations are printed and inference runs after every line. A #!/usr/bin/env zelph script therefore behaves the way it looks, and it may be called anything โ€” report, not report.zph. Loading a file with .import from inside a session is the other thing: that makes it a quiet module, see Scripts and Modules. zelph leaves with a non-zero status when something failed, so a script can be used in a pipeline or a Makefile.

Let's try a basic example:

Berlin "is capital of" Germany
Germany "is located in" Europe
(X "is capital of" Y, Y "is located in" Z) => (X "is located in" Z)

After entering these statements, zelph will automatically infer that Berlin is located in Europe:

(Berlin "is located in" Europe) โ‡ {(Germany "is located in" Europe) (Berlin "is capital of" Germany)}

Note that none of the items used in the above statements are predefined, i.e. all are made known to zelph by these statements. In section Semantic Network Structure you'll find details about the core concepts, including syntactic details.

A statement may span several lines. zelph reads lines until it has a complete statement โ€” a subject, a predicate and at least one object โ€” so a line that stops short of that waits for the rest. Two forms are easy to type by accident: a p (the object forgotten) and (a p b) on its own, which is a term, i.e. a statement prefix, and is exactly how the renderer prints a nested fact and how .explain takes its argument. Both wait, and the next statement line is appended to them:

zelph> (a p b)
zelph> c q d
zelph> S c O
Answer: (a p b) c q
Answer: (a p b) c d

The query requests the predicate c since that is precisely what the two lines built: the term (a p b) became the subject of a statement having the predicate c and objects q and d.

At end of input an unfinished statement is reported (Input ends inside an unfinished statement: (a p b)), and a .-command typed while one is pending says which one it is.

Two values need whitespace between them. A line the parser cannot read at all is refused rather than buffered, and the most frequent cause is a value written against an opening parenthesis โ€” the f(x) of ordinary mathematical notation:

zelph> a b x(c d e)
Error in line "a b x(c d e)": Syntax error: Could not parse statement. "x(" is a
value glued to a "(": the grammar separates two values by whitespace, so write
"x (" if a group was meant. Function notation such as "f(x)" exists only inside
a notation island -- see ".import math-syntax".

Two Statement Prefixes

Besides the dot-commands, two prefixes modify how a statement is read. They are not commands and take no arguments โ€” they attach to the statement itself.

? โ€” ask for a result. Most standard-library modules expose their answer under =, which normally means asserting the request, letting the fixpoint run, and querying the result separately. ? does all three in one line, and keeps the inference pass quiet:

zelph> .import decimal-arithmetic
zelph> ? &12 * &34
Answer: (&12 * &34) = &408

It is repeatable: once the result fact exists, asking again answers from the graph without re-deriving anything.

: โ€” the self-fact prefix. Many requests are facts whose subject and object are the same node, (T simplify T). The prefix spells that once: :simplify T is (T simplify T), in input and in output. See The Self-Fact Prefix.

The two combine, which is the usual way to drive the mathematical modules:

zelph> .import math
zelph> <x> ~ polyring
zelph> ? :topoly $( (x+1)^2 )
Answer: (:topoly ((x + &1) ^ &2)) = (x poly <(pos zint &1) (pos zint &2) (pos zint &1)>)

When a request has no answer, nothing is printed. Throughout the standard library that is deliberate: partiality is expressed by absence, never by a default value.

The Standard Library

zelph ships with a standard library of scripts. When a script given to .import is not found at the given path, zelph searches the standard library โ€” there, the .zph extension is optional:

.import math                 # the whole mathematics stack in one import
.import sparql               # SPARQL query interface
.import wikidata-classes     # Wikidata class hierarchy: culprits, chains, reports
.import decimal-arithmetic   # rule-based arithmetic, base 10 (+ - * / mod cmp ^)
.import binary-arithmetic    # the same, base 2 (full-adder/subtractor axioms)
.import binary-nand-arithmetic  # the same, derived from a single NAND axiom
.import primes               # primality by trial division
.import nn                   # neural network helpers

The three arithmetic modules are interchangeable: each claims the module ID arithmetic via .provides, so anything built on top of arithmetic uses whichever you imported first. See Mathematics for the modules stacked above them.

Examples โ€” including every script referenced throughout this documentation โ€” live in the examples/ subdirectory and are addressed with their subpath:

.import examples/english
.import examples/neural/nn-wikidata-demo

Search order: $ZELPH_STDLIB (if set) โ†’ stdlib/ next to the zelph executable โ†’ ../share/zelph relative to the executable (e.g. /usr/share/zelph) โ†’ /usr/local/share/zelph and /usr/share/zelph on Unix-like systems.

All installation methods on this page install the standard library automatically. The portable release archives contain it as a stdlib/ directory next to the binary โ€” keep the two together (or point ZELPH_STDLIB at the directory) if you relocate the binary; otherwise .import <name> cannot fall back to the library.

Note: some import/export examples read data files (e.g. taxonomy.json) from the current working directory; run those from within their examples directory or copy the data files first.

Loading and Saving Network State

zelph allows you to save the current network state to a binary file and load it later:

.save network.bin          # Save the current network
.load network.bin          # Load a previously saved network

The .load command is general-purpose:

  • If the file ends with .bin, it loads the serialized network directly (fast).
  • If the file ends with .json or .json.bz2 (a Wikidata dump), it imports the data and automatically creates a .bin cache file for future loads.

Data Cleanup Commands

zelph provides powerful commands for targeted data removal:

  • .prune-facts <pattern> โ€“ Removes only the matching facts (statement nodes).
    Useful for deleting specific properties without affecting the entities themselves. A pattern without variables removes exactly the one fact it names; a pattern that matches nothing changes nothing.

  • .prune-nodes <pattern> โ€“ Removes matching facts and the nodes bound to the pattern's variable.
    Requirements: exactly one variable (subject or a single object), fixed relation. Two variables are rejected โ€” the variable names what gets deleted, so there can only be one.
    Warning: a deleted node takes everything it is a part of with it โ€” every fact naming it, every fact naming one of those, and every rule one of them is a condition or a conclusion of โ€” including facts and rules unrelated to the pattern, plus its names. Use with caution!

  • .prune-nodes <variable> (<conditions>) โ€“ The same, selected by a conjunction whose named variable says which bindings die.
    Any number of variables and predicates is allowed there; the other conditions are the filter that selected the victims, and their own facts survive. With a transitive path condition this replaces a hand-written list of subclasses:
    .prune-nodes A (A P31 C, C P279โˆ— Q6999) removes every instance of a class at or below Q6999.

Both commands remove claims. A statement that exists only as a rule's own condition or consequence is graph structure rather than data โ€” queries do not answer it and .explain calls it a rule pattern โ€” so the prune commands leave it alone and say so. Use .node to get its ID and .remove if you really mean to delete that structure.

  • .cleanup โ€“ Removes all isolated nodes and cleans name mappings. The engine's core nodes (!, nil, conjunction, negation) are exempt, since they carry no edges until something uses them.

Example:

.lang wikidata
A P31 Q8054                 # Query all proteins
.prune-facts A P31 Q8054    # Remove only "instance of protein" statements
.prune-nodes A P31 Q8054    # Remove statements AND all protein nodes (with all their properties!)
.cleanup                    # Clean up any remaining isolated nodes

Full Command Reference

Type .help inside the interactive session for a complete overview, or .help <command> for details on a specific command.

Session

  • .help [command] โ€“ Show this help or detailed help for a specific command
  • .quit โ€“ Exit REPL (quits zelph)
  • .licenses โ€“ Show third-party libraries and licenses

Scripts, Loading & Saving

  • .import <script> [args...] โ€“ Load and execute a zelph (.zph, optional) or Janet (.janet) script; falls back to the standard library
  • .provides <id> [id2 ...] โ€“ Claim module IDs in the import registry
  • .load <file> โ€“ Load a saved network (.bin) or import Wikidata JSON dump (creates .bin cache)
  • .load-partial <file|manifest> [...] โ€“ Load selected chunks as a read-only partial view (see .help .load-partial)
  • .save <file.bin> โ€“ Save the current network to a binary file
  • .save-predicates <file.bin> <predicate> [...] โ€“ Save only the facts of the given predicates (a slice; see Publishing a Predicate Slice)
  • .stat-file <file.bin> โ€“ Show serialized-file chunk statistics without loading the network
  • .index-file <file.bin> <json> โ€“ Emit a JSON byte-offset index for a serialized .bin file

Languages & Names

  • .lang [code] โ€“ Show or set current language (e.g. en, de, wikidata)
  • .name <node|id> <new_name> โ€“ Set name in current language
  • .name <node|id> <lang> <new_name> โ€“ Set name in specific language
  • .delname <node|id> [lang] โ€“ Delete name in current language (or specified language)

Giving a node a name that another node already holds in that language merges the two, with a warning naming both โ€” that is how one states, after the fact, that a node written by hand and an imported entity are the same thing. A node is the hash of what it is built from, so everything built on the node that disappears is re-created under the id its new components give it, and folds into an equal fact where the graph already holds one. Core nodes are never the ones that disappear, and a variable and a non-variable cannot be merged at all.

Exploring the Network

  • .stat โ€“ Show network statistics (nodes, RAM usage, name entries, languages, rules)
  • .explain [<fact>] [depth] โ€“ Reconstruct why a fact holds (proof tree; no arg: last output, 0 = unlimited depth); alias: .why
  • .list <count> โ€“ List first N existing nodes (internal map order, with details)
  • .clist <count> โ€“ List first N nodes named in current language (sorted by ID if feasible)
  • .node [<name|id|fact>] โ€“ Show detailed node information; defaults to last output node
  • .out <name|id|fact> [count] โ€“ List details of outgoing connected nodes (default 20)
  • .in <name|id|fact> [count] โ€“ List details of incoming connected nodes (default 20)
  • .mermaid [<name|id|fact>] [depth] [max_neighbours] โ€“ Generate a Mermaid HTML graph; defaults to last output node
  • .list-predicate-usage [max] โ€“ Show predicate usage statistics (top N most frequent predicates)
  • .list-predicate-value-usage <name|id|fact> [max] โ€“ Show object/value usage statistics for a specific predicate (top N most frequent values)

Inference & Rules

  • .run โ€“ Run full inference (from Janet: (zelph/run))
  • .run-once โ€“ Run a single inference pass (from Janet: (zelph/run-once))
  • .run-delta โ€“ Run inference seeded only by the facts added since the last run; costs time in the size of the addition rather than of the graph (from Janet: (zelph/run-delta), see Reasoning incrementally)
  • .run-export <file> โ€“ Run inference and write what that run derives to a JSON Lines file (see Exporting Derivations)
  • .auto-run โ€“ Toggle automatic execution of .run after each input; takes no argument (default: on). Auto-run is tied to processing an input line, so a program that only calls the Janet API has to run the engine itself with (zelph/run).
  • .deductions [all|focus|quiet|off] โ€“ Set the deduction printing mode (default: quiet)
  • .list-rules โ€“ List all defined inference rules
  • .remove-rules โ€“ Remove all inference rules

Editing & Removing

  • .remove <name|id> โ€“ Remove a node and everything it is a part of (destructive)
  • .prune-facts <pattern> โ€“ Remove all facts matching the query pattern (only statements)
  • .prune-nodes <pattern> โ€“ Remove matching facts AND all involved subject/object nodes
  • .prune-nodes <var> (<conditions>) โ€“ ... selected by a conjunction, deleting what <var> binds
  • .cleanup โ€“ Remove isolated nodes and clean name mappings (core nodes exempt)
  • .new โ€“ Clear the complete network and re-initialize the core nodes

Clusters

  • .cluster [name] โ€“ Show clusters, or activate one ('default' = no cluster)
  • .cluster-drop <name> โ€“ Remove a cluster INCLUDING all nodes created in it (rollback)
  • .cluster-merge <from> <to> โ€“ Move a cluster's membership into another ('default' = keep nodes, forget cluster)

Wikidata

  • .wikidata-constraints <json> <dir> โ€“ Export property constraints as zelph scripts to a directory
  • .wikidata-qualifiers <json> [P1 P2 ...] โ€“ Import statement qualifiers from a Wikidata dump (all, or only listed qualifier properties)
  • .export-wikidata <json> <id1> [id2 ...] โ€“ Extract exact JSON lines for Q-IDs (no import)

Engine Behaviour

  • .parallel โ€“ Toggle parallel processing (default: on)
  • .anchors [on|off] โ€“ Show or set anchor-based candidate lookups in unification (default: on)
  • .semi-naive [on|off|check] โ€“ Show or set the fixpoint evaluation strategy (default: on)
  • .fact-stores [on|off] โ€“ Show or disable the fact-path acceleration stores (memory vs. speed)
  • .contradiction-records [on|off] โ€“ Show or disable writing each contradiction into the graph (memory vs. repeated reports)

Logging & Profiling

  • .log <max-depth> โ€“ Enable detailed reasoning logging up to given recursion depth (0 = off, -1 = counters only)
  • .log-janet โ€“ Toggle logging of Janet function calls (inputs/outputs)
  • .prof [reset] โ€“ Dump reasoning profiler counters (requires .log -1 or .log N); 'reset' starts a fresh window

What's Next?

  • Mathematics โ€” proving polynomial identities, symbolic differentiation, and a stack built from a single logic gate upwards
  • Explore the Core Concepts to understand how zelph represents knowledge
  • Learn about Rules and Inference to leverage zelph's reasoning capabilities
  • Check out the Example Script for a comprehensive demonstration