ADR-0024: Rules from chat in the template¶
Status: Accepted Date: 2026-10-12 Deciders: Alex Nodeland
Builds the template's part of phase 5 of relayr RFC-0001, as stackr RFC-0002 designs it, on relayr's rules from chat (relayr ADR-0028).
Context¶
relayr turns a rule drafted in a thread into a stored rule in reflexr, once a person who may install rules accepts it. The template's bridge (ADR-0021) had no stored rules: no namespace for them, no action they may run, and no drafting tool.
Decision¶
- Stored rules live in the
chatnamespace, and run onlynotify(chat_rules.py).notifyposts a notice in the thread a rule names, with the rule's name and description. reflexr's limits apply: a throttle, a timeout, and no code. chat:*getsAllowlist(threads="any"): notices in any thread, and nothing more, through relayr's namespace allowlist (relayr ADR-0029). ItsturnsstaysFalse.- A rule from chat acts as itself. Its actors, in reflexr and in artifactr, are checked as its grantr
RulePrincipal. That principal holds therulerole in its own workspace with no grant (grantr ADR-0029), never the application's powers. A restricted thread stays out of its reach. - relayr's
relayr:rules-from-chatis one of the application's code rules (CODE_RULES), so its runs install, update and archive as the application, which holds those actions (grantr ADR-0027). - Who may approve:
- With grantr,
may_changeasks grantr whether the person holdsreflexr:<change>in the workspace. - Without access control, the template passes relayr's
anyone.
- With grantr,
- The notes agent drafts with
draft_rule.RuleDraftis among artifactr's types, so a draft is a proposal people review in the thread. - The wiring order changes.
draft_ruleneeds reflexr's workspaces, and the reactor needs the bridge's actions, which act in artifactr. Socreate_appbuilds the bridge, then Automation withBridge.actions(), then Collaboration withBridge.drafting(automation), and then callsbridge.connect(...). The actions are delegates that act onceconnecthas run, asBridge.watchdoes. The delegates for rules from chat come fromSTORED_RULES.actions, each declaring its params model, so reflexr checks stored rules' params at startup, and each hands the params on as reflexr validated them. - relayr's
relayr:rules-from-chatacts as the application, so its "is live" notice reaches the thread a rule was proposed in, restricted or not. That is by design: whoever accepted it there could see the thread. make smoke-appgives a service principal's key a draft to propose, has the user accept it, and checks two things: the rule tells its thread of an alert, and a forged acceptance is refused at reflexr's publish gate. For determinism it proposes the raw artifact, skipping the agent,check_draftand the preview, which the template's tests cover.
Options considered¶
| Option | For | Against |
|---|---|---|
notify alone (chosen) |
The smallest useful action, easy to bound | Rules from chat can only tell a thread |
| Every registered action | Rules from chat as capable as code rules | A person's draft could run the triage agent, with its cost, on any event |
| Delegated actions, connected later (chosen) | One order of construction; mirrors watch |
The actions can't run before connect |
The reactor built in the bridge's connect |
No delegates | Automation's part loses its reactor until then, and its tests change shape |
Consequences¶
- Easier: a person can ask the notes agent for a rule, read its preview, and put it live by accepting it.
- Harder: an action added to
STORED_RULESreaches every rule from chat, and needs its grant in grantr'srulerole. It gets its delegate fromSTORED_RULES, and needs a params model. - Harder: without access control, every user may install rules, so anyone can put a
chat:rule live directly through reflexr's REST and MCP, with no review. Use grantr wherever that matters.