agentsope/agentsop-signature-design
>- Decision rubric for promoting a prose prompt into a typed DSPy Signature. This is an 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 lines; the LM output is consumed by code (parsed, branched on, stored) rather than read by a human; the same prompt is reused across >1 call site; or a teammate asks "should this be a Signature?". Do NOT activate for one-shot throwaway prompts, or for HOW-TO questions about DSPy modules /optimizers/compile — defer those to the [[dspy]] skill and the Signature, prompt as a function, prompt contract, when to formalize a prompt.
npx skills add https://github.com/agentsope/SkillAlchemy --skill agentsop-signature-design
> *"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 names
Three 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+format
Zero 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]).
Take agentsope/agentsop-signature-design from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.