Ahead-of-time compiler/linker for LLM agent prompts — compose instruction sets from interchangeable parts.
Project description
prompt-linker
Compile an LLM agent's instruction set from interchangeable, independently-testable parts — chosen at build time, the way a program is linked from objects.
prompt-linker is an ahead-of-time compiler/linker for agent prompts. You write a stable algorithm as skeletons with named slots, write the variable behavior as interchangeable implementations, and declare which implementation fills each slot in a build manifest. The linker inlines the chosen implementations into the skeletons and emits one flat, fully-resolved instruction that the agent runs with zero runtime indirection.
Why
A configurable agent has variable behavior threaded through a stable algorithm. Two usual ways to manage that both rot:
- inline conditionals (
if mode == X …) entangle every variation into one monolith — and the branching is scattered through the prose, so you can no longer see at a glance where a scenario forks, which branch is live, or test one branch in isolation; - runtime resolution scatters the same behavior across tool-calls resolved on every iteration.
prompt-linker makes each variable behavior an interchangeable implementation behind a contract, so the skeleton never knows which one it got (substitutability — "Liskov for prompts" as a design metaphor: the linker proves the structure deterministically — every slot bound exactly once to an existing impl of the right contract — while semantic fit of the text is reviewed by the opt-in AI verifier, not proven; prose has no scope isolation, so that honesty is by design). Branching stops being buried in the text and becomes explicit data in the manifest — one line per binding, so where a scenario forks and which branch is active is readable in one place. The payoff is readable, independently-testable building blocks and Open–Closed growth: a new behavior is a new implementation + a manifest line, the algorithm is never edited. As a side effect the indirection is paid once, in deterministic code (zero model tokens) — the bonus, not the reason.
What you get beyond templating
A template engine gets you variable substitution. The value of prompt-linker is everything wrapped around that step:
- A verifier that fails closed. Before a byte is written, every slot must resolve to exactly one
existing implementation of the right contract; a missing, ambiguous, or duplicate binding is a typed
error, never a silent default. A linker without a verifier is a generator of silently-incoherent
prompts — here they ship as a pair, plus an opt-in AI verifier (
check) for conformance and whole-artifact coherence. - Deploy as a first-class consumption mode.
deploylays a build out into host paths and records a committeddeploy-receipt.json(config hash + per-file sha256); it refuses to overwrite hand-edited or foreign files, prunes what a rebuild dropped, anddeploy --checkis a CI/pre-commit gate that catches stale or hand-edited artifacts. Distribution is reproducible and auditable, not a copy script. - Reproducibility by construction. Output is content-addressed (
compiled/<name>-<config-hash>), so a host can freeze a run to the exact artifact it started with — a manifest edit can never mutate the bytes an in-flight run reads. - A security layer for the prompt supply chain. A deterministic scan runs inside every
verify/compile(invisible/bidi characters hard-fail; capability-envelope violations hard-fail; heuristics warn — and a contract that declares a capabilityallowed silences that noise), and opt-in chunk-hash pinning (approve→ committedchunks.lock) turns any unreviewed corpus edit into a build failure. - Provenance everywhere. Every compiled file carries a
GENERATEDheader with the full resolved binding and its hash;list --explainanddiffshow which layer won each binding and what it overrode. - CI-ready surface. Every command but
inittakes--jsonand emits one machine-readable result object — advisories, hashes, and output paths included.
Concepts
| Term | Is |
|---|---|
| Configuration | a self-contained directory (manifest + skeletons/ + contracts/) that compiles independently; a project may hold many |
| Contract | a named, reusable signature (inputs, postcondition, invariant); declared once, with an optional default impl |
| Slot | an occurrence of a contract in a skeleton — {{ slot: <contract-id> }}, the hole to fill |
| Implementation | one concrete block that satisfies a contract and fills its slots |
| Skeleton | a prompt template containing slots |
| Skill | a deliverable bundle (a top-level subdir of skeletons/) — a convention, not required |
| Patch | a small named set of contract → impl bindings — typically one axis of variation (a platform, a verbosity level) |
| Preset | a named, ordered composition of patches (applied left→right, later wins); a point in the configuration space |
| Manifest | Linker.yaml: names the configuration, declares patches: + presets: (+ global overrides:) |
| Binding / Resolution | a contract → impl pair / the resolved set of them (defaults → preset (patches) → overrides) |
| Linker | deterministic step that inlines implementations into skeletons |
| Verifier | proves every slot is bound once and each implementation satisfies its contract |
Quick look
# Linker.yaml
preset: team
overrides: { continuation: spawn-next } # run-local contracts only
patches: # small named binding sets — one per axis of variation
base: { task-source: local, continuation: stop }
network: { task-source: tracker }
presets: # a preset is an ordered list of patches (later wins)
solo: [base]
team: [base, network] # start from base, then apply the network axis
# skeletons/autonomous-agent/run.md
## Phase 1 — Acquire work
{{ slot: task-source }}
prompt-linker compile --preset team → a flat compiled/<name>-<config-hash>/autonomous-agent/run.md
with the tracker implementation inlined — clean text the agent reads with no hint it was assembled
from parts (the output dir is content-addressed so a host can freeze a run by recording its path);
provenance lives in a GENERATED header. (compile --annotated adds per-block markers for an AI
verifier — a debug build, never shipped.) Copy Examples/StarterTemplate
to begin your own configuration, or read Examples/TestConfiguration
for a fuller, runnable one.
Install & adopt
prompt-linker is on PyPI — consume it as an installed package, not a vendored copy:
pipx install prompt-linker # or: uvx prompt-linker …
prompt-linker init prompts/my-config # adopt the tool in the current repo
Use pipx or uvx — they put the prompt-linker command on your PATH (and uvx runs it
without installing at all). Plain pip install prompt-linker also works, but then you manage
PATH: pip drops the command in a Scripts//bin dir that is not always on it (notably with Windows
Store Python). If the bare command isn't found, run it PATH-independently with python -m prompt_linker … — that always works wherever python does. From a source checkout the entry point
is PYTHONPATH=src python -m prompt_linker.
init is the one-shot, idempotent way for a host repo to adopt the tool. Run from the repo root, it:
- scaffolds a starter configuration into
DIR(defaultprompts/my-config), with the manifestname:set to the dir basename andlinker_version:stamped to the installed version; - installs a Claude Code consumer skill into
.claude/skills/prompt-linker/that teaches a host agent to consume the tool (no internals); - seeds
configs.mdnext to the skill — a consumer-owned registry of this repo's configurations — and appends a row per scaffolded config; - appends
compiled/to your.gitignore.
A non-empty DIR fails closed unless --force; pass --no-skill to scaffold only, or --skill-only
to (re)install just the skill.
Upgrading / re-installing
The consumer skill ships inside the package, so a newer prompt-linker carries a newer skill — upgrade the package, then refresh the installed skill:
pipx upgrade prompt-linker # or reinstall outright: pipx install --force prompt-linker
prompt-linker init --skill-only # re-install just the skill from the new package
The skill is version-stamped managed (x-prompt-linker: {version, sha} in its front-matter): init
overwrites a matching-or-absent skill silently — including on upgrade — but refuses a hand-edited one
without --force, so customize by editing the source and re-init, never the installed copy. Your
configs.md is never overwritten, so the repo's config list survives every upgrade. Find the version to
pin a build against with prompt-linker --version (also stamped in the skill's x-prompt-linker.version).
Usage
Run a command against a configuration directory (its ROOT):
prompt-linker verify Examples/TestConfiguration # check coherence; writes nothing
prompt-linker compile Examples/TestConfiguration --preset team # → compiled/ (gitignored)
prompt-linker deploy Examples/DeployExample --root . # install/distribute a build into host paths
prompt-linker check Examples/TestConfiguration --security # AI verifier (opt-in; needs [ai] extra)
prompt-linker approve Examples/TestConfiguration # pin the corpus → chunks.lock (review gate)
Two ways to consume a build: use-in-place (compile, then point the runtime agent at the printed
content-addressed path — commit nothing) or install/distribute (deploy — lay the build out into
host paths another tool discovers by location, recorded in a committed deploy-receipt.json;
deploy --check is the CI/pre-commit freshness gate).
verify, compile, and deploy are deterministic and fail closed (and include a security scan);
check is the opt-in, model-using AI verifier (conformance, coherence, injection review).
Security — chunk-hash pinning (opt-in). prompt-linker approve records the sha256 of every source
chunk (contracts/ + skeletons/) into a committed chunks.lock; verify/compile then fail closed
on any drift until you review the change and re-approve. It is a tripwire, not a detector — its value
is realized by a review gate (e.g. CODEOWNERS on contracts/**, skeletons/**, chunks.lock) so a
corpus edit cannot land unreviewed (Docs/Verification.md §3.4).
Editor validation. The package ships a JSON Schema for Linker.yaml; one
# yaml-language-server: $schema=… comment (scaffolds and Examples carry it already) gives any
yaml-language-server editor autocomplete, hover docs, and typo flagging for the manifest while you
type — see Docs/Commands.md.
CI / scripting. Every command but init takes --json for one machine-readable result object on
stdout; compile --json exposes out_dir/config_hash as the hook a launch wrapper uses to freeze a
run to its content-addressed build. Full command reference: Docs/Commands.md.
Versioning & reproducibility
prompt-linker is consumed as an installed package, never vendored — so the only lever for a
reproducible build is pinning the version exactly (prompt-linker==X.Y.Z). A compiler's output can
shift across versions (slot handling, the GENERATED header, advisories), so identical sources can build
differently under a different tool. The bump level signals intent (see the
versioning policy): MAJOR = breaking surface/format/output, MINOR =
backward-compatible additions (and output-correcting fixes), PATCH = no intended output/surface change.
Only an exact pin guarantees byte-identical output.
- Find the version to pin:
prompt-linker --version(also stamped in the installed skill'sx-prompt-linker.version). - Make drift loud: record
linker_version: X.Y.Zin a config'sLinker.yaml(initstamps it) andverifywarns when the installed CLI differs.
Status
✅ Stable — v2.2.0 on PyPI (prompt-linker; the badge above shows the current version). Since
1.0.0 the CLI surface, the emitted formats, and the Linker.yaml schema are under SemVer (see the
versioning policy). The
deterministic linker/verifier, the opt-in AI verifier, host adoption (init), and both consumption
modes (compile / deploy) all run; --json machine-readable output and opt-in chunk-hash pinning
(approve) round out the surface. Docs: the design in Docs/PromptLinker.md, the CLI in
Docs/Commands.md, verification layers in
Docs/Verification.md, emitted errors in Docs/Errors.md;
open questions and planned work in Roadmap/Roadmap.md.
Implementation: Python 3.11+, distributed via PyPI (pipx run prompt-linker / uvx prompt-linker). The npm name is reserved; a native JS build is deferred until there is demand for
embedding the linker as a JS library.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file prompt_linker-2.2.0.tar.gz.
File metadata
- Download URL: prompt_linker-2.2.0.tar.gz
- Upload date:
- Size: 151.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
059882842c6f908becd016272271c492b7dcb4a3594d3ca87ee3324d70540a8d
|
|
| MD5 |
7277fcaf2833ac33fb472999272d0fca
|
|
| BLAKE2b-256 |
c8cf16e685b0da3db63671892497efe7870af38e0d2cd6936527a1fc24df33cc
|
File details
Details for the file prompt_linker-2.2.0-py3-none-any.whl.
File metadata
- Download URL: prompt_linker-2.2.0-py3-none-any.whl
- Upload date:
- Size: 85.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
62edfe92d2c47a197d1427663b9fef6ed7bcef3b7369dba614ae5e3e010fc435
|
|
| MD5 |
04eb82783633cf5bc2e9a36fc15a5d4d
|
|
| BLAKE2b-256 |
a30ca8a4ed84f5b4642ccb1c3a67ca16119b5d61fdb430dbff23aa8af81dbd12
|