Skip to content

fromPick ​

Function, in @jr2/orchestrator/src/pick.ts. It is an export of @jr2/orchestrator, on the Authoring surface page, in Effects.

Overload 1 ​

ts
function fromPick<
  N extends string,
  I extends z.ZodObject,
  C,
  E extends EventObject,
>(
  def: EventDef<N, I>,
): (args: { context: C; event: E; self: AnyActorRef }) => z.infer<I>

Make the input of the invoke of a state that a pick enters: the params of the pick go to the input of the invoked actor by name. With fill, the Machine adds the keys that the Agent must not choose. So one expression shows the security boundary: the pick is what the Agent gives, and fill is what the Machine adds.

TypeScript checks the params and the keys of fill together against the input type of the invoked actor. It finds a missing key or a key of the wrong type, but it accepts an extra key.

So createMachine refuses:

  • a param that is no key of the input of the Effect, and that nothing else reads: machine "<id>": pick "<pick>" enters state "<state>" (Effect "<slot>") with fromPick: "<param>" is no key of the Effect's input (<keys>), and nothing else reads it — the Agent supplies it and it reaches nothing; name an argv slot for it, read it in `fill`, store it with assign, or drop it from the def
  • a key that fill sets and that is also a param: machine "<id>": pick "<pick>" enters state "<state>" (Effect "<slot>"): `fill` sets "<param>", which is also a param of the pick — the Machine's value wins and the Agent's is dropped; rename one, or drop it from the def
  • a fromPick in a state that another event enters: machine "<id>": state "<state>" (Effect "<slot>") reads the params of pick "<pick>" but is entered on "<event>" — that event does not carry them

The input of an Open argv is one string for each key, so TypeScript checks no key for it. A key that the bound argv names and the input does not give stops the Effect when it starts.

The state that the pick enters has one of two places:

  • A child state of the Agent's state, as in the first example. The Turn stays open, and the pick is outstanding until the Effect ends. Use this shape when the Agent must see the result. The Agent reads the Effect's output as the answer only when the route after the Effect stays in the Agent's state, as onDone: "ready" does. Then the Agent can pick again. A route that leaves the Agent's state ends the Turn when the Effect ends, and no Agent reads the output.
  • A sibling state of the Agent's state, as in the second example. The pick leaves the Agent's state, so the Turn ends before the Effect runs, and the Agent does not see the output. Use this shape for a pick that ends the work of the Agent.

Parameters

  • def (EventDef): The pick, as defineEvent made it.

Returns: An input mapper for the invoke of the state.

Example

ts
import { z } from "zod";
import { agent, defineEvent, fail, fromPick, http, jr2Setup } from "@jr2/orchestrator";

const postNote = defineEvent({
  name: "post_note",
  description: "Post a note to the team channel.",
  input: z.object({ text: z.string() }),
});

const finish = defineEvent({
  name: "finish",
  description: "The note is posted. This ends your work.",
  input: z.object({}),
});

export const machine = jr2Setup({
  types: {} as { context: {}; input: {} },
  events: [postNote, finish],
  actors: {
    writer: agent({
      model: "anthropic/claude-haiku-5-5",
      instructions: "You write one short note for the team.",
      workspace: "none",
    }),
    note: http({
      method: "POST",
      url: "https://hooks.example.com/notes",
      input: z.object({ text: z.string() }),
      request: ({ text }) => ({ json: { text } }),
    }),
    fail: fail(),
  },
}).createMachine({
  id: "note",
  on: { "agent.fault": ".failed" },
  initial: "writing",
  states: {
    writing: {
      invoke: {
        src: "writer",
        input: {
          prompt: "Write a note that says the release is out, post it, and check the reply.",
        },
      },
      initial: "ready",
      states: {
        ready: { on: { post_note: "posting", finish: "#done" } },
        posting: {
          // The `text` of the pick goes to the input of `note` as it is. The route goes back
          // to `ready`, in `writing`: the writer reads the output and picks again.
          invoke: { src: "note", input: fromPick(postNote), onDone: "ready" },
        },
      },
    },
    done: { id: "done", type: "final" },
    failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
  },
});

