Loading a rule pack¶
Fathom supports two ways to ship rules into an Engine: as a directory tree
you maintain alongside your application, or as an installable Python package
discovered at runtime through a setuptools entry point. Both routes funnel
through the same loaders, so the YAML you author looks identical either way —
only the delivery mechanism differs.
Load from a directory¶
Engine.from_rules(path, **kwargs) is a classmethod that returns a configured
Engine. Any keyword arguments are forwarded to the Engine(...) constructor.
from fathom.engine import Engine
engine = Engine.from_rules("examples/01-hello-allow-deny")
result = engine.evaluate()
The loader tries two discovery strategies, in order:
- Subdirectory convention (preferred). If the pack directory contains any
of
templates/,modules/,functions/, orrules/, each present subdirectory is passed to the matchingengine.load_templates,load_modules,load_functions, orload_rulesmethod. - Key-inspection fallback. If none of those subdirectories exist, every
*.yamlfile directly underpathis opened and routed by its top-level key:templates→ templates loader,modulesorfocus_order→ modules loader,functions→ functions loader,rulesorruleset→ rules loader.
Both strategies load in the same fixed order: templates → modules →
functions → rules. The order matters because templates define the slot
schemas that rules bind against, modules establish the namespaces that rules
live in, and functions may be referenced from a rule's left-hand side via the
raw-CLIPS test: clause — so everything a rule depends on must be compiled
before the rule itself.
Directory layout¶
For anything larger than a toy example, use the subdirectory convention. The
reference shape is what examples/01-hello-allow-deny ships with:
my-pack/
├── templates/
│ └── *.yaml
├── modules/
│ └── *.yaml
├── functions/
│ └── *.yaml
└── rules/
└── *.yaml
Every YAML file under a given subdirectory is loaded. The glob is *.yaml
only — files ending in .yml are not picked up by from_rules, so stick
to the long extension. Empty or missing subdirectories are fine; the loader
simply skips them.
The key-inspection fallback is handy for tiny single-file packs where
splitting into subdirectories would be overkill, but it's strictly less
expressive: it only scans the top level of path (no recursion) and each
file must declare exactly one top-level key that the loader recognises.
Load a distributed pack via entry point¶
Once a pack is packaged as a Python distribution, load it by name:
Engine.load_pack delegates to RulePackLoader, which walks the
fathom.packs entry-point group, imports the registered module, resolves its
on-disk location via module.__path__, and then runs the same
templates/ → modules/ → functions/ → rules/ subdirectory load as
from_rules does.
To expose your own pack, add an entry to your pyproject.toml:
Here my_package/rules/ is an importable package directory that contains the
familiar templates/, modules/, functions/, and rules/ subdirectories
full of YAML. After pip install, any process with Fathom installed can call
engine.load_pack("my-pack").
Packs shipped with Fathom¶
Fathom currently ships four first-party rule packs, registered under the same
entry-point group in its own pyproject.toml:
owasp-agenticnist-800-53hipaacmmc
Error handling¶
If you pass a name that isn't registered under fathom.packs,
RulePackLoader.discover raises CompilationError with
construct="pack:<name>". The same error is raised if the registered module
has no resolvable path (neither __path__ nor __file__), which in practice
only happens for exotic namespace-package setups.
When to use which¶
Engine.from_rules(path)— your application owns the rules, they live in a directory inside the repo (or a mounted volume), and you want the fastest edit-reload loop. Best for development, single-tenant deployments, and environment-specific overrides.engine.load_pack(name)— the rules are a redistributable asset consumed by multiple applications, need independent versioning, and can be published alongside your Python wheels. Enablespip install compliance-packstyle workflows and clean upgrades.
Nothing stops you from mixing both in one Engine: call load_pack for a
shared baseline, then load_rules or load_templates on a local directory
for application-specific overlays.
Related reading¶
- Python SDK reference — full
EngineAPI, including everyload_*method used here. - Writing rules — YAML authoring conventions for the files inside a pack.
- YAML schema reference — the top-level keys
(
templates,modules,functions,rules) that the key-inspection fallback looks for.