Vercel Logo

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:

agent/lib/classification.ts
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.

agent/tools/classify_issue.ts
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:

agent/lib/routing.ts
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 info

The manifest should now report four root tools:

Compile       ready
Diagnostics   0 errors, 0 warnings
Tools         4 tools
Subagents     0 subagents

Before 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     → documentation
Give confidence too much power

Temporarily route any classification above 0.95 without approval. A highly confident public API classification now bypasses the gate. Confidence describes the model's answer; it grants no authority.

AI SDK returns text

Pass Output.object({ schema: classificationSchema }). Asking for JSON in prose does not create a typed boundary.

The route changes between runs

Keep routing in routeClassification. If the model writes the route directly, policy becomes probabilistic.

Commit

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?

supported.