---
title: "Classify, Then Authorize"
description: "Generate a structured issue classification with AI SDK, then use deterministic TypeScript to select its lane and approval requirement before later capability gates enforce consequential boundaries."
canonical_url: "https://vercel.com/academy/creating-a-software-factory/classify-then-authorize"
md_url: "https://vercel.com/academy/creating-a-software-factory/classify-then-authorize.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-08-29T01:48:00.567Z"
content_type: "lesson"
course: "creating-a-software-factory"
course_title: "Creating a Software Factory"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Classify, Then Authorize

# Classify, then authorize

A model can correctly classify “Add delivery priority” as a high-risk public API change. That classification should not authorize implementation.

```text
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:

```ts title="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.

```ts title="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:

```ts title="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:

```bash
pnpm typecheck
pnpm exec eve info
```

The manifest should now report four root tools:

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

Before the next lesson writes tests, predict these routes:

```text
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
```

\*\*Note: 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.

\*\*Warning: AI SDK returns text\*\*

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

\*\*Warning: The route changes between runs\*\*

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

## Commit

```bash
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.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
