-
Notifications
You must be signed in to change notification settings - Fork 3
Domains Prompts
The prompts domain compiles the system prompt for every session and fleet worker. It reads markdown fragments from src/domains/prompts/fragments/** at startup, validates them, and assembles a deterministic, layered system prompt from disk content plus typed runtime inputs. The compiler separates immutable identity and contract text from volatile runtime guidance so that a local model's prefix cache survives changes to model, autonomy, tool surface, or memory.
The domain owns three responsibilities:
-
Fragment loading and validation —
loadFragmentswalkssrc/domains/prompts/fragments/**/*.md, parses YAML frontmatter, rejects duplicate ids and malformed fragments, and returns aFragmentTablekeyed by fragment id. -
Layered prompt assembly —
compile(session) andcompileWorker(fleet worker) render fragments into ordered sections, compute a SHA-256 hash of the final text, and return aCompiledSessionPromptwith per-section token estimates and a flat fragment manifest. -
Session-source snapshotting —
createPromptsBundleinextension.tscaptures a per-session snapshot of project context, customization rules, workspace root, and Clio repo awareness. This snapshot freezes the disk-input inputs so that recompile cycles caused by runtime changes (turn scope, skill inventory) still read from the same project sources.
The memory-intervention.ts module is a separate prompt for the memory intervention sub-agent: it defines the system prompt that instructs a small model to write memory notes and occasionally pass a reminder back to the action agent. It is not part of the main session prompt compilation pipeline.
| File | Key symbols | Role |
|---|---|---|
src/domains/prompts/compiler.ts |
compile, compileWorker, SESSION_PROMPT_SECTION_ORDER, canonicalToolPromptHints, renderToolContractBlock, renderWorkerToolContractBlock, workerSafetyOneLiner
|
Pure compiler: fragments + typed inputs → CompiledSessionPrompt
|
src/domains/prompts/fragment-loader.ts |
loadFragments, parseFragment, walk, fragmentIdRegex
|
Disk I/O, YAML frontmatter parsing, id validation |
src/domains/prompts/extension.ts |
createPromptsBundle, captureSessionSourceSnapshot, sessionSourceSnapshot, invalidateSessionSources, renderCustomizationFragments, workspaceRootFragment, clioRepoAwarenessFragments, selfDevelopmentSkillFragments
|
Domain bundle: lifecycle, snapshot cache, customization rendering |
src/domains/prompts/contract.ts |
PromptsContract, CompileSessionPromptInput, CompileWorkerPromptInput
|
Public interface consumed by session and dispatch domains |
src/domains/prompts/hash.ts |
sha256, canonicalJson
|
Single hash primitive and deterministic JSON serialization |
src/domains/prompts/preload.ts |
selectProjectPreload, FULL_PROJECT_CONTEXT_MAX_CHARS, FULL_PROJECT_CONTEXT_MAX_LINES, ProjectPreloadClass
|
Project context budgeting and partial-preload classification |
src/domains/prompts/preload-prefix.ts |
safePrefixOffsets, sourceLineCount
|
Conservative block boundaries for partial preload |
src/domains/prompts/index.ts |
createPromptsDomainModule |
Entry point that wires the extension factory |
src/domains/prompts/manifest.ts |
PromptsManifest |
Domain manifest declaring dependencies on config, context, resources, agents |
src/domains/prompts/memory-intervention.ts |
MEMORY_INTERVENTION_SYSTEM_PROMPT, buildMemoryInterventionUserPrompt
|
Separate system prompt for the memory intervention agent |
loadFragments (src/domains/prompts/fragment-loader.ts:122) resolves the default root to src/domains/prompts/fragments relative to the package root via resolvePackageRoot(). It then recursively walks directories with readdirSync and collects .md files in lexicographic order.
Each file is parsed by parseFragment (src/domains/prompts/fragment-loader.ts:59):
-
Frontmatter regex matches
---\n...\n---\nbody. A missing or malformed frontmatter throwsfragment-loader: <relPath>: missing or malformed YAML frontmatter. -
YAML parsing uses the
yamlpackage; parse failures throw with the error message. -
Validation:
idmust match/^[a-z0-9-]+(?:\.[a-z0-9-]+)+$/(dotted namespace),versionmust be1,descriptionmust be a non-empty string, anddynamicmust be a boolean when present. -
Content hash:
sha256(raw)of the entire file content, used as the reproducibility seed.
The returned FragmentTable is a map from fragment id to LoadedFragment. Duplicate ids throw: fragment-loader: duplicate fragment id "<id>" in <relPath1> and <relPath2>.
Fragment files are organized under src/domains/prompts/fragments/ in subdirectories:
-
identity/—clio.md(main session identity),clio-worker.md(worker identity),self-awareness.md(Clio's self-knowledge),docs-routing.md,settings-routing.md -
operating/—contract.md(shared operating contract),delegation.md(fleet delegation rules),skills.md(skill activation policy),worker.md(worker-specific contract) -
safety/—default.md,yolo.md(autonomy level fragments) -
dispatch/—read-only.md(dispatch read-only restriction)
compile(table, inputs) in src/domains/prompts/compiler.ts is the core pure function. It takes a FragmentTable and CompileInputs and returns a CompiledSessionPrompt.
The volatility order is SESSION_PROMPT_SECTION_ORDER: identity, operating-contract, delegation, skills, safety, tool-contract, fleet, retrieval-hints, project-context, harness-awareness, memory, runtime. A legacy order (LEGACY_SESSION_PROMPT_SECTION_ORDER) is exported for test purposes only; nothing in src/ passes sectionOrder: "legacy-0.3.8".
The volatility rule: sections earlier in the order are more stable across runtime inputs. Identity and the operating contract are the most stable; the tool contract and runtime block are the most volatile.
Each section is rendered by a dedicated function:
-
Identity: renders
identity.cliofragment body, plusidentity.self-awarenesswith{CLIO_DOCS_PATH},{CLIO_SRC_PATH},{CLIO_CODEWIKI_PATH},{CLIO_SETTINGS_PATH}, and{CLIO_STATE_PATH}placeholders substituted viaresolvePackageRoot()andresolveClioDirs(). The self-awareness block also includesidentity.docs-routing(when gateway is on the surface andclio_docsis allowed) andidentity.settings-routing(whencontextis on the surface), with{SETTINGS_CHANGE_POLICY}substituted based on autonomy level andcanConfigureClio. -
Operating contract:
operating.contractbody plus optionalDEMO_GUIDANCEwhensessionInputs.demois true. -
Delegation:
operating.delegationfragment, rendered only whensessionCanDispatchis true (gateway is on the surface,dispatchtool is allowed, provider supports tools). -
Skills:
operating.skillsfragment with{SKILL_ACTIVATION_POLICY}substituted based onmodelMayActivateSkills()and autonomy level. -
Safety:
renderSafetySectionrendersAutonomy: <level>. <one-liner>plus the safety fragment body. The approval semantics sentence isSESSION_APPROVAL_SEMANTICSfor attached sessions orHEADLESS_SESSION_APPROVAL_SEMANTICSfor headless runs. -
Tool contract:
renderToolContractBlockrenders the# Tool Contractheading,TOOL_RESULT_TRUST_CONTRACT, the direct tool surface list, capability kind explanations, inventory guidance, and per-tool hints sorted by tool name. -
Fleet:
renderFleetBlockreturns the fleet roster string whensessionCanDispatchis true and mode is notanswer. - Retrieval hints: static guidance about what is and is not preloaded.
-
Project context:
renderProjectBlockreturns the project context files text. - Harness awareness: the self-awareness block described above.
-
Memory:
renderMemoryBlockdeduplicates the# Memoryheader — if the input already starts with a Memory heading, it is not re-prepended. -
Runtime:
renderRuntimeBlockrenders provider, model, context window, and thinking guidance.
compile appends inputs.additionalFragments after the main sections but before the turn scope. These are rendered inline fragments from the extension (workspace root, Clio repo awareness, self-development skills, project rules, operator profile).
renderTurnGuidance renders the current task scope from turnConstraints. This is the most volatile section and is pushed last.
prefixIdentity(identity, contract) computes the UTF-8 byte length and SHA-256 of the identity and operating contract concatenated. This is returned as stablePrefix in the CompiledSessionPrompt. The test in tests/contracts/prompt-skill-policy.test.ts verifies that switching autonomy from default to yolo changes the system prompt hash but restores the identical prompt on switch-back, confirming determinism.
compileWorker(table, inputs) in src/domains/prompts/compiler.ts compiles the canonical stable prompt for a fleet worker. Key differences from session compilation:
-
Section order is fixed:
identity,operating-contract,tool-contract,safety, optionaldispatch.read-only,persona, additional fragments,turn-scope. -
Identity:
identity.clio-coder-workerfragment. -
Operating contract:
renderWorkerOperatingContractconcatenatesoperating.contract,operating.worker, andWORKER_CLAIM_GUIDANCE(the worker-side mirror of the parent's spot-check guidance). -
Tool contract:
renderWorkerToolContractBlockhandles three cases:providerSupportsTools === false(no tools),providerSupportsTools === null(unknown inventory), and normal (canonical tool names and hints). -
Safety:
renderWorkerSafetySectionusesworkerSafetyOneLinerwhich always says "workspace edits and recognized commands run; other commands require approval" plus the permission routing sentence fromworkerPermissionSentence. -
Read-only:
dispatch.read-onlyfragment is included wheninputs.readOnly === true. -
Persona: the worker's persona fragment, which must be stable (
persona.dynamic === false) and non-empty.
compileWorker throws on:
-
persona.dynamic === true— the worker persona must be a stable fragment -
persona.body.trim().length === 0— the persona must not be empty -
hasCanonicalContext !== contextIsAttached— the caller must match the actual attached tool surface -
hasBoundSkills === true && hasCanonicalContext === false— bound skills require canonical context
PromptsContract (src/domains/prompts/contract.ts) exposes three methods:
-
inputEpoch()— returns a string combining the fragment epoch, agents domain revision, and session source epoch. This is used by the prompt cache to detect when a recompile is required. -
compileSessionPrompt(input)— compiles the session prompt with project context, fleet roster, and customization fragments. -
compileWorkerPrompt(input)— compiles the worker prompt and returnsrulesAppliedandoperatorProfileAppliedprovenance.
createPromptsBundle (src/domains/prompts/extension.ts:57) creates the domain bundle. Its lifecycle:
-
start()— callsloadFragments()and setsfragmentEpoch. Subscribes toContextSourcesChanged(invalidates session sources for the given cwd),ConfigHotReload(invalidates all session sources, reloads fragments if the diff touches prompt files), andPluginsReloaded(reloads fragments). -
stop()— unsubscribes all handlers and clears session source snapshots.
sessionSourceSnapshot(sessionId, cwd) captures and caches a snapshot per (sessionId, cwd) pair. The snapshot includes:
-
Project context — from
contextDomain().renderPromptContext(cwd), unless--no-context-filesis set. -
Customization — project rules from
.clio-coder/rules/**and the operator profile. - Workspace root — absolute path, OS account, hostname.
- Clio repo awareness — whether the workspace is Clio's own source tree.
The snapshot is invalidated by invalidateSessionSources(cwd):
-
cwd === undefinedclears all snapshots. -
cwdspecified: only snapshots for descendant workspaces (relative to the given cwd) are deleted. Ancestor handbooks are layered into descendant sessions, including sessions captured before the ancestor had a handbook.
renderCustomizationFragments renders project rules and the operator profile into inline RenderedPromptFragment objects:
-
Project rules:
selectActiveRulesselects rules from the project's.clio-coder/rules/**that apply to the current working context. Unconditional rules load with project context; path-scoped rules activate when a matching file is in working context. -
Operator profile:
renderOperatorProfilerenders the operator's custom profile.
Both are best-effort: load failures inject nothing.
selectProjectPreload (src/domains/prompts/preload.ts) selects how much project context to include in the prompt:
-
Full preload: the entire project context fits within
FULL_PROJECT_CONTEXT_MAX_CHARS(24,000 UTF-16 code units) andFULL_PROJECT_CONTEXT_MAX_LINES(220 lines). -
Partial preload: the context exceeds the budget. The function allocates excerpts from the nearest handbook first, using
safePrefixOffsetsto find conservative block boundaries (respecting code fences). It includes a<project-preload>header explaining which lines are omitted and how to read them. - None: the context is empty.
The ProjectPreloadClass tracks mode, included vs. available characters and lines, per-source coverage, and whether the provider supports tools (for the retrieval guidance text).
sha256 (src/domains/prompts/hash.ts) is the single hash primitive. canonicalJson provides deterministic JSON serialization: object keys are sorted recursively, arrays preserve element order, undefined values in objects are dropped, and non-finite numbers and symbols throw. This ensures that any structural input (fragment manifests, rendered prompt text) lands on a byte-identical hash whenever the underlying content is equivalent.
The upstream caller is src/interactive/turn-context.ts, which calls deps.prompts.compileSessionPrompt via ensureSessionPrompt. The flow:
-
Turn context builds a
CompileSessionPromptInputwithsessionId,sessionInputs(provider, model, tool surface, constraints, etc.),autonomy, andcwd. -
createPromptsBundle.compileSessionPromptresolves the session source snapshot, selects project preload, renders fleet roster, and callscompilewithadditionalFragmentsfrom the snapshot. -
compilerenders all sections inSESSION_PROMPT_SECTION_ORDER, computes the system prompt hash, and returns theCompiledSessionPrompt.
The downstream dependency is the Pi SDK, which consumes the systemPrompt string and the attached tool schemas. The prompts domain never imports Pi SDK types; it exports erased shapes through CompiledSessionPrompt.
Another upstream caller is src/domains/dispatch/extension.ts, which calls prompts.compileWorkerPrompt to compile each fleet worker's system prompt.
loadFragments throws on duplicate ids and malformed fragments. The FragmentTable is immutable once loaded. The extension's reload() function (triggered by ConfigHotReload when the diff touches prompt or fragment paths, or by PluginsReloaded) reloads fragments and increments fragmentEpoch, which changes inputEpoch() and forces a recompile on the next turn.
The session source snapshot is frozen at first capture. Subsequent compileSessionPrompt calls with the same sessionId and cwd read from the cached snapshot, even if project context has changed on disk. This is intentional: the snapshot freezes the disk-input inputs so that runtime recompile cycles (turn scope changes, skill inventory changes) do not re-read project files. The snapshot is invalidated only by:
-
ContextSourcesChangedbus event (explicit context operation in the workspace or an ancestor) -
ConfigHotReload(config invalidation) - A new session
compileWorker enforces that the worker persona is a stable fragment (persona.dynamic === false). This prevents the worker's system prompt from containing dynamic content that would change the prompt between dispatches, breaking prefix caching for the worker model.
The compiler ensures byte-for-byte reproducibility through:
-
Deterministic section order (
SESSION_PROMPT_SECTION_ORDER) -
Sorted tool names (
canonicalWorkerTools,capabilityNames.sort()) -
Sorted and deduplicated tool hints (
canonicalToolPromptHintssorts by tool name and hint text, then deduplicates) -
canonicalJsonfor any structural serialization -
sha256for content hashing
The test in tests/contracts/prompt-skill-policy.test.ts verifies that switching autonomy from default to yolo changes the system prompt hash but restores the identical prompt on switch-back, confirming determinism.
New fragments are added as markdown files under src/domains/prompts/fragments/** with YAML frontmatter:
---
id: namespace.fragment
version: 1
description: One-line description
dynamic: false
---
Fragment body here.The id must be a dot-separated namespace (e.g., identity.clio, safety.default). The dynamic flag defaults to false; set it to true for fragments whose content changes at runtime (these cannot be used as worker personas).
The fragment is loaded at startup by loadFragments and becomes available to the compiler by its id. The compiler references fragments by id in lookupFragment, which throws if the id is not found.
New sections are added by:
- Adding the section id to
SESSION_PROMPT_SECTION_ORDERinsrc/domains/prompts/compiler.ts. - Rendering the section content in the
compilefunction'srenderedmap. - Calling
push(sectionId, content)in the order loop.
The volatility rule governs placement: more stable sections go earlier in the order.
Project rules and the operator profile are rendered by renderCustomizationFragments in src/domains/prompts/extension.ts. New customization sources are added by extending captureCustomizationSources and renderCustomizationFragments. The result includes activeRuleIds and operatorProfileApplied provenance, which dispatch reads for receipt accounting.
The memory intervention prompt is a separate concern from the main session prompt. MEMORY_INTERVENTION_SYSTEM_PROMPT in src/domains/prompts/memory-intervention.ts is a static string that defines the behavior of the memory intervention sub-agent. It is not loaded from fragments and is not part of the FragmentTable.
Tests that the prompt points to Clio's self-knowledge sources (live settings, bundled docs, code map) whenever the tool that reaches them is on the surface. Key cases:
-
points settings questions at the live snapshot whenever context is on the surface— verifies thatcontext(scope="settings")appears whencontextis on the tool surface, regardless of skill discovery or turn constraints. -
teaches the configure_clio preview only where the tool is registered and autonomy lets it run— verifies that theconfigure_cliopreview sentence appears only whencanConfigureCliois true and autonomy isdefaultoryolo, and that the sentence text differs between autonomy levels. -
names the shipped code map and never routes to the removed docs scope— verifies that the compiled prompt referencesdist/assets/codemap.jsonand does not contain unsubstituted{CLIO_*}placeholders.
Tests that a headless clio-coder run denies approval asks instead of pausing. Key cases:
-
tells a headless session at <level> that approval-required calls are denied— verifies thatHEADLESS_SESSION_APPROVAL_SEMANTICSappears in the safety section whenheadless: true, and thatSESSION_APPROVAL_SEMANTICSdoes not. -
keeps the operator-confirmation sentence for an attached session at <level>— verifies the reverse. -
marks the session inputs headless only when the entry says so— verifies that theheadlessflag is threaded from the turn context into the compiled session inputs.
Tests that gateway-dependent guidance is gated on the tool surface. Key cases:
-
gates documentation and skill catalog guidance for <surface>— verifies that Clio documentation routing and skill catalog guidance appear only when gateway and/or context are on the surface, and that the retiredcontext(scope="docs")andcontext(scope="library")calls never appear. -
lists the direct surface, points tool usage at the gateway, and shrinks the attached schema bytes— verifies the attached-schema budget and that the gateway schema stays small.
Tests that compiled skill instructions agree with actual session-skill admission. Key cases:
-
compiled skill instructions agree with actual session-skill admission at <level>— verifies that the skill activation policy text in the prompt matches whethermodelMayActivateSkills()allows activation, and that thecontext(scope="skills")call appears only when context is on the surface. -
omits skill activation guidance at <level> with context=<bool>, tools=<bool>— verifies that the skills section is absent when context or tools are not on the surface. -
recipe-bound worker admission remains narrowed at <level>— verifies that a worker withhasBoundSkills: truedoes not include the skill activation guidance. -
autonomy transitions change the existing prompt cache identity and compiled activation guidance— verifies that switching autonomy changes the prompt hash but restoring the same autonomy restores the identical prompt.
Tests the prompt cache identity and recompile behavior. Key cases:
-
is deterministic and changes for every live byte-affecting input class— verifies that the cache identity changes for every class of input that affects the prompt bytes (turn constraints, skill inventory, prompt epoch, target, runtime, model, autonomy, session, cwd, working context, context window, etc.). -
resolves runtime inputs and exact attached schemas before deciding reuse— verifies thatcompileSessionPromptis called once for identical resolved inputs, and that changing attached schemas, context window, memory, skill count, or turn constraints triggers a recompile. -
recompiles on prompt-input epoch changes and reports only byte changes as cold— verifies that an epoch change triggers a recompile, but a byte-identical output does not report a cold prefix.
-
Fragment ids are immutable once referenced. The compiler references fragments by id in
lookupFragment, which throws if the id is not found. Renaming a fragment id requires updating all references incompileandcompileWorker. -
exactOptionalPropertyTypesis on. Pass optional fields with...(x !== undefined ? { x } : {}), neverx: undefined. The compiler'sSessionPromptInputsandWorkerPromptInputshave many optional fields. -
The stable prefix is identity + operating contract only. Changing either fragment invalidates the stable prefix for every session. The
DEMO_GUIDANCEappended to the operating contract incompiledoes not affect the stable prefix because it is appended after the contract body in therenderedmap, but it does affect the system prompt hash. -
canonicalToolPromptHintssorts and deduplicates. If you add a tool hint with the same tool name and hint text as an existing hint, it will be silently dropped. The sorting is lexicographic on tool name, then hint text. -
The memory block deduplicates the
# Memoryheader. If you change the memory section to start with a different heading, the deduplication logic inrenderMemoryBlockwill not match and the header will be duplicated. -
Worker personas must be stable.
compileWorkerthrows onpersona.dynamic === true. If you need dynamic content in a worker prompt, useadditionalFragmentsinstead. -
The session source snapshot is frozen at first capture. Changes to project context on disk are not reflected in a running session until the session is invalidated by a
ContextSourcesChangedevent or a config hot reload. This is intentional for prefix cache stability. -
The
--no-context-filesflag suppresses project context. WhennoContextFilesis true, the context domain dependency is removed fromPromptsManifest.dependsOnandprojectContextis not captured in the session source snapshot.
Source and generation metadata
title: "Prompt Compiler"
summary: "How prompt fragments are loaded from disk, compiled into a stable system prompt, and cached for prefix reuse."
sources:
- "src/domains/prompts/compiler.ts"
- "src/domains/prompts/fragment-loader.ts"
- "src/domains/prompts/extension.ts"
- "src/domains/prompts/contract.ts"
- "src/domains/prompts/hash.ts"
- "src/domains/prompts/preload.ts"
- "src/domains/prompts/memory-intervention.ts"
- "src/domains/prompts/index.ts"
symbols:
- "compile"
- "compileWorker"
- "loadFragments"
- "createPromptsBundle"
- "selectProjectPreload"
- "canonicalToolPromptHints"
- "canonicalJson"
- "sha256"
tests:
- "tests/contracts/self-knowledge-prompt.test.ts"
- "tests/contracts/headless-approval-prompt.test.ts"
- "tests/contracts/gateway-prompt.test.ts"
- "tests/contracts/prompt-skill-policy.test.ts"
- "tests/extended/prompt-cache-correctness.test.ts"
invariants:
- "Fragment ids are dot-separated namespaces validated at load time; duplicate ids throw."
- "The compiled system prompt is byte-for-byte reproducible for identical inputs; `canonicalJson` sorts object keys recursively and drops undefined values."
- "The stable prefix (identity + operating contract) survives changes to any runtime input, as measured by `stablePrefix.bytes` and `stablePrefix.hash`."
- "Worker persona must be a stable fragment; `compileWorker` throws on `persona.dynamic === true` or an empty persona body."
validate:
- "pnpm run test:file -- tests/contracts/self-knowledge-prompt.test.ts"
- "pnpm run test:file -- tests/contracts/headless-approval-prompt.test.ts"
- "pnpm run test:file -- tests/contracts/gateway-prompt.test.ts"Clio Coder · Repository · Website · Documentation
Wiki v0.1 · Developing implementation reference · Source snapshot: 657dce13d. Authored architecture documents define the product contracts.
- Clio Coder GUI Client
- apps / clio-coder-gui
- Apps clio coder gui server
- Apps clio coder gui tests
- apps
- Architecture
- Command-line surfaces
- Core
- Domains agents
- Config Domain
- Context Domain
- Dispatch domain
- Domains evidence
- Domains extensions
- Domains gateway
- domains
- Domains interop
- Domains lifecycle
- Domains memory
- Middleware Domain
- Domains mux
- Domains observability
- Domains plugins
- Prompt Compiler
- Domains providers
- Domains quota
- Domains resources
- Domains safety
- Domains scheduling
- Domains session
- Vendored Tool Registry and Resolution
- Engine
- Engine acp
- Engine apis
- engine
- Entry point
- Interactive
- interactive
- Interactive overlays
- Interactive renderers
- clio-coder wiki
- Scripts
- Contract tests
- Tests extended
- tests
- Tools
- Tools data
- tools
- Tools verify
- Worker runtime