examples/ folder consumers copy from),
not as a §4B contract. Revisit only if a consumer asks for programmatic blocks by name.§4A) so an agent can act with zero wiring —
but it ships no built-in prompts, so every consumer hand-writes the system prompt, the
tool-use discipline, the "you have a task tool, delegate" guidance, the output-format rules,
from scratch and slightly differently. A shipped, named, composable prompt library — a
prompt cheatsheet — is the missing battery. Modeled on VS Code Copilot's two shipped shapes:
task prompts (extensions/copilot/assets/prompts/plan.prompt.md — frontmatter
name/description/agent/argument-hint + a body) and default instruction blocks (model-aware
system-prompt building blocks, e.g. OpenAIReminderInstructions,
extensions/copilot/src/extension/prompts/node/agent/openai/defaultOpenAIPrompt.tsx:138-145,
composed from getEditingReminder in defaultAgentInstructions.tsx).parseFrontmatter,
js/src/skill.ts:90, SPEC.md §3) and already composes a system string from parts
(system() = [systemPrompt, skillsPrompt()].join("\n\n"), client.ts:424-425, SPEC.md §8).
This ADR ships content through the machinery that exists; it does not introduce a
prompt-rendering framework (no prompt-tsx, no PromptSizing — that stays out of scope, see
Non-goals). Kept as plain data so it is byte-identical across all five ports.Where a consumer wires an agent today:
new Client({ systemPrompt: "You are a coding agent. Be careful. Use tools. ...", ... })
— that string is theirs to invent, per app, per language, and it drifts. Copilot instead
composes the system prompt from named, reusable blocks (role + reminders + tool guidance) and
ships task prompts users invoke by name (/plan, /explain). toolnexus has the assembler
(system()) and the parser (§3) but ships no library of blocks or task prompts to feed
them. This ADR adds that library as a new shipped source §4B, beside built-in tools §4A.
Priority order: PB1 system-prompt block catalog → PB2 task-prompt templates (the cheatsheet) → PB3 model-aware variants.
Every agent needs the same handful of prose blocks: a role, tool-use discipline, an output
contract, a delegation note (once task from ADR 0005 exists), a safety line. Shipping them named
and composable means a consumer writes blocks: ["role.coding", "tooluse.default", "delegate.note"]
instead of a wall of hand-rolled text — and every toolnexus agent reads consistently. Copilot ships
exactly these as defaultAgentInstructions fragments (getEditingReminder, reminder-instruction
classes).
// Shipped as plain data in every port: name → markdown string (byte-identical).
// e.g. "role.coding", "role.research", "role.planner", "tooluse.default",
// "tooluse.parallel", "delegate.note", "output.concise", "safety.default"
export const BUILTIN_PROMPTS: Record<string, string>
// ClientOptions / system() addition — compose blocks into the system prompt.
interface ClientOptions {
// Named built-in blocks prepended (in order) before systemPrompt, before skillsPrompt().
// Unknown names are ignored + warned once (mirrors the §2/§3/§4A unknown-filter rule).
promptBlocks?: string[]
}
// New assembly order for system() (SPEC §8):
// [ ...resolve(promptBlocks), systemPrompt ?? "", skillsPrompt() ].filter(Boolean).join("\n\n")
A consumer can read a block for reference (BUILTIN_PROMPTS["tooluse.default"]) — that is the
cheatsheet aspect — or reference it by name and let system() inline it.
promptBlocks: ["role.coding","tooluse.default"] with no systemPrompt produces a system
string = those two blocks joined by \n\n, byte-identical across all five ports.promptBlocks unset ⇒ system() is byte-identical to today.New SPEC.md §4B with the frozen block set (names + exact markdown) — this is the parity
surface: the strings must match byte-for-byte across ports, so they live in a shared
examples/prompt-blocks/ golden and the ADR change adds them to SPEC.md §4B verbatim.
Copilot ships *.prompt.md files — named, parameterized task starters a user invokes
(/plan <what>), each targeting an agent and carrying an argument-hint
(plan.prompt.md). This is the literal "prompt cheatsheet": a shelf of ready task prompts
(plan, review, explain, test, refactor, summarize) that expand a short invocation into
a full, well-formed prompt — optionally routed to a registered agent (ADR 0005 §7D).
// Shipped data: name → template. Same frontmatter vocabulary as a skill/Copilot prompt file.
interface PromptTemplate {
name: string
description: string
argumentHint?: string
agent?: string // optional: route to a registered AgentDef (ADR 0005 G2)
body: string // markdown with ${arg} / {{arg}} placeholders
}
export const BUILTIN_PROMPT_TEMPLATES: Record<string, PromptTemplate>
// Helper on Client — expand a template and run it (routes to `agent` if set, via ADR 0005 `task`).
class Client {
prompt(name: string, args?: Record<string, string>, toolkit?: Toolkit): Promise<RunResult>
}
Consumers can also load their own *.prompt.md files through the exact skill discovery path
(§3 walker + parseFrontmatter) — a prompt file is a SKILL.md sibling with a body and no
tool. Substitution is a single documented rule (missing arg ⇒ empty + warn once) held identical
across ports.
client.prompt("explain", { target: "foo.ts" }) expands the shipped explain template with the
arg substituted and runs it; result is a normal RunResult.agent: "researcher" routes through the ADR 0005 task/registry path (skipped
as pending if 0005 isn't landed — this ADR degrades gracefully to "run on the default agent").*.prompt.md discovered from a dir participates identically to shipped templates.${arg} substitutes empty with one warning; extra args ignored.SPEC.md §4B sub-section: the template struct, the frozen shipped set, and the one substitution
rule. Shared fixture examples/prompt-templates/ with golden expansions.
The same instruction reads better tuned per model family — Copilot resolves different default
reminders for OpenAI vs others (DefaultOpenAIPromptResolver.resolveReminderInstructions,
defaultOpenAIPrompt.tsx:133-135). toolnexus already branches ClientStyle = "openai" | "anthropic"
(client.ts:10), so a block may ship a per-style variant chosen by the client's style — same
name, style-appropriate wording.
// A block value may be a string OR a per-style map; resolve() picks by the client's ClientStyle.
type PromptBlock = string | Partial<Record<ClientStyle, string>>
export const BUILTIN_PROMPTS: Record<string, PromptBlock>
Default: a plain-string block applies to every style (today's behavior). Only blocks that have a per-style map vary. Keep this small — most blocks should stay single-string; per-style variants are the exception, not the rule.
{ openai, anthropic } resolves to the matching variant per the client's style.SPEC.md §4B: the PromptBlock union + style-resolution rule. Fixture covers both styles.
PromptSizing/priority-pruning
(OpenAIReminderInstructions.render(state, sizing)) is a whole framework; toolnexus ships
prompt content, not a renderer. Budget/compaction, if ever wanted, is a separate ADR — it
is deliberately out of scope here.promptBlocks (like built-in tools are inert until reached through toOpenAI() — §4A end).
Nothing is added to the system prompt implicitly.{role.coding, role.research, role.planner, tooluse.default, delegate.note, output.concise},
templates {plan, review, explain, test} — because every name added to §4B is a cross-port
byte-parity obligation forever.${arg} vs {{arg}} for PB2 — pick one and freeze it in §4B
(must be identical across ports and not collide with markdown a template body might contain).promptBlocks is unset
(e.g. a tooluse.default when tools are present), or is the library strictly opt-in? Recommend
strictly opt-in to preserve today's byte-identical system().Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |