OPEN TO FULL-TIME ROLES (Available to start immediately. Remote preferred worldwide.)// STACK:WORDPRESS & WOOCOMMERCEREACT & NEXT.JSTYPESCRIPTWEB PERFORMANCEAI INTEGRATIONSAUTOMATION (N8N, MAKE)// PROTOCOL: MCP · JSON-RPC 2.0// STATUS: PRODUCTION READY
OPEN TO FULL-TIME ROLES (Available to start immediately. Remote preferred worldwide.)// STACK:WORDPRESS & WOOCOMMERCEREACT & NEXT.JSTYPESCRIPTWEB PERFORMANCEAI INTEGRATIONSAUTOMATION (N8N, MAKE)// PROTOCOL: MCP · JSON-RPC 2.0// STATUS: PRODUCTION READY
HDRX
• 16 min read
AIPrompt EngineeringLLMTypeScriptNext.jsCI/CDEval

Prompt Engineering as Code: Versioning, Testing, and CI/CD for LLM Prompts

A practical framework for treating prompts as software artifacts — semantic versioning, Zod-validated structured output, regression eval suites, and CI/CD gating, built from the Prompt Pocket architecture.

Most teams running LLMs in production still manage prompts as loose strings — inline in application code, scattered across .env files, or edited directly in a vendor dashboard with no diff, no review, and no test suite. This works until it doesn’t: a “small copy tweak” silently breaks structured output parsing, a model version bump changes tone or format without anyone noticing, or a support agent discovers a jailbreak three weeks before engineering does.

Prompt Engineering as Code (PEaC) treats the prompt as a versioned, testable, reviewable software artifact — subject to the same discipline as any other code that ships to production: schema contracts, regression suites, code review, and CI/CD gates. This is the architecture built for the Prompt Pocket project, and the rest of this post breaks down the reasoning, the failure modes it prevents, and the implementation details that matter once you go past a toy prototype.

Why Prompts Need Software Engineering Discipline

What breaks when prompts are treated as strings, not artifacts?

Four failure modes show up consistently in production LLM systems that skip prompt engineering discipline:

  1. Silent behavioral drift — a prompt edited in a dashboard changes downstream behavior with no code review, no diff visible to reviewers, and no automated check that output shape or quality held steady.
  2. Schema breakage without a stack trace — the model returns prose instead of JSON, omits a required field, or hallucinates an enum value outside your allowed set. Without runtime validation this fails downstream silently or crashes deep in business logic, far from the actual cause.
  3. Model-swap regressions — upgrading from one model version to another (or switching providers) changes formatting habits, verbosity, or instruction-following fidelity in ways manual QA won’t catch until a customer complains.
  4. No blast-radius control — a bad prompt change ships straight to 100% of traffic because there’s no staging gate, no eval suite, and no rollback path distinct from a full deploy.

Prompt-as-code closes these gaps by applying the same guardrails you already use for application code: version control, typed contracts, automated regression testing, and staged rollout.

Prompt-as-Code vs. Ad-Hoc Prompting: A Direct Comparison

Dimension Ad-hoc prompting Prompt-as-code (PEaC)
Storage Inline strings, dashboard UI, .env Versioned files in git, reviewed via PR
Change tracking None, or informal Slack message Git diff + semantic version bump
Output contract Implicit, parsed with regex/hope Explicit schema (Zod/Pydantic/JSON Schema), validated at runtime
Regression detection Manual spot-checking, if any Automated eval suite run in CI against a golden dataset
Rollback Redeploy previous app version Point back to previous prompt version, no app deploy needed
Model upgrades “Try it and see” Eval suite re-run against new model, diffed against baseline scores
Adversarial input handling Discovered in production Tested against an adversarial/red-team input set pre-merge
Ownership Whoever last edited the dashboard Code owners via CODEOWNERS, PR review required

Core Principle 1: Strict Schemas for Structured Output

Why can’t you trust natural-language output directly in production pipelines?

