Skip to content

ADR-0074 Architecture decision record

An unrouted mechanism event is refused at load and faults the run ​

jr2 sends agent.fault to the Machine that invoked an Agent when the Agent's Turn cannot go on (ADR-0016), and workspace() sends workspace.lost to its body when the Sandbox is gone (ADR-0012). Both go by sendBack, and xstate drops an event that no active state handles. So a Machine that does not route agent.fault waits for ever in the Agent's state with a dead Agent, and a body that does not route workspace.lost shows healthy on the feed with no Sandbox (ADR-0022). Six of the 12 trials of an eval in which an Assistant writes jr2 Workflows did not route agent.fault. Each of those Machines loaded, and each could wait for ever.

Decision ​

  • The walk refuses a Machine with no route for a mechanism event. It refuses a state that invokes an Agent, or an Open body that lends an Agent or allows a model, when that state and all its ancestors have no agent.fault route, and a state of a workspace() body that the body can stay in when that state and all its ancestors have no workspace.lost route. The states that a body can stay in are each leaf that is not final, and each compound state whose nested final state has no onDone on the compound state: the body then stays in the compound state. An event goes to the active leaves first, so a route on each leaf, or above it, is a route for the compound state above them. A final state of the root ends the body, and a final state of a parallel region leaves the other regions active, so neither is judged. One route on the root satisfies each rule. The agent.fault refusal is in jr2Setup().createMachine, beside the lost-output refusal, so jr2 check, jr2 up and the Orchestrator's load all show it. The workspace.lost refusal is in workspace(), which is the one place that knows that a Machine is a body.
  • At runtime, a mechanism event that no transition took faults the run error. The run host sees it in the inspection stream: a microstep for agent.fault or workspace.lost with no transitions, in the Machine that got the event (for workspace.lost, the body, because the wrapper always takes its own transition to forward it). fault names the event, the state and the event's reason. This catches the route that the walk cannot judge: a guard that is false when the event comes.
  • In a Chart, the agent.fault route is optional, and the Machine that invoked the Open body routes the rest. The Open body sends an agent.fault that no transition of the Chart takes to the Machine that invoked it, unchanged: { type: "agent.fault", instanceId, reason }, with the Instance ID of the Chart's Agent (ADR-0070). It does not step the Chart. That Machine's agent.fault route takes the event, as it takes the fault of an Agent of its own, and the exit from the Open body's state stops the body. A route that does not exit the state keeps the Chart where it is, and the Chart's Agent is gone: the Chart goes on only on its other events, or ends at its wall-clock ceiling, as a route with actions only does to a state of a Machine. With no route that takes it, the runtime rule above faults the run. So one route on the root of a Machine is the route for its own Agents and for the Agents of its Charts. An Agent of a child Machine sends to that Machine. The Chart door does not refuse a Chart state with no route, and jr2Setup().createMachine does not refuse the Machine that a Chart compiles to. Under an Open body that lends no fail, a Chart has no correct state for the route: a final state means done, and a state that waits holds the run until the wall clock. The Chart wrapper, which is TypeScript and checked by the walk, decides.
  • Routed means that the runtime took a transition. A route with actions only counts, because it can be a deliberate reenter: true that invokes the Agent again. jr2 does not judge what a route does.
  • An unrouted mechanism event ends the run error, never failed. The Machine did not choose that end, so it is a fault and not a Failure (ADR-0073), and its Sandbox stays as evidence.

Considered options ​

  • Drop the event, as before. Rejected: the run waits for ever and looks live, and nothing tells a person why.
  • Only the walk. Rejected: a guarded route that is false at runtime still drops the event.
  • Only the runtime. Rejected: half of the trials would load and then fault on the first Agent fault, when the walk can tell the author before the first run.
  • Make agent.fault the Agent actor's error, so that xstate faults the parent when no onError handles it. Rejected: ADR-0016 and the Chart vocabulary route the event by name, and onError already carries defects and Failures; an Agent's fault would be a third thing on it.
  • A Chart's unrouted agent.fault ends the Open body as an error, which the Chart wrapper catches with an onError on its Open body state. Rejected: that error and a defect of the body are the same plain error, so the Chart wrapper cannot fail the run for a Chart Agent's fault without failing it for a defect too, and one Machine routes the fault of its own Agent on agent.fault and the fault of a Chart's Agent on onError.
  • A mark on that error, such as isAgentFault(error), with a matching Chart guard. Rejected: a new name on the Authoring surface, and still two routes for one event.
  • Require the route in a Chart too, with a line of the Chart door. Rejected: under an Open body that lends no fail, the planner can write no correct target, and the trials of the chart-refusal claim wrote a faulted final state, which ends the run done. A route the planner must write also adds a rule to the text that a model reads.

Consequences ​

  • The text "When no state routes it, xstate drops the event, and the run stays in the Agent's state" in the FaultTelemetry TSDoc (actor.ts) and in mechanism-events.md changes to this rule.
  • A Machine in the docs, the templates or the fixtures that does not route a mechanism event gets a route, or the walk refuses it.
  • A Chart needs no route, so the Chart fixtures and the Charts of the docs keep none that they do not use, and the Chart briefing and the grammar's fragment do not show one. Their Chart wrappers route agent.fault, or the walk refuses them.
  • An Assistant learns the rule from the refusal line of jr2 check, so the line names the state, the event and the route to write.

The docs of Kit version 0.5.1. Each page is also at its .md URL.