@jr2/machines
@jr2/machines ships Machines, not Workflows. A Machine becomes a Workflow when a file in the Instance's workflows/ folder exports it, with each Open part bound by customize. Install it beside @jr2/orchestrator, at the same version: it is a peer of the kit, never a copy of it.
Contents
TaskInput
Type, in @jr2/machines/src/task.ts.
type TaskInput = {
prompt: string;
branch?: string | undefined;
model?: string | undefined;
thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | undefined;
};The run input of task: what a caller sends to start a run, for example with jr2 run <workflow> --input '{"prompt":"…"}'.
prompt is required. branch, model and thinkingLevel are optional. Each field has its own doc.
Fields
prompt:stringWhat to do, in prose. Required. The coder's first Turn is this prompt, with the Worktree it works in.
branch(optional):stringThe branch to cut and commit on. Default:
jr2/task-<instance id>, where<instance id>is the seed Instance ID of the run (instanceIdinjr2 status). It is not the run id. Two runs never use the same branch. Required whentaskruns inside another Machine, such as apool(): there is no Instance ID to name the branch after.model(optional):stringThe coder's model for this run only, as
"<provider>/<modelId>". Default: the model of the coder Agent.thinkingLevel(optional):"off" | "minimal" | "low" | "medium" | "high" | "xhigh"The coder's thinking level for this run only:
off,minimal,low,medium,highorxhigh. Default: the thinking level of the coder Agent.
Example
import type { TaskInput } from "@jr2/machines";
export const input: TaskInput = {
prompt: "Make the failing test in test/greet.test.ts pass.",
branch: "fix/greet",
thinkingLevel: "high",
};TaskOutput
Type, in @jr2/machines/src/task.ts.
type TaskOutput = { branch: string };The output of a run of task that ends done: { branch }, the branch of the work that the human approved. A run whose Workspace is lost has no output of this type: it fails, and the run ends failed with the reason the Workspace was lost (ADR-0073).
Fields
branch:string
Example
import type { TaskOutput } from "@jr2/machines";
export function describe(output: TaskOutput): string {
return `approved on ${output.branch}`;
}body
Constant, in @jr2/machines/src/task.ts.
const body: StateMachine<…>Type: StateMachine, from xstate.
The Machine inside task: the coder's loop and the review Gate, with no workspace() around it. Use task to run it. Use body only to put the same loop in a workspace() of your own. For example, your workspace() can have Repo Slots or a run input that differ from those of task. Its input is the run input plus the workspace handles, and it works in the first Repo Slot of those handles.
Example
import { customize, workspace } from "@jr2/orchestrator";
import { body, type TaskInput } from "@jr2/machines";
const fixed = workspace(body, {
repos: { app: { url: "https://github.com/you/app.git" } },
spec: ({ input }: { input: TaskInput }) => ({ branch: input.branch ?? "jr2/fix" }),
});
export const machine = customize(fixed, {
agents: { coder: { model: "anthropic/claude-haiku-5-5" } },
});See also
task
Constant, in @jr2/machines/src/task.ts.
const task: WorkspaceMachine<…>Type: WorkspaceMachine.
A Machine in which a coder Agent works on one prompt in a Workspace until a human approves the work. The coder works in a Sandbox and commits on a branch. Then the run waits at the review Gate. The human answers request_changes with notes, and the coder continues the same conversation, or approve, and the run ends. A run has no limit on the number of rounds.
task does not push. While the run waits at the Gate, the Sandbox stays. You can exec into it and read the branch. To keep the work, push it yourself. approve removes the Sandbox, and every commit that was not pushed goes with it. If the Sandbox is lost, task fails: the run ends failed with the reason the Workspace was lost. A parent Machine catches that Failure with an onError that isFailure guards.
task leaves two parts Open, so you bind them with customize() where you register it:
repos: the whole Repo Slot map. Name every slot, and at least one. The first slot is the Repo that the coder works in. The coder can read the others.agents.coder.model: the coder's model, as"<provider>/<modelId>". You can also change the coder's other fields, such asinstructionsorthinkingLevel.
jr2 up refuses a Workflow that leaves either part Open, and prints the customize() line that binds it. You can also give task an image, a user or a Size (resources). Without an image, it uses the Instance's images/default, or jr2's Harness image when there is none. Without a Size, it uses the Instance's sandbox.resources, or the kit's Size when it has none. Inside another Machine, task needs branch in its input, or it refuses: task: no `branch` on the door and no Instance ID to derive one from.
Example
import { customize } from "@jr2/orchestrator";
import { task } from "@jr2/machines";
export const machine = customize(task, {
repos: {
app: { url: "https://github.com/you/app.git" },
lib: { url: "https://github.com/you/lib.git" },
},
agents: { coder: { model: "anthropic/claude-haiku-5-5" } },
});See also