Template¶
A template declares the shape of a fact: its name and the typed slots
that facts of that kind carry. Templates compile to CLIPS deftemplate
constructs and live in the MAIN module regardless of which rule module
references them. For the conceptual role of templates among Fathom's five
primitives, see Five Primitives.
Top-level fields — TemplateDefinition¶
| Field | Type | Default | Required | Description |
|---|---|---|---|---|
name |
str |
— | yes | CLIPS identifier. Must match ^[A-Za-z_][A-Za-z0-9_\-]*$. Emitted as (deftemplate MAIN::<name> …). |
description |
str |
"" |
no | Author-facing prose. Not emitted to CLIPS. |
slots |
list[SlotDefinition] |
— | yes | One or more slot definitions. Empty list is accepted by Pydantic but rejected at compile time. |
ttl |
int \| None |
None |
no | Fact expiry in seconds. Not emitted to CLIPS; the engine retracts a fact of this template once it is older than ttl, checked at every evaluation. A fact a rule asserts is timed from the first evaluation that sees it. |
scope |
Literal["session", "fleet"] |
"session" |
no | Consumed by the fleet layer. Not emitted to CLIPS. A rule assert into a fleet template is a CompilationError at load. |
The name field is validated by _name_must_be_clips_ident at model
construction and re-checked (for emptiness) by compile_template.
Slot fields — SlotDefinition¶
| Field | Type | Default | Required | Description |
|---|---|---|---|---|
name |
str |
— | yes | Slot identifier. Must match ^[A-Za-z_][A-Za-z0-9_\-]*$, checked at model construction. Emitted as (slot <name> …). |
type |
SlotType |
— | yes | One of string, symbol, float, integer. See SlotType. |
required |
bool |
False |
no | If True, FactManager rejects asserts that omit this slot with a ValidationError (src/fathom/facts.py:405-414). Not emitted to CLIPS; the rule-RHS assert path bypasses this check because it doesn't run through FactManager._validate. |
allowed_values |
list[str] \| None |
None |
no | Emitted to CLIPS only for string and symbol slots (see _CLIPS_ALLOWED_MAP). On every slot type FactManager enforces it at assert time, comparing str(value) against the list. |
default |
str \| float \| int \| None |
None |
no | Emitted as (default <value>). String defaults are CLIPS-quoted and escaped; numeric defaults are emitted raw. |
SlotType enum¶
SlotType is a StrEnum with four members. The compiler maps each to a
CLIPS type keyword via _CLIPS_TYPE_MAP and — for the two symbolic
types — to an allowed-values directive via _CLIPS_ALLOWED_MAP.
| YAML value | CLIPS type keyword | Allowed-values directive |
|---|---|---|
string |
STRING |
allowed-strings |
symbol |
SYMBOL |
allowed-symbols |
float |
FLOAT |
(none — silently dropped) |
integer |
INTEGER |
(none — silently dropped) |
A float slot accepts a Python int as well as a float; FactManager
widens it before it reaches CLIPS, which types 1 as INTEGER and would
otherwise reject it. bool is refused for both. An integer slot takes
only int, or a float with no fractional part. NaN and infinities
are refused for both. A string slot takes an int or float as its decimal text but
refuses bool.
Source: src/fathom/compiler.py lines 29–40.
CLIPS emission¶
compile_template (in src/fathom/compiler.py) emits a multi-line
deftemplate form. Every template is scoped to the MAIN module;
per-module scoping happens on defrule, not on deftemplate.
YAML input¶
templates:
- name: access-request
description: An agent's request to perform an action on a resource.
slots:
- name: subject
type: symbol
- name: action
type: string
allowed_values: [read, write, delete]
- name: amount
type: integer
default: 0
CLIPS output¶
(deftemplate MAIN::access-request
(slot subject (type SYMBOL))
(slot action (type STRING) (allowed-strings "read" "write" "delete"))
(slot amount (type INTEGER) (default 0)))
Emission rules¶
- First line:
(deftemplate MAIN::<name>. - Each slot is indented four spaces and wrapped as
(slot <name> <parts>). - Slot parts are emitted in this fixed order:
(type <CLIPS_TYPE>),(<allowed-directive> <values>)(string/symbol only),(default <value>). - For
stringslots, eachallowed_valuesentry and any string default is run through_escape_clips_string(backslash first, then double-quote) and wrapped in"…".symbolallowed values are emitted unquoted. - Numeric defaults (
int,float) are emitted viastr(slot.default)with no quoting. - The template closes with
)on its own line.
Validators¶
Pydantic-level rejections (raised at TemplateDefinition(...) or during
YAML load):
- Any key not listed in the tables above, on either model — both
TemplateDefinitionandSlotDefinitionareextra="forbid", so a misspelled field is an error rather than a silently dropped line. - A top-level key in a template file other than
templates→CompilationErrorfromparse_template_file(see File-level keys). TemplateDefinition.namethat does not match^[A-Za-z_][A-Za-z0-9_\-]*$→ValueErrorfrom_name_must_be_clips_ident.SlotDefinition.namethat does not match the same pattern →ValueErrorfrom_validate_clips_ident.- On a
symbolslot, anallowed_valuesentry that is not a CLIPS identifier →ValueError. Symbol allowed values are emitted unquoted, so an unchecked entry closes thedeftemplateand appends constructs of its own. - On a
symbolslot, a stringdefaultthat is not a CLIPS identifier →ValueError, for the same reason. - On an
integerorfloatslot, a stringdefaultof any kind →ValueError: the value is interpolated verbatim, so it has to be numeric.
string slots are exempt from the last three: their values are escaped and
quoted on the way out, so no content in them can escape the construct.
Compile-time rejections (raised by Compiler.compile_template):
namethat is an empty string →CompilationErrorwithconstruct="template:<empty>".slotsis an empty list →CompilationErrorwithconstruct="template:<name>". Note: theslotsfield has no Pydanticmin_length=1constraint, so an empty list passes model validation and only fails at compile time.
What is not emitted¶
These YAML fields are accepted by the model but do not appear in the
compiled CLIPS deftemplate:
TemplateDefinition.description— metadata only.TemplateDefinition.ttl— enforced by the engine, which retracts expired facts at every evaluation; it has no CLIPS counterpart. A template withttl: 300emits the samedeftemplateas one without.TemplateDefinition.scope— consumed by the fleet layer, not the compiler.SlotDefinition.required— the flag itself is not emitted to CLIPS. Runtime enforcement happens inFactManager._check_required(src/fathom/facts.py:405-414) on the SDK and REST paths, but it is not checked on the rule-RHS assert path.SlotDefinition.allowed_valuesonfloatorintegerslots — not emitted, because these types have no entry in_CLIPS_ALLOWED_MAP.FactManagerstill enforces it on asserted facts; a rule'sthen.assertis not checked.
If you need one of these to influence runtime behavior today, enforce it
in a rule (for example, an explicit allowed-values-style test CE for a
numeric slot) rather than relying on the template to do so.
See also¶
- Five Primitives — conceptual overview of templates, facts, rules, modules, and functions.
- YAML Compilation — how YAML documents are loaded and compiled to CLIPS source.
- Audit & Attestation — how asserted-fact slots surface in audit records.