answer
Function, in @jr2/orchestrator/src/pick.ts. It is an export of @jr2/orchestrator, on the Authoring surface page, in Effects.
function answer<
TContext extends MachineContext,
TExpressionEvent extends EventObject,
TParams extends ParameterizedObject["params"] | undefined,
TEvent extends EventObject,
TActor extends ProvidedActor,
>(
fn: (args: { context: TContext; event: TExpressionEvent; chain: readonly ChainLink<TActor, TEvent>[] }) => unknown,
): ActionFunction<TContext, TExpressionEvent, TEvent, TParams, TActor, never, never, never, never> & AnswerActionSet what the Agent reads when jr2 answers its pick. It is an xstate action: write it in entry, exit or the actions of a transition. Without it, the Agent reads the last reply since the pick.
fn gets { context, event, chain }. chain is every reply since the pick, oldest first. Each link is { kind: "output", src, output }, the output of an invoked actor, or { kind: "event", event }, an event that a Gate or a nested Agent sent. TypeScript types each link, but not the order or the number of links. What fn gives back goes to the Agent as JSON. A value with no JSON form clears the answer: the Agent then reads the last reply. When more than one answer runs, the last one wins.
Where you write it decides which pick it answers:
- At or below the state of an Agent, it answers only a pick of that Agent.
- On a transition below no Agent's state, it answers a pick of the Agent whose event took the transition.
- On an
entryor anexitbelow no Agent's state, it sets nothing, andjr2 upprints a warning:an answer on the <entry|exit> of "<state>" is below no Agent's state, so it shapes no pick — an <entry|exit> there is no transition an Agent's event takes; move it under "<agent state>", or onto a transition (ADR-0067).
The error of an invoked actor is no link, and the Agent does not see it. To tell the Agent, write answer on the onError route. An Effect never takes that route for an outcome: every outcome is output.
Parameters
fn: Gets{ context, event, chain }and gives back what the Agent reads, as a value with a JSON form.
Returns: An xstate action.
Example
import { z } from "zod";
import {
agent,
answer,
command,
defineEvent,
fail,
jr2Setup,
workspace,
type Workspaced,
} from "@jr2/orchestrator";
const runTests = defineEvent({
name: "run_tests",
description: "Run the test suite. Its result is this call's answer.",
input: z.object({}),
});
const finished = defineEvent({
name: "finished",
description: "Say that the tests pass.",
input: z.object({}),
});
type Input = Workspaced<{}, "app">;
const body = jr2Setup({
types: {} as { context: Input; input: Input },
events: [runTests, finished],
actors: {
coder: agent({
model: "anthropic/claude-haiku-5-5",
instructions: "You are a careful engineer.",
}),
tests: command(["npm", "test"]),
fail: fail(),
},
}).createMachine({
id: "fix-tests",
context: ({ input }) => input,
on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
initial: "coding",
states: {
coding: {
invoke: { src: "coder", input: { prompt: "Make the tests pass." } },
on: { finished: "done" },
initial: "ready",
states: {
ready: { on: { run_tests: "testing" } },
testing: {
invoke: {
src: "tests",
onDone: {
target: "ready",
// The Agent reads this string, not the whole output of the command.
actions: answer(({ event }) =>
event.output.outcome === "exited" && event.output.code === 0
? "The tests pass."
: `The tests fail:\n${"tail" in event.output ? event.output.tail : event.output.reason}`,
),
},
},
},
},
},
done: { type: "final" },
failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
},
});
export const machine = workspace(body, {
repos: { app: { url: "https://github.com/you/app.git" } },
spec: () => ({ branch: "fix-tests" }),
});See also