Because LLMs are non-deterministic text generators, not typed function calls — even with low temperature, output format drifts (a stray markdown fence, an extra sentence before the JSON, a field renamed by the model’s own “helpful” instinct). Any pipeline that consumes this output downstream needs a validation boundary, exactly like you’d validate an external API response you don’t control.

The pattern: define the contract with a schema library (Zod in TypeScript, Pydantic in Python), request structured output from the model (tool-calling / function-calling mode where available, or a JSON-mode system prompt as fallback), and parse-and-validate before any business logic runs:

import { z } from "zod"

export const CodeReviewOutputSchema = z.object({
  status: z.enum(["approved", "changes_requested"]),
  findings: z.array(
    z.object({
      severity: z.enum(["critical", "important", "minor"]),
      file: z.string(),
      description: z.string(),
    })
  ),
})

export type CodeReviewOutput = z.infer<typeof CodeReviewOutputSchema>

// At the API boundary — fail loud, fail typed, not silently downstream
const result = CodeReviewOutputSchema.safeParse(rawModelOutput)
if (!result.success) {
  // Never let malformed model output reach business logic.
  // Log the validation error, the raw output, and the prompt version
  // that produced it — this triple is your debugging surface.
  throw new StructuredOutputValidationError(result.error, promptVersion)
}

The three things this buys you that regex-parsing or “just ask nicely” doesn’t:

  • A single point of failure that’s observable. When output shape breaks, you get a typed validation error tied to a specific prompt version — not a TypeError: undefined is not a function three layers deep in your app.
  • A contract you can test against without calling the model. Unit tests can assert schema behavior on fixture data instantly and for free, separate from the (slow, non-deterministic, costly) integration tests that actually call the LLM.
  • A migration path when the contract changes. Schema changes become explicit diffs reviewable in a PR, not implicit renegotiations between prompt text and downstream parsing code that happen to still work.

Structured Output: Tool-Calling Mode vs. JSON-Mode System Prompt

Two ways to get structured output, with different reliability profiles:

  • Native tool/function calling (Claude’s tool use, OpenAI’s function calling) — the model is constrained toward a schema at the API level. Higher adherence, works well for nested and strict enum schemas, and integrates naturally with a schema library like Zod when you generate the tool definition from the same schema you validate against.
  • JSON-mode via system prompt instruction (“respond only with valid JSON matching this shape”) — lower reliability, more prone to prose leakage or malformed brackets, but necessary when the workflow doesn’t fit a tool-call shape (e.g., the “tool” is the final answer, not an intermediate action). Always pair this with runtime validation and a retry-with-error-feedback loop, not blind trust.

Prefer tool-calling for anything with a strict schema and low failure tolerance; reserve JSON-mode prompting for cases where tool-calling semantics don’t map cleanly onto the task.

Handling Validation Failures: Retry Strategy

A validation failure isn’t necessarily terminal. A pragmatic retry ladder:

  1. Re-prompt with the validation error appended — feed the Zod/Pydantic error message back to the model as context (“your previous response failed validation: findings[0].severity must be one of critical/important/minor, got ‘high’”) and request a corrected response. This resolves the majority of transient format failures because the model can self-correct given specific feedback.
  2. Fall back to a stricter extraction prompt — if retry #1 fails, drop to a narrower, single-purpose prompt whose only job is reformatting the previous raw output into the schema.
  3. Surface a typed error to the caller — after N retries (2–3 is typical), stop retrying silently. Bubble up a structured error the caller can handle explicitly, rather than looping indefinitely and burning tokens.

Never retry unboundedly — cap attempts, and log every failure with the prompt version, model version, and raw output for later eval-suite inclusion (today’s failure is tomorrow’s regression test case).

Core Principle 2: Semantic Versioning for Prompts

How should you version a prompt?

Treat each system prompt like a package: give it a semantic version (v1.2.0), store it in git alongside (or as) code, and require every version bump to pass an automated eval suite against a reference dataset before it’s eligible for production traffic.

