Customize
Type, in @jr2/orchestrator/src/customize.ts. It is an export of @jr2/orchestrator, on the Authoring surface page, in Composition.
type Customize<M extends AnyStateMachine> = {
agents?: [AgentSlots<Reached<M>>] extends [never]
? never
: { [K in AgentSlots<Reached<M>>]?: Partial<AgentDefinition> };
effects?: [OpenEffectSlots<Reached<M>>] extends [never]
? never
: { [K in OpenEffectSlots<Reached<M>>]?: BindingOf<LogicAt<Reached<M>, K>> };
actors?: [ChildSlots<Reached<M>>] extends [never]
? never
: { [K in ChildSlots<Reached<M>>]?: Customize<ChildAt<Reached<M>, K>> };
image?: string;
user?: string | UserContainer;
resources?: Size;
repos?: [RepoSlotsOf<M>] extends [never] ? never : { [K in RepoSlotsOf<M>]?: RepoSlot<InputFrom<WorkspaceOf<M>>> };
};The parts of one Machine that customize() can retune: its Agents, its Open Effects, the Machines it composes, and the Sandbox of its workspace(). The type comes from the Machine itself, so it offers only the parts that the Machine declares. A key for a part the Machine does not declare does not compile. The agents, effects, actors and repos keys take no value when the Machine has no such part. image, user and resources compile on every Machine, and customize() refuses them when the Machine composes no workspace().
Every key is optional. Write only what you change.
Fields
agents(optional):[ AgentSlots<Reached<M>> ] extends [ never ] ? never : { [K in AgentSlots<Reached<M>>]?: Partial<AgentDefinition>; }Retune the Agents that this Machine carries, by slot name:
{ coder: { model } }. Each override goes over the definition that the author wrote, field by field, so{ model }alone keeps the author's instructions. When the author left the model Open, this is where you bind it. Allowed fields: the fields of anagent()definition. At run time,customize()refuses a slot that holds no Agent:customize(): machine "<id>" carries no Agent slot "<slot>" — its Agents are: <slots>.effects(optional):[ OpenEffectSlots<Reached<M>> ] extends [ never ] ? never : { [K in OpenEffectSlots<Reached<M>>]?: BindingOf<LogicAt<Reached<M>, K>>; }Bind the Open parts of this Machine's Effects, by slot name:
{ url }for anhttpEffect whose url is Open,{ argv }for acommandwhose argv is Open,{ secret }for an Effect that namessecret(open),{ target: { url, token: secret("<name>") } }for adispatchwhose target is Open. For an Effect with two Open parts, one object holds both keys, and a binding that leaves one out does not compile. Only Effects with an Open part are offered. SeeEffectBindingfor the values and the refusals.actors(optional):[ ChildSlots<Reached<M>> ] extends [ never ] ? never : { [K in ChildSlots<Reached<M>>]?: Customize<ChildAt<Reached<M>, K>>; }Retune a Machine that this Machine composes, by slot name. The value has the same shape as these parts, one level down. At run time,
customize()refuses a slot that holds no Machine:customize(): machine "<id>" composes no Machine under "<slot>".image(optional):stringThe Sandbox Image: a
file:URL to a docker context that your module ships (import.meta.resolve("./image")), whichjr2 upbuilds, or a registry ref. It replaces the image of theworkspace(). When the Machine composes noworkspace(),customize()refuses it at run time.user(optional):string | UserContainerThe User Container: its image, as a
file:URL or a registry ref, or aUserContainerthat also gives its part of the Size. It replaces the whole User Container, its part of the Size included. When the Machine composes noworkspace(),customize()refuses it at run time.resources(optional):SizeThe Size, layered field by field over the Size of the
workspace():{ limits: { memory } }keeps the author's cpu. Onlylimits.memoryandlimits.cpu. SeeSize. Only for a Machine that composes aworkspace().repos(optional):[ RepoSlotsOf<M> ] extends [ never ] ? never : { [K in RepoSlotsOf<M>]?: RepoSlot<InputFrom<WorkspaceOf<M>>>; }Bind the Repo Slots of the
workspace(), by slot name. Each value is one of these:- a url
- a Binding
{ url, ref? } - a function
({ input }) => url | { url, ref? }over the run input open, to leave the slot for the next composer to bind
You can bind an Open slot, or give a bound or per-run slot a different Binding. A slot that the Machine does not declare does not compile. Some Machines, such as
task, leave their whole slot map Open. Then you name every slot, and at least one. The keys are your names for the Repos, and the Machine says what their order means.
Example
import { customize, type Customize } from "@jr2/orchestrator";
import { task } from "@jr2/machines";
const parts: Customize<typeof task> = {
repos: { app: { url: "https://github.com/you/app.git", ref: "main" } },
agents: { coder: { model: "anthropic/claude-haiku-5-5", thinkingLevel: "high" } },
};
export const machine = customize(task, parts);See also