Skip to content

ADR-0006: Rules as typed, serializable data

Status: Accepted Date: 2026-09-28 Deciders: Alex Nodeland

Context

Reflex had two filter APIs (filter classes, and callables of (event, context)), trigger functions that the loop never called, and registration through decorators with side effects on a global registry. None of it could be serialized, inspected or checked before running.

Rules should be as easy for an agent to write as for a person: the planned third system lets a chat agent propose a rule that a person accepts.

Decision

  • A Rule is a Pydantic model: name, when (a condition), scope, then (an action reference), and policies (retry, ordering, on_dead_letter, start, enabled).
  • A condition is a pipeline of stages, each a discriminated union of kinds:
  • filter (stateless): on(types), where(field, op, value), all, any, not, and predicate(name)
  • dedupe: drop events whose key was seen within a window
  • pattern (stateful): each, count(at_least, within), sequence(steps, within), absence(within)
  • throttle: at most N firings per period
  • A fluent builder produces the models: on(ServiceError).where(F.severity >= 7).count(at_least=3, within=timedelta(minutes=1)). F builds field references, which are checked against the event types the filter admits when the rule is built.
  • Escape hatches are named: predicate("business_hours") refers to a registered, pure Python function.
  • Actions are referenced by name ({"action": "triage"}). run(action) names the action and lets the reactor register it; rules loaded from JSON resolve names against registered actions.
  • A JSON Schema for rules is generated from the models and checked in. A rule's identity is its name; a hash of its definition detects changes, which reset its state.

Options considered

Option Serializable Checked before running Expressiveness
Typed models with a builder (chosen) Yes, with a schema Yes: field references and action names High, with named escape hatches
Python decorators and predicates No Only by running Highest
Declarative YAML or JSON only Yes By schema Limited, and a second language

Trade-off analysis

Decorators are the quickest to write, but a rule that is only code cannot be listed, diffed, validated, stored or proposed by an agent. Typed models keep Python's ergonomics through the builder and add all of that. The named escape hatch covers what the condition kinds cannot express, without making the rule opaque.

Consequences

  • Easier: rules can be shown in an admin UI, stored, versioned, generated by an agent and validated before they run.
  • Harder: every new condition kind needs a model, a reducer, fixtures and a schema update.
  • Runtime rule management (storing and installing rules through the API) is additive and left for after v0.1.

Action items

  1. [ ] Implement the rule models, builder and schema generation (RFC-0001 phase 1).