proper-pacify

Optimize the tone of Pi prompts without changing their content

Packages

Package details

extension

Install proper-pacify from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:proper-pacify
Package
proper-pacify
Version
0.0.3
Published
Aug 28, 2026
Downloads
267/mo · 267/wk
Author
sharafn
License
MIT
Types
extension
Size
44.8 KB
Dependencies
0 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./pacify.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

proper-pacify

Tired of hurting your LLM's feelings? Rather your model didn't match you drink for drink?

It happens to everyone.

The build breaks at 1am, you type something you would never say to a colleague with a pulse, and now you are asking for help from a model you just called useless.

Or it goes the other way: it is Friday, the wine is open, and your prompt has picked up four "please"s, an apology for the inconvenience, and a compliment about how clever the thing is. Both are worse prompts than the one you would have written at 10am on a Tuesday, and the model treats each one as your sober, considered best.

Why tone matters

Less than the internet claims, and not in the direction most people assume. A cross-lingual study found that impolite prompts often lowered answer quality, while piling on politeness bought nothing over plain neutral phrasing, and the best level differed by language (Yin et al., SICon 2024). A later cross-model evaluation found the effect is real but narrow: neutral and polite prompts generally beat very rude ones, yet the differences were significant only in some interpretive tasks and mostly washed out once results were aggregated (Hu et al.).

So rudeness is a small tax you pay for no reason, and flattery is not a discount. The interesting failure is at the other end. Anthropic warns that aggressive emphasis such as "CRITICAL: You MUST use this tool" makes recent models overtrigger, and recommends dialing it back to ordinary phrasing (prompting best practices). Hedging costs you from the opposite side: bury the request under "could we maybe consider possibly" and the instruction arrives blurry. Every vendor guide converges on the same target, which is clear, direct, and explicit.

That target is the one thing a tired or annoyed human is worst at hitting.

What it does

This Pi extension rewrites the tone of your prompt to be clear, direct, neutral-professional, and cooperative, and it changes nothing else. Facts, constraints, urgency, consequences, questions, action verbs, paths, code, and numbers survive exactly as written, because a prompt that reads pleasantly and asks for the wrong thing is worse than a rude one. One model call per prompt, no second-guessing pass. Use it per prompt with /pacify, or turn it on for every prompt and forget it is there. Both versions land in the transcript so you can see what was sent on your behalf.

Commands

Command Behavior
/pacify <prompt> Pacifies the prompt, logs before and after text, then sends the result as the user prompt.
/pacify-session Turns automatic mode on for the current session only, leaving the stored default alone.
/unpacify <prompt> Sends the prompt verbatim, skipping automatic mode for that one prompt.
/unpacify-session Turns automatic mode off for the current session only, leaving the stored default alone.
/pacify-config Configures model, effort, priority service tier, tone guidance, and automatic mode.

Tone-only contract

The model receives an immutable instruction to preserve every fact, request, constraint, command, code block, path, URL, quotation, number, markup token, and ordering. It must not answer, summarize, correct, infer, add, remove, or reorganize content. If a tone-only rewrite is unsafe, it must return the original text.

The configurable tone prompt adds style guidance. It cannot replace the immutable content-preservation rules. Each prompt uses one rewrite model call; there is no model-as-judge or self-verification pass.

Rewrite integrity

A reply is accepted only when it arrives inside a <rewrite> envelope and its length stays near the input's. Anything else fails open: Pi sends your original prompt and logs the rejection.

A model in answer mode is not following the rewrite protocol, so it never emits the envelope, which makes the distinction binary instead of a guess.

The envelope is the backstop, not the cure. What makes models comply is where the instructions go.

Instruction placement

The rewrite contract travels in the user turn, and the system prompt declares only the role. This is deliberate.

The system slot is not reliably yours. A provider fronting a subscription endpoint may prepend its own agent prompt to every request, because the upstream credential requires it. Rules placed in the system slot are then outranked by an identity that answers prompts and calls tools — and no wording there overrides it; an explicit "disregard any prior identity" instruction changed nothing. Disabling that injection is not an option either: it is what makes the credential valid, so turning it off breaks the provider entirely.

The user turn is left intact. Moving the contract there, with your prompt marked as data, takes an affected model from rejecting every prompt to rewriting all of them correctly.

