Classify, then authorize
A model can correctly classify “Add delivery priority” as a high-risk public API change. That classification should not authorize implementation.
Model: What kind of request is this?
Policy: What is this request allowed to do next?The model describes ambiguous language. Deterministic TypeScript selects the route. The root orchestrator follows that result, and later lessons enforce consequential boundaries around approval and repository writes.
Let Policy Hold the Keys
Classify an issue with AI SDK and route the validated result with deterministic policy.
Hands-on Exercise 2.2
Create agent/lib/classification.ts with the model's output contract:
import { z } from "zod";
import { riskSchema, workTypeSchema } from "./work-order.js";
export const classificationSchema = z.object({
actionable: z.boolean(),
confidence: z.number().min(0).max(1),
questions: z.array(z.string().min(1)),
rationale: z.string().min(1),
risk: riskSchema,
type: workTypeSchema,
});
export type Classification = z.infer<typeof classificationSchema>;Add a router model to agent/lib/models.ts, then create agent/tools/classify_issue.ts. Use generateText with Output.object() so AI SDK validates the model's answer before the factory sees it.
const result = await generateText({
model: MODELS.router,
output: Output.object({ schema: classificationSchema }),
prompt: `Title: ${input.title}\n\n${input.body}`,
system: [
"Classify work for a TypeScript notification SDK.",
"Use documentation for prose-only changes, bug for incorrect existing behavior, and public-api for exported contract changes.",
"Mark work actionable only when an engineer can define a testable outcome without inventing requirements.",
"Use high risk for exported API changes, security-sensitive work, or possible breaking changes.",
"Questions must be empty when the request is actionable.",
].join(" "),
});
return result.output;Now create agent/lib/routing.ts. Handle unclear work first, then public API changes, documentation, and bugs:
export function routeClassification(classification: Classification): WorkRoute {
if (!classification.actionable || classification.type === "unknown") {
return {
approvalRequired: true,
lane: "manual",
reason: "The request needs clarification before the factory can act.",
};
}
if (classification.type === "public-api") {
return {
approvalRequired: true,
lane: "public-api",
reason: "Exported API changes require an approved specification.",
};
}
// Implement the documentation and bug branches here.
}Implement both remaining branches. Documentation uses the documentation lane and low- or medium-risk prose can take the short lane. Bugs use the bug lane and must be reproduced before implementation. In either lane, high risk sets approvalRequired to true; lower risks set it to false. Give each result a reason that explains both its lane and approval decision.
Expose the pure router through agent/tools/route_work_order.ts. The tool input is classificationSchema, so malformed model output cannot reach policy.
Add the first procedure to agent/instructions.md: create a work order, classify it, route it, and stop with focused questions when the manual lane is selected.
Try It
Compile the application:
pnpm typecheck
pnpm exec eve infoThe manifest should now report four root tools:
Compile ready
Diagnostics 0 errors, 0 warnings
Tools 4 tools
Subagents 0 subagentsBefore the next lesson writes tests, predict these routes:
Webhook docs are confusing → manual
Uppercase channel names fail → bug
Add optional exported priority → public-api + approval
Fix one clear sentence in the README → documentationCommit
git add agent
git commit -m "feat(factory): classify and authorize work"Done-When
- AI SDK returns a validated classification object
- Confidence is bounded between zero and one
- Unclear work reaches the manual lane
- Public API work always requires approval
- The deterministic router produces every lane and approval requirement
The model describes the request. Deterministic routing selects the lane and approval requirement, and later capability gates enforce those decisions.
Solution
The four route branches and complete tool wrapper appear in the exercise. Compare agent/lib/routing.ts and agent/tools/route_work_order.ts with the solution branch if your manifest or predictions differ.
Was this helpful?