Metadata
Both the prompt and each individual variant carry one opaque metadata map. The library stores
these maps, echoes them through accessors, and never interprets or acts on them. They exist so
application code can attach whatever annotations are needed and read them back.
| Where | Field | Purpose |
|---|---|---|
| Prompt (top-level) | metadata | Arbitrary prompt annotations — model hints, param hints, tags, ownership. |
| Each variant | metadata | Per-variant labels for the caller’s selection logic — weights, group labels, tags. |
Neither map is schema-constrained beyond “an object”, and neither affects rendering or the content
hashes. output_model is a separate top-level string field with the same opaque, carried-only
contract.
There is one documented exception: if the prompt-level metadata map contains a key named "guard"
(any value), check() suppresses the untrusted_without_guard advisory for that prompt. The
library only checks for the key’s presence — its value is not read.
Declaring metadata
Section titled “Declaring metadata”name: summaryrole: userbody: "Summarise {{ article }}."variables: article: type: string trusted: falsemetadata: # prompt-level: anything to carry model_hint: claude-sonnet-4-6 max_tokens: 512 owner: team-contentvariants: terse: body: "TL;DR of {{ article }}." metadata: # per-variant: drives the caller's selection logic weight: 0.2 group: experiment-q4Reading it back
Section titled “Reading it back”The accessors return the maps as-is; application code interprets them.
//! Reading prompt-level and per-variant metadata back out. The library stores the//! opaque `metadata` maps and echoes them through accessors; it never interprets//! them. Standalone — `cargo run --example guides_metadata_reading_it_back`.
use prompting_press::Prompt;use std::fs;
/// Stand-in for the caller's routing logic — the library never calls this.fn route_to_model(_hint: &str) {}
fn main() -> Result<(), Box<dyn std::error::Error>> { let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/examples"); let p = Prompt::from_yaml(&fs::read_to_string(format!("{dir}/summary_metadata.yaml"))?)?;
// metadata() returns &serde_json::Map<String, serde_json::Value>. if let Some(hint) = p.metadata().get("model_hint") { route_to_model(hint.as_str().unwrap_or("default")); }
// Per-variant metadata is on each Variant in p.variants(). // The caller reads it in selection logic — the library never does. if let Some(terse) = p.variants().get("terse") { let _weight = terse.metadata.get("weight"); }
// The accessors return the maps as-is; nothing is interpreted or mutated. assert_eq!( p.metadata().get("model_hint").and_then(|v| v.as_str()), Some("claude-sonnet-4-6") ); assert_eq!( p.metadata().get("max_tokens").and_then(|v| v.as_i64()), Some(512) ); let terse = p.variants().get("terse").expect("terse variant exists"); assert_eq!( terse.metadata.get("weight").and_then(|v| v.as_f64()), Some(0.2) ); assert_eq!( terse.metadata.get("group").and_then(|v| v.as_str()), Some("experiment-q4") );
println!("model_hint = {:?}", p.metadata().get("model_hint")); Ok(())}"""Reading prompt-level and per-variant metadata back out.
The library stores the opaque ``metadata`` maps and echoes them throughaccessors; it never interprets them. The accessors return the maps as-is;application code interprets them. Standalone — run it directly, or the doc-sampletest harness executes it and its assertions."""
from __future__ import annotations
from pathlib import Path
from prompting_press import Prompt
# The caller reads the definition; the library does no file I/O itself.# Resolve the file next to this program (a real app uses its own path)._HERE = Path(__file__).parent
p = Prompt.from_yaml((_HERE / "summary_metadata.yaml").read_text())
p.metadata # => {"model_hint": "claude-sonnet-4-6", "max_tokens": 512, "owner": "team-content"}p.metadata["model_hint"] # application code decides what to do with it
# per-variant metadata (each variant is a plain dict):p.variants["terse"]["metadata"] # => {"weight": 0.2, "group": "experiment-q4"}
# The accessors return the maps as-is; nothing is interpreted or mutated.assert p.metadata == { "model_hint": "claude-sonnet-4-6", "max_tokens": 512, "owner": "team-content",}assert p.metadata["model_hint"] == "claude-sonnet-4-6"assert p.variants["terse"]["metadata"] == {"weight": 0.2, "group": "experiment-q4"}/** * Reading prompt-level and per-variant metadata back out. The library stores the * opaque `metadata` maps and echoes them through accessors; it never interprets * them. The accessors return the maps as-is; application code interprets them. * * Standalone — the doc-sample harness type-checks and runs this file; its * assertions are in-program. */
import assert from "node:assert/strict";import { readFileSync } from "node:fs";import { fileURLToPath } from "node:url";import { Prompt } from "prompting-press";
// The caller reads the definition; the library does no file I/O itself.// Resolve the file next to this program (a real app uses its own path).const defFile = (name: string) => fileURLToPath(new URL(name, import.meta.url));
const p = Prompt.fromYaml(readFileSync(defFile("summary-metadata.yaml"), "utf8"));
p.metadata; // => { model_hint: "claude-sonnet-4-6", max_tokens: 512, owner: "team-content" }p.metadata.model_hint; // application code decides what to do with it
// per-variant metadata (p.variants is undefined when there are no named variants):p.variants?.["terse"]?.metadata; // => { weight: 0.2, group: "experiment-q4" }
// The accessors return the maps as-is; nothing is interpreted or mutated.assert.equal(p.metadata.model_hint, "claude-sonnet-4-6");assert.equal(p.metadata.max_tokens, 512);assert.equal(p.metadata.owner, "team-content");assert.deepEqual(p.variants?.["terse"]?.metadata, { weight: 0.2, group: "experiment-q4",});Updating metadata on a derived prompt
Section titled “Updating metadata on a derived prompt”metadata is overlayable like any other top-level field — derive replaces the whole map
(shallow), so read-then-spread to add a key without dropping the rest. See
Deriving a prompt.
docs current as of 0.2.0