> ## Documentation Index
> Fetch the complete documentation index at: https://yukta.infenia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rule files reference

> The JSON format of the rules engine: reactions, timed rules, input answers, vetoes and approval gates, with the fields a SpEL condition can read and the presets.

# Rule files reference

A rule document is the `settings` of a `rules` supervisor: a JSON object with a `rules` list, checked when it is loaded. A load error names the rule that is wrong and stops the document from being used.

A rule has an `id` and exactly one of: `when` with `then` (a reaction), `deny` (a veto) or `requireApproval` (a gate).

**Example**

```json theme={"system"}
{
  "schemaVersion": 1,
  "rules": [
    { "id": "pause-on-repeated-http-failure",
      "when": { "event": "NodeStatusChanged", "status": "FAILURE", "module": "HTTP_REQUEST",
                "count": 3, "withinSeconds": 60 },
      "then": { "command": "PauseWorkflow" } },
    { "id": "no-kill",
      "deny": { "command": "StopWorkflow", "if": "mode == 'KILL'", "reason": "use DRAIN or CANCEL" } }
  ]
}
```

## Document

### `schemaVersion`

* **Type:** number
* **Default:** `1`
* **Required:** no
* **Description:** Version of the document; any value other than `1` is refused.

### `rules`

* **Type:** list of rules
* **Default:** empty
* **Required:** yes
* **Description:** Rules, in order.

### `id`

* **Type:** string
* **Default:** none
* **Required:** yes
* **Description:** Names the rule in logs and errors.

## `when`

Every field that is set must match; an omitted field matches any value.

### `event`

* **Type:** string
* **Default:** none
* **Required:** no, except that a rule with `every` must not have it
* **Description:** `NodeStatusChanged`, `WorkflowStatusChanged`, `ControlOutcome`, `LogLine`, `EventsMissed`, `InputRequested`, `InputEscalated`, `InputAnswered`, `InputExpired` or `InputCancelled`.

### `status`

* **Type:** string
* **Default:** none
* **Required:** no
* **Description:** Status of a status event, for example `FAILURE`.

### `module`

* **Type:** string
* **Default:** none
* **Required:** no
* **Description:** The node's plugin type, for node events, for example `HTTP_REQUEST`.

### `nodeId`

* **Type:** string
* **Default:** none
* **Required:** no
* **Description:** A specific node, for node and log events.

### `workflowId`

* **Type:** string
* **Default:** none
* **Required:** no
* **Description:** A specific workflow, for status events.

### `if`

* **Type:** SpEL expression
* **Default:** none
* **Required:** no
* **Description:** Condition over the event's fields ([below](#condition-fields)). One that cannot be evaluated counts as not matching.

### `count`

* **Type:** integer
* **Default:** `1`
* **Required:** no
* **Description:** How many matching events trigger the rule.

### `withinSeconds`

* **Type:** integer
* **Default:** `60`
* **Required:** no
* **Description:** Window in which `count` events must occur, counted per execution.

### `every`

* **Type:** duration text
* **Default:** none
* **Required:** no
* **Description:** Makes the rule timed: it runs on that period over the executions the supervisor owns. Forms `30s`, `5m`, `2h`; at least one second. A timed rule needs a `command` in `then` and a `reason`, takes no `event`, and its `if` can read `read.<STATUS>` (the number of owned executions in that status; a status nobody has is `0`). It acts on its own, so the command must be in the supervisor's `initiative` list. One tick looks at no more than 200 executions.

**Example**

```json theme={"system"}
{ "id": "too-many-running",
  "when": { "every": "30s", "status": "RUNNING", "if": "read.RUNNING > 10" },
  "then": { "command": "PauseWorkflow", "reason": "more than 10 runs are running" } }
```

## `then`

Exactly one of `command`, `answer` and `escalate`.

### `command`

* **Type:** string
* **Default:** none
* **Required:** one of the three
* **Description:** `PauseWorkflow`, `ResumeWorkflow`, `StopWorkflow`, `PauseNode`, `ResumeNode`, `StopNode`, `Restart` or `RestartFromNode`. Node commands address the node that triggered the rule; the command goes to the execution that produced the event.

### `mode`

* **Type:** `DRAIN`, `CANCEL` or `KILL`
* **Default:** `DRAIN`
* **Required:** no
* **Description:** For `StopWorkflow`.

### `reason`

* **Type:** string
* **Default:** `Stopped by rule` for a stop
* **Required:** only for a timed rule
* **Description:** Recorded with a stop and shown in the audit.

### `immediate`

* **Type:** boolean
* **Default:** `false`
* **Required:** no
* **Description:** For `StopNode`: interrupt instead of draining.

### `from`

* **Type:** string
* **Default:** the node that triggered the rule
* **Required:** no
* **Description:** For `RestartFromNode`: a node id, or `metadata.<key>` of the event. `START`, or a key the event lacks, restarts the whole run.

A restart starts a new run; in that run's events `attempt` counts the restarts the rules made (0 for a run nobody restarted), so `"if": "attempt < 2"` bounds a retry loop. The count is kept in memory for the newest 1000 restarted runs.

### `answer`

* **Type:** object
* **Default:** none
* **Required:** one of the three
* **Description:** Answers the input request of an `InputRequested` or `InputEscalated` event. The rule must name one of those events, or none (then it matches only those two).

