Authoring Rules¶
How to write, validate, test, and ship Fathom routing rules for Nautilus.
Rule file anatomy¶
Rules are YAML, evaluated by the CLIPS-backed Fathom engine. A file declares a module, a ruleset, and a list of rules:
module: nautilus-routing
ruleset: my-org-rules
version: "1.0"
rules:
- name: deny-finance-after-hours
description: "Deny finance sources outside business hours."
salience: 180
when:
- template: agent
conditions:
- slot: purpose
bind: ?purpose
- template: source
conditions:
- slot: id
bind: ?sid
- slot: classification
operator: eq
value: confidential
then:
action: deny
reason: "finance data is unavailable after hours"
assert:
- template: denial_record
slots:
source_id: "?sid"
reason: "finance data is unavailable after hours"
rule_name: "deny-finance-after-hours"
when— a list of fact patterns. Each entry matches a template (agent,source,intent,session_exposure, ...) with slot conditions.bind: ?varcaptures a slot value;test:entries embed raw CLIPS predicates (e.g. thefathom-dominateshierarchy check).then—action: allow | deny | scopeplus facts toassert.action: denyis required for the rule to emit a__fathom_decisionfact and appear in the rule trace.salience— higher fires first. The shipped defaults use purpose-mismatch denial at 200, classification denial at 150, routing at 100; place your rules relative to those.
The shipped templates live in nautilus/rules/templates/, the default
rules in nautilus/rules/rules/, and module declarations in
nautilus/rules/modules/ — read them as worked examples.
The denial-record invariant¶
The router runs post-evaluation consistency checks (fail-closed: a
violation raises PolicyEngineError rather than mis-routing). Two of
them constrain rules you write:
- Every
denial_recordmust reference a declared source — itssource_idslot must match a source id from the registry. Bind it from the matchedsourcefact (source_id: "?sid"); never hardcode. - Every
denial_recordmust carry a non-emptyreasonandrule_name.
A rule that asserts an unlinked or anonymous denial record will fail the
whole request with a ConsistencyError — by design, since an
unattributable denial cannot be audited.
Scope constraints are checked too: a scope_constraint must reference a
source that has a routing decision, and its operator must be in the
adapter allowlist (=, eq, !=, IN, NOT IN, LIKE, IS NULL).
Checks default on; rules.consistency_checks: false opts out for
performance-sensitive deployments.
Wiring rules into the broker¶
Pre-built packs (NIST, HIPAA) load by name — see Rule Packs.
Validate → test → ship¶
1. Static validation¶
nautilus rules validate my-rules.yaml
nautilus rules validate my-rules.yaml --sandbox --replay-n 1000
Catches structural errors: unknown templates, malformed conditions,
missing required slots. --sandbox additionally replays recent audit
entries against the rule. Exit 1 on any error.
2. Full test run¶
nautilus rules test --file my-rules.yaml \
--audit-log /var/lib/nautilus/audit.jsonl \
--threshold 0.6 --json
Runs the full validator pipeline per rule:
- static — same checks as
validate; errors exit 1. - shadow — detects rules in the file that can never fire because a broader rule with higher salience always wins. Shadowed rules WARN and lower the confidence score.
- sandbox replay — replays the audit log; a rule that would flip a previously-allowed request to denied (or vice versa) is a regression and exits 1.
- score — a confidence breakdown per rule; if the minimum score in
the file is below
--threshold(default 0.6), exit 2.
Exit codes: 0 pass, 1 validation/regression failure, 2 below
threshold. Without --audit-log the sandbox stage has no history and
WARNs insufficient_history instead of replaying — always test against
a recent production audit log before shipping.
3. Lineage¶
Lists the rule lineage for a module: which rules were promoted, when, and from which proposal.
Debugging a misfiring rule¶
- Run the request and pull its audit entry
(
GET /v1/audit/{request_id}) —rule_tracelists every decision fact with the emitting rule name and salience. - If a source is denied unexpectedly, check salience interplay: a higher-salience deny always beats your allow.
- If the request fails with
ConsistencyError, the message carries the failing check name and offending ids — usually a denial record missing itssource_idbinding.
RKM: rules proposed by the system¶
Beyond hand-written rules, the Rule Knowledge Management subsystem
proposes new rules from observed traffic, runs them through the same
validator pipeline, and queues them for human review
(GET /v1/rkm/queue, approve/reject endpoints). See
RKM lifecycle.