rules engine decides with a JSON document and needs no code and no model. If the decision needs judgement, use an agent supervisor.
Prerequisites: Assign a supervisor (you create and assign one the same way). Add -H "X-API-Key: $YUKTA_API_KEY" if your server has keys.
Steps
1
Start from a preset, or from nothing
GET /api/supervisor-engines/rules/presets lists ready-made documents: failure-guard (pause a workflow when 3 nodes fail within 60 seconds), approval-gate (a stop waits for a person), stall-watchdog (pause a run silent for 10 minutes) and too-many-running. Fetch one, change it and save it as your own:2
Write the rules
A rule is a reaction (
when an event matches, then send a command), a veto (deny a command) or a hold (requireApproval). Every when field you set must match; omit a field to match anything. Save as ci-guard.json:ci-guard.json
when.eventisNodeStatusChanged,WorkflowStatusChanged,ControlOutcome,LogLine,EventsMissedor an input event (InputRequested,InputEscalated,InputAnswered,InputExpired,InputCancelled).countandwithinSeconds(default 1 and 60) are counted per run.ifis a condition over the event’s fields (status,module,nodeId,metadata['code'],risk,context['environment']…). One that cannot be evaluated counts as not matching, so a faulty reaction never fires; adenycondition that cannot be evaluated refuses, so a faulty veto never lets a command through.then.commandisPauseWorkflow,ResumeWorkflow,StopWorkflow(withmode),PauseNode,ResumeNode,StopNode,RestartorRestartFromNode. The supervisor must also list that command’s name (PauseWorkflowCommand) inallowedCommands, or the command is dropped and counted asforbidden.denyandrequireApprovalread the command’s fields:executionId,actor,nodeId,mode,reason,immediate, and for holdsworkflowId. A deny beats a hold.operatorAccessnames what a person may send through this owner. A vetoed or held command must be listed here, or a person’s command is refused earlier with409 CONTROLLED_BY_SUPERVISOR.
3
Save it and give it something to own
Rules are checked when saved, so a bad condition, an unknown command or a typo is reported with the rule that is wrong:
4
Answer or escalate input requests
Instead of a This is
command, then can hold answer or escalate for an InputRequested or InputEscalated event. The supervisor needs canAnswer: true and a maxRisk that reaches the request (LOW or MEDIUM; HIGH is people only). The answer must be one of the request’s option ids.examples/16-supervisor-profiles/input-requests.json. The rationale is recorded as rule <id>: <rationale>. The prompt and context were written by the asking step, often a model: compare them, but do not base an approval on their claims alone. See Route input requests.5
Change the rules without touching the rest
PUT /api/supervisors/{id}/engine/rules replaces only the rules document and keeps allowedCommands, operatorAccess and the other shared settings. Every save is a version (the newest 20); saving swaps the running rules in whole.PUT /api/supervisors/{id}. MCP: get_rules, save_rules, list_supervisor_versions, rollback_supervisor.6
Try the rules on a past run first
Before you swap rules into production, replay a draft over a finished run: Replay a supervisor.
Verify it worked
Startsteer/slow (a run that lasts 45 seconds) and send a refused stop:
DRAIN stop passes the veto and ends it. For a held command (here prod-stop on a workflow named prod-deploy) the answer is 202 with {"status":"AWAITING_APPROVAL","requestId":"..."} and an input request of risk HIGH that only a person can approve (Ask for input). To see what the supervisor did, list its decisions:
applied, rejected, dry-run, forbidden, not-owned, rate-limited, awaiting-approval and so on. GET /api/control/audit shows the same commands with their senders.
Variations
- Timed rules.
"when": {"every": "30s", "status": "RUNNING", "if": "read.RUNNING > 10"}runs on a timer over the runs the supervisor owns (read.<STATUS>counts them). A timed rule acts on its own, so it needs areasonand its command in the supervisor’sinitiativelist (andmaxInitiativePerMinute, default 10). Thetoo-many-runningandstall-watchdogpresets are timed. - Bound a retry loop.
RestartandRestartFromNodestart a new run; its events carryattempt(0 for a run nobody restarted)."if": "attempt < 2"stops the chain. The count lives in memory and starts again after a restart. - Do not lose events. Event delivery is best effort, but a gap is never silent: a rule on
EventsMissed(missedholds the count) can pause or fail safe. - A rules file for the whole server.
yukta.supervisor.rules.filestarts a rule supervisor from a file, but it owns nothing, so it reviews nothing and can never answer. Use a saved supervisor with assignments instead.
Related
- Rule files reference for every field, event and command.
- Supervisor shell reference for
allowedCommands,initiativeandoperatorAccess. - Supervisors.