| Field | Type | Default | Description |
| - | - | - | - |
| `answer.decision` | string | none (required) | The decision; for `APPROVAL` with options and `CHOICE` it must be one of the request's option ids. |
| `answer.text` | string | none | Free text for `TEXT` requests. |
| `answer.rationale` | string | none | Why; recorded as `rule <id>: <rationale>`. |
| `answer.confidence` | number 0 to 1 | `1` | Confidence of the answer. |

The server decides whether an answer counts: the supervisor needs `canAnswer: true` and a `maxRisk` that reaches the request's risk, and a `HIGH` request is answered by people only. The answer names the request version the rule saw and is refused if the request changed.

### `escalate`

* **Type:** object
* **Default:** none
* **Required:** one of the three
* **Description:** Hands the input request up, on the same events as `answer`.

| Field | Type | Default | Description |
| - | - | - | - |
| `escalate.reason` | string | `escalated by rule <id>` | Why it is handed up. |
| `escalate.advice` | string | none | A recommendation for whoever answers next. |

## `deny`

Refuses matching commands before they run.

| Field | Type | Default | Description |
| - | - | - | - |
| `command` | string | none (required) | Command name, with or without the `Command` suffix, for example `StopWorkflow`. |
| `if` | SpEL | always | Condition over the [command fields](#condition-fields). A condition that cannot be evaluated refuses the command. |
| `reason` | string | `denied by rule <id>` | Shown to the caller. |

## `requireApproval`

Parks matching commands and raises an `APPROVAL` input request. A matching `deny` wins.

| Field | Type | Default | Description |
| - | - | - | - |
| `command` | string | none (required) | Command name, as in `deny`. |
| `if` | SpEL | always | Condition over the command fields. |
| `risk` | `LOW`, `MEDIUM`, `HIGH` | `HIGH` | Who may approve; `HIGH` only a person. |
| `route` | string | the default route | The input route that decides. |
| `reason` | string | `approval required by rule <id>` | Shown to whoever approves. |
| `ttl` | integer (seconds) | the server's default | How long the request waits. |

**Example**

```json theme={"system"}
{ "id": "prod-stop",
  "requireApproval": { "command": "StopWorkflow", "if": "workflowId == 'prod-deploy'",
                       "risk": "HIGH", "reason": "production stop" } }
```

## Condition fields

A condition is a read-only SpEL expression over one map of fields: no method calls, no types, no assignment. Every field of the kind is present, with `null` when it does not apply.

| Source | Fields |
| - | - |
| Every event | `executionId`, `sessionId`, `nodeId`, `status`, `module`, `workflowId` |
| `NodeStatusChanged` | `workflowId`, `nodeId`, `module`, `status`, `metadata` (a map: `metadata['code']`) |
| `WorkflowStatusChanged` | `workflowId`, `status` |
| `ControlOutcome` | `command`, `actor`, `applied`, `reason` |
| `LogLine` | `nodeId`, `line`, `level` (`DEBUG`, `INFO`, `WARN`, `ERROR`) |
| `EventsMissed` | `status` (`EVENTS_MISSED`), `missed` |
| `InputRequested`, `InputEscalated` | `sessionId`, `workflowId`, `nodeId`, `requestId`, `status` (`INPUT_REQUESTED` or `INPUT_ESCALATED`), `kind`, `risk`, `state`, `tier`, `requester`, `requesterType`, `prompt`, `context`, `options` (option ids), `advice`; `reason` for `InputEscalated` |
| `InputAnswered` | `workflowId`, `requestId`, `decision`, `responder`, `responderType` |
| `InputExpired`, `InputCancelled` | `workflowId`, `requestId`; `reason` for `InputCancelled` |
| A command under review (`deny`, `requireApproval`) | `executionId`, `actor`, `sessionId`, `workflowId`, `nodeId`, `mode`, `reason`, `immediate` |
| A timed rule | `read.<STATUS>` |
| A restarted run | `attempt` |

The `prompt` and `context` of a request were written by the asking step, often an AI agent, and are redacted; a condition may compare them but their content is untrusted. `context['key']` is `null` when the key is missing.

## Presets

Complete rule documents, from `GET /api/supervisor-engines/rules/presets` and `list_supervisor_presets`.

| Preset | Behaviour |
| - | - |
| `failure-guard` | Pauses a workflow when 3 nodes fail within 60 seconds. |
| `approval-gate` | A `StopWorkflow` waits for a person to approve it (`risk` `HIGH`). |
| `stall-watchdog` | Every 30 seconds, pauses a running workflow that has done nothing for 10 minutes. Needs `PauseWorkflowCommand` in `initiative`. |
| `too-many-running` | Every 30 seconds, pauses each running workflow while more than 10 are running. Needs `PauseWorkflowCommand` in `initiative`. |

## Where a rule document is read

| Source | Notes |
| - | - |
| The `settings` of a `rules` supervisor | `PUT /api/supervisors/{id}`; the document alone with `PUT /api/supervisors/{id}/engine/rules`, `save_rules`, `supervisors rules set`. The shared settings stay. |
| `yukta.supervisor.rules.file` | A JSON file read at start-up by a bean supervisor. |

Try a draft on a past run in dry run with `replay_rules`, `supervisors rules replay` or `POST /api/supervisors/{id}/engine/rules/replay`.

## Related

* [Supervisor engines](https://yukta.infenia.com/reference/supervisors/engines.md)
* [Supervisor shell](https://yukta.infenia.com/reference/supervisors/shell.md)
* [Write a rules supervisor](https://yukta.infenia.com/guides/supervise/write-a-rules-supervisor.md)
* [Expressions reference](https://yukta.infenia.com/reference/expressions.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.