Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Decision rubric for promoting a prose prompt into a typed DSPy Signature. This is an ENHANCE overlay on top of the [[dspy]] library skill: it does NOT teach DSPy syntax — it answers the coder-agent decision "when do I stop hand-writing a prompt string and declare it as a `dspy.Signature`, and how do I name/describe its fields so the optimizer and the calling code both get a clean contract." Activate when: a prompt string grows past ~50 lines; the LM output is consumed by code (parsed, branched o
.claude/skills/agentsope-agentsop-signature-design/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 142% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 259% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 176% | 0% |
| case-22 | ✗→✓ | ▲ Improved | 284% | 0% |
| case-06 | ✓→✓ | = Same ✓ | 268% | 0% |
> "DSPy uses the field names as the only natural-language hint the optimizer has about intent before it sees > data. Name them like you'd name function parameters in well-written code." > — derived from dspy.ai/learn/programming/signatures/], see references/R1-source-evidence.md
This skill is the decision layer, not the library layer. It tells you when a prose prompt has become "load-bearing" enough to deserve a typed Signature, and how to shape its fields. For the actual API (dspy.Signature, InputField, OutputField, Predict, ChainOfThought, compile, save) defer to the dspy]] skill; for the full program→evaluate→optimize SOP defer to agentsop-dspy]].
Activate this overlay the moment a hand-written prompt crosses any one of three load-bearing thresholds.
| Trigger | Concrete signal | Why it matters | |---|---|---| | Length | A single prompt string grows past ~50 lines of f-string / template | Long prose prompts hide their I/O contract inside narration; the agentsop-dspy]] skill names this exact symptom: "hand-written prompts grow past ~50 lines; brittleness on model swap" (R1, claim S1) | | Code-consumed output | The LM response is parsed, branched on, or stored by downstream code (not just shown to a human) | If code reads the output, the output has a type. An untyped prompt forces brittle regex/JSON-scraping at every call site | | Reuse | The same prompt (or a copy-pasted variant) is called from >1 call site or in a loop | Reuse means the contract is now an API surface. Drift between copies is a guaranteed bug source |
Secondary signals (each strengthens, none alone is sufficient):
R1, claim S6; see agentsop-dspy]] Case B).Do NOT activate when:
R1, claim S7).A Signature is a typed function contract for a single LM call. Promote a prose prompt to a Signature exactly when the prompt becomes load-bearing — when something other than a one-time human reader depends on its shape.
Think of the progression as the same lifecycle a script goes through when it earns a function:
prose prompt string → typed Signature
───────────────────────── ─────────────────────────
"You are an expert... given class Classify(dspy.Signature):
the ticket below, output """Route a support ticket."""
the category and a one-line ticket: str = dspy.InputField()
reason. Categories are..." category: Literal[...] = dspy.OutputField()
reason: str = dspy.OutputField(desc="<=15 words")
inline narration of I/O explicit, named, typed I/O
human reads / eyeballs code parses category, logs reason
each caller copies the blob one contract, N callers import it
optimizer sees nothing optimizer rewrites instructions, keeps field namesThree load-bearing ideas (all sourced; see references/R1-source-evidence.md):
field names. question -> answer ≠ query -> response. Name fields like function parameters in clean code (R1, claim S2). This is the reason promotion is worth it: you convert narration into a machine-readable intent signal.
rewrites instructions and demos — but it never changes field names, field count, or types (R1, claim S5). So the Signature is the stable seam between "what I own" and "what the compiler owns." A prose prompt has no such seam — everything is tangled.
overhead. The payoff appears only when the prompt is long, code-consumed, or reused. Below that line, raw prompting wins (R1, claims S7, S1).
The PyTorch analogy from agentsop-dspy]] holds: a Signature ≈ a forward() shape contract. You don't write a nn.Module for a one-line lambda; you write one when the shape is reused and trained.
A four-step gate. Run it top-to-bottom; each step has an exit criterion. Implementation of any step lives in the dspy]] skill — this SOP only tells you what decision to make at each step.
Run the §1 trigger table. If zero triggers fire → stop, keep the prose prompt. Promotion is overhead. Exit: at least one of {>50 lines, code-consumed output, reused} is true.
Read the prose prompt and extract every variable thing the LM is given (inputs) and every distinct thing it must return (outputs). A common smell: the prose says "output the category and a confidence and a reason" — that is three output fields, not one paragraph to regex later. Exit: you can list inputs and outputs as a flat set of named slots, each with a Python type.
Rename each slot to read like a function parameter. text → ticket, out → category, resp → reason. The name carries the optimizer's only pre-data intent signal (R1, claim S2). Avoid generic input/output. Exit: every field name would be self-explanatory to a teammate reading only the field list.
Add InputField(desc=...) / OutputField(desc=...) only when the field name alone is ambiguous or the value needs a constraint the name can't carry (format, length, units, allowed values). The DSPy cheatsheet's own example adds a desc on output (answer, desc="often between 1 and 5 words") but leaves the input bare (R1, claim S3). Over-describing every field bloats the prompt and fights the optimizer. Exit: descriptions exist for exactly the fields that need disambiguation, and no others.
Picking Predict vs ChainOfThought vs ReAct, then evaluating and compiling, is out of scope for this decision skill — that is the dspy]] skill (modules) and agentsop-dspy]] (Stage 1–3 workflow). Your deliverable from this skill is a well-shaped Signature, handed to those skills. Exit: Signature is named, typed, minimally described, and committed; you have switched contexts to dspy]].
If, after compiling (in dspy]]), the optimizer plateaus, the most common root cause is an ambiguous Signature — not a bad optimizer (R1, claim S8). Loop back to Step 1: are inputs/outputs really separated? Are field names carrying intent?
Eight operations. The first two are the gate; the rest are the shaping rubric. Full Trigger/Action/Output/Evidence records are in intermediate/operation_candidates.json.
Promote prose → Signature when you can check ≥1 box. Each box maps to a §1 trigger:
[ ] LENGTH prompt string > ~50 lines
[ ] CONSUMED LM output is parsed / branched on / stored by code (not just human-read)
[ ] REUSED same prompt called from > 1 site, or inside a loop
[ ] (bonus) about to model-swap, OR a metric already exists, OR blob mixes instruction+demos+formatZero boxes → do not promote. One box → promote. (Source: §1 triggers; R1 S1, S6, S7.)
| # | Trigger | Action | Output | Evidence | |---|---|---|---|---| | OP-1 | Prompt crosses a §1 threshold | Run the §4.1 checklist | promote / keep-prose decision | R1 S1, S7 | | OP-2 | Decision = promote | Extract variable inputs + distinct outputs into named slots | Flat list of typed slots | R1 S2 | | OP-3 | Slots listed | Rename each to a semantic, parameter-style name | Field names that read as intent | R1 S2 | | OP-4 | Names set | Add desc= only to underspecified fields | Minimal descriptions | R1 S3 | | OP-5 | Output has fixed value set | Type the output field (Literal[...] / bool / int) instead of str | Typed OutputField | R1 S3, S5 | | OP-6 | Reasoning would help quality | Note "needs CoT" but defer module choice to dspy]] | Hand-off note | R1 S4 (module table is dspy's) | | OP-7 | Optimizer plateaus later | Loop back: re-audit Signature for ambiguity before blaming optimizer | Revised Signature | R1 S8 | | OP-8 | Output consumed by code AND must be machine-valid | Pair the Signature with a grammar/JSON enforcer (Outlines) — Signature shapes intent, enforcer guarantees syntax | Signature + enforcement layer | R1 S9 |
customer_email, not the text the user pasted.Literal["bug","billing","other"] over str when theset is closed (OP-5) — the type is documentation and a parse guard.
desc= for what the name can't say: format, length, units, allowed values, edge-case handling.| Side | Add a desc when… | Skip the desc when… | |---|---|---| | InputField | The input has a non-obvious format/source ("raw OCR text, may contain noise"), or the LM tends to misread which input is which | The field name fully explains it (question, ticket) — the cheatsheet leaves question bare (R1 S3) | | OutputField | You need to constrain the value: length ("≤15 words"), format ("ISO-8601 date"), or allowed set | The output type already constrains it (e.g. Literal[...] or bool carries the spec) |
Rule of thumb: input descs prevent confusion; output descs prevent malformed values. Default to fewer descs; add one only when you can name the specific failure it prevents.
困境: A support-triage prompt is 80 lines: persona + 4 categories with examples + output-format spec + edge-case rules. The output ("category, confidence, escalate?") is parsed by routing code. Promote it verbatim into one Signature, or restructure first?
约束:
category and escalate) → §1 CONSUMED trigger fires.决策步骤:
R1 S1).ticket: str. Outputs = category:Literal...], confidence: float, escalate: bool (OP-2, OP-5). The four category descriptions and examples are **demos/instructions the optimizer will own** — drop them from your code (mental model #2, R1 S5).
escalate: bool replaces parsing the word"yes" out of prose.
desc only on confidence ("0–1, calibrated") since the name underspecifies the range (OP-4, §4.4).ChainOfThought (reasoning helps category choice) and to compile against theexisting routing-accuracy metric.
结果: An 80-line blob collapses to a 5-field typed contract; the router drops all string-scraping; the prose that was the prompt becomes optimizer-owned instructions/demos.
可提取的操作: When promoting a mega-prompt, keep only the I/O shape in code; let the instruction/demo prose become the optimizer's territory. Split fused outputs; type closed-set and boolean outputs.
困境: A teammate has a 6-line prompt that runs once in a migration script ("classify these 200 rows once, then we throw the script away") and asks you to "make it a proper Signature so it's robust."
约束:
决策步骤:
no second reader over time (disposable script). This is the boundary the agentsop-dspy]] skill flags: compile only after the I/O contract stabilizes and will be reused (R1 S7).
Literaloutput stops bad values landing in the DB), but do not optimize/compile it. A typed dspy.Predict(Sig) with no compile is cheap and gives the parse guard without the compile-loop overhead.
DSPy without a metric is just verbose prompting (R1 S10). Say so explicitly.
结果: A 10-line typed Signature with no compile: enough to make the written column type-safe, not enough to waste a compile budget on a script that's about to be deleted.
可提取的操作: CONSUMED alone justifies a typed Signature (parse safety) but NOT optimization. Separate "promote to typed contract" from "compile/optimize" — they have different gates.
pure boilerplate. If no §1 trigger fires, the prose prompt is the correct artifact (R1 S7).
class Sig: input: str; output: str defeats the entire point — theoptimizer gets zero intent signal and code still can't trust the output shape. Field names ARE the contract (R1 S2). Generic names are the most common silent failure.
result: str and regex-scraping three valuesout of it re-creates the fragility you were escaping. Split into typed fields (OP-2, OP-5).
freezing it in your Signature both bloats your code and fights the compiler (mental model #2, R1 S5).
boilerplate. Stabilize first (R1 S7).
desc= to every field reflexively. Descriptions you can't tie to a specific prevented failure arenoise that bloats the prompt; the cheatsheet leaves obvious inputs bare (R1 S3).
OutputField pushes towardstructure but does not enforce grammar — pair with Outlines/Guidance when machine-validity is mandatory (R1 S9, OP-8).
skill stops at "the Signature is shaped."
LMQL), orthogonal to Signature design (R1 S9).
transfers, but the implementation does not (see §7 for the cross-framework mapping).
The decision ("promote prose → typed contract when the prompt becomes load-bearing") is framework-agnostic. Only the artifact differs. This overlay's rubric (§4) tells you when to reach for any column below.
| Approach | What the "contract" is | Optimizable? | Enforces output syntax? | Best when | |---|---|---|---|---| | Raw prompt string | None — narration only | No | No | One-shot, human-read, unstable, no reuse (§6 boundary) | | DSPy Signature | Named + typed I/O fields; field names carry intent for the optimizer (R1 S2) | Yes — instructions/demos rewritten on compile, field shape preserved (R1 S5) | Pushes toward structure, no hard guarantee (R1 S9) | Load-bearing prompt + a metric exists / model-swap planned. Implementation: dspy]] | | Pydantic output model | A typed schema the response is validated against after generation | No (it's validation, not prompt-tuning) | Yes — validation raises on mismatch | You need a hard post-hoc type check but are not optimizing the prompt | | instructor (Pydantic + LLM) | Pydantic model used both as prompt scaffold and parse target; auto-retries on validation failure | No prompt-optimization loop; retries only | Yes — re-asks the LM until the schema validates | You want structured-output-with-retries on a raw provider SDK, no compile pipeline |
How they compose (not mutually exclusive):
output is valid JSON/grammar at the token level (R1 S9, OP-8).
optimizer that instructor lacks, and instructor adds validation-retry that a bare Signature lacks. Pick DSPy when you have a metric and want to compile; pick instructor when you just need structured output + retries on a raw SDK.
type gate with no prompt machinery.
Bottom line: this skill decides whether the prompt deserves a typed contract. If yes and you're in DSPy with a metric → the contract is a Signature, and you continue in dspy]] / agentsop-dspy]]. If you only need validation → Pydantic/instructor. If the prompt isn't load-bearing → no contract at all.
overlay defers ALL implementation to it.
selection, cost guardrails). This overlay is the narrow "should this prose become a Signature" slice of its Stage 1.
All claims tagged S1–S10 are sourced verbatim in references/R1-source-evidence.md, drawn from the local dspy-sop-skill/SKILL.md Signatures material and the upstream DSPy docs it cites (dspy.ai/learn/programming/signatures/], dspy.ai/cheatsheet/], arxiv.org/abs/2310.03714]).
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 12,540 | 11,338 | -10% | 1 | 1 | 0% | 2,256 | 7,201 | +219% | 0 | 0 | — |
case-02 | fail→pass | 16,825 | 11,746 | -30% | 1 | 1 | 0% | 2,970 | 7,200 | +142% | 0 | 0 | — |
case-03 | fail→fail | 13,077 | 9,856 | -25% | 1 | 1 | 0% | 2,441 | 7,023 | +188% | 0 | 0 | — |
case-04 | fail→fail | 19,369 | 14,456 | -25% | 1 | 1 | 0% | 3,249 | 7,916 | +144% | 0 | 0 | — |
case-05 | fail→fail | 10,303 | 10,699 | +4% | 1 | 1 | 0% | 1,940 | 7,317 | +277% | 0 | 0 | — |
case-06 | pass→pass | 11,437 | 10,343 | -10% | 1 | 1 | 0% | 1,885 | 6,934 | +268% | 0 | 0 | — |
case-07 | pass→pass | 16,069 | 11,859 | -26% | 1 | 1 | 0% | 2,801 | 7,332 | +162% | 0 | 0 | — |
case-08 | pass→pass | 8,179 | 9,353 | +14% | 1 | 1 | 0% | 1,456 | 6,984 | +380% | 0 | 0 | — |
case-09 | pass→pass | 11,959 | 9,443 | -21% | 1 | 1 | 0% | 1,869 | 6,836 | +266% | 0 | 0 | — |
case-10 | pass→pass | 3,355 | 3,608 | +8% | 1 | 1 | 0% | 521 | 5,850 | +1023% | 0 | 0 | — |
case-11 | pass→pass | 15,164 | 13,336 | -12% | 1 | 1 | 0% | 2,291 | 7,355 | +221% | 0 | 0 | — |
case-12 | pass→pass | 15,211 | 8,765 | -42% | 1 | 1 | 0% | 2,355 | 6,546 | +178% | 0 | 0 | — |
case-13 | pass→pass | 14,947 | 9,643 | -35% | 1 | 1 | 0% | 2,414 | 6,878 | +185% | 0 | 0 | — |
case-14 | pass→pass | 13,085 | 5,867 | -55% | 1 | 1 | 0% | 1,919 | 6,230 | +225% | 0 | 0 | — |
case-15 | pass→pass | 12,616 | 6,769 | -46% | 1 | 1 | 0% | 2,110 | 6,411 | +204% | 0 | 0 | — |
case-16 | fail→pass | 11,386 | 7,774 | -32% | 1 | 1 | 0% | 1,888 | 6,784 | +259% | 0 | 0 | — |
case-17 | pass→pass | 12,109 | 6,878 | -43% | 1 | 1 | 0% | 2,120 | 6,390 | +201% | 0 | 0 | — |
case-18 | fail→pass | 15,486 | 6,398 | -59% | 1 | 1 | 0% | 2,302 | 6,362 | +176% | 0 | 0 | — |
case-19 | pass→pass | 15,373 | 11,092 | -28% | 1 | 1 | 0% | 2,393 | 7,157 | +199% | 0 | 0 | — |
case-20 | pass→pass | 12,631 | 10,452 | -17% | 1 | 1 | 0% | 1,897 | 6,959 | +267% | 0 | 0 | — |
case-21 | pass→pass | 17,666 | 12,742 | -28% | 1 | 1 | 0% | 2,914 | 7,388 | +154% | 0 | 0 | — |
case-22 | fail→pass | 10,604 | 7,556 | -29% | 1 | 1 | 0% | 1,723 | 6,614 | +284% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +18 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.