Example

ts
import { z } from "zod";
import { agent, defineEvent, fail, fromPick, http, jr2Setup } from "@jr2/orchestrator";

const postNote = defineEvent({
  name: "post_note",
  description: "Post the note to the team channel. This ends your work.",
  input: z.object({ text: z.string() }),
});

export const machine = jr2Setup({
  types: {} as { context: {}; input: {} },
  events: [postNote],
  actors: {
    writer: agent({
      model: "anthropic/claude-haiku-5-5",
      instructions: "You write one short note for the team.",
      workspace: "none",
    }),
    note: http({
      method: "POST",
      url: "https://hooks.example.com/notes",
      input: z.object({ text: z.string() }),
      request: ({ text }) => ({ json: { text } }),
    }),
    fail: fail(),
  },
}).createMachine({
  id: "note",
  on: { "agent.fault": ".failed" },
  initial: "writing",
  states: {
    // The pick leaves this state, so it ends the Turn of the writer.
    writing: {
      invoke: {
        src: "writer",
        input: { prompt: "Write a note that says the release is out, and post it." },
      },
      on: { post_note: "posting" },
    },
    // A sibling of `writing`: the Effect runs after the Turn, and no Agent reads its output.
    posting: {
      invoke: { src: "note", input: fromPick(postNote), onDone: "done" },
    },
    done: { type: "final" },
    failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
  },
});

See also

Overload 2 ​

ts
function fromPick<
  N extends string,
  I extends z.ZodObject,
  C,
  E extends EventObject,
  R extends object,
>(
  def: EventDef<N, I>,
  fill: (args: { context: C; params: z.infer<I> }) => R,
): (args: { context: C; event: E; self: AnyActorRef }) => z.infer<I> & R

Make the input from the params of the pick and the keys that fill adds. fromPick tells the rest, and what createMachine refuses.

Parameters

  • def (EventDef): The pick, as defineEvent made it.
  • fill: Gets { context, params } and gives back only the keys that the Machine adds. Do not set a key that is also a param.

Returns: An input mapper for the invoke of the state.

Example

ts
import { z } from "zod";
import { agent, bearer, defineEvent, fail, fromPick, http, jr2Setup, secret } from "@jr2/orchestrator";

const postComment = defineEvent({
  name: "post_comment",
  description: "Post a comment on the ticket.",
  input: z.object({ body: z.string() }),
});

export const machine = jr2Setup({
  types: {} as { context: { ticket: string }; input: { ticket: string } },
  events: [postComment],
  actors: {
    triager: agent({
      model: "anthropic/claude-haiku-5-5",
      instructions: "You triage tickets.",
      workspace: "none",
    }),
    comment: http({
      method: "POST",
      url: "https://jira.example.com/rest/api/2/issue",
      auth: bearer(secret("jira")),
      input: z.object({ ticket: z.string(), body: z.string() }),
      request: ({ ticket, body }) => ({ path: [ticket, "comment"], json: { body } }),
    }),
    fail: fail(),
  },
}).createMachine({
  id: "triage",
  context: ({ input }) => input,
  on: { "agent.fault": ".failed" },
  initial: "triaging",
  states: {
    triaging: {
      invoke: {
        src: "triager",
        input: ({ context }) => ({
          prompt: `Read ticket ${context.ticket}, then post one comment on it.`,
        }),
      },
      initial: "ready",
      states: {
        ready: { on: { post_comment: "posting" } },
        posting: {
          invoke: {
            src: "comment",
            // The Agent gives `body`. The Machine gives `ticket`, so the Agent cannot post on
            // another ticket.
            input: fromPick(postComment, ({ context }) => ({ ticket: context.ticket })),
            onDone: "#done",
          },
        },
      },
    },
    done: { id: "done", type: "final" },
    failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
  },
});

See also

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