Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reasoning

akh-medu uses the egg e-graph library for symbolic reasoning via equality saturation. E-graphs efficiently represent equivalence classes of expressions, and rewrite rules transform them until a fixed point is reached.

How E-Graphs Work

An e-graph (equality graph) stores expressions and their equivalences simultaneously. When a rewrite rule fires, the e-graph doesn't destructively replace -- it adds the new expression to the same equivalence class.

Before rule: bind(unbind(X, Y), Y) => X
E-graph: { bind(unbind(A, B), B) }

After rule:
E-graph: { bind(unbind(A, B), B), A }  <- both in the same e-class

This means multiple rules can fire without conflict, and the optimal result is extracted after all rules have been applied.

AkhLang

akh-medu defines AkhLang, a domain-specific language for the e-graph:

#![allow(unused)]
fn main() {
define_language! {
    pub enum AkhLang {
        // VSA operations
        "bind" = Bind([Id; 2]),
        "unbind" = Unbind([Id; 2]),
        "bundle" = Bundle([Id; 2]),
        "permute" = Permute([Id; 2]),
        // Knowledge operations
        "triple" = Triple([Id; 3]),
        "sim" = Similarity([Id; 2]),
        // Leaf nodes
        Symbol(Symbol),
    }
}
}

Built-in Rewrite Rules

The engine ships with algebraic rules for VSA operations:

RulePatternResultPurpose
Bind-unbind cancelunbind(bind(X, Y), Y)XVSA algebra: unbinding reverses binding
Unbind-bind cancelbind(unbind(X, Y), Y)XSymmetric cancellation
Bind commutativebind(X, Y)bind(Y, X)XOR is commutative
Bundle commutativebundle(X, Y)bundle(Y, X)Majority vote is commutative
Bind self-inversebind(X, X)identityXOR with self = zero

Skills can contribute additional rules that are loaded dynamically.

Using the Reasoner

CLI

# Simplify an expression
akh-medu reason --expr "unbind(bind(Dog, is-a), is-a)"
# Output: Dog

# Verbose mode shows the e-graph state
akh-medu reason --expr "unbind(bind(Dog, is-a), is-a)" --verbose

Rust API

#![allow(unused)]
fn main() {
use egg::{rewrite, Runner, Extractor, AstSize};
use akh_medu::reason::AkhLang;

let rules = akh_medu::reason::built_in_rules();

let expr = "unbind(bind(Dog, is-a), is-a)".parse()?;
let runner = Runner::default()
    .with_expr(&expr)
    .run(&rules);

let extractor = Extractor::new(&runner.egraph, AstSize);
let (cost, best) = extractor.find_best(runner.roots[0]);
println!("Simplified to: {} (cost {})", best, cost);
}

Forward-Chaining Inference

The agent's infer_rules tool runs rewrite rules as forward-chaining inference:

  1. Existing triples are encoded as e-graph expressions.
  2. Rewrite rules fire, potentially producing new triple expressions.
  3. New triples are extracted and committed to the knowledge graph.
akh-medu agent infer --max-iterations 10 --min-confidence 0.5

E-Graph Verification

During inference, VSA-recovered results can optionally be verified by the e-graph. If unbind(bind(A, B), B) doesn't simplify to A, it suggests the recovery was noisy and confidence is reduced by 10%.

This is controlled by InferenceQuery::with_egraph_verification():

#![allow(unused)]
fn main() {
let query = InferenceQuery::default()
    .with_seeds(vec![dog_id])
    .with_egraph_verification();
}

Skills and Custom Rules

Skill packs can contribute domain-specific rewrite rules:

# In a skill's rules.toml
[[rules]]
name = "transitive-is-a"
lhs = "(triple ?a is-a ?b) (triple ?b is-a ?c)"
rhs = "(triple ?a is-a ?c)"

When a skill is loaded, its rules are compiled into egg rewrites and added to the engine's rule set. When the skill is unloaded, the rules are removed.

Design Rationale

Why e-graphs? Traditional rule engines apply rules destructively -- once a rule fires, the original expression is gone. E-graphs keep all intermediate forms, avoiding the phase-ordering problem where the order of rule application matters. With equality saturation, all rules fire simultaneously and the optimal result is extracted at the end.

Why algebraic verification? VSA recovery via unbind(subject, predicate) is approximate -- the recovered vector is searched against item memory for the nearest match. This can produce false positives, especially with high symbol density. The e-graph provides a cheap mathematical sanity check: if the algebraic identity doesn't hold, the recovery is suspect.