Human approval
When a person must approve or reject work before an agent continues, read this because the SDK can create an inbox request whose answer wakes the requesting node.
Use this recipe when a decision belongs to a person, not a timeout or an agent guess. Run npx tsx examples/guides/human-approval.ts /path/to/repo; it prints an inbox ticket id. Answer the page in the crtr human inbox and the worker wakes with that answer before it reports its result.
import Crouter from '@crouter/sdk';
import { resolve } from 'node:path';
const client = new Crouter();
const cwd = resolve(process.argv[2] ?? process.cwd());
const worker = await client.nodes.create({
name: 'approval worker',
cwd,
root: true,
root_lifecycle: 'terminal',
no_kickoff: true,
});
const request = await client.human.requests.create({
creator_cwd: cwd,
requester_node_id: worker.node_id,
delivery: { placement: 'panel', inbox: true, reply: true },
page: {
dialect: 'jsx',
source: `export default function Approval() {
return (
<Page title="Approve the release?" subtitle="The worker will continue with your choice.">
<UserQuestion
id="approval"
label="Release action"
body="Approve to continue the release, or reject to stop it."
mode="single"
options={[
{ id: 'approve', label: 'Approve', recommended: true },
{ id: 'reject', label: 'Reject' },
]}
/>
</Page>
);
}`,
},
});
const ticket = await client.human.requests.retrieve(request.request_id);
if (ticket.inbox_ticket_id === null) throw new Error('the approval request was not added to the inbox');
await client.nodes.message(worker.node_id, {
body: `A human approval request is open. Wait for its reply. When it arrives, state whether the release was approved or rejected, then push a final report.`,
});
console.log(`approval ticket: ${ticket.inbox_ticket_id}`);
console.log('Answer it in the crtr human inbox. The worker will wake with the response.');
const outcome = await client.nodes.waitForOutcome(worker.node_id);
console.log(`worker outcome: ${outcome.kind}`);
if (outcome.kind !== 'result') process.exitCode = 1;The application creates the page through client.human.requests.create() and names the worker as requester_node_id. delivery.reply: true makes the settled answer travel back to that node. The page's UserQuestion supplies a known response shape, so the person sees an explicit approve or reject decision instead of an unstructured prompt.
The worker is terminal because this is a bounded approval run: it waits after setup, wakes when the person answers, then publishes its final result. The pending human request keeps that wait durable. The application does not poll the node or invent a fallback deadline; the human reply is a canvas event. client.human.inbox can list, inspect, and answer inbox tickets when your application provides its own human interface.
Observed against the local daemon after an approve response:
approval ticket: a643479ca7a79758bf86f32d24bd8c9b22e16509cd60996c5e4fe0bc5f097d45
Answer it in the crtr human inbox. The worker will wake with the response.
worker outcome: resultSee lifecycle and wakes for the wake model and scopes and trust for why the daemon, rather than the application, owns delivery of the answer.
Fan-out pipeline
When independent parts of one job need separate agent work before one synthesis, read this because an orchestrator can spawn children, wait for reports, and publish a final result while the SDK streams progress.
API reference
Every value and type exported by @crouter/sdk, read from the TypeScript compiler.