Metadata-Version: 2.4
Name: prompt-linker
Version: 2.1.0
Summary: Ahead-of-time compiler/linker for LLM agent prompts — compose instruction sets from interchangeable parts.
Project-URL: Homepage, https://github.com/ega-ega/prompt-linker
Project-URL: Source, https://github.com/ega-ega/prompt-linker
Author: Egor Shaldov
License-Expression: MIT
License-File: LICENSE
Keywords: agent,compiler,linker,llm,pipeline,prompt
Requires-Python: >=3.11
Requires-Dist: pyyaml>=6.0
Provides-Extra: ai
Requires-Dist: anthropic<1,>=0.69; extra == 'ai'
Provides-Extra: dev
Requires-Dist: jsonschema>=4.18; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: types-jsonschema>=4.18; extra == 'dev'
Description-Content-Type: text/markdown

# prompt-linker

[![PyPI](https://img.shields.io/pypi/v/prompt-linker.svg)](https://pypi.org/project/prompt-linker/)
[![Downloads](https://img.shields.io/pypi/dm/prompt-linker.svg)](https://pypi.org/project/prompt-linker/)
[![Python](https://img.shields.io/pypi/pyversions/prompt-linker.svg)](https://pypi.org/project/prompt-linker/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)

> 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.** `deploy` lays a build out into host paths and records
  a committed `deploy-receipt.json` (config hash + per-file sha256); it refuses to overwrite
  hand-edited or foreign files, prunes what a rebuild dropped, and `deploy --check` is 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 opt-in chunk-hash pinning (`approve` → committed `chunks.lock`) turns any
  unreviewed corpus edit into a build failure.
- **Provenance everywhere.** Every compiled file carries a `GENERATED` header with the full resolved
  binding and its hash; `list --explain` and `diff` show which layer won each binding and what it
  overrode.
- **CI-ready surface.** Every command but `init` takes `--json` and 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

```yaml
# 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
```

```markdown
# 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`](./Examples/StarterTemplate)
to begin your own configuration, or read [`Examples/TestConfiguration`](./Examples/TestConfiguration)
for a fuller, runnable one.

## Install & adopt

prompt-linker is on PyPI — consume it as an **installed package**, not a vendored copy:

```sh
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:

1. **scaffolds** a starter configuration into `DIR` (default `prompts/my-config`), with the manifest
   `name:` set to the dir basename and `linker_version:` stamped to the installed version;
2. **installs a Claude Code consumer skill** into `.claude/skills/prompt-linker/` that teaches a host
   agent to *consume* the tool (no internals);
3. seeds **`configs.md`** next to the skill — a consumer-owned registry of this repo's configurations —
   and appends a row per scaffolded config;
4. 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:

```sh
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):

```sh
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`](./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`](./Docs/Commands.md#editor-validation--the-linkeryaml-json-schema).

**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`](./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](./CHANGELOG.md#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's
  `x-prompt-linker.version`).
- Make drift loud: record `linker_version: X.Y.Z` in a config's `Linker.yaml` (`init` stamps it) and
  `verify` warns when the installed CLI differs.

## Status

✅ **Stable — v2.1.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](./CHANGELOG.md#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`](./Docs/PromptLinker.md), the CLI in
[`Docs/Commands.md`](./Docs/Commands.md), verification layers in
[`Docs/Verification.md`](./Docs/Verification.md), emitted errors in [`Docs/Errors.md`](./Docs/Errors.md);
open questions and planned work in [`Roadmap/Roadmap.md`](./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

[MIT](./LICENSE)
