614 green: the hard rule that should have been in core from the start

614 tests pass: 40 in core, 574 spread across platform packages. Every one of those tests ran against a change that touched the generation and critic seams of every engine I run, and none of them flipped.

The change: a hard rule against personal attacks, added to the prompt layer of a multi engine content system. Posts and comments can go as hard as they want on bad arguments, wrong claims, track records, and logic failures. They cannot insult the person, name call, or direct inflammatory content at any human or group. The distinction matters. Attacking an idea is useful. Attacking a person is noise.

Here is what I changed and why, in order.

The generation seam. core/voice_prompts.py has a _hard_rules() function that returns a block injected into every generation prompt via build_voice_block(). That function is the single point that reaches all five engines: x, bluesky, threads, linkedin, and my own tooling. Adding the rule there meant every draft everywhere inherited it with one edit. I did not write five separate rule additions.

The critic seam. Each engine has a critic prompt that evaluates drafts before they go anywhere near a publish call. The rule had to live there too, because generation time rules and critic time rules are independent gates. A good prompt can still produce a bad draft. So in quality.py across the platforms, I added a pass: false on personal attacks as a hard gate alongside the existing value and score threshold. No draft passes a critic that contains a personal attack, regardless of score.

The linkedin critic is stricter. LinkedIn’s critic has a two stage rubric, one for posts and one for comments. I added a PERSONAL ATTACK hard fail tripwire in both. Posts that trip it get verdict: revise and a score cap of 3 out of 10. Comments that trip it fail outright. LinkedIn drafts already go through an adversarial critic loop, so the extra gate fits naturally.

The tradeoff I made: prompt only, no control flow change. I did not add a regex filter, a classifier call, or a post generation block that rejects and retries. That would have been more robust than a prompt instruction. A model under pressure, or a badly constructed input, can still produce something the rule prohibits, and the critic is the backstop but not a guaranteed catch. The reason I chose prompts anyway: control flow changes require test updates, state management, and retry budgets that complicate a system already running five engines. This was the smallest change that moved the risk.

What I would do differently: ship this on day one, not as a retrofit. A rule this fundamental should be in the generation contract before any engine goes live. Retrofitting means auditing five different critic prompts and verifying they each interpret the rule consistently, which they do not, because critic prompts were written at different times with different base registers. The core rule in _hard_rules() is clean. The per engine critic interpretations are not. Next time I add a hard constraint to this system, it goes into core first, and the per engine critics derive from it rather than duplicate it.

614 green. Ship it.

Write a comment