open source · deterministic · model-agnostic

Stop shipping prompts you can’t see.

System prompts end up as string spaghetti scattered through your codebase — nobody knows what’s shared, what’s safe to change, or what the model actually receives. promptbook turns them into a folder of small plain files you can see: Storybook for prompts, with an Obsidian-style graph.

Feed the skill to your agent
$ npx skills add markbrutx/promptbook
Your agent reads it, then writes fragments and rules for you.
examples/sports-broadcastlive graph
loading graph…
a real prompt system — every node is a file, every edge a reference
the problem

This is in your codebase right now.

Prompt logic hides in template literals: conditionals buried in strings, copies drifting across files, no way to tell which variants exist. A book makes the same logic visible — as data, not code.

app/reply.tstoday
const system = `You are a support assistant
for ${brand}. Be concise.${
  tone === "terse"
    ? TERSE_RULES : WARM_RULES
}${locale === "de" ? DE_RULES : ""}`;

// three sprints later, in another file…
const escalation = SYSTEM_V2
  .replace("{persona}", persona) + formatFor(model);

Which variants exist? What changed last sprint? What does the model actually receive? grep and pray.

prompts/with promptbook
prompts/
├─ fragments/
│  ├─ persona.md
│  ├─ reply-tone-warm.md
│  ├─ reply-tone-terse.md
│  └─ guardrails.md
└─ rules/
   └─ reply.yaml

# the whole decision, one rule:
- when: { tone: terse }
  replace: { reply-tone-warm: reply-tone-terse }

Same logic, as files. Every variant browsable, every edit's blast radius visible before it ships.

how it works

Three moves. No framework.

Plain files in, one pure function out. The engine never calls a model — the only stochastic step stays in your code, behind an adapter.

01

Put prompts in a folder

Fragments are Markdown, rules are YAML — data, not code. They diff, review and version like everything else in your repo. One command scaffolds it: npx @markbrutx/promptbook-cli init.

fragments/persona.md
---
id: persona
---
You are a support assistant for ${brand}.
Warm, precise, never invents order data.
rules/reply.yaml
base: [persona, reply-tone-warm,
       guardrails, reply-task, locale]
rules:
  - when: { tone: terse }
    replace: { reply-tone-warm:
               reply-tone-terse }
02

See the whole system

promptbook view is Storybook for your prompts: browse every assembled variant, flip a context axis, watch it re-assemble — or zoom out to the graph.

promptbook viewlocalhost
▸ replyescalationgraph ⌘G
tone=tersemodel=claudelocale=de
03

Ship with resolve()

Same folder + same context → byte-identical prompt, in Node, edge or browser. The trace explains every decision.

app.ts
import { resolve } from
  "@markbrutx/promptbook-core";

const { text, trace } = await resolve({
  promptsDir: "./prompts",
  prompt: "reply",
  context: { tone: "terse",
             model: "claude" },
});

// text  → the exact prompt, every time
// trace → which rules fired, and why
for developers

Edit without fear

Every fragment knows which prompts use it. See the blast radius of a change before it ships — not in an incident review.

for agents

A safe prompt engineer

One skills command teaches your coding agent to read, edit and migrate prompt books without breaking them.

for teams

Review prompts like code

Plain files diff cleanly in PRs. Lint flags dead fragments, eval pins fixtures — prompt changes get code-grade review.

why promptbook

Everything about a prompt, visible before you run it.

Which fragments are shared, what is safe to change, and what the final prompt looks like under a given context — all answerable without a single model call.

Know what an edit breaks — before prod

Every fragment knows which compositions include it. Touch persona and all ten places it lands light up — the blast radius is on screen, not in an incident.

persona is used by four compositionshalf-time-recappush-headlinesocial-postticker-overlaypersonaused in 10 / 10

See what the model actually receives

Every composition, every variant, live. Flip a context axis and watch the prompt re-assemble, segments colored by the fragment they came from.

sport=tennistier=vipplatform=socialmodel=claude
persona
sport-tennis
tier-vip
format-xml

Same input, same prompt — provable

resolve() is pure: same folder + context → byte-identical output. The trace shows which rules fired and why, what got replaced, what forbid removed.

when tier=vip → replace tier-free → tier-vip
when compliance=kid-safe → replace compliance-standard
when compliance=kid-safe → forbid sponsor-mention
order: persona → guardrails → … → locale
warnings: 0 · byte-identical on every run

One prompt, every model's dialect

The target model is just another context axis. One logical prompt compiles to a JSON contract for one model and XML tags for another — no forked copies.

- when: { model: gpt }
  replace: { format-prose: format-json }
- when: { model: claude }
  replace: { format-prose: format-xml }
gpt → JSONclaude → XMLdefault → prose

Overrides you can predict

CSS for prompts: rules apply in declaration order, later wins, forbid is the final filter. You can compute the outcome in your head — no solver to appease.

1base orderpersona · guardrails · task · format
2later rule winswhen tier=vip → replace tier-free
3forbid — final filteralways wins, no solver

Agents and CI speak it natively

One command teaches your agent to write fragments and rules safely. The CLI gives CI the same verbs — lint dead fragments, gate bundles, eval fixtures.

$ npx skills add markbrutx/promptbook
→ install · migrate · doctor · annotations
$ promptbook resolve sponsor-mention --ctx compliance=kid-safe
resolvelslintevalbundlewatchview
straight answers

The four questions everyone asks.

Is this a framework?

No. Three plain file kinds — Markdown fragments, YAML rules, JSON fixtures — plus one pure function. No orchestration, no chains, no lock-in: delete promptbook tomorrow and your prompts are still readable Markdown.

Does it call models?

Never. The engine is deterministic glue — resolve() just assembles a string. The model call stays in your code, behind whatever client you already use.

Which models does it work with?

All of them. The target model is just a context value, so one logical prompt can resolve to a different format per model — JSON for one, XML for another — without forking the text.

My prompts are already strings in code.

That’s the expected starting point. The skill bundle includes a migrate skill: your coding agent moves string literals into a book incrementally, one prompt at a time.

Open the book.

The full sports-broadcast book — 10 compositions, 30 fragments, six context axes — running entirely in your browser. No install, no model calls.