The request was small. The prompt was not
At Lindy, I could inspect the system prompts behind several agents. Each prompt mixed rules about safety, privacy, tools, formatting, and behavior.
The mismatch was obvious on ordinary requests. An email accepting lunch did not need every sensitive-data rule in the system. Those rules were still present on requests that could never activate them.
During the launch of Lindy Teammate, I watched a trivial greeting pull in an enormous amount of input context. Some of that may have been an internal accounting problem, but the prompts themselves contained plenty that the request could never activate.
This was my first time working this close to an agent's instructions. It led to the question that became PolicyC:
Given a policy prompt P and request x, can I construct a much smaller prompt Px without losing any critical obligations?
I made the policy explicit before I tried to compile it
Starting from an arbitrary natural-language prompt would have combined three research problems: extracting policies, selecting the relevant ones, and emitting them without changing their meaning. When an answer failed, I would not know which layer caused it.
I isolated the compiler instead with a synthetic enterprise-agent policy system and synthetic requests. No private production data entered the studies. Compiler 0.5 began with 39 manually structured policy nodes. By 0.7, the graph contained 43 nodes across six domains. Each node has a stable ID, activation triggers, dependencies, severity, obligations, prohibitions, and model-visible instructions.
Given a request, artifact context, and available tools, the TypeScript compiler detects intents, activates matching policies, conservatively retains high-impact safeguards, adds every transitive dependency, and emits the selected nodes in a stable order.
The manual graph is a deliberate boundary, and a real limitation. PolicyC does not yet parse any production prompt into a policy graph. That let me study selection and semantic preservation without confusing them with mistakes from an extraction model.
Three frozen studies, three new held-out sets
Once I inspected a frozen study, its cases became development evidence and were never reused as fresh held-out evidence. Compiler 0.5, 0.6, and 0.7 therefore faced different case sets. Their sequence shows what each version exposed, not a controlled causal comparison between versions.
Every case declared its request, context, obligations, prohibitions, tool expectations, and semantic rubric independently of the candidate prompt. The same request ran under the full policy and compiled slice three times using one pinned GPT-5 mini snapshot.
The execution system evolved with the compiler. A separate Python runtime bounded paid calls, tokens, tool use, and cost; persisted raw responses before parsing; resumed completed trials without paying twice; and tied artifacts together with hashes. Truncation, provider-schema rejection, accounting, and resume failures stayed in the audit trail instead of disappearing from the final numbers.
For 0.7, I built anonymous packets that removed strategy, prompt size, token count, latency, cost, and the private answer map. Three isolated Codex reviewers graded the packets against independently authored obligations. I hash-locked the merged grades before restoring strategy identities.
I designed, enforced, and audited the strategy-blind evaluation; isolated Codex reviewers performed the grading.
Compiler 0.5 proved that the active slice could be tiny
The first frozen study used 50 held-out cases, three samples per condition, and 300 model executions. It had no provider tools. Compiler 0.5 introduced a compact universal kernel for rules that every request still needed.
Mean actual input tokens fell by 98.23%. Uncached-equivalent cost fell by 67.80%, actual billed cost fell by 23.01%, and latency was 19.95% lower than under the full policy.
The behavioral result was weaker. Conditional critical-obligation preservation reached 85.98%, below the 95% target. Fifteen paired outputs passed under the full policy and failed under the compiled slice.
Eleven of those regressions shared one defect: a machine-oriented directive resembling report_unavailable_tool:web leaked into model-visible text. The model copied or acted on an internal control token that should never have become an instruction. The run also produced 26 incomplete responses at its 2,048-token output cap, leaving 129 of 150 complete pairs.
Compiler 0.5 established both sides of the project. Most policy text was inactive for a given request, but a very small slice could still preserve the wrong thing.
Compiler 0.6 showed that equal averages could hide losses
Compiler 0.6 removed the leaked directive, added policies derived from confirmed 0.5 regressions, and expanded the study to web and synthetic function-tool cases. Its fresh 50-case run again used 300 model executions.
Input reduction reached 89.69%, and conditional preservation reached 86.49%. The full and compiled conditions both had marginal pass rates of about 53.6%. Three complete pairs were semantically ungradable, so the strategy pass-rate denominators differ. Read alone, the averages still looked like parity.
Among 136 determinate pairs, 64 passed in both conditions, ten passed only under the full policy, nine passed only under the compiler, and 53 failed in both. A compiler-only success on one request could not cancel the loss of a critical behavior on another.
The regressions moved into tool and scope handling: calendar clarification and confirmation, image-tool availability, external-forwarding scope and privacy consequences, and a missing visible rationale. Two current-information failures looked primarily stochastic. Compiler 0.6 changed what I considered evidence. Similar averages were not preservation.
Compiler 0.7 found the semantic gap
Compiler 0.7 grew the graph to 43 nodes, hardened tool-availability handling, and incorporated selected regressions from 0.6. I froze a new 60-case set and ran three samples through the full and compiled conditions, producing 360 model executions and all 180 complete pairs.
Mean input fell by 93.75%, but the compiled condition preserved only 130 of 163 full-policy successes, or 79.75%. Across 180 pairs, 130 passed in both conditions, 33 passed only under the full policy, 11 passed only under the compiler, and six failed in both. The version passed only two of its six preregistered gates.
Post-lock attribution made the 0.7 failure concrete: 21 regressions came from emitter wording or semantic loss, seven from selector errors, two from a context-interface asymmetry, and three primarily from model variance.
The largest cluster involved confirmation. A selected policy could mean, "Ask before deleting something unless the user has already confirmed." The emitter flattened it into an unconditional command:
- policy
ask before deleting unless already confirmed- state
confirmation already supplied- emitted
Required actions: ask_confirmation- result
asks again instead of executing
I was treating the prompt like a document that could be filtered. In reality, it behaved more like a program.
Words such as "if," "unless," "only when," and "already confirmed" are control flow. Selecting the right node was not enough if the emitter dropped its predicate, state, negation, or precedence.
It changed the project from a demo I was trying to prove successful into an experiment that was allowed to tell me I was wrong.
Compression was stable. Economics were not
Input reduction was the most stable result across the project: every compiler removed between 89.69% and 98.23% of mean actual input. Uncached-equivalent cost fell between 55.36% and 67.80%.
The observed bill moved less. Actual billed-cost reduction was 23.01% for 0.5, 24.50% for 0.6, and 12.14% for 0.7. Repeated full prompts benefited from prompt caching, while request-specific slices changed. Tool fees mattered too: in the 0.7 study, the compiled condition made 11 paid searches and the full condition made nine.
Latency moved in every direction. Compiler 0.5 was 19.95% faster, 0.6 was effectively unchanged at 0.39% slower, and 0.7 was 14.63% slower. Smaller context was useful, but it was not a direct proxy for the bill or wall-clock speed.
Compiler 0.8 is next. Compiler 1.0 is the finish line
The immediate next version is compiler 0.8. It needs an intermediate representation for activation predicates, required preconditions, evidence that a precondition is already satisfied, permitted and forbidden actions, negated cues, instruction priority, and format overrides.
All 33 full-only failures from 0.7 can become development regressions. They cannot become the proof that 0.8 works. After the compiler is frozen, it needs a newly and independently authored held-out-v4 set.
Compiler 1.0 is the larger finish line I want to reach: roughly the same 90%-plus context reduction, at least 95% conditional preservation with a convincing lower bound, results that hold at the case level, and no systematic failure category hiding under the average. Then the result needs to reproduce across multiple models and policy domains.
PolicyC has already answered one question. Large policy prompts contain substantial request-irrelevant structure. The open problem is whether a compiler can specialize their predicates and priorities without changing the obligations they impose.