Typed extraction
When an application needs one bounded structured answer with no follow-up, read this because nodes.parse returns a typed outcome union instead of hiding agent failures.
Typed extraction
Use this recipe to extract a small fact set from files in one working directory. Run it with npx tsx examples/guides/typed-extraction.ts /path/to/repo; it prints a package name and version or exits with an explicit agent outcome.
import Crouter from '@north-light/crouter-sdk';
import { resolve } from 'node:path';
import { z } from 'zod';
const client = new Crouter();
const cwd = resolve(process.argv[2] ?? process.cwd());
const extraction = await client.nodes.parse({
prompt: 'Read package.json in this directory. Return its name and version.',
cwd,
root: true,
root_lifecycle: 'terminal',
deadline: '5m',
output_schema: z.object({
name: z.string(),
version: z.string(),
}),
});
if (extraction.kind === 'result') {
console.log(`package ${extraction.output_parsed.name}@${extraction.output_parsed.version}`);
} else if (extraction.reason === 'declined') {
console.error(`agent declined: ${extraction.declined?.reason ?? 'no reason supplied'}`);
process.exitCode = 2;
} else {
console.error(`agent failed: ${extraction.reason}`, extraction.detail);
process.exitCode = 1;
}
nodes.parse() creates a terminal root, waits for it, and returns the structured result typed from the Zod schema. The schema belongs at the boundary where your application consumes the answer, not in a second parser after the run.
Observed against the local daemon in this checkout:
package @north-light/crouter@0.3.332
Handle every outcome arm. A result contains output_parsed; a decline says the agent could not honestly meet the schema; another failure carries its reason and detail. Transport and daemon failures still throw, so let those reach your normal application error handling.
This is the right shape for bounded work: one prompt, one answer, and no future message. If a user or external event must return to the same agent later, use a resident node instead. See nodes and the canvas for why an SDK node has durable identity, and lifecycle and wakes for the terminal-versus-resident decision.
SDK recipes
When composing crouter SDK namespaces into an application, read this because it helps you choose a complete runnable starting point.
Event-driven assistant
When an assistant must react to later webhook, queue, or file-watcher events, read this because a resident node sleeps until nodes.message delivers the next event.