jr2Setup
Function, in @jr2/orchestrator/src/setup.ts. It is an export of @jr2/orchestrator, on the Authoring surface page, in Machine and events.
function jr2Setup<
TContext extends MachineContext = MachineContext,
const TDefs extends readonly EventDef[] = readonly [],
TActors extends Record<string, UnknownActorLogic> = {},
TActions extends Record<string, ParameterizedObject["params"] | undefined> = {},
TGuards extends Record<string, ParameterizedObject["params"] | undefined> = {},
TDelay extends string = never,
TTag extends string = string,
TInput = NonReducibleUnknown,
TOutput extends NonReducibleUnknown = NonReducibleUnknown,
TEmitted extends EventObject = EventObject,
TMeta extends MetaObject = MetaObject,
>(
def: {
types?: {
context?: TContext;
input?: TInput;
output?: TOutput;
tags?: TTag;
emitted?: TEmitted;
meta?: TMeta;
};
events?: TDefs;
actors?: TActors;
actions?: {
[K in keyof TActions]: ActionFunction<
TContext,
WorkflowEvent<TDefs>,
WorkflowEvent<TDefs>,
TActions[K],
ToProvidedActor<MergedActors<TActors>>,
ToParameterizedObject<TActions>,
ToParameterizedObject<TGuards>,
TDelay,
TEmitted
>;
};
guards?: {
[K in keyof TGuards]: GuardPredicate<TContext, WorkflowEvent<TDefs>, TGuards[K], ToParameterizedObject<TGuards>>;
};
delays?: {
[K in TDelay]: DelayConfig<
TContext,
WorkflowEvent<TDefs>,
ToParameterizedObject<TActions>["params"],
WorkflowEvent<TDefs>
>;
};
} = {},
): SetupReturn<
TContext,
WorkflowEvent<TDefs>,
MergedActors<TActors>,
{},
TActions,
TGuards & SentByGuards,
TDelay,
TTag,
TInput,
TOutput,
TEmitted,
TMeta
>Set up a Machine that takes workflow events: the jr2 form of xstate's setup(). Write a Machine with it when an Agent or a Gate sends events into the Machine, or when the Machine declares a door. Call .createMachine(config) on the result, as with setup(). The Machine you get is a plain xstate Machine.
What it adds to setup():
eventsreplacestypes.events. The event type of the Machine comes from thedefineEventevents you list, plus the events jr2 itself sends (agent.fault,workspace.lost). An event that a party sent hasevent.sender. A Machine that takes no workflow event leaves outevents.- The
gateactor is ready to invoke by name:invoke: { src: "gate" }. - Each Agent slot (
actors: { coder: agent(…) }) gets its Menu from the states that invoke it. - The
sentBy(id)guard is ready to use. createMachinealso takesinput: a zod object for the input that starts a run. jr2 checks each start against it. Without it, a run can start with any input. Thesetup()of xstate does not read thisinput, so a Machine with a door usesjr2Setup, also when it takes no workflow event.jr2 upand the publish door refuse a registered Machine that has a zodinputifjr2Setupdid not make the Machine:workflow "<name>": machine "<id>" has a zod `input` on createMachine, but jr2Setup did not make this Machine, so no door reads that input …
The output of a run is the root output of createMachine, beside id and states, as in xstate v5. xstate does not read the output of a final state as the output of the Machine. It gives that output to the root output as event.output. Write output: ({ event }) => event.output as Output at the root, or make the output from context there. createMachine refuses an output on a final state directly below the root when the root has no output (see the list below).
createMachine refuses a Machine that it cannot run as written. Among the refusals:
- an
onkey that no listed event has:machine "<id>" handles event "<key>", which no def in jr2Setup({ events }) declares (declared: <names>) — a typo, or a missing defineEvent - a listed event that no state takes:
machine "<id>" declares "<name>", which no state handles — a pick no state takes is on no Menu and no Gate: handle it, or drop it from jr2Setup({ events }) - an entry of
eventsthatdefineEventdid not make, or one name listed twice:machine "<id>": `events` entry is not a defineEvent() def: <entry>,machine "<id>": duplicate event "<name>" in `events` - an
agent(), an Effect or aworkspace()written oninvoke.srcinstead of underactors:machine "<id>": state "<state>" invokes <part> inline — a Part is a key of actors, so it has a name that the ledger, the report and customize() share: actors: { <name>: <factory>(...) }, then invoke: { src: "<name>" }.<part>isan Agent,an Effectora Workspace.<factory>isagent, the Effect's kind (such ascommand), orworkspace. - an
outputon a final state directly below the root, when the root has nooutput:machine "<id>": final state "<state>" declares an output, and the root has no output — xstate v5 gives a run only its root output, so this output reaches nothing and the run ends with none: write it at the root, as output: ({ context }) => ..., or keep it on the final state and pass it on with output: ({ event }) => event.output at the root. With more than one such final state, it names each one and puts the words in the plural:final states "<state>", "<state>" declare an output,these outputs reach nothing,write one at the rootandkeep them on the final states and pass them on. Anoutputon a final state inside a compound state is legal: theonDoneof that compound state gets it asevent.output. - a parallel state at or below a state that invokes an Agent:
machine "<id>": state "<state>" is parallel, at or below Agent "<slot>"'s state "<state>" — a parallel region below an Agent's state is reserved for picks the Agent does not wait for, which jr2 does not build; background work goes above the Agent's state - a state below an Agent's state that invokes something and that nothing enters:
machine "<id>": state "<state>" below Agent "<slot>"'s state "<state>" is entered by nothing, so what it invokes (<src>) never runs — target it from a pick or a route, or delete it - a state that invokes an Agent when that state and each state above it have no
agent.faultroute:machine "<id>": state "<state>" invokes an Agent, and no route for "agent.fault" is on it or on a state above — jr2 sends agent.fault when the Agent's Turn cannot go on, and with no route the run faults: route it on that state or on a state above it, as on: { "agent.fault": "<the state that handles it>" } (ADR-0074). A state that invokes an Open body that lends an Agent or allows a model gets the same line, withinvokes an Open body that can run an Agent: the body sends it a Chart's unroutedagent.fault. When both kinds have no route, one line names both, joined by, and. One route on the root is a route for each state. A Chart's Machine does not get this refusal: in a Chart the route is optional (ADR-0070).
It also refuses a sentBy that names no party (see sentBy). It refuses a pick param that nothing reads, where the pick enters a state that invokes an Effect (see fromPick). Shapes that are legal but probably not what you mean, such as a mode that nothing enters, are warnings that jr2 up prints.
Parameters
def: Whatsetup()takes, witheventsin place oftypes.events. A Machine that needs none of it callsjr2Setup().types(optional):objectThe types of the Machine, as in
setup():context,input,output,tags,emittedandmeta. Notevents: the event type comes fromevents.context(optional):TContextinput(optional):TInputoutput(optional):TOutputtags(optional):TTagemitted(optional):TEmittedmeta(optional):TMeta
events(optional):TDefsThe workflow events this Machine takes: each one made by
defineEvent. Leave it out when the Machine takes none. Each listed event must be the name of anonkey. Eachonkey must be listed, if it is not a jr2 or xstate event (an event with a.in its name).actors(optional):TActorsThe actors the states invoke by name, as in
setup(): Agent slots (agent(…)), Effects (command(…),http(…),push(…)), child Machines,workspace(…)and plain xstate logic. The key is the slot's name. An actor namedgatereplaces jr2's Gate.actions(optional):{ [K in keyof TActions]: ActionFunction<TContext, WorkflowEvent<TDefs>, WorkflowEvent<TDefs>, TActions[K], ToProvidedActor<MergedActors<TActors>>, ToParameterizedObject<TActions>, ToParameterizedObject<TGuards>, TDelay, TEmitted>; }Named actions, as in
setup(). Theireventhas the type of this Machine's events.guards(optional):{ [K in keyof TGuards]: GuardPredicate<TContext, WorkflowEvent<TDefs>, TGuards[K], ToParameterizedObject<TGuards>>; }Named guards, as in
setup().sentByis there already; do not name a guardjr2.sentBy.delays(optional):{ [K in TDelay]: DelayConfig<TContext, WorkflowEvent<TDefs>, ToParameterizedObject<TActions>["params"], WorkflowEvent<TDefs>>; }Named delays, as in
setup().
Returns: What setup() returns. Call .createMachine(config) on it.
Example
import { z } from "zod";
import { agent, defineEvent, fail, jr2Setup } from "@jr2/orchestrator";
const proposed = defineEvent({
name: "proposed",
description: "Call this with your plan.",
input: z.object({ plan: z.string() }),
});
const approve = defineEvent({ name: "approve", input: z.object({}) });
const reject = defineEvent({ name: "reject", input: z.object({ reason: z.string() }) });
export const machine = jr2Setup({
types: {} as { input: { goal: string }; context: { goal: string } },
events: [proposed, approve, reject],
actors: {
planner: agent({
model: "anthropic/claude-haiku-5-5",
instructions: "You plan small releases.",
workspace: "none",
}),
fail: fail(),
},
}).createMachine({
id: "plan",
input: z.object({ goal: z.string() }),
context: ({ input }) => ({ goal: input.goal }),
on: { "agent.fault": ".failed" },
initial: "planning",
states: {
planning: {
invoke: { src: "planner", input: ({ context }) => ({ prompt: `Plan this: ${context.goal}` }) },
on: { proposed: "review" },
},
review: {
invoke: { src: "gate" },
on: { approve: "approved", reject: "planning" },
},
approved: { type: "final" },
failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
},
});See also