prompt-is-a-layered-program
Keep a prompt as six provider-neutral layers and compile it per model, so a model or provider switch fails loudly at build time instead of quietly weakening the request.
A PromptProgram holds the six layers of one model call: instructions, tools, examples, context, task and output contract. It carries no provider names and no request syntax, so the same program can target any model.
ModelProfile holds every provider difference. It records whether the model enforces a JSON schema and whether it accepts temperature, lists the schema keywords to strip, and can override the instructions. profile_from_litellm() fills the flags from LiteLLM's bundled model map and accepts overrides. compile_request() places each layer in its request slot, or raises UnsupportedLayerError when the model cannot carry it. run() sends the request through LiteLLM's completion() and validates the reply against the full Pydantic model, including constraints stripped from the wire. It retries within an attempt budget, then raises ContractError.
The 17 tests cover each layer's placement and every failure mode from the post. Unsupported parameters raise instead of being dropped. Schema constraints leave the wire but are still checked on the client. A bounded retry ends in a named error. The tests run offline with a scripted fake model and never call a provider.
git clone https://github.com/tensorupai/tensorup-examplescd tensorup-examples/prompt-is-a-layered-program && uv sync && uv run pytestTo run it live, export OPENAI_API_KEY or ANTHROPIC_API_KEY, then run TENSORUP_LIVE=1 uv run python -m promptlayers.triage. TENSORUP_MODEL takes any LiteLLM model id.
To use it in your own agent, write your prompt as a PromptProgram and keep one ModelProfile per model you deploy. Call compile_request() in CI for each of them, so a model that cannot carry a layer fails the build before it reaches production.