---
title: Customize
description: "The parts of one Machine that `customize()` can retune: its Agents, its Open Effects, the Machines it composes, and the Sandbox of its `workspace()`."
---

# `Customize`

Type, in `@jr2/orchestrator/src/customize.ts`. It is an export of `@jr2/orchestrator`, on the [Authoring surface](../authoring-surface.md) page, in Composition.

```ts
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 an `agent()` 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 an `http` Effect
  whose url is Open, `{ argv }` for a `command` whose argv is Open, `{ secret }` for an Effect
  that names `secret(open)`, `{ target: { url, token: secret("<name>") } }` for a `dispatch`
  whose 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.
  See [`EffectBinding`](type-effectbinding.md) for 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): `string`

  The Sandbox Image: a `file:` URL to a docker context that your module ships
  (`import.meta.resolve("./image")`), which `jr2 up` builds, or a registry ref. It replaces the
  image of the `workspace()`. When the Machine composes no `workspace()`, `customize()` refuses it
  at run time.
- `user` (optional): `string | UserContainer`

  The User Container: its image, as a `file:` URL or a registry ref, or a
  [`UserContainer`](type-usercontainer.md) that also gives its part of the Size. It replaces the whole User
  Container, its part of the Size included. When the Machine composes no `workspace()`,
  `customize()` refuses it at run time.
- `resources` (optional): [`Size`](type-size.md)

  The Size, layered field by field over the Size of the `workspace()`: `{ limits: { memory } }`
  keeps the author's cpu. Only `limits.memory` and `limits.cpu`. See [`Size`](type-size.md). Only for a
  Machine that composes a `workspace()`.
- `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**

```ts
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**

- [Bind a shipped Machine's Open parts](../../guides/bind-open-parts.md)
- [Composition](../../concepts/composition.md)
- [ADR-0049: A Machine carries its parts and composes by invoke](../decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md)
