Skip to content

@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.

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: string

    What to do, in prose. Required. The coder's first Turn is this prompt, with the Worktree it works in.

  • branch (optional): string

    The branch to cut and commit on. Default: jr2/task-<instance id>, where <instance id> is the seed Instance ID of the run (instanceId in jr2 status). It is not the run id. Two runs never use the same branch. Required when task runs inside another Machine, such as a pool(): there is no Instance ID to name the branch after.

  • model (optional): string

    The 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, high or xhigh. Default: the thinking level of the coder Agent.

Example

ts
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.

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

ts
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.

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

ts
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.

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 as instructions or thinkingLevel.

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

ts
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

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