agentsope/agentsop-per-model-artifacts
>- Lifecycle SOP for **per-model prompt artifacts** — the compiled prompts, instructions, few-shot demos, edit-format pins, and embedding-bound indices that change behavior when the underlying LM, dataset, or framework version changes. Activate when adopting compiled prompts (DSPy, GEPA, BootstrapFewShot output), when supporting multiple LMs in production, when a provider deprecates a model snapshot, or when a framework deprecates a config surface (LlamaIndex `ServiceContext` → `Settings`, Aider edit-format defaults). Do NOT activate for one-off raw prompt edits or for truly model-agnostic system prompts that have deprecation, prompt per model, prompt breaks on new model, version compiled prompts.
npx skills add https://github.com/agentsope/SkillAlchemy --skill agentsop-per-model-artifacts
> *"Prompts are effectively the weights of an LLM application."*
> — DSPy core philosophy [arxiv.org/abs/2310.03714]
>
> *"Treat the compiled program as a (program × LM) pair. Changing the LM invalidates the artifact — recompile."*
> — dspy-sop SKILL, Dilemma Case B
Activate this skill when any of the following appears in the user's intent, codebase, or workflow:
| Trigger | Signal |
|---|---|
| Adopting compiled prompts | compiled.save("v1.json"), dspy.load_program(), BootstrapFewShot, MIPROv2, GEPA, LangChain Hub hub.push/pull, prompt files checked into prompts/ or artifacts/ |
| Multi-LM production | The same program runs against ≥2 of: gpt-4o-*, gpt-4o-mini-*, gpt-4.1-*, claude-3-5-sonnet-*, claude-3-7-sonnet-*, claude-3-opus-*, Llama-3-*, Llama-3.1-*, DeepSeek-V3, gemini-2.5-pro |
| Provider deprecation | OpenAI/Anthropic deprecation notice mentioning a pinned snapshot; alias rollover (gpt-4o → new dated snapshot); silent model behavior drift reports |
| Framework deprecation | LlamaIndex ServiceContext → Settings; LangChain LLMChain → LCEL; DSPy major version bump; aider edit-format default change |
| Symptoms | prompts/system_prompt.txt (no model in path), generic alias pins (model="gpt-4o"), missing parent_artifact lineage, hand-edited compiled JSON, no held-out test set re-runs |
| Cross-skill bridges | DSPy compile produced output → ship via this SOP. LlamaIndex index baked with embed model → tag artifact per this SOP. Aider edit-format pin → per-model config artifact per Recipe 6 (R2) |
Do NOT activate when:
┌────────── ARTIFACT ──────────┐
│ │
program × LM (snapshot) × dataset (hash) → compiled.json + metadata
(code) (provider+date) (canonical hash)
│ │
└─ change any axis → new artifact ─┘
Three load-bearing claims:
program.json on Llama-3-8B typically loses 15–30 points (R1 §5). The asymmetry is real and documented.gpt-4o is a moving alias; gpt-4o-2024-08-06 is an immutable identifier. Pinning to the alias means your behavior changes silently when OpenAI rolls the alias forward. Anthropic does not even roll aliases — Sonnet 3.5 (claude-3-5-sonnet-20240620) and Sonnet 3.5 v2 (claude-3-5-sonnet-20241022) are different models with different behavior; the org that pinned the wrong one in 2024-10 ate a regression in 2025-10 when the older one was retired (R1 §4).sha256 over canonicalized dataset bytes, embedded in the artifact path, makes this impossible to confuse (OP-9).| PyTorch checkpoint | Per-model prompt artifact |
|---|---|
| .pt weights file | program.json (compiled prompt) |
| Architecture (forward pass) | DSPy Signature + Module graph (source-of-truth Python) |
| Dataset version | dataset_sha256_8 in path |
| Hyperparameters | optimizer config (MIPROv2 auto="light", seed, demos) |
| Framework version | dspy_version, python_version in metadata |
| Eval score on val | dev_score in REGISTRY |
| Final test score | test_score in REGISTRY |
| Model registry (MLflow) | artifacts/REGISTRY.jsonl or MLflow (Recipe 5) |
| Promotion to prod | git tag prompt/<program>/<snapshot>/v<n> |
The same engineering discipline applies. Anyone who would not check a .pt into prod without a checkpoint registry should not check a compiled prompt into prod without an artifact registry.
A bare compiled.save("v1.json") produces a JSON file that, viewed in isolation, looks model-agnostic. The instructions and demos are text — they could be for any model. But they were *selected by an optimizer running calls against a specific LM*. The model's distributional response to the bootstrapping queries shaped which demos got kept. The artifact is implicitly LM-conditioned without saying so. The SOP exists to make this conditioning explicit and auditable.
A five-stage lifecycle. Each stage has an exit criterion.
src/<program>/program.py) defines the DSPy module graph.dspy.LM("openai/gpt-4o-2024-08-06").sha256 → use first 8 hex chars (a8f1c2e9) as the dataset tag.MIPROv2(auto="light") per dspy-sop). Record cost_usd, wall_seconds.Exit: Held-out test score recorded. No artifact saved yet.
artifacts/<program>/<provider>/<model-snapshot>/<dataset>-<sha8>/v<n>.json where n is the next integer after existing files in that directory.compiled.save(path) for the JSON; optionally also compiled.save(path_dir, save_program=True) for whole-program reproducibility.artifacts/REGISTRY.jsonl with the schema in R2 Recipe 1.Exit: Artifact + REGISTRY entry on disk, both staged for PR.
10. CI loads the new artifact, identifies its parent_artifact (previous artifact for same program × model × dataset), re-runs held-out test set, compares.
11. Fail PR if new_test_score - parent_test_score < -GATE (default GATE = 0.02, 2 points on 0-1 scale).
12. Block merge unless gate passes or a human attaches the artifact-override label (with justification in PR body).
Exit: PR merged to main, REGISTRY canonical.
13. New LM snapshot announced (e.g. gpt-4o-2024-11-20 replacing 2024-08-06). Or considering family swap (GPT-4o → Sonnet 3.5).
14. Load existing artifact unchanged. Configure DSPy to the new LM. Run held-out test set.
15. Compute Δ-score vs original.
|Δ| ≤ 2 points: tag artifact transferable[old→new] in REGISTRY. May ship without recompile.|Δ| > 2 points: tag recompile_required[old→new]. Proceed to Stage 5.16. Emit swap-report-<old>-to-<new>.md artifact regardless of outcome (audit trail).
Exit: Swap report committed, recompile decision recorded.
17. If recompile_required: re-run the same optimizer config against the new LM. Produces a new artifact at the new model's path. Increment v<n>.
18. If transferable: keep using the old artifact; mark in REGISTRY which model snapshots it's certified-transferable to.
19. Keep BOTH old and new artifacts checked in. Production deploy script reads the git tag, not HEAD.
20. Loop back to Stage 2 for the new artifact.
Exit: Production deploys an artifact whose REGISTRY entry shows passing test score against the model it will actually run on.
| Trigger | Action | Output | Evidence |
|---|---|---|---|
| Compile finishes successfully | Path <program>/<provider>/<snapshot>/<dataset-sha8>/v<n>.json; append REGISTRY line with parent_artifact pointer | Grep-able path + lineage | R2 Recipe 1; OP-1, OP-9 |
| About to pin a model | Use dated snapshot never an alias: gpt-4o-2024-08-06 not gpt-4o; claude-3-5-sonnet-20241022 not claude-3-5-sonnet | Metadata header inside artifact + REGISTRY | R1 §4 (alias rollover); OP-2 |
| Multiple compiles share a program code | All keyed by program field in REGISTRY (string), commit field links to source-of-truth Python at compile time | Reproducible: git checkout <commit> restores the program code that produced it | OP-3 |
| Promoting to production | git tag prompt/<program>/<snapshot>/v<n>. Deploy reads the tag, not main HEAD | Atomic rollback via git checkout <tag> | MLflow Stage analogy (R1 §7); OP-10 |
| Need whole-program portability | compiled.save(dir, save_program=True) for pickled program + state; ship both | Self-contained; survives source-tree reorgs (until Python/DSPy version mismatch) | DSPy save_program docs [dspy.ai/tutorials/saving/] |
| Trigger | Action | Output | Evidence |
|---|---|---|---|
| Provider releases new snapshot | OP-4 swap-test old artifact against new model on held-out test set | Δ-score; recompile_required boolean | R2 Recipe 2 |
| Family swap candidate (GPT-4o → Sonnet) | Swap-test BOTH directions (artifact-from-A run on B; artifact-from-B run on A); record asymmetry | Two Δ-scores; choose recompile direction | R1 §5 (down-transfer 15-30pt drop) |
| Aider edit-format default change | Re-run aider edit benchmark on fixed task subset for affected model | edit-format-bench delta; pin if changed | aider SKILL line 302 (udiff 20→61%) |
| LlamaIndex Settings.embed_model change candidate | Refuse to swap without rebuilding index. Re-embed against full corpus; tag new index artifact | New indices/<name>/openai__<embed-model>__<dim>/<corpus-sha8>/ | LlamaIndex SKILL anti-pattern A2; R2 Recipe 7 |
| Trigger | Action | Output | Evidence |
|---|---|---|---|
| PR touches artifacts/**/v*.json | CI regression-gate: re-run held-out test against new artifact and parent artifact | CI status artifact-regression: pass\|fail\|overridden | R2 Recipe 3; OP-5 |
| PR tries to add file under artifacts/ without REGISTRY append | Pre-commit hook fails | Local hook error; can't push | OP-8 |
| PR pins a snapshot ≤90 days from deprecation | Pre-commit hook warns; CI fails unless override label | Surfaces deprecation debt at compile time | OP-7; R2 Recipe 4 |
| Hand edit to compiled JSON | Pre-commit hook fails: artifact files are read-only except via compile.py | Forces recompile path | OP-8; dspy-sop anti-pattern #3 |
| Trigger | Action | Output | Evidence |
|---|---|---|---|
| Looking up "which artifact is in prod for this program?" | git ls-remote --tags origin 'prompt/<program>/*' | Tag list with snapshot + version | OP-10 |
| Reproducing a past compile | REGISTRY entry has commit, dspy_version, optimizer_config, dataset_sha256_8, seed; replay against original LM snapshot | Bit-identical or near-identical artifact | R2 Recipe 1; OP-3 |
| Auditing "what changed when score regressed" | Diff REGISTRY entries; if dataset_sha256_8 changed → dataset drift; if optimizer_config changed → config drift; if dspy_version changed → framework drift; if commit changed without code changes → environment drift | Root-cause class identified | OP-3 |
困境 (Dilemma): Production runs a DSPy-compiled program against gpt-4o-2024-05-13 at 82% test score. OpenAI announces deprecation in 60 days. The team wants to move to Sonnet 3.7 (claude-3-7-sonnet-20250219) for unrelated cost/latency reasons. Swap-test (OP-4) shows the unchanged artifact scores 74% on Sonnet 3.7 — Δ = −8 points. Recompile, accept, or hybrid?
约束 (Constraints):
决策步骤 (Decision steps):
MIPROv2(auto="light") first (R2 cost guardrail). If light gives ≥80%, stop. If <80%, escalate to medium only after Stage 1 review of the program/metric.max_bootstrapped_demos lower (Sonnet 3.7's thinking emits richer reasoning per-demo, so fewer richer demos > more thin demos — R1 §1).artifacts/<program>/. The GPT-4o-2024-05-13 artifact stays available for canary-rollback during the cutover window.prompt/<program>/claude-3-7-sonnet-20250219/v1 only after the regression gate passes against the Sonnet 3.7 held-out test set.swap-report-gpt-4o-2024-05-13-to-claude-3-7-sonnet-20250219.md showing: transfer Δ −8, recompiled Δ +3 vs original. Audit trail.结果 (Outcome): Recompiled Sonnet 3.7 artifact lands at 83% on the same held-out set. Cost: $4 + reviewer time. The +1 point over the GPT-4o original is bonus; the value was avoiding the −8 cliff.
可提取的操作 (Extractable operation): A negative swap-test Δ exceeding the regression gate is a recompile signal, not a "ship anyway" signal. The artifact path makes both old and new available simultaneously — there is no migration risk to keeping both.
困境: Quarterly canary re-runs the held-out test set against the production artifact (gpt-4o-2024-08-06, no model change). Score dropped from 78% to 73% over three months. No code changed. No artifact changed. No registry entry changed. What happened?
约束:
决策步骤:
v<n+1> under the same model directory: artifacts/<program>/openai/gpt-4o-2024-08-06/<dataset-sha>/v<n+1>.json. The compiled_at field in REGISTRY makes the temporal lineage clear even though the snapshot string is unchanged.结果: Recompile recovers score to 79% (+1 over baseline, because the optimizer found demos that work with the new server behavior). Lesson: snapshot pins protect against MOST drift but not all. Canary catches what pins miss.
可提取的操作: The snapshot string is necessary but not sufficient. A periodic canary against held-out test data is the only true contract with the model provider. Store sample-output hashes per artifact so silent drift is detectable, not just inferable.
困境: Team upgrades from LlamaIndex 0.9.x to 0.11.x. ServiceContext(llm=..., embed_model=...) is deprecated in favor of Settings.llm = ... / Settings.embed_model = ... (LlamaIndex SKILL §4.4). There are 14 indices in production, each baked with various embed models including text-embedding-ada-002 (deprecated) and text-embedding-3-small (current). What needs to be touched?
约束:
ServiceContext → Settings is mechanical, ~1 hour.text-embedding-3-small) is ~$200 and 6 hours.text-embedding-ada-002 MUST be re-embedded — that model is going away (R1 §3).决策步骤:
manifest.json with embed_model and dim, treat as unknown — must re-embed defensively.text-embedding-ada-002): must re-embed. New artifact path indices/<name>/openai__text-embedding-3-small__1536/<new-corpus-sha8>/.llama_index_version.Settings.embed_model at runtime doesn't match a loaded index's manifest.embed_model (R2 Recipe 7 loader guard). This converts a silent data-corruption bug into a startup error.indices/<name>/openai__text-embedding-ada-002__1536/_archived/. Roll back if production answers degrade. (Anti-pattern: deleting the old index "to save space" before validating the new one.)结果: 9 of 14 indices needed code-only migration; 5 needed re-embed. Total cost ~$300 and 1 engineer-day. The discipline of per-index manifests caught two indices that had been silently broken (ada-002 query vector against 3-small index from a botched earlier migration).
可提取的操作: Framework deprecations cascade into per-artifact decisions. The "easy" code migration is rarely the full migration — interrogate every artifact's framework binding (embed model, edit format, tokenizer version) before declaring a deprecation handled.
prompt.txt or compiled.json with no model in its path or metadata is implicitly "for any model." Empirically, prompts transfer down (big → small) badly and sideways unpredictably (R1 §5). The artifact must encode its LM.artifacts/v3.json is unauditable. artifacts/rag_synth/openai/gpt-4o-2024-08-06/devset-v3-a8f1c2e9/v3.json is. Long paths are good; they replace metadata files no one reads.gpt-4o, claude-3-5-sonnet, llama-3-8b-instruct — all moving targets. Pin gpt-4o-2024-08-06, claude-3-5-sonnet-20241022, and an HF revision SHA respectively.null for the very first).dataset_sha256_8 in the path makes drift visible.dspy_version in REGISTRY enables targeted rollback.10. Deleting old artifacts on new compile. Disk is cheap, history is precious. Keep old artifacts checked in. Production deploys read git tags, not HEAD — the old artifact stays reachable for rollback.
11. Treating LangChain Hub as the lifecycle. Hub is a viewer + diff tool. It doesn't bind to model, dataset, or run a regression gate. Pair with REGISTRY.jsonl or MLflow (R1 §8).
12. Promoting an artifact without swap-test on the actual production LM. "It scored well in compile" — but compile was against the same LM that production uses, right? Not always: the optimizer LM can differ from the task LM (dspy-sop §4.4). Swap-test against the actual production LM before tagging.
compiled.save("v1.json") flat. Adopt this SOP when adding the second artifact or moving to a managed environment.How four ecosystems handle the per-model artifact problem.
save_program is the artifactcompiled.save("v1.json") — state JSON, needs source Python at load. Use for code-review-friendly diffs.compiled.save("v1/", save_program=True) — pickled program + state. Use for portability across source-tree refactors.program_cls().load("v1.json") requires the Python class import; dspy.load("v1/") for whole-program. from langchain import hub
hub.push("user/rag-qa", prompt, parent_commit_hash=last)
hub.pull("user/rag-qa:v3")
rag-qa-gpt4o / rag-qa-sonnet35.mlflow.dspy.log_model), full param/metric history per run, signature, version with explicit Stage labels (None / Staging / Production / Archived). with mlflow.start_run():
mlflow.log_params({"model": MODEL, "dataset_sha8": SHA8})
mlflow.log_metrics({"dev_score": d, "test_score": t})
mlflow.dspy.log_model(compiled, "program",
registered_model_name=f"rag_synth-{MODEL}")
client.transition_model_version_stage(
name=f"rag_synth-{MODEL}", version=v, stage="Production")
.aider.conf.yml per-model edit format, params (R2 Recipe 6).manifest.json with embed_model, dim, corpus_sha8. Loader refuses to query if Settings.embed_model at runtime mismatches manifest.| Layer | Native artifact mechanism | Gap this SOP fills |
|---|---|---|
| DSPy | save_program (JSON or pickled dir) | Adds LM/dataset/metric binding, registry, gate |
| LangChain Hub | Versioned text templates with diff UI | Adds model-pinning enforcement, eval gate |
| Manual jsonl + git | None — this SOP IS the recipe | Provides Recipe 1 schema and gate scripts |
| MLflow Model Registry | Stages, signatures, run history | Adds dataset_sha8 convention, deprecation scan |
| Aider | Per-model edit-format defaults in code | Adds change-gate (re-run bench on edit-format change) |
| LlamaIndex | Settings + index files | Adds manifest-based loader guard, embed-model in path |
artifacts/
├── REGISTRY.jsonl
└── <program>/
└── <provider>/
└── <model-snapshot>/ # e.g. gpt-4o-2024-08-06
└── <dataset>-<sha8>/ # e.g. devset-v3-a8f1c2e9
├── v1.json
├── v2.json
└── v3.json
{"path":"...","sha256":"...","program":"...","provider":"...","model":"...","dataset":"...","dataset_sha256_8":"...","optimizer":"...","optimizer_config":{...},"metric":"...","dev_score":0.0,"test_score":0.0,"cost_usd":0.0,"wall_seconds":0,"dspy_version":"...","compiled_at":"...","commit":"...","parent_artifact":null}
no-edit-artifact: rejects any change under artifacts/** that doesn't also append to REGISTRY.jsonl.no-alias-pin: scans Python source for model="gpt-4o", model="claude-3-5-sonnet" etc. with no date suffix; fails.no-deprecated-pin: scans for snapshots within 90 days of deprecation per scripts/deprecation_table.py; fails without override label.artifact-regression: re-run held-out test on new artifact + parent; fail if Δ < −2 points.swap-test-on-snapshot-change: when a new provider snapshot is announced (via webhook or scheduled scan), open a PR running OP-4 against affected artifacts.New compile? ──► Stage 1-3 (compile, version, gate)
│
New snapshot from provider? ──► Stage 4 swap-test
│ ├── |Δ| ≤ 2 pts ──► tag transferable; ship
│ └── |Δ| > 2 pts ──► Stage 5 recompile
│
Framework deprecation? ──► R2 Recipe 7 pattern: list artifacts, bucket by binding, migrate per-artifact
│
Canary score drift on stable snapshot? ──► Case B path: recompile against same snapshot
│
Family swap (GPT-4o ↔ Sonnet)? ──► Stage 5 recompile, always; swap-test gives the magnitude, not the decision
/Users/5imp1ex/Desktop/Skill-Workplace/output/dspy-sop-skill/SKILL.md Dilemma Case B./Users/5imp1ex/Desktop/Skill-Workplace/output/aider-sop-skill/SKILL.md §"edit format" table.ServiceContext → Settings deprecation: /Users/5imp1ex/Desktop/Skill-Workplace/output/llamaindex-sop-skill/SKILL.md §4.4 / anti-pattern A4.references/R1-source-evidence.md, references/R2-versioning-recipes.md.Take agentsope/agentsop-per-model-artifacts 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.