Apply semver semantics deliberately, not just as a label:

  • MAJOR (v2.0.0) — breaking change to the output contract or fundamental behavior. Downstream consumers must update their parsing/handling logic. Example: changing findings[].severity from a 3-value enum to a 5-value enum.
  • MINOR (v1.3.0) — behavior change that’s backward compatible with the existing schema. Example: adding a new optional field, improving instruction clarity, adding a few-shot example that shifts tone.
  • PATCH (v1.2.1) — wording fix, typo correction, or clarification that shouldn’t measurably change output distribution. Example: fixing a grammatical error in the system prompt that doesn’t touch instructions.

Prompt-as-Config vs. Prompt-as-Code: Which Storage Model Should You Use?

There are two defensible storage strategies, and the right one depends on how often prompts change relative to deploys, and who’s allowed to change them:

Prompt-as-code — prompts live as .ts/.md/.txt files in the same repo as the application, versioned via git commits, deployed via the normal CI/CD pipeline. Best when:

  • Prompt changes should go through the same review gate as code (recommended default for anything with side effects, like the contact tool pattern in this site’s own MCP server).
  • You want prompt version and application version to move together, with no risk of drift between what the code expects and what the prompt produces.
  • Rollback = git revert, no extra infrastructure needed.

Prompt-as-config — prompts live in an external store (database, feature-flag service, dedicated prompt-management platform) fetched at runtime, independently deployable from application code. Best when:

  • Non-engineering stakeholders (content, support, growth) need to iterate on prompt wording without a code deploy.
  • You need instant rollback or A/B testing across prompt versions without redeploying the app.
  • The trade-off you’re accepting: an extra runtime dependency, a need to still enforce review/versioning discipline outside of git (easy to lose if the config platform doesn’t support it natively), and a harder-to-audit change history unless the platform logs it well.

A hybrid is common in practice: prompt templates and schemas live as code (reviewed, versioned, typed), while specific variables injected into the template (tone presets, few-shot examples pulled from a curated set) can live in config for faster non-engineering iteration — as long as the schema contract itself stays code-owned.

Git-Based Prompt Versioning: A Concrete Directory Layout

prompts/
├── code-review/
│   ├── v1.0.0.ts       # initial version, archived
│   ├── v1.1.0.ts       # added severity triage instructions
│   ├── v1.2.0.ts       # current production version
│   └── schema.ts       # CodeReviewOutputSchema — shared across versions
├── recruiter-evaluate/
│   ├── v1.0.0.ts
│   └── schema.ts
└── registry.ts          # maps { promptId, environment } -> active version

Keep old versions in the tree rather than deleting them on bump — this is what makes regression comparison and rollback trivial: a rollback is a one-line change in registry.ts pointing back at v1.1.0, not a git revert archaeology exercise. Pair every version bump with a CHANGELOG entry describing why, not just what — future debugging sessions need the reasoning, not just the diff.

Core Principle 3: Building an Eval Harness and Golden Dataset

What is a golden dataset, and why does every serious prompt eval suite need one?

A golden dataset is a curated, versioned set of representative inputs paired with either exact expected outputs or scoring criteria — the reference set every prompt version is evaluated against before shipping. It plays the same role fixtures/ plays in traditional software testing, adapted for non-deterministic output.

A well-constructed golden dataset mixes:

  • Happy-path cases — typical, well-formed inputs covering the main use cases the prompt needs to handle.
  • Edge cases — empty inputs, extremely long inputs, inputs in unexpected languages, ambiguous requests.
  • Known-regression cases — every production bug caused by a prompt regression gets added here permanently once fixed, so it can never silently reappear. This is the single highest-leverage habit in prompt eval maintenance.
  • Adversarial / red-team cases — prompt injection attempts, jailbreak attempts, requests to leak the system prompt, off-topic requests designed to derail the assistant persona.

How do you structure a prompt eval suite?

A minimal but production-viable eval harness has four stages:

