Module¶
A module is a CLIPS namespace for rules. Every rule lives in exactly one module, and Fathom's deterministic ordering is driven by the order those modules are pushed onto the CLIPS focus stack. For the role of modules among Fathom's five primitives, see Five Primitives; for the focus-stack mechanics at evaluation time, see Runtime & Working Memory.
Top-level fields — ModuleDefinition¶
| Field | Type | Default | Required | Description |
|---|---|---|---|---|
name |
str |
— | yes | CLIPS identifier. Must match ^[A-Za-z_][A-Za-z0-9_\-]*$. Emitted as (defmodule <name> …). |
description |
str |
"" |
no | Author-facing prose. Not emitted to CLIPS. |
priority |
int |
0 |
no | Author-facing metadata. Not emitted to CLIPS and not used to drive the focus stack in the current runtime (see below). |
The name field is validated by _name_must_be_clips_ident at model
construction and re-checked (for emptiness) by compile_module.
CLIPS emission¶
compile_module (in src/fathom/compiler.py) emits a single-line
defmodule form. Every non-MAIN module imports ?ALL from MAIN so
that constructs declared in MAIN are visible to rules in the module —
the built-in __fathom_decision decision template, and the
?*fathom-decision-seq* defglobal each rule increments before asserting
into it.
YAML input¶
CLIPS output¶
Emission rules¶
- The emitted form is exactly
(defmodule <name> (import MAIN ?ALL))— one line, no variations fordescriptionorpriority. descriptionis never emitted. It exists for authoring only.priorityis never emitted as a CLIPS construct attribute. CLIPS has no module-level priority concept; module ordering is done by the focus stack, not by attributes ondefmodule.- An empty
nameat compile time raisesCompilationErrorwithconstruct="module:<empty>".
Priority and the focus stack¶
ModuleDefinition.priority is a stored integer (default 0) that the
runtime does not read when setting focus. Focus order is driven by
the explicit focus_order: list at the top of the module YAML file (or
by a later Engine.set_focus(...) call). When no focus_order: is
given anywhere in the pack, load_modules falls back to the order the
modules were declared in — see
When focus_order is omitted.
At evaluation time, _setup_focus_stack in src/fathom/evaluator.py
emits a single (focus ...) eval listing the modules in the order they
appear in focus_order — (focus A B C) gives A the focus first and
queues B and C behind it. priority is never consulted during this step.
Every module in the list runs: CLIPS drains the first module's agenda,
then moves to the next. A decision in an earlier module does not
short-circuit the ones after it. Because the evaluator is
last-write-wins, the module you list last is the one whose decision
the caller sees — so a guardrail module belongs at the end of
focus_order, not the start.
What priority is actually used for today:
- It is surfaced by
fathom inspectas metadata for each loaded module (seesrc/fathom/cli.py). - It is preserved on the
ModuleDefinitionin the engine'smodule_registryfor external tooling.
See Runtime & Working Memory for the full focus-stack mechanics.
When focus_order is omitted¶
A pack that declares modules and no focus_order: is focused on every
module that call declared, in declaration order: files in load order,
and within a file the order the modules: list is written.
Engine.load_modules applies this once, after every module file is
parsed, and only when that call parsed no focus_order: of its own — a
focus_order: in any of its files wins outright, and a partial one is
read as the author excluding a module on purpose.
Focus is engine-wide and cumulative across calls: whatever a call
contributes is appended to the order already in place, never substituted
for it. Loading a second rule pack therefore leaves the first one
enforced. Engine.set_focus(...) is the exception and is documented as
such — it replaces the whole order, which is how you narrow focus
deliberately.
The fallback exists because omitting focus_order: is not a request for
a different order; it is the absence of a request. CLIPS drains only the
agenda of the module that holds the focus, so with no focus set at all,
rules scoped to a declared module never fire and the caller silently
receives the default decision — a wrong answer rather than an error.
Declaration order is not a substitute for stating the order you want.
It is stable, but it moves when you add a module or rename a file, and
because the evaluator is last-write-wins, the module that lands last
writes the decision. Any pack whose modules must run in a particular
sequence should say so with focus_order:.
Declaring focus order¶
modules:
- name: classification
description: Label-derivation rules.
- name: access-control
description: Core access decisions.
priority: 100 # metadata only — does not affect ordering
focus_order:
- classification
- access-control
Here classification runs first because it appears first in
focus_order, and access-control runs after it — so access-control
writes the final decision. The priority: 100 is informational and does
not reorder anything.
The MAIN module¶
MAIN is created implicitly by Fathom the first time load_modules is
called, via (defmodule MAIN (export ?ALL)) (see src/fathom/engine.py).
Authors do not declare MAIN in YAML. All deftemplate constructs —
including the built-in __fathom_decision template that Fathom
installs at engine startup, alongside the ?*fathom-decision-seq*
defglobal — live in MAIN, and every non-MAIN module imports from it. Rules are scoped to a module by the module:
key on the enclosing RulesetDefinition; see Rule for the
rule emission.
Validators¶
Pydantic-level rejections (raised at ModuleDefinition(...) or during
YAML load):
namethat does not match^[A-Za-z_][A-Za-z0-9_\-]*$→ValueErrorfrom_name_must_be_clips_ident.
Compile-time rejections (raised by Compiler.compile_module):
namethat is an empty string →CompilationErrorwithconstruct="module:<empty>".
Duplicate module names are rejected by parse_module_file (at YAML
load) and again by Engine.load_modules (across files), both as
CompilationError.
What is not emitted¶
These YAML fields are accepted by the model but never appear in the
compiled CLIPS defmodule:
ModuleDefinition.description— metadata only.ModuleDefinition.priority— metadata only; does not influence focus-stack ordering in the current runtime. Ordering comes fromfocus_order:, or from declaration order when that key is absent.
See also¶
- Five Primitives — conceptual overview of templates, facts, rules, modules, and functions.
- Runtime & Working Memory — focus-stack mechanics and the evaluation loop.
- Rule — how rules are scoped to a module via
RulesetDefinition.module. - Template — why templates live in
MAIN.