Skip to main content
The resolver is the component that turns a prompt ID (like summarize-v1) into actual prompt content your harness can use. It works through four levels in strict priority order — the first level to find a match wins.
This ordering means you can always override any pinned version locally for development without changing your consumption manifest or your CI configuration.

The ResolverChain class

The resolution chain is implemented in promptops/resolver/chain.py:
The chain reads resolution rules from the consumption manifest. If the manifest has an override for a prompt ID, it goes straight to LocalResolver. Otherwise it tries WorkspaceResolver first (no manifest entry needed), then falls back to GitRefResolver or PackagedResolver based on the pin format.

Level 1 — Local override

LocalResolver reads a prompt from a local file path. This is the highest-priority level and is always checked first when an override is present in the consumption manifest. When to use: local development against a separate prompt repo without publishing. Configure in consumption.yaml:
The resolver supports .yaml, .yml, and .json files. It caches results by mtime to avoid re-reading unchanged files. If the override path does not exist, LocalResolver raises FileNotFoundError. This prevents silent fallthrough to a stale pinned version when the override file is missing.
Use local overrides during rapid iteration. When you’re done, remove the override key and rely on the git ref pin for reproducibility.

Level 2 — Workspace

WorkspaceResolver finds prompts in the local promptops/ directory — no manifest entry needed. This is how same-repo prompts work without any publishing step. Paths tried (in order):
Minimal mode: if your project has three or fewer prompt files, WorkspaceResolver also checks the shorter prompts/ and evals/ paths (without the promptops/ prefix). This is auto-detected by counting files matching promptops/prompts/*.yaml and similar globs. Quick eval resolution: when the resolver finds a file in evals/, it checks for a prompt field. If present, it returns a synthetic prompt spec:
If WorkspaceResolver.resolve() returns None, the chain continues to level 3 or 4.

Level 3 — Git ref

GitRefResolver retrieves prompt content from a specific git commit, tag, or semver range. This is the default pinning mechanism for separate prompt repos — zero publishing overhead. Paths tried inside the git tree (in order):

Pin formats for git ref

Configure in consumption.yaml:
For remote repos:
How semver resolution works: GitRefResolver runs git tag --list (or git ls-remote --tags for remote repos) and finds all tags that satisfy the semver range using its own match_semver() implementation. It selects the highest matching stable version. Prerelease versions (e.g., v1.2.3-rc1) are excluded unless the range explicitly selects them. Fallback for git archive: if the remote server disables git archive, the resolver falls back to a full git clone + git checkout in a temporary directory and reads the file from there. All results are cached in memory by (prompt_id, pin) key.

Level 4 — Packaged artifact

PackagedResolver retrieves prompts from published artifacts — GitHub Releases, OCI registries, npm packages, or PyPI wheels. This level is triggered when the pin starts with a packaged-artifact prefix.

Pin formats for packaged artifacts

Configure in consumption.yaml:
Resolution flow for packaged artifacts:
  1. If the pin is a sha256: digest, PackagedResolver fetches the artifact, validates it against prompt-package.schema.json, and finds the prompt spec with the matching id inside the package’s specs array.
  2. For all other URL/registry formats, the resolver fetches the artifact, validates it against provider-artifact.schema.json, verifies the optional signature, then reads the package_digest field and calls resolve() recursively with that digest (treating it as a level-4 sha256 lookup).
Caching: fetched artifacts are cached to ~/.promptops/cache/ using a URL-safe filename derived from the ref. If a network fetch fails but a cached copy exists, the resolver uses the cache. If no cache is available, it raises RuntimeError.

How the consumption manifest drives resolution

The consumption manifest (promptops/manifests/consumption.yaml) is the single config file that controls how the resolver behaves for your app.
The ResolverChain.resolve(prompt_id, manifest) call:
  1. Validates the manifest against consumption-manifest.schema.json.
  2. Looks up the resolution rules for prompt_id (override, pin, id mapping).
  3. Applies the four-level chain based on those rules.

Pin format decision guide

Use a local override when you are iterating on a prompt in a checked-out repo and don’t want to publish or commit.