Skip to content

S.C.A.F.F. prompt structure

Specify · Context · Action · Format · Failure-modes

Most developer complaints about AI assistants ("it hallucinated", "it gave me the wrong API", "it wrote code that doesn't fit the codebase") trace back to under-specified prompts. S.C.A.F.F. is the minimum prompt structure that makes a useful first draft likely.

S.C.A.F.F. is the foundation that V.E.R.I.F.Y. validates. A bad prompt makes verification expensive; a good prompt makes verification a short check.

When to use S.C.A.F.F.

  • Every non-trivial prompt that will produce code, configuration, documentation, or a Decision Record that may reach production.
  • Whenever a previous, less-structured prompt produced a result that needed three or more rounds of correction.
  • Whenever the team is teaching prompting to new joiners; start them here.

For one-line completions in an inline editor (renaming a variable, auto-completing a known method signature) the full structure is overkill. The structure pays off the moment the prompt produces more than ~10 lines of output.

The five parts

S. Specify

State the problem in one or two sentences. Not the solution; not the implementation; the problem. A specification is a contract: it should be testable, falsifiable, and small enough to fit in working memory.

Anti-shape: "Build me a payments service." Too large, no contract.

Better: "Add an endpoint that accepts a refund request for an existing order, validates the order is in paid state, and emits a RefundRequested event. Idempotent on order_id."

C. Context

Give the model what it would not otherwise know:

  • The codebase conventions that matter (naming, layout, framework versions).
  • The adjacent code the model will need to fit alongside (paste it, reference it, or attach it as files).
  • The constraints: runtime, performance budget, dependency policy, license compatibility.
  • The non-goals: things it should not attempt to fix, even if it notices them.

Models will infer context if you don't supply it. Their inferences are plausible-shaped guesses, not facts. Stanford CCS 2023 found that developers given an AI assistant wrote less secure code and were more confident it was secure1. Confidence rises faster than correctness when context is missing.

A. Action

Tell the model what to produce. A function? A diff? A test? A design note? Be specific about the artifact, not the activity.

  • "Write a function" is an activity.
  • "Produce a single Python function apply_refund(order_id: str) -> Refund with a docstring, plus a unit test in the existing pytest style" is an artifact.

If the action is a sequence (e.g., "first sketch the API, then implement it, then write tests") say so explicitly. The model will not guess your preferred sequencing reliably.

F. Format

State the shape of the output so it lands somewhere useful:

  • Language and version.
  • File path or paths.
  • Whether to produce a unified diff, a full file, or a snippet.
  • Whether comments are wanted or unwanted.
  • Whether explanations are wanted (for learning) or unwanted (for paste-into-editor speed).

Format mistakes are the most common cause of "the model produced something correct that I still had to rewrite."

F. Failure-modes

State what would make this output unacceptable, in advance.

  • "Do not introduce a new dependency."
  • "Do not change behavior of apply_payment."
  • "Do not return placeholder values; if a value is unknown, raise a NotImplementedError so I see it."
  • "Do not silently catch exceptions."

Failure-modes are the difference between a prompt that produces a draft a human can verify and a prompt that produces a draft that looks fine and quietly violates an invariant.

This last F is what most teams miss. It is also the highest-leverage addition: a single line preventing the most common failure (e.g., "do not change the public signature of any function") prevents most of the painful review cycles.

A minimal SCAFF template

[S. Specify]
<one-paragraph problem statement; testable, falsifiable, scoped>

[C. Context]
- Repo conventions: <link or summary>
- Adjacent code: <paste / file references>
- Constraints: <runtime, deps, perf, license>
- Non-goals: <what not to touch>

[A. Action]
Produce: <artifact, not activity>
Sequence: <if multi-step>

[F. Format]
- Language / version: <e.g., Python 3.11>
- Output shape: <diff | full file | snippet>
- Comments / explanation: <wanted | not wanted>
- Target path(s): <where the output lands>

[F. Failure-modes]
- Do not <highest-risk drift you want prevented>
- Do not <second-highest>
- If unsure, <preferred failure behavior>

Teams that adopt this template typically end up with a one-paragraph project preamble (Context + Format) saved as a snippet, and write only S/A/Failure-modes per prompt. That is fine; the template is a checklist, not a script.

Worked examples

Bad prompt

"Write me a refund endpoint for our orders service."

What this lacks:

  • No specification of which order states allow refunds.
  • No idempotency requirement.
  • No language, framework, or path.
  • No mention of the existing event bus, despite the team using one.
  • No failure-mode constraints. The model is free to introduce a new dependency, change adjacent code, or silently swallow errors.

The likely output is plausible-looking code that violates two or three invariants the developer holds in their head but did not write down.

Good prompt

[S] Add a refund endpoint to the orders service. Accepts a POST /orders/{id}/refund with a JSON body { amount, reason }, validates the order is in paid state, decrements available balance on the order, and emits a RefundRequested event. Idempotent on (order_id, request_id).

[C] Codebase: Python 3.11, FastAPI, SQLAlchemy 2.x. Event bus wrapper at app/events/bus.py; use it, do not call the broker directly. Order model at app/models/order.py. Existing endpoints in app/api/orders.py show conventions: dependency-injected DB session, Pydantic v2 request/response models, no business logic in the route handler.

[A] Produce two artifacts in this order: 1. The Pydantic request/response models and the route handler in app/api/orders.py. 2. The service-layer function refund_order in app/services/refunds.py, with type hints and a docstring.

[F] Output as a unified diff against main. Python 3.11. No inline comments unless they explain a non-obvious invariant. No prose explanation outside the diff.

[Failure-modes] - Do not introduce new dependencies. - Do not change the signature of any existing public function. - Do not catch Exception broadly; let unexpected errors propagate. - If a precondition cannot be checked from the order model alone, raise NotImplementedError rather than guessing.

The same model, given the same task, produces a draft that fits the codebase and verifies in a single pass. The cost is two minutes of typing.

Sizing guidance

Sizing guide

  • Small (< 50). A shared prompt-snippets.md in the repo with Context and Failure-modes blocks is enough. One owner.
  • Mid (50–500). Maintain a per-team prompt library starter keyed by service or domain. Review the library quarterly.
  • Enterprise (500+). Promote the prompt library to a first-class repository with versioning and per-domain owners, integrated with the training plan. Treat organization-wide failure-mode clauses (e.g., "never paste customer PII into a prompt") as policy, enforced through the AUP.
  • Regulated. Failure-modes clause must reference the data classes defined in data handling. Prompts themselves are auditable artifacts and retained per audit trail.

Anti-patterns

Don't

  • Don't ship vague prompts. "Build me X" is not a prompt; it is a wish. A wish produces wish-shaped output.
  • Don't skip Context. A model that has no codebase conventions will invent plausible ones, and the team will spend the saved time paying for the invention.
  • Don't omit the Failure-modes block. Most painful AI-generated defects come from a known-but-unstated invariant the model violated.
  • Don't copy-paste prompts that have drifted from intent. A template six months out of date is worse than no template; it encodes the old invariants and signals confidence the team no longer has.
  • Don't put secrets, customer PII, or proprietary IP into the Context block without checking the AUP and the tool's data handling. The cost of a leaked prompt is the same as the cost of a leaked file.

Next


  1. Perry, Srivastava, Kumar, Boneh, "Do Users Write More Insecure Code with AI Assistants?", arXiv:2211.03622, ACM CCS 2023. [primary] ↩