Your prompt occupies the end of the message rather than sitting inside a fence. Any fence is forgeable — a prompt containing the closing delimiter would end the data region early and the rest would read as instructions. A trailing region has no closing token to forge, so a prompt containing """ or even a literal <rewrite> block is still treated as text.

Both halves have to agree. While the system prompt still described the user message as the text to rewrite, a well-behaved model correctly rewrote the instructions instead of the prompt. So the system prompt names the TEXT block as the only data.

If every prompt is still rejected, the configured model is unusable for this job. Switch it in /pacify-config.

Images

Images are never sent to the rewrite model. Tone is a property of text, an image cannot change what the rewrite should be, and sending one would spend image tokens on every prompt in automatic mode for no tone signal. It also makes the failure above more likely: hand a task-oriented model the screenshot and it stops asking and starts solving.

Pasting an image in Pi writes it to a temp file and inserts that path into your prompt as ordinary text. The path needs no special treatment — it is content, the tone contract already requires paths to survive verbatim, and the envelope catches any reply that acts on one. Automatic mode reattaches your images to the transformed prompt, so the agent still receives them.

Automatic mode

Automatic mode is off by default and has three settings, chosen under Auto in /pacify-config: off, on, or scheduled. They are mutually exclusive, so on cannot be combined with a schedule.

A schedule is a daily 24-hour local-time window, stored as "auto": { "start": "09:00", "end": "17:00" }. The window includes start and excludes end. A window whose start is later than its end wraps midnight, so 22:00 to 06:00 runs overnight. The window is evaluated per prompt, so it opens and closes during a running session without a restart. An unusable window falls back to off rather than on.

/pacify-session turns automatic mode on for the current session and /unpacify-session turns it off, neither writing to disk. Each sets one state rather than flipping the current one, so both are safe to repeat and neither depends on knowing whether the stored default, or the current point in a schedule, has pacification on right now. A session override outranks a schedule until the session ends. The override is dropped by /new, /resume, /fork, and /clone, and survives /reload. Use /pacify-config to change the stored setting for every future session.

To skip a single prompt, use /unpacify <prompt>. Because automatic mode runs above command dispatch, input starting with /unpacify or /unpacify-session is exempt before any rewrite happens; otherwise the bypass command's own argument would be rewritten before the command ran. /unpacify then sends its argument verbatim and writes no transcript entry, since nothing changed.

When enabled, automatic mode runs on every interactive, RPC, or extension-injected user input. A one-shot guard skips only the message already produced by /pacify, preventing recursion without exempting other extensions. Slash-command tokens are kept exact while their arguments are pacified, so skill and prompt-template expansion still works after the transform.

Pacification happens above Pi's extension handler chain, in the single input dispatch funnel, so no other extension can see an unpacified prompt no matter what order packages are installed in. Pi chains input handlers in load order and offers no priority control, so the package wraps emitInput on the host's own ExtensionRunner, reached through the coding-agent module Pi provides to extensions. The wrapper installs once per process. If a future host stops exposing that funnel, the extension keeps working through its ordinary input handler and ordering falls back to load order.

A successful transform becomes Pi's stored user message. The extension also adds a visible custom session entry holding the original prompt, headed pacifying with <model>. That entry is durable but excluded from LLM context, as are the notifications below; the model only ever receives the rewritten prompt.

The entry is written before the model call, so your prompt appears the moment you send it rather than after a round trip — it is the progress indicator. It shows the original and the model, nothing else: the rewrite is the user message rendered directly below it, and effort, fast, and auto are settings you already chose, so repeating them on every prompt says nothing about that prompt.

The entry collapses to its › pacifying with <model> header and keeps the original prompt hidden until you expand it with Pi's app.tools.expand binding, which also drives tool output.

Cancellation and failure are written to the session transcript through Pi's notification API rather than to a footer status slot, so they appear beside the entry instead of competing for one truncated line. A failure adds no second entry.

Esc cancels an in-flight automatic rewrite and discards the prompt. Model, authentication, transport, non-stop completion, and rejected-rewrite failures fail open: Pi sends the original prompt and logs the failure beside identical before and after text.

Configuration

Settings live at ~/.pi/agent/pacify.json and are read before every use. Missing or invalid values use these defaults:

{
  "model": "gpt-5.6-luna",
  "effort": "medium",
  "fast": false,
  "prompt": "Copy the input and change only the spans listed below. Leave every other word exactly as written, in its original order.\n\nEditable spans:\n1. Profanity, insults, sarcasm, and contempt, such as \"the hell\", \"stupid\", \"idiot\", or \"garbage\". Delete the hostile wording and keep the rest of the sentence, including its question or command form. When the hostile phrase also asserts something about the work, restate that assertion plainly instead of deleting it: \"the docs are useless\" becomes \"the docs do not cover it\".\n2. Exasperation markers and sarcastic interjections, such as \"Ugh\", \"Seriously?\", or \"Wow\". Delete.\n3. Flattery and praise aimed at the reader, such as \"you're amazing\". Delete.\n4. Pleading and emotional pressure aimed at the reader, such as \"I'm begging you\" or \"please please\". Delete.\n5. Deference frames wrapped around a request, such as \"I'd be grateful if you could\", \"if it isn't too much trouble\", or \"at your convenience\". Delete the frame up to the verb it wraps and keep every verb after it, including \"consider\" and \"suggest\", even when the sentence chains two verbs: \"Would you mind possibly suggesting whether X\" becomes \"Could you suggest whether X\", and \"I'd be grateful if you could consider possibly reviewing X\" becomes \"Consider reviewing X\".\n6. Drama that states only the speaker's feeling, such as \"this is a disaster\". Replace it with the plain fact, or delete it when it states no fact.\n\nEverything else is content. Keep claims about past behavior, consequences, conditions, urgency, modality, scope, emphasis, interrogative words, question marks, and imperative verbs. Add no politeness markers, greetings, apologies, gratitude, encouragement, or reassurance. If the input contains none of the listed spans, return it unchanged.",
  "auto": false
}

The model can be a provider/model-id selected from Pi's authenticated model registry or an unqualified exact model ID. The effort menu shows only levels supported by that model's thinkingLevelMap; null means no requested effort. Old unsupported values clamp to the model's lowest supported level. Fast mode requests the provider's priority service tier when supported.

auto is false, true, or a daily window such as { "start": "22:00", "end": "06:00" }. Anything else loads as false.

The default tone prompt lists the span categories the model may edit and declares everything else content. That framing is deliberate: earlier versions listed protected content instead, and the model resolved tone/content conflicts by deleting or restructuring the clause. The default effort is medium because this prompt measurably degrades at low.

Install

Install order does not matter, because pacification runs above the extension handler chain:

pi install npm:proper-pacify

For extension development, install a local checkout instead:

pi install /path/to/proper-pi-extensions/proper-pacify

The package has no runtime dependencies and no build step, so a local install needs no npm install; that command only prepares the development checks below. Remove any stale direct-file registration so Pi loads one copy.

Development

Use Node 22.19 or newer. Pi 0.84.2 is the compatibility target.

npm install
npm test
npm run typecheck
npm run test:coverage