Claude Skill

system-prompt

Optimize an agent's system prompt, developer message, or policy text — the instructions that shape its behavior. Use when the artifact to improve is a prompt or policy file rather than tools or a skill package: the agent lacks a rule, misses the required output format, or applies

LLM Mart · 0 points · 14 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download skillberry-ai-cap-evolve-skills_capabilities_system-prompt-49fcedb.zip · 13 KB
Part of skillberry-ai/cap-evolve — 22 skills

Install

skills CLI npx skills add https://github.com/skillberry-ai/cap-evolve/tree/main/skills/capabilities/system-prompt
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install skillberry-ai-cap-evolve@llmmart
Git git clone https://github.com/skillberry-ai/cap-evolve.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole skillberry-ai/cap-evolve collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Capability: system prompt

This capability treats one or more prompt/policy text files (prompt.txt, policy.md, SYSTEM.md) as the optimizable artifact — whatever text the runtime prepends to the agent's context as its instructions, output contract, and decision policy.

Prose is the right lever when the agent lacks something it could be told: a format, a rule, a decision criterion. It is the wrong lever when the agent already has the rule and does not act on it — that needs the behavior enforced in code, so it belongs to whatever capability edits the agent's tools, not here. Classify the failure clusters first (./guidance/diagnose/SKILL.md, when present) and spend prose only on the clusters this capability can actually move.

Pick the lever

Each item is a bounded edit class. Fix the biggest cluster with the narrowest lever that reaches it; ship every class the traces call for in one candidate. Examples are 1-line and generic; depth is in references/concepts.md.

  1. Rewrite a rule for clarity, positively framed — say what TO do, specifically. A prohibition fences off one wrong path; a positive instruction names the target. Ex: "Don't be vague" → "State the record ID in every reply."
  2. Add the reason to a bare rule — a rule paired with its rationale extends to cases the rule's author never wrote down; a bare imperative does not. Ex: "Never use ellipses" → "Never use ellipses — the output is read by a TTS engine that cannot pronounce them."
  3. Consolidate redundant rules — merge duplicates into one, keeping every distinct constraint. Ex: three "confirm before deleting" lines → one "Confirm before any destructive action (delete, overwrite, send)."
  4. Add a rule the source requires but the prompt omits — it must trace to a real source (the policy doc, the runner, the task spec), never be invented. The added rule may introduce a constraint the prompt lacked, or state a stricter condition on an existing one. It may not broaden an existing permission or flip a decision the agent currently gets right: that changes behavior for every task in the class, including the passing ones whose gold answer was the stricter behavior. When a cluster needs different behavior, name the exact condition that separates the qualifying cases instead. Ex: the source says refunds need a manager code → "Require a manager code before any refund."
  5. Add an example — one or a few <example>-tagged exemplars to pin a format that is hard to describe in prose. Ex: one <example> showing the exact JSON envelope expected. Examples are re-read every turn, so add the smallest set that pins the shape and treat a larger set as a hypothesis to gate, not a free win.
  6. Restructure — separate instructions, context, examples, and input into their own sections or tags so the model does not conflate them, and put long reference data before the instruction that acts on it.
  7. Add a role / goal line — one sentence on who the agent is and what "done" means, when the prompt has none. Ex: "You are a careful support agent; resolve the request in one turn."
  8. Tighten the output contract — make the required shape explicit and exact. Ex: "Reply with only a JSON object {status, reason} — no prose." If the scorer reads the agent's final message, the contract must require the agent to state every value the scorer checks: agents routinely perform the action correctly and never report the result, and the scorer sees only the omission. (A missing action, as opposed to a missing report of it, is not fixable here — see the scope note above.)
  9. Soften over-strong wording — when a cluster shows the agent over-doing rather than under-doing (excess tool calls, over-engineering, triggering a behavior where it did not apply), downgrade CRITICAL/MUST/ALWAYS to "Use … when …". The edit that fixes an over-eagerness cluster is a cut, not an addition.

Never drop a needed rule — change, consolidate, or add

When an edit removes text, every distinct constraint that text carried must survive somewhere: rewritten, merged into a combined rule, or relocated. Deletion is legitimate when the information is genuinely redundant, contradicted by the source, or now enforced deterministically elsewhere — and in the first two cases prefer rewriting the conflicting rule. Consolidation cuts words, never rules.

