The rules where getting it wrong costs you.
Most business rules can stay where they are. These are the ones where the consequence is real: money moves, a claim is denied, a limit is breached.
Rules only, so changing one needs no release.
What one looks like
signals:
day_total_after_this:
expr: state.wallet.buckets.daily_volume_bucket + event.data.amount_usd
rules:
daily_volume_exceeded:
all:
- path: "event.type"
op: eq
value: "transfer_intent"
- path: "signals.day_total_after_this"
op: gt
value: "{{ constants.daily_volume_cap_usd }}"
effects:
verdict: rejected
state_changes:
wallet:
change_buckets:
cooldown_24h: 1
response:
reason: "Daily volume cap exceeded" The point is the signal above the rule. state.wallet.buckets.daily_volume_bucket is a running total of dollars that survives between events, so a rule can read what this wallet has already done today and decide on that. A stateless rule engine cannot do this without a separate database beside it.
What a rule can read
State is kept per entity, and the entities are yours to declare: a wallet, an account, an agent, a device, a claim, a matter.
| Kept on the entity | What people put in it |
|---|---|
| Counter | dollars moved today, refunds this week, tool calls in one agent run |
| Counter over a window | fixed or sliding, five minutes to a month: velocity, repeat offenses, a cooldown that expires on its own |
| Label | verified, under review, sanctions hit, escalated once already |
| Metadata | tier, country, the queue a case belongs to, the last decision made |
| A call to your own service | a fraud or credit score, a sanctions check, your own model, anything you can answer over HTTP |
A score arrives with the event or the engine fetches it through a function you define. The vendor that produced it is written into the decision record, so a year later you can say what was decided and whose number it was decided on.
Two ways in
An agent's call through a gateway, or a direct call from your own service before it acts. The second one needs no agent at all: ordinary application code asks the engine whether something is allowed, and acts on the answer.
The same mechanism, a different discipline
A daily transfer cap and a repeat-offender counter on a moderation queue are the same rule with different names: a running total for one entity, a threshold, and a declared answer when it is crossed. That is how one engine covers risk and compliance and trust and safety, with your teams writing different rules on it.