interface EvalCase {
  id: string
  input: Record<string, unknown>
  // Exact-match assertions where output is deterministic enough (schema shape,
  // enum membership, presence of required fields).
  assertions: (output: unknown) => { pass: boolean; reason?: string }[]
  // Optional: for cases where correctness is qualitative, not exact-match.
  rubric?: string
}

async function runEvalSuite(
  promptVersion: string,
  model: string,
  cases: EvalCase[]
): Promise<EvalReport> {
  const results = await Promise.all(
    cases.map(async (c) => {
      const rawOutput = await callModel(promptVersion, model, c.input)
      const parsed = OutputSchema.safeParse(rawOutput)

      const schemaAssertion = {
        pass: parsed.success,
        reason: parsed.success ? undefined : parsed.error.message,
      }

      const customAssertions = parsed.success
        ? c.assertions(parsed.data)
        : []

      const judgeScore = c.rubric
        ? await runLlmAsJudge(c.rubric, c.input, rawOutput)
        : null

      return { caseId: c.id, schemaAssertion, customAssertions, judgeScore }
    })
  )

  return summarizeReport(promptVersion, model, results)
}
  1. Schema validation — does the output even conform to the contract? This alone catches most catastrophic regressions and is fully deterministic.
  2. Deterministic assertions — string/field-level checks that don’t need a judge: “does status equal the expected value,” “is findings non-empty for this known-buggy input,” “is the response under N tokens.”
  3. LLM-as-judge scoring — for qualitative dimensions (tone, helpfulness, adherence to persona) that resist exact-match assertions, use a second model call with an explicit rubric to score the output. Critical caveats below.
  4. Aggregate report with pass/fail threshold — the suite produces a single gating signal: percentage of cases passing schema validation, mean judge score vs. the previous version’s baseline, and a hard list of any known-regression cases that failed (these should block merge unconditionally, not just lower an average).

LLM-as-Judge: What It’s Good For, and Where It Lies to You

LLM-as-judge — using a second (often stronger, or differently-prompted) model call to score the output of your primary prompt against a rubric — is the practical answer to evaluating open-ended qualities that can’t be checked with a regex or an equality assertion: tone, helpfulness, faithfulness to source material, instruction-following on subjective criteria.

It has real, well-documented failure modes you need to design around, not ignore:

  • Self-preference bias — a judge model tends to score outputs from its own model family more favorably. If you’re comparing Claude output against a competing model’s output, use a judge that isn’t the same family as either candidate, or explicitly control for this in your scoring methodology.
  • Verbosity bias — judges frequently rate longer answers as “better” independent of actual quality. Constrain the rubric explicitly against this (“do not favor length; concise correct answers should score equally to verbose correct answers”).
  • Position bias in pairwise comparison — when asking a judge to pick between output A and output B, the position presented first is favored disproportionately. Mitigate by running the comparison twice with positions swapped and requiring a consistent verdict.
  • Rubric ambiguity compounds silently — a vague rubric (“is this a good response?”) produces noisy, low-agreement scores. Rubrics need to be as explicit and structured as the output schema itself — enumerate the specific criteria and their relative weight.

Treat LLM-as-judge scores as a signal to investigate, not a ground truth to trust blindly — especially near a pass/fail threshold. Spot-check a sample of judge scores against human judgment periodically to detect judge drift.

Detecting Silent Prompt Regressions When Upgrading Model Versions

Model upgrades are one of the highest-risk, least-tested moments in a prompt’s lifecycle, because the prompt text doesn’t change — only the model interpreting it does. A practical detection workflow:

  1. Run the full eval suite against the new model version before flipping any production traffic, using the exact same prompt version and golden dataset used for the current production baseline.
  2. Diff aggregate scores, not just pass/fail — a model swap can keep the same pass rate while quietly shifting response length, verbosity, refusal rate, or formatting habits (e.g., a new model version adding markdown where the old one didn’t, breaking a plaintext-only consumer).
  3. Pay special attention to structured-output adherence rate — different model versions have different native tool-calling reliability; a version bump that looks fine on prose quality can regress schema conformance specifically.
  4. Run the adversarial/red-team subset separately — instruction-following on safety/persona boundaries is exactly the kind of behavior that shifts unpredictably across model versions, and a general quality eval can mask a regression here if adversarial cases are a small fraction of the total suite.
  5. Canary the new model version on a small percentage of production traffic with output logging enabled, comparing real-world output distribution against the eval-predicted distribution, before full cutover.

