Skip to content

Pin instructions where the model actually reads them - #155

Merged
teamchong merged 3 commits into
mainfrom
feat/pin-instructions
Jul 27, 2026
Merged

Pin instructions where the model actually reads them#155
teamchong merged 3 commits into
mainfrom
feat/pin-instructions

Conversation

@teamchong

Copy link
Copy Markdown
Owner

Problem

Opus 5 stops following the instructions you gave it. It overthinks, wanders
into files nobody asked about, and answers a question adjacent to the one you
typed. The rules are in CLAUDE.md, which the model read thousands of tokens
ago and now treats as background. Repeating the rule in the current turn fixes
it every time, which says the problem is distance, not comprehension.

Mechanism

pxpipe reads @pxpipe pin <text> out of the user message, strips the command
line from the copy sent upstream, and re-emits the active pins as a
<system-reminder> block appended to the last user message. Every request now
ends with the rules, immediately before the model starts generating.

Pins are re-derived from the transcript on each request, so the block is a
pure function of the conversation. No server-side state and a resumed session
reconstructs the same block.

Cost: the pin block is not cacheable

It sits after the cached prefix, on purpose. Moving it earlier would put the
rules back in background position and invalidate the prompt cache on every
edit. So the block is re-sent uncached each turn and billed as fresh input.

The dashboard already accounts for it: those characters are counted as
pinChars, not booked as savings, so the savings number stays honest.

Keep pins short. A few lines is the intended size; a pasted style guide is paid
for on every request.

Usage

Two tiers, same syntax:

  • an @pxpipe pin line in CLAUDE.md / AGENTS.md is durable
  • a line typed in chat lives for the session

Command-only turns are answered by the proxy and never reach the model:
@pxpipe pin reports, @pxpipe unpin <n> and unpin all remove.

You: @pxpipe pin no walls of text
pxpipe · 1 pinned          (local reply, no tokens)

...40 turns later, that line is still the last thing before your question.

A turn counts as command-only by what the user typed. Harness-injected
<system-reminder> blocks are stripped first, otherwise Claude Code's own
notices make a bare @pxpipe unpin look like real work and bill it as a model
turn.

Rules stated in CLAUDE.md sit thousands of tokens behind the live turn and
get read as background. `@pxpipe pin <text>` moves them to the end of the
request, immediately before generation.

Pins are re-derived from the transcript each request, so the block is a pure
function of the conversation and a resumed session rebuilds it identically.
The block lands after the cached prefix, so it never invalidates the cache,
and its characters are tracked as pinChars rather than booked as savings.
Comment thread src/core/pin.ts Fixed
Comment thread src/core/pin.ts Fixed
Comment thread src/core/pin.ts Fixed
Comment thread src/core/pin.ts Fixed
CodeQL flagged all four PIN_CMD_RE call sites: the pattern ran on every
line of every prompt, and its leading, lazy, and trailing groups all
matched whitespace, so a line of many tabs made the engine retry every
way to split the run before failing.

Before:
  /^[ \t]*@pxpipe[ \t]+(pin|unpin)\b[ \t]*(.*?)[ \t]*$/

After:
  /^@pxpipe[ \t]+(pin|unpin)\b(.*)$/  with .trim() in JS

Also skip the strip when the tail cannot take the pin block, so a body
that cannot be appended to keeps its rules instead of losing them.
Second CodeQL alert, same rule, different pattern: the lazy body under a
global replace rescans to the end of the string for every
`<system-reminder>` that never closes, so a message carrying many of them
costs O(n²). Prompt text reaches this on every request.

Before:
  raw.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '')

After:
  indexOf scan; an unterminated open ends the scan and the rest stays,
  since it is text the user can see.
@teamchong
teamchong merged commit dbaf960 into main Jul 27, 2026
6 checks passed
@teamchong
teamchong deleted the feat/pin-instructions branch July 27, 2026 12:54
teamchong added a commit that referenced this pull request Jul 27, 2026
foldPins only scanned the leading <system-reminder> run of message 0, where
Claude Code inlines CLAUDE.md. OpenCode inlines AGENTS.md into the system
prompt instead, unwrapped and unlabelled, so #155 folded nothing, stripped
nothing, and raised no error under OpenCode.

Scan the system prompt too, at the durable 'file' tier. No reminder gate
there: the user cannot type into the system prompt, so every line in it was
placed by the harness, and a pin command found there came from a file.

Strip mirrors stripFromMessage, cache_control handoff included, since system
blocks carry the breakpoint that ends the cacheable prefix. It must run
before the system text is collected for imaging, or the stripped lines get
baked into the rendered slab.

Messages API only. foldPins still has exactly one call site, so Codex and
Gemini remain no-ops.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants