feat(compile): add a caller system policy, as a prompt modifier - #195
Merged
Conversation
ethanj
force-pushed
the
feat/compile-system-policy
branch
from
August 25, 2026 01:26
9743bd4 to
2038a9c
Compare
Based on #170 by @TigerOfCountryYao, rebased onto current main and finished against the review. Partly answers #144. His design is what ships: the additive framing, its wording verbatim ("Additional system policy (follow in addition to every built-in instruction above):"), the placement after the built-in instructions and before the source material, blank treated as omitted so the default prompt stays byte-identical, and the PROMPT_VERSION bump. The invalidation mechanism, the isolation and the tests below are not his. `compile({ systemPolicy })` appends deployment-specific editorial or publication guidance to the built-in compile prompts, for an SDK host that needs it without forking the prompts. Additive rather than a replacement, and placed before the source material so the whole instruction block still precedes the content it describes. Blank or omitted leaves the prompt byte-identical. It is ADVISORY. A policy makes a model more likely to follow a rule; it cannot make it obey one, and nothing downstream verifies that it did. Anything that must hold belongs in a lint rule or a trust gate, and the docs say so rather than letting "policy" imply a boundary. The policy is registered as a PROMPT MODIFIER, which is the substantive change from #170. A policy alters what the prompt asks for without changing a source byte, so without this a settled project reports "Nothing to compile" and keeps content generated under a policy the operator has already replaced. Registering it in activePromptModifiers means it inherits the whole mechanism from #188: invalidation on change, candidate-aware deduplication so a pending review candidate produced under one policy does not satisfy a run asking for another, the scoped-refresh guard, and per-page provenance. Bumping PROMPT_VERSION alone does not achieve this, because two policies share a version. The policy lives in AsyncLocalStorage, matching quietScope and verboseScope in utils/output.ts. The SDK documents that concurrent calls are fully isolated with no global state, and the compile lock is per ROOT, so two callers compiling different projects genuinely overlap in one process. A module variable that saves and restores handles nesting and fails on interleaving: measured, two overlapping runs observed ["Policy B", undefined] where they should observe ["Policy A", "Policy B"] — one project reading another's policy and the other losing its own. That reaches further than prompt text, since the policy feeds the digest written to state.json, candidates and page provenance. The DIGEST enters the modifier set, never the text, because that set is hashed into state.json, stamped onto every page's frontmatter, and published through the JSON export. The prompt builders read the policy from run state rather than taking it as a parameter, so their signatures are unchanged and the option is not threaded through extraction, page rendering, the review pipeline and seed pages, none of which otherwise need to know it exists. PROMPT_VERSION advances to v2, and is now pinned by a test: nothing in the repo asserted its value before, so a silent revert would mislabel every page compiled afterwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUdoRq1DJq23aJuhK9QK7X
ethanj
force-pushed
the
feat/compile-system-policy
branch
from
August 25, 2026 02:48
2038a9c to
fd29023
Compare
This was referenced Aug 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Picks up #170, rebased onto current
mainand finished against the review. @TigerOfCountryYao is credited as co-author. Partly answers #144.What it does
compile({ systemPolicy })appends deployment-specific editorial or publication guidance to the built-in compile prompts, for an SDK host that needs it without forking the prompts. Additive rather than a replacement, placed before the source material, and blank or omitted leaves the prompt byte-identical.It is advisory. A policy makes a model more likely to follow a rule; it cannot make it obey one, and nothing downstream verifies that it did. Anything that must hold belongs in a lint rule or a trust gate, and the docs say so rather than letting the word "policy" imply a boundary.
The substantive change from #170
The policy is registered as a prompt modifier. It alters what the prompt asks for without changing a source byte, so without this a settled project reports "Nothing to compile" and keeps content generated under a policy the operator has already replaced. A pending review candidate has the same problem.
Registering it in
activePromptModifiersmeans it inherits the whole mechanism from #188: invalidation on change, candidate-aware deduplication, the scoped-refresh guard, and per-page provenance. BumpingPROMPT_VERSIONalone does not achieve this, since two different policies share a version.The digest enters the modifier set, never the text. That set is hashed into
state.json, stamped onto every page's frontmatter, and published through the JSON export, so carrying the prose would put an operator's editorial instructions into three artifacts that outlive the run.Smaller than the original
The prompt builders read the policy from run state rather than taking it as a parameter, so their signatures are unchanged and the option is not threaded through extraction, page rendering, the review pipeline and seed pages.
withRunSystemPolicyrestores the previous value rather than clearing, so overlapping SDK callers on different roots cannot strand a policy they did not set.Isolation
The policy lives in
AsyncLocalStorage, matchingquietScopeandverboseScopeinutils/output.ts. The SDK documents that concurrent calls are fully isolated with no global state, and the compile lock is per root, so two callers compiling different projects genuinely overlap in one process.A module variable that saves and restores handles nesting and fails on interleaving. Measured on an earlier revision of this branch, two overlapping runs observed
["Policy B", undefined]where they should observe["Policy A", "Policy B"]- one project reading another's policy, and the other losing its own. That reaches past prompt text, since the policy feeds the digest written tostate.json, candidates and page provenance.Two tests pin it, one on the policy and one on the digest, and both yield before reading: the broken implementation only fails once the runs actually interleave.
Tests
Thirteen unit cases plus five that drive the real pipeline twice over byte-identical sources: policy A to B, policy cleared, policy unchanged, blank treated as absent, and a pending candidate produced under a different policy. Only the pipeline cases can detect the wiring.
Every control is mutation-tested: removing the policy from the modifier set turns 7 red, reverting
PROMPT_VERSIONturns 1 red, and removing it from the prompts turns 3 red.On #144
This is the SDK primitive underneath that request, not a closure of it. #144 asks for a
SOUL.md-style file, and a file or CLI surface on top of this is a reasonable next step.Not included
#169's embeddings flag. The feature has no dependency on it, only a shared test file, so this is unstacked.