The core discipline: a model version bump is a prompt regression risk, not just an infrastructure change — route it through the same eval gate as an actual prompt edit.

Core Principle 4: CI/CD Gating for Prompts

How do you wire prompt evals into CI/CD?

Treat the eval suite as a required check, exactly like a unit test suite blocking merge:

# .github/workflows/prompt-eval.yml
name: Prompt Eval Gate
on:
  pull_request:
    paths:
      - "prompts/**"

jobs:
  eval:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - name: Run eval suite against changed prompt versions
        run: npm run eval:prompts -- --diff-against=main
      - name: Fail if any known-regression case fails
        run: npm run eval:check-regressions
      - name: Fail if schema-conformance rate drops below threshold
        run: npm run eval:check-schema-rate -- --min=0.98
      - name: Post score diff as PR comment
        uses: actions/github-script@v7
        with:
          script: |
            // Post aggregate score delta vs. main branch's current prompt version
            // directly on the PR, so reviewers see quality impact, not just a diff of text.

Key properties this pipeline enforces:

  • Prompt changes trigger the same eval gate as code changes — a scoped paths: filter means it only runs when prompts/** changes, keeping CI fast, but it runs unconditionally when it matters.
  • Known-regression cases are a hard block, not part of an averaged score — a single previously-fixed bug reappearing should never be able to hide behind an otherwise-good aggregate.
  • Schema-conformance rate has its own explicit threshold, separate from qualitative judge scores — structural breakage and “slightly worse tone” are different severity classes and should gate independently.
  • Score diffs are visible to human reviewers at review time, not buried in a build log — this is what makes prompt review substantively different from silently approving a wording change.

Staged Rollout for Prompt Changes

Once a prompt version passes CI, treat production rollout the same way you’d treat any behavior change with unknown real-world edge cases:

  1. Shadow mode — run the new prompt version against real production inputs without serving its output to users, logging results for comparison against the currently-live version.
  2. Percentage rollout — serve the new version to a small traffic percentage (5–10%), monitoring schema-validation failure rate, latency, and token cost in real time against the baseline.
  3. Full cutover with the previous version pinned and ready — keep the prior version’s registry entry intact so rollback is a config change, not a redeploy.

Core Principle 5: System Prompt vs. User Prompt Separation and Injection Defense

Why does the system/user prompt boundary matter for security?

The system prompt carries the instructions and constraints you control; the user prompt (and anything else user-influenced — retrieved documents, tool outputs, uploaded files) carries content you don’t. Prompt injection exploits the fact that models process both as text in the same context, so instructions embedded in user-controlled content can attempt to override system-level instructions if the boundary isn’t reinforced.

Practical defenses, layered rather than relied on individually:

  • Explicit boundary framing in the system prompt — instruct the model to treat content within designated tags/delimiters as data, not instructions, and to ignore any instruction-like text found there. This reduces but does not eliminate injection risk.
  • Least-privilege tool design — if the model has tool access, scope each tool’s capability to the minimum needed (read-only vs. side-effecting, as this project does explicitly with its MCP tools — five read-only tools plus a single side-effecting contact tool with hard input caps). An injected instruction is far less dangerous if the available tools can’t do damage even when misused.
  • Output-side validation, not just input-side filtering — structured output schemas (Principle 1) double as an injection mitigation: even if an injected instruction partially succeeds, if the model’s output still has to conform to a strict schema, the blast radius of “the model said something it shouldn’t” is contained by what the schema even allows the field to contain.
  • Adversarial cases in the eval suite are your regression tests for injection resistance — every known injection pattern that succeeded once (even in testing, not just production) gets added to the golden dataset’s adversarial subset permanently, exactly like a known-regression functional case.

Few-Shot vs. Zero-Shot: When Does the Extra Prompt Weight Pay Off?

Zero-shot (instructions only, no examples) is the right default: it’s cheaper in tokens, easier to version and diff, and modern instruction-tuned models follow well-specified instructions reliably without examples for most well-scoped tasks.

Reach for few-shot specifically when:

  • Output format is unusual or highly structured in a way instructions alone under-specify — showing 2–3 examples of the exact target shape often resolves ambiguity that a paragraph of instructions leaves room for.
  • Tone/style calibration matters more than instructions can capture — “professional but not stiff” is easier to demonstrate than describe precisely.
  • Edge-case handling needs to be pinned down — an example showing exactly how to handle an empty result set or an ambiguous input disambiguates behavior that free-text instructions tend to leave underspecified.

The cost side of the trade-off: few-shot examples increase every request’s token cost and latency, and — this is the part teams underweight — examples themselves need version control and eval coverage, because a stale or subtly wrong few-shot example silently biases every subsequent output. Treat the few-shot set as part of the versioned prompt artifact, not a one-time addition you forget about.

Core Principle 6: Latency, Determinism, and Cost Benchmarking

How do you benchmark models for a production prompt decision?

Comparing models (e.g., Claude Sonnet vs. Claude Haiku vs. a competing model) for a specific production use case needs more than a vibe check on a handful of manual prompts — it needs the same golden dataset and eval harness used for regression testing, scored along three independent axes:

  • First-attempt success rate — the percentage of golden-dataset cases that pass schema validation and deterministic assertions on the first call, with no retry. This is the number that determines real-world retry overhead and effective latency, not just raw model speed.
  • Cost per successful output, not cost per token — a cheaper model with a lower first-attempt success rate can cost more overall once retry token spend is factored in. Compute (avg tokens per attempt × avg attempts to success × price per token) as the real comparison metric, not sticker price per million tokens.
  • Latency at the percentile that matters for the UX, not the mean — a chat interface cares about p50/p90 time-to-first-token; a batch pipeline cares about p99 total completion time. Pick the percentile that matches the actual user-facing constraint.

Temperature and Determinism: What’s the Right Trade-off for a Structured-Output Pipeline?

Lower temperature (near 0) increases output consistency, which matters most when:

  • The task has a single objectively-correct structure (data extraction, classification, code review verdicts).
  • You need eval-suite scores to be reproducible run-over-run, since a highly stochastic prompt makes it hard to distinguish “this version regressed” from “this run happened to sample badly.”

Higher temperature earns its cost when:

  • The task is genuinely generative (creative copy, brainstorming, varied conversational responses) and sameness across calls would feel robotic.
  • Diversity of output is itself a feature (e.g., generating multiple candidate approaches for a human to pick from).

Even at temperature 0, don’t expect bit-for-bit determinism across calls — infrastructure-level non-determinism (batching, hardware differences) means near-zero temperature reduces variance but doesn’t guarantee identical output. Design your eval assertions around semantic equivalence and schema conformance, not exact string equality, unless the task is narrow enough that exact match is realistic.

Putting It Together: A Reference Workflow

  1. Prompt change proposed as a PR touching prompts/<feature>/vX.Y.Z.ts, with a CHANGELOG entry explaining the reasoning.
  2. CI runs the eval suite for that prompt against the golden dataset: schema conformance, deterministic assertions, LLM-as-judge scoring, adversarial subset.
  3. Known-regression cases and schema-conformance threshold gate the merge unconditionally; qualitative score deltas are surfaced to the reviewer as a PR comment for a judgment call.
  4. On merge, the new version is deployed but not yet live — registry.ts still points at the previous version.
  5. Shadow mode, then percentage rollout, monitoring schema-failure rate and cost in real time.
  6. Full cutover, with the previous version retained in the tree for instant rollback.
  7. Any production issue traced back to the prompt gets its triggering input added to the golden dataset as a permanent known-regression case before the fix ships.

This is the same lifecycle any well-run codebase applies to a risky code change — the only genuinely new part is that the “tests” include a non-deterministic judge, and the “contract” is enforced by a schema validator instead of a compiler. Everything else — versioning, review, staged rollout, regression protection — is standard engineering discipline applied to an artifact that happens to be natural language.

Technical FAQ

What’s the difference between prompt versioning and prompt A/B testing?

Versioning is about controlled, auditable change management — every version is tested, reviewed, and reproducible, with a clear current-production pointer. A/B testing is a rollout strategy that can sit on top of versioning — serving two already-versioned, already-eval-passed prompt variants to different traffic splits to compare real-world metrics. You need versioning regardless of whether you A/B test; A/B testing is optional and layered on top.

Do I need a dedicated prompt-management platform, or can I just use git?

Git plus a lightweight eval harness covers the majority of use cases, especially when prompt changes are made by engineers and should go through code review anyway. Reach for a dedicated platform when non-engineering stakeholders need self-serve iteration without a deploy, or when you need built-in A/B infrastructure and analytics you don’t want to build yourself — but be aware you’re trading git’s free audit trail and review gate for a tool that needs to be configured to replicate them.

How large should a golden dataset be before it’s useful?

Useful before it’s exhaustive: even 15–30 well-chosen cases covering happy paths, known edge cases, and 2–3 adversarial inputs catch the majority of catastrophic regressions (schema breakage, obvious instruction-following failures). Grow it incrementally and permanently — every production bug becomes a new permanent case — rather than trying to design a “complete” dataset up front, which is both impossible and a poor use of time relative to shipping.

Can LLM-as-judge scoring replace human review entirely?

No — treat it as a scaling mechanism for catching regressions between human reviews, not a replacement for human judgment on ambiguous or high-stakes cases. Judge models carry the same failure modes as any LLM (bias, inconsistency near threshold values, blind spots on subtle correctness issues) and periodic human spot-checks against judge scores are necessary to detect judge drift over time.

How do you test a prompt against prompt injection specifically?

Build an adversarial subset of the golden dataset containing known injection patterns: instructions embedded in user content attempting to override the system prompt, attempts to exfiltrate the system prompt verbatim, attempts to invoke tools outside the intended scope, and encoded/obfuscated instruction attempts (e.g., injected instructions split across multiple turns or hidden in formatting). Score these cases on whether the model’s output — validated against the strict schema — shows any sign of compliance with the injected instruction, not just whether it “refused” in plain text.

What’s the practical difference between prompt-as-code and prompt-as-config for a small team?

For a small team where the same engineers write prompts and ship code, prompt-as-code is almost always the simpler, lower-overhead choice — no extra infrastructure, and git already gives you review, versioning, and rollback for free. Prompt-as-config earns its complexity once a non-engineering team member needs to iterate on wording independently of a deploy cycle, or once you need instant, code-deploy-free rollback across many concurrently-running prompt variants.

How do you know when a prompt version bump should be MAJOR vs MINOR?

Ask whether an existing downstream consumer’s parsing or handling logic would break unmodified against the new output. If yes — a field renamed, an enum value removed, an output shape restructured — it’s MAJOR. If the schema stays fully backward compatible (new optional fields, refined instructions that don’t change the contract) but behavior shifts, it’s MINOR. Wording-only fixes with no measurable behavior change are PATCH.

What should trigger adding a case to the golden dataset outside of a production bug?

Any input discovered during manual testing that produces unexpected output, any edge case identified during code review of the prompt itself, any adversarial input surfaced during a security review, and any case where a model version upgrade produced a different result than the previous version — even if the new result happens to still be acceptable, it’s worth capturing as a case to detect future drift on that same input.

Questions or project ideas?

Reach out directly to discuss architecture, optimization, or AI agents.

Get in Touch →