---
title: "Keep Policy Out of the Prompt"
description: "Make company policy inspectable and testable instead of asking a model to reproduce thresholds from prose."
canonical_url: "https://vercel.com/academy/enterprise-apps-agents/keep-policy-out-of-the-prompt"
md_url: "https://vercel.com/academy/enterprise-apps-agents/keep-policy-out-of-the-prompt.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-08-28T23:02:55.088Z"
content_type: "lesson"
course: "enterprise-apps-agents"
course_title: "Enterprise Apps and Agents"
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>

# Keep Policy Out of the Prompt

# Keep policy out of the prompt

Vendor Review has rules the company can state exactly:

- Requests at or above a defined annual cost require Procurement review
- Requests involving restricted data require Security review
- Requests missing required fields cannot proceed

Company policy lives in code. The model does not get a vote. Open the unfinished `lib/policy.ts` from the scaffold and implement both rules.

```typescript
export function routeByPolicy(
  request: VendorRequestInput
): PolicyRoute {
  const reviewers = new Set<ReviewerGroup>();
  const reasons: string[] = [];

  if (request.annualCost >= 50_000) {
    reviewers.add("procurement");
    reasons.push("Annual cost is at least $50,000");
  }
  if (request.dataTypes.includes("restricted")) {
    reviewers.add("security");
    reasons.push(
      "Vendor will handle restricted company data"
    );
  }

  return {
    requiresHumanReview: reviewers.size > 0,
    reviewerGroups: [...reviewers],
    reasons,
    policyVersion: "vendor-routing-v1"
  };
}
```

The model handles work that benefits from judgment: classifying the vendor category, identifying missing context in the business purpose, and describing concerns for a reviewer.

Define that boundary with structured output:

```typescript
const assessmentSchema = z.object({
  category: z.enum([
    "productivity",
    "development",
    "data",
    "security",
    "other"
  ]),
  suggestedRisk: z.enum(["low", "medium", "high"]),
  missingInformation: z.array(z.string()).max(5),
  summary: z.string()
});

const result = await generateText({
  model: ASSESSMENT_MODEL,
  instructions: assessmentInstructions,
  prompt: formatRequestForAssessment(request),
  output: Output.object({ schema: assessmentSchema })
});

return result.output;
```

The complete implementation reads the validated object from `result.output` in `lib/ai.ts`. Do not parse `result.text` or treat an unvalidated string as the assessment.

Structured output constrains the response shape. It does not guarantee correct judgment, prevent prompt injection, or authorize an action. Those concerns remain separate.

## Combine without hiding the source

The API response should preserve policy and assessment as distinct objects. Do not collapse them into one unexplained `risk` field.

```text
policy:       cost threshold → Procurement review
assessment:   data vendor, medium suggested risk, missing retention details
final route:  waiting for Procurement
```

## Write down who decides what

Complete **Who decides what** in `docs/readiness.md`. Put each current behavior under deterministic policy, model judgment, or human authority. If the team cannot place it, the feature is not ready to build.

## Summary

Use code for rules the company can state exactly. Use a model for ambiguity and judgment. Use an authorized person for final approval. Keeping those sources separate makes the result easier to test, explain, and change.

## Check your work

Pass a `$50,000` request with restricted data to `routeByPolicy()`. The result should require both Procurement and Security, include two plain-language reasons, and name `vendor-routing-v1`. Change the model’s suggested risk in your head. The route should not move.

Compare your implementation with [`lib/policy.ts` on `complete`](https://github.com/vercel-labs/academy-enterprise-apps-agents/blob/complete/lib/policy.ts).


---

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