An optimizer that deletes a needed rule can make one iteration's metric go up and leave the class permanently broken, so the check is mechanical as well as stated: apply() counts constraint-bearing lines before and after every edit and reports a net loss in report["warnings"]. A warning is not a failure — a legitimate consolidation triggers it too — it is a prompt to state, in PROCESS.md, where each dropped constraint went. op: "set" on a whole file is the edit most likely to lose one silently.

Keep the edit general

  • Never hardcode a task's specifics. A rule must state the general policy that holds across the class, not one task's case or answer. Good: "Reverse the charge to the original payment method on file." Bad: "If the record id is <TASK_SPECIFIC_ID>, apply the amount that task expects." Baking an id, value, date, or answer into the prompt overfits, gets rejected by the held-out gate, and can mislead other tasks. Use a failing task's specifics to identify the class, then write the general rule. The test for any edit: would this help on a task the optimizer has never seen?
  • Resolve conflicts, don't stack rules. Before editing, list the rules that govern the same action and check that no two give a different verdict on the same input; rewrite toward the stricter one rather than dropping either. A contradiction is the one failure mode detectable by reading the artifact alone, so it is worth the pass.
  • Consolidate as constraints move out of the prompt. When a rule is now enforced deterministically elsewhere, remove its now-redundant prose: the enforcement is authoritative and the duplicate sentence only competes for attention. The prompt should get shorter as constraints become enforced, not longer. (This drops no rule — the constraint still lives, enforced elsewhere.)
  • Watch length, but measure it. validate() reports each file's line, token, and constraint-line counts. There is no universal length threshold worth quoting; compare a candidate against the accepted candidates of your own run and treat a prompt that grows every iteration without moving val as the signal to prune.

Handlers (scripts/abstract.py)

materialize(dir) -> {file: text} · apply(dir, edits) -> {changed, warnings} · validate(dir, baseline=None) -> {ok, files, stats, problems, warnings} · is_empty(dir) -> bool. Edit ops: set, append, ensure_contains. Pass baseline (a directory or a {file: text} dict, e.g. the parent candidate) to have validate report a constraint-line drop against it. A project adapter's apply can call these directly.

How to run

python scripts/check.py
python scripts/run.py --path <capability_dir>                        # candidate + validity
python scripts/run.py --path <candidate_dir> --baseline <parent_dir>  # + rule-loss check

References

  • references/concepts.md — what the prompt controls, the six authoring practices and five failure modes in full, how to adapt a prompt to the runtime reader's capability tier, pitfalls, and cited sources. Read once before your first non-trivial edit, and again when a candidate is accepted but barely moves the metric.
Files (cap-evolve)
  • references
    • concepts.md 6.4 KB
      # Concepts — optimizing a system prompt
      
      Depth behind `SKILL.md`'s lever menu: what the prompt actually controls, the
      authoring practices and failure modes in full, how to adapt to the runtime reader,
      and the pitfalls that look like improvements.
      
      ## What the system prompt controls
      
      - **Role & task framing** — who the agent is and what "done" means.
      - **Output contract** — the exact shape the downstream consumer or scorer expects.
        The common silent failure is a capable agent that formats its answer wrong and
        scores zero, so diagnose shape before content.
      - **Decision rules** — when to call which tool, when to ask versus act, refusal and
        escalation rules. Many agents are scored on adherence to such rules.
      - **Reasoning scaffolds & exemplars** — added inline to shape how the model works
        through the task before answering.
      
      ## Adapting to the runtime reader
      
      The right prompt edit depends on WHO reads this prompt at runtime (see the
      `THE READER` block in your instructions, if present). Most of the advice here — soften
      imperatives, explain the reason, keep exemplars minimal — assumes a strong reader.
      Flip it for a weaker one:
      
      - **strong reader:** lean, reasoning-first prose; give the reason; keep exemplars
        minimal; soften brittle imperatives, because over-constraining hurts this reader.
      - **mid / weak reader:** be explicit. Prefer imperative step-by-step rules; include at
        least one worked exemplar per non-trivial behavior; keep decision chains short; make
        the output contract rigid and literal; and push behavioral rules into tool code
        rather than prose this reader will skip.
      
      When no reader is declared, default to the strong-reader advice — and say so in
      `PROCESS.md` so a later run can set the tier deliberately.
      
      ## The six authoring practices
      
      1. **Be clear, direct, and specific — write for a capable new hire with no context.**
         If a colleague with minimal context would be confused by the instruction, so will
         the model. Spell out the desired output and the constraints; number steps when the
         order matters.
      2. **Give the reason, not just the command.** A rule with its rationale extends to
         cases the author never enumerated. The canonical rewrite: "never use ellipses" →
         "the output is read by a TTS engine that cannot pronounce ellipses."
      3. **Say what TO do, not only what NOT to do.** "Compose your reply in flowing prose"
         beats "do not use markdown" — a prohibition fences off one path, a positive
         instruction names the target.
      4. **Structure deliberately.** Wrap instructions, context, examples, and input in
         their own sections or tags so the model does not conflate them; put long reference
         data before the instruction that acts on it, and keep the output contract adjacent
         to the point where the model produces the output rather than buried mid-preamble.
      5. **Define the output contract explicitly, and use exemplars where prose cannot
         describe the shape.** For structured output, a schema or enum in the tool surface
         constrains it more reliably than prose asking for it.
      6. **Keep the prompt lean and self-consistent, and tune trigger strength to the
         reader.** Redundant preambles and conflicting clauses compete for attention; when a
         cluster shows over-eagerness, soften `CRITICAL/MUST/ALWAYS` rather than adding more.
      
      ## The five failure modes
      
      1. **Missing or loose output contract** — right content, wrong shape, zero reward.
         The most common silent prompt failure; diagnose shape before content. When the
         scorer reads the final message, the contract must require the agent to state every
         value the scorer checks. *Illustration:* on a customer-service benchmark scored on
         communicated figures (a total, a refund, a saving, a count, a balance), agents
         performed the write correctly and never reported the number — "After computing a
         refund, state the exact figure in your final message (e.g. 'Your refund is
         $42.00')" recovered the class. Read it as one instance of the general shape, not as
         a rule about money.
      2. **Conflicting or over-broad instructions** — a later clause contradicts an earlier
         one and the resolution is not predictable. Detectable by reading the artifact
         alone: list the rules governing the same action, check for two different verdicts on
         one input, rewrite toward the stricter.
      3. **Redundant preamble** — repeated or stale guidance competes with the rules that
         matter. Length is not safety, and `validate()` reports the counts so growth is
         visible across iterations.
      4. **Negative-only phrasing** — "don't do X" with no positive alternative leaves the
         model to guess what Y is.
      5. **Stale over-strong language** — anti-laziness `MUST/ALWAYS` phrasing that helped
         an older reader produces over-engineering, excess tool calls, and behaviors
         triggered where they did not apply. The fix for an over-doing cluster is a cut.
      
      ## Edit model
      
      Artifact = one or more text files (`prompt.txt`, `policy.md`, `SYSTEM.md`). Edit ops:
      `set`, `append`, `ensure_contains`. `validate` requires at least one non-empty prompt
      file, reports per-file line/token/constraint-line counts, and — given a `baseline` —
      warns when the candidate carries fewer constraint-bearing lines than its parent. That
      warning implements `SKILL.md`'s never-drop rule mechanically; it flags a net loss for
      a human or the optimizer to justify, and cannot tell a legitimate consolidation from a
      lost rule.
      
      ## Pitfalls
      
      - **Verbosity creep** — each iteration tends to add. Prune deliberately; the counts
        from `validate()` make the trend visible.
      - **Overfitting to the scorer's quirks** rather than the task — watch the val→test
        gap on an accepted candidate.
      - **Prompt-injection surface** — a rule that task input can override is not a rule.
        Prefer phrasing that survives adversarial input, and enforce anything security-
        relevant in code.
      - **Editing prose where the failure was never a knowledge gap** — the most expensive
        wasted iteration in this capability, because a longer prompt looks like progress.
      
      ## Sources
      
      - OpenAI / Anthropic prompt-engineering guides (instruction following, output
        contracts, exemplars) — https://platform.openai.com/docs/guides/prompt-engineering ,
        https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview
      - "Large Language Models Are Human-Level Prompt Engineers" (APE), arXiv:2211.01910.
      - "Large Language Models as Optimizers" (OPRO), arXiv:2309.03409.
      - tau-bench (adherence to decision rules as a scored behavior), arXiv:2406.12045.
      
  • scripts
    • abstract.py 6.5 KB
      """system-prompt capability — concrete handlers for a prompt/policy artifact.
      
      A "system prompt" capability is one or more text files (default ``prompt.txt``)
      that constitute the agent's instructions/policy. These handlers are concrete (a
      prompt is just text), so there is nothing to stub — they form a small reusable
      library a project adapter's ``apply`` can call.
      
      Edit schema (what an optimizer may emit, mirrored by the mock ops):
          {"file": "prompt.txt", "op": "set"|"append"|"ensure_contains", "text": "..."}
      
      ``apply`` and ``validate`` also account for CONSTRAINT-BEARING lines, so the
      never-drop-a-needed-rule invariant is reported rather than merely documented: an
      optimizer that deletes a rule to make one iteration's metric go up leaves the class
      permanently broken, and ``op: "set"`` can do it in a single edit. The accounting is a
      crude line count -- it flags a net loss for a human or the optimizer to justify and
      cannot tell a legitimate consolidation from a lost rule -- so it is a warning, never a
      failure.
      """
      
      from __future__ import annotations
      
      from pathlib import Path
      
      DEFAULT_FILES = ["prompt.txt", "policy.md", "SYSTEM.md"]
      
      # Markdown scaffolding that carries no constraint on its own.
      _STRUCTURE_CHARS = set("-=*_ \t")
      
      
      def rule_lines(text: str) -> int:
          """Count the constraint-bearing lines of a prompt.
      
          Headings, code fences and horizontal rules structure a prompt without stating a
          rule; every other non-blank line might state one. Deliberately crude: the number
          only has to make a NET LOSS visible, not judge meaning.
          """
          n = 0
          for line in text.splitlines():
              s = line.strip()
              if not s or s.startswith("#") or s.startswith("```") or set(s) <= _STRUCTURE_CHARS:
                  continue
              n += 1
          return n
      
      
      def _stats(parts: dict) -> dict:
          """Per-file size signals so "the preamble is too long" becomes a number."""
          return {
              name: {"lines": len(text.splitlines()),
                     "tokens": len(text) // 4,  # ~chars/4, the same estimate skill-package uses
                     "rule_lines": rule_lines(text)}
              for name, text in parts.items()
          }
      
      
      def _rule_loss(before: str, after: str) -> int:
          """How many constraint-bearing lines an edit removed (0 when it added or held)."""
          return max(0, rule_lines(before) - rule_lines(after))
      
      
      def materialize(capability_dir: Path) -> dict:
          """Read the prompt artifact into a named-component dict (gepa's view)."""
          capability_dir = Path(capability_dir)
          parts = {}
          for name in DEFAULT_FILES:
              f = capability_dir / name
              if f.exists():
                  parts[name] = f.read_text(encoding="utf-8")
          if not parts:
              # fall back to any single .txt/.md file present
              for f in sorted(capability_dir.glob("*.txt")) + sorted(capability_dir.glob("*.md")):
                  parts[f.name] = f.read_text(encoding="utf-8")
          return parts
      
      
      def apply(capability_dir: Path, edits: list[dict] | None = None) -> dict:
          """Apply edits to the prompt files. Returns a report of what changed."""
          capability_dir = Path(capability_dir)
          report = {"changed": [], "warnings": []}
          for e in edits or []:
              target = capability_dir / e["file"]
              op = e.get("op", "set")
              text = e.get("text", "")
              cur = target.read_text(encoding="utf-8") if target.exists() else ""
              if op == "set":
                  new = text
              elif op == "append":
                  new = cur + text
              elif op == "ensure_contains":
                  new = cur if text.strip() and text.strip() in cur else cur + text
              else:
                  raise ValueError(f"unknown op {op!r}")
              if new != cur:
                  lost = _rule_loss(cur, new)
                  target.write_text(new, encoding="utf-8")
                  report["changed"].append(e["file"])
                  if lost:
                      report["warnings"].append(
                          f"{e['file']}: op {op!r} removed {lost} constraint-bearing line(s) "
                          f"({rule_lines(cur)} -> {rule_lines(new)}). Change/consolidate/add rather "
                          f"than delete: confirm every dropped constraint survives somewhere "
                          f"(rewritten, merged, or enforced deterministically) and say where in "
                          f"PROCESS.md.")
          return report
      
      
      def is_empty(capability_dir: Path) -> bool:
          """Return True when the capability directory has no meaningful content yet.
      
          "Meaningful" is judged after ``strip()`` — the same notion of non-empty that
          ``validate()`` uses — so a missing prompt file and an empty/whitespace-only one
          are both treated as an empty seed (nothing for the optimizer to build on yet)."""
          return not any(v.strip() for v in materialize(Path(capability_dir)).values())
      
      
      def validate(capability_dir: Path, baseline: Path | dict | None = None) -> dict:
          """A prompt artifact is valid if it has at least one non-empty text file.
      
          A capability with no non-empty (non-whitespace) prompt content is accepted as a
          valid empty-seed starting state so the optimizer can create the initial content
          from failing trajectories.
      
          ``baseline`` is the text this candidate was derived from -- a directory (e.g. the
          parent candidate) or an already-materialized ``{file: text}`` dict. When given,
          a file carrying fewer constraint-bearing lines than its baseline is reported in
          ``warnings``. ``warnings`` is always present on both branches so a caller can read
          it without guarding.
          """
          capability_dir = Path(capability_dir)
          if is_empty(capability_dir):
              return {"ok": True, "empty": True, "files": [], "stats": {},
                      "problems": [], "warnings": []}
          parts = materialize(capability_dir)
          nonempty = {k: v for k, v in parts.items() if v.strip()}
          warnings = []
          if baseline is not None:
              base = baseline if isinstance(baseline, dict) else materialize(Path(baseline))
              for name, text in sorted(base.items()):
                  lost = _rule_loss(text, parts.get(name, ""))
                  if lost:
                      warnings.append(
                          f"{name}: {lost} constraint-bearing line(s) fewer than the baseline "
                          f"({rule_lines(text)} -> {rule_lines(parts.get(name, ''))}). A needed "
                          f"rule must be changed, consolidated, or relocated -- not dropped; "
                          f"record where each one went.")
          return {"ok": bool(nonempty), "files": list(nonempty), "stats": _stats(nonempty),
                  "problems": [] if nonempty else ["no non-empty prompt file found"],
                  "warnings": warnings}
      
    • check.py 3 KB
      """Round-trip materialize → apply → validate on a temp prompt artifact."""
      
      from __future__ import annotations
      
      import json
      import sys
      import tempfile
      from pathlib import Path
      
      import _bootstrap  # noqa: F401
      
      import abstract
      
      
      def main() -> int:
          report = {"skill": "system-prompt", "ok": False, "problems": [], "notes": []}
          with tempfile.TemporaryDirectory() as d:
              cap = Path(d)
              (cap / "prompt.txt").write_text("You are helpful.", encoding="utf-8")
              parts = abstract.materialize(cap)
              if "prompt.txt" not in parts:
                  report["problems"].append("materialize did not read prompt.txt")
              rep = abstract.apply(cap, [{"file": "prompt.txt", "op": "ensure_contains", "text": " Be concise."}])
              if "prompt.txt" not in rep["changed"]:
                  report["problems"].append("apply did not record a change")
              v = abstract.validate(cap)
              if not v["ok"]:
                  report["problems"].append(f"validate failed: {v['problems']}")
              if "warnings" not in v:
                  report["problems"].append("validate omitted 'warnings' on the non-empty branch")
              report["notes"].append("materialize/apply/validate round-trip ok")
      
              # The two ops that can destroy content, plus the unknown-op guard.
              abstract.apply(cap, [{"file": "prompt.txt", "op": "append", "text": "\nCite sources.\n"}])
              if "Cite sources." not in (cap / "prompt.txt").read_text(encoding="utf-8"):
                  report["problems"].append("apply op=append did not concatenate")
              rep = abstract.apply(cap, [{"file": "prompt.txt", "op": "set", "text": "Be helpful.\n"}])
              if (cap / "prompt.txt").read_text(encoding="utf-8") != "Be helpful.\n":
                  report["problems"].append("apply op=set did not replace the file")
              if not rep["warnings"]:
                  report["problems"].append("apply op=set dropped rule-bearing lines without warning")
              report["notes"].append("set/append ops behave; a rule-dropping set is flagged")
              try:
                  abstract.apply(cap, [{"file": "prompt.txt", "op": "nope", "text": "x"}])
              except ValueError:
                  pass
              else:
                  report["problems"].append("apply accepted an unknown op")
      
              # validate against a baseline sees the loss the edit above introduced.
              vb = abstract.validate(cap, baseline={"prompt.txt": "Rule one.\nRule two.\nRule three.\n"})
              if not vb["warnings"]:
                  report["problems"].append("validate(baseline=) missed a rule-bearing-line drop")
      
          # The empty-seed branch is a valid starting state, not a failure.
          with tempfile.TemporaryDirectory() as d:
              ve = abstract.validate(Path(d))
              if not (ve["ok"] and ve.get("empty") and ve["warnings"] == []):
                  report["problems"].append(f"validate on an empty dir should be ok/empty: {ve}")
              report["notes"].append("empty seed validates as a valid starting state")
          report["ok"] = not report["problems"]
          print(json.dumps(report, indent=2))
          return 0 if report["ok"] else 1
      
      
      if __name__ == "__main__":
          sys.exit(main())
      
    • run.py 1.1 KB
      """Expose a system-prompt artifact as a Candidate for the algorithm to optimize."""
      
      from __future__ import annotations
      
      import argparse
      import json
      import sys
      from pathlib import Path
      
      import _bootstrap  # noqa: F401
      
      from cap_evolve import Candidate
      
      import abstract
      
      
      def main(argv=None) -> int:
          p = argparse.ArgumentParser(prog="system-prompt")
          p.add_argument("--path", required=True, help="capability dir holding the prompt file(s)")
          p.add_argument("--baseline", help="the dir this candidate was derived from (e.g. the parent "
                                           "candidate); warns when a prompt file lost rule-bearing lines")
          args = p.parse_args(argv)
          parts = abstract.materialize(Path(args.path))
          v = abstract.validate(Path(args.path), baseline=Path(args.baseline) if args.baseline else None)
          cand = Candidate(id="seed", component="system-prompt", text_parts=parts, dir=str(args.path))
          print(json.dumps({"candidate": cand.to_dict(), "valid": v}, indent=2))
          return 0 if v["ok"] else 1
      
      
      if __name__ == "__main__":
          sys.exit(main())
      
    • _bootstrap.py 3.6 KB
      """Thin shim: locate cap_evolve, then defer to cap_evolve._bootstrap.
      
      Skill scripts ``import _bootstrap`` first. The real path-resolution logic lives
      ONCE in ``cap_evolve._bootstrap`` (so it can't drift across skills); this shim
      only has to find that package, which means a minimal upward walk for ``core/`` —
      the single bit of bootstrapping that genuinely must run before cap_evolve is
      importable. Everything else delegates.
      """
      
      from __future__ import annotations
      
      import os
      import sys
      from pathlib import Path
      
      
      def _seed_path() -> None:
          """Minimal: put a dir containing the cap_evolve package on sys.path.
      
          ``CAPEVOLVE_CORE`` is honoured BEFORE any ambient import. An editable install of a
          *different* cap-evolve checkout registers a ``sys.meta_path`` finder, which outranks
          both ``sys.path`` and ``PYTHONPATH`` — so "cap_evolve imports fine" is not evidence that
          it imports the checkout you are standing in. Deferring to the ambient package here made
          an explicit override unreachable, and the symptom was a stale core silently answering
          for this one (``ModuleNotFoundError: cap_evolve.constraints`` from a checkout that
          predates that module). An explicit env var wins.
          """
          env = os.environ.get("CAPEVOLVE_CORE")
          want = Path(env).resolve() if env else None
          if want and (want / "cap_evolve" / "__init__.py").exists():
              loaded = sys.modules.get("cap_evolve")
              already = getattr(loaded, "__file__", None)
              if already and Path(already).resolve().parent.parent == want:
                  return                      # right checkout already imported: touch nothing
              p = str(want)
              if p in sys.path:
                  sys.path.remove(p)
              sys.path.insert(0, p)
              if loaded is not None:
                  # Evicting a module makes a re-import yield a DIFFERENT object, so anything
                  # already holding a reference fails an `is` check. Only ever do it when the
                  # loaded package really is the wrong checkout — otherwise this "fix" becomes
                  # the bug (it broke two identity assertions in core/tests exactly once).
                  for name in [m for m in sys.modules
                               if m == "cap_evolve" or m.startswith("cap_evolve.")]:
                      sys.modules.pop(name, None)
              for finder in list(sys.meta_path):
                  if "cap_evolve" in getattr(finder, "MAPPING", {}):
                      sys.meta_path.remove(finder)
              return
          # A checkout's own core outranks an ambient install. Without this, a skill script run
          # from checkout X silently executed against checkout Y's cap_evolve (an editable install
          # registers a sys.meta_path finder, which outranks sys.path), and the only symptom was
          # missing modules — or, worse, a green result measured against the wrong tree.
          here = Path(__file__).resolve()
          own = next((p / "core" for p in here.parents
                      if (p / "core" / "cap_evolve" / "__init__.py").exists()), None)
          if own is not None:
              os.environ.setdefault("CAPEVOLVE_CORE", str(own))
              return _seed_path()
          try:
              import cap_evolve  # noqa: F401
              return
          except Exception:
              pass
          cands = []
          for parent in here.parents:
              cands.append(parent / "core")
              cands.append(parent)
          for c in cands:
              if (c / "cap_evolve" / "__init__.py").exists():
                  p = str(c)
                  if p not in sys.path:
                      sys.path.insert(0, p)
                  return
      
      
      _seed_path()
      from cap_evolve._bootstrap import ensure_core  # noqa: E402
      
      # Anchor the upward walk at THIS skill script's location (not the core module's).
      ensure_core(Path(__file__).resolve())
      
  • meta.yaml 314 B
    component: capability
    name: system-prompt
    summary: Optimize an agent's system prompt / policy text (instructions, output contract, decision policy).
    entry: scripts/run.py
    abstract: scripts/abstract.py
    check: scripts/check.py
    needs: []
    provides: [candidate]
    compatible_with:
      optimizers: ["*"]
      algorithms: ["*"]
    
  • SKILL.md 8.5 KB
    ---
    name: system-prompt
    description: Optimize an agent's system prompt, developer message, or policy text — the instructions that shape its behavior. Use when the artifact to improve is a prompt or policy file rather than tools or a skill package: the agent lacks a rule, misses the required output format, or applies the wrong decision criterion. Covers the safe edit classes (rewrite, consolidate, add a sourced rule, reorder, tighten the output contract, soften over-strong wording), how to keep an edit general instead of overfitting one task, and the rule that a needed constraint is never deleted. A failure where the agent already knows the rule and skips the action belongs to the capability that edits tool code, not here.
    component: capability
    argument-hint: "--path DIR"
    allowed-tools: Read, Write, Edit, Bash
    provides: [candidate]
    needs: []
    sources: [tau2bench]
    ---
    
    # Capability: system prompt
    
    This capability treats one or more prompt/policy text files (`prompt.txt`,
    `policy.md`, `SYSTEM.md`) as the optimizable artifact — whatever text the runtime
    prepends to the agent's context as its instructions, output contract, and decision
    policy.
    
    Prose is the right lever when the agent lacks something it could be *told*: a
    format, a rule, a decision criterion. It is the wrong lever when the agent already
    has the rule and does not act on it — that needs the behavior enforced in code, so
    it belongs to whatever capability edits the agent's tools, not here. Classify the
    failure clusters first (`./guidance/diagnose/SKILL.md`, when present) and spend
    prose only on the clusters this capability can actually move.
    
    ## Pick the lever
    
    Each item is a bounded edit class. Fix the biggest cluster with the narrowest lever
    that reaches it; ship every class the traces call for in one candidate. Examples are
    1-line and generic; depth is in [`references/concepts.md`](references/concepts.md).
    
    1. **Rewrite a rule for clarity, positively framed** — say what TO do, specifically.
       A prohibition fences off one wrong path; a positive instruction names the target.
       *Ex:* "Don't be vague" → "State the record ID in every reply."
    2. **Add the reason to a bare rule** — a rule paired with its rationale extends to
       cases the rule's author never wrote down; a bare imperative does not.
       *Ex:* "Never use ellipses" → "Never use ellipses — the output is read by a TTS
       engine that cannot pronounce them."
    3. **Consolidate redundant rules** — merge duplicates into one, keeping every
       distinct constraint. *Ex:* three "confirm before deleting" lines → one "Confirm
       before any destructive action (delete, overwrite, send)."
    4. **Add a rule the source requires but the prompt omits** — it must trace to a real
       source (the policy doc, the runner, the task spec), never be invented. The added
       rule may introduce a constraint the prompt lacked, or state a stricter condition on
       an existing one. It may not broaden an existing permission or flip a decision the
       agent currently gets right: that changes behavior for every task in the class,
       including the passing ones whose gold answer was the stricter behavior. When a
       cluster needs different behavior, name the exact condition that separates the
       qualifying cases instead. *Ex:* the source says refunds need a manager code →
       "Require a manager code before any refund."
    5. **Add an example** — one or a few `<example>`-tagged exemplars to pin a format
       that is hard to describe in prose. *Ex:* one `<example>` showing the exact JSON
       envelope expected. Examples are re-read every turn, so add the smallest set that
       pins the shape and treat a larger set as a hypothesis to gate, not a free win.
    6. **Restructure** — separate instructions, context, examples, and input into their
       own sections or tags so the model does not conflate them, and put long reference
       data before the instruction that acts on it.
    7. **Add a role / goal line** — one sentence on who the agent is and what "done"
       means, when the prompt has none. *Ex:* "You are a careful support agent; resolve
       the request in one turn."
    8. **Tighten the output contract** — make the required shape explicit and exact.
       *Ex:* "Reply with only a JSON object `{status, reason}` — no prose." If the scorer
       reads the agent's final message, the contract must require the agent to state every
       value the scorer checks: agents routinely perform the action correctly and never
       report the result, and the scorer sees only the omission. (A missing action, as
       opposed to a missing report of it, is not fixable here — see the scope note above.)
    9. **Soften over-strong wording** — when a cluster shows the agent over-doing rather
       than under-doing (excess tool calls, over-engineering, triggering a behavior where
       it did not apply), downgrade `CRITICAL/MUST/ALWAYS` to "Use … when …". The edit
       that fixes an over-eagerness cluster is a cut, not an addition.
    
    ## Never drop a needed rule — change, consolidate, or add
    
    When an edit removes text, every distinct constraint that text carried must survive
    somewhere: rewritten, merged into a combined rule, or relocated. Deletion is
    legitimate when the information is genuinely redundant, contradicted by the source,
    or now enforced deterministically elsewhere — and in the first two cases prefer
    rewriting the conflicting rule. Consolidation cuts *words*, never *rules*.
    
    An optimizer that deletes a needed rule can make one iteration's metric go up and
    leave the class permanently broken, so the check is mechanical as well as stated:
    `apply()` counts constraint-bearing lines before and after every edit and reports a
    net loss in `report["warnings"]`. A warning is not a failure — a legitimate
    consolidation triggers it too — it is a prompt to state, in `PROCESS.md`, where each
    dropped constraint went. `op: "set"` on a whole file is the edit most likely to lose
    one silently.
    
    ## Keep the edit general
    
    - **Never hardcode a task's specifics.** A rule must state the general policy that
      holds across the class, not one task's case or answer. *Good:* "Reverse the charge
      to the original payment method on file." *Bad:* "If the record id is
      `<TASK_SPECIFIC_ID>`, apply the amount that task expects." Baking an id, value,
      date, or answer into the prompt overfits, gets rejected by the held-out gate, and
      can mislead other tasks. Use a failing task's specifics to identify the class, then
      write the general rule. The test for any edit: *would this help on a task the
      optimizer has never seen?*
    - **Resolve conflicts, don't stack rules.** Before editing, list the rules that
      govern the same action and check that no two give a different verdict on the same
      input; rewrite toward the stricter one rather than dropping either. A contradiction
      is the one failure mode detectable by reading the artifact alone, so it is worth
      the pass.
    - **Consolidate as constraints move out of the prompt.** When a rule is now enforced
      deterministically elsewhere, remove its now-redundant prose: the enforcement is
      authoritative and the duplicate sentence only competes for attention. The prompt
      should get shorter as constraints become enforced, not longer. (This drops no rule
      — the constraint still lives, enforced elsewhere.)
    - **Watch length, but measure it.** `validate()` reports each file's line, token, and
      constraint-line counts. There is no universal length threshold worth quoting;
      compare a candidate against the accepted candidates of your own run and treat a
      prompt that grows every iteration without moving val as the signal to prune.
    
    ## Handlers (scripts/abstract.py)
    
    `materialize(dir) -> {file: text}` · `apply(dir, edits) -> {changed, warnings}` ·
    `validate(dir, baseline=None) -> {ok, files, stats, problems, warnings}` ·
    `is_empty(dir) -> bool`. Edit ops: `set`, `append`, `ensure_contains`. Pass
    `baseline` (a directory or a `{file: text}` dict, e.g. the parent candidate) to have
    `validate` report a constraint-line drop against it. A project adapter's `apply` can
    call these directly.
    
    ## How to run
    
    ```
    python scripts/check.py
    python scripts/run.py --path <capability_dir>                        # candidate + validity
    python scripts/run.py --path <candidate_dir> --baseline <parent_dir>  # + rule-loss check
    ```
    
    ## References
    
    - [`references/concepts.md`](references/concepts.md) — what the prompt controls, the
      six authoring practices and five failure modes in full, how to adapt a prompt to
      the runtime reader's capability tier, pitfalls, and cited sources. **Read once
      before your first non-trivial edit**, and again when a candidate is accepted but
      barely moves the metric.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related