prose
The sentences the agents write to each other.
Reference
- Runs
- nothing. It reads the run journal itself.
- Ships as
- on, as a warning
- Needs
- nothing
How it works
Every other writing check reads your code. This one reads the handoffs.
What a contract is
When hyperpower runs, each step writes a JSON file into
.hyperpower/runs/<run-id>/. The next step reads it. That file is the contract,
and it is the only thing the next agent sees. A real one, shortened:
{
"step": "understand",
"summary": "This handles the settings flow properly.",
"behaviour": "The stale value is returned by loadSettings",
"assumptions": [
{ "id": "a1",
"claim": "the settings endpoint probably returns a bare array",
"confidence": "low" }
],
"seams": [
{ "must_agree_on": "the json format" }
]
}
The shape is typed and validated, so claim is always a string. Whether that
string means one clear thing is a separate question, and no schema can check it.
That is this check's job.
It matters most for claim. A reviewer files a finding, then a skeptic reads
the code and returns confirmed, refuted, or plausible. Plausible means the code
could not settle it. A vague claim cannot be confirmed or refuted, so it comes
back plausible, and plausible is the one result nobody can close. Fuzzy writing
upstream turns into a human reading diffs downstream.
The thirteen fields it reads
Keys that hold a sentence. Everything else in the file, including ids, paths,
numbers and the deliberately unshaped notes array, is skipped.
| Field | Holds | Written by |
|---|---|---|
claim |
a fact relied on, or a defect found | every step |
reason |
why an option was dropped | every step |
summary |
how the affected code works today | the mapper |
behaviour |
the requirement as observable behaviour | the mapper, the designer |
covers |
what an existing test covers | the mapper |
question |
something still unresolved | the mapper, the planner |
why_unresolved |
why searching did not settle it | the mapper |
task |
one requirement to implement | the planner |
asserts |
what a test must assert | the planner |
approach |
the chosen approach | the planner, the fix loop |
constraint |
what bounds a choice | the planner |
why |
why a file changes, or why a decision went that way | the planner, the designer, the mapper |
failure_scenario |
inputs, then the wrong output | the reviewer |
One more field is checked differently. must_agree_on names what two sides of a
boundary have to agree about, and it takes one of six terms rather than a
sentence: shape, nullability, unit, encoding, ordering,
error contract.
The seven rules
Each rule has a name you use in config. The right-hand column is the same sentence written so the next agent cannot misread it.
| Rule | Catches | Rewrite |
|---|---|---|
vague |
a word nothing can verify | handles QUIC properly → reads the SNI from the first QUIC datagram |
passive |
the actor is missing | the error is swallowed → handleFlow swallows the error |
hedge |
doubt inside the sentence | getUser may return null → getUser returns null, with confidence: low |
pronoun |
opens on It, This, That, They | This breaks on retry. → retryWrite breaks on the second attempt. |
one-sentence |
two sentences in a one-sentence field | split it, or narrow the claim |
too-long |
over the word limit, 25 by default | split it, or narrow the claim |
vocabulary |
a must_agree_on outside the six terms |
the json format → shape |
hedge is the one that pays for the others. The contract already carries doubt
as typed values: confidence is high, medium or low, and the skeptic's verdict
is confirmed, refuted or plausible. A hedge in the sentence duplicates a field
that already exists, and the two can disagree. getUser may return null with
confidence: high states two different things, and the next agent picks one.
Assert in the sentence, doubt in the field.
Configuring it
Nothing is fixed. Rules, fields, word lists and the vocabulary are all lists
under gates.prose, and each takes the same three forms.
| Form | Effect |
|---|---|
vague: [...] |
replaces the shipped list |
vague_add: [...] |
keeps the shipped list and appends |
vague_remove: [...] |
keeps the shipped list and drops |
_add and _remove are what you normally want. Replacing words.vague
means retyping twenty-five words to change one.
Start narrow while a repo has a backlog:
gates:
prose:
rules: [vague, hedge]
Then teach it your tree and promote it once a run comes back clean:
gates:
prose:
blocking: true
max_sentence_words: 20
rules_remove: [passive]
words:
vague_add: [flow, pipeline]
hedge_remove: [should]
fields_add: [acceptance_note]
vocabulary:
must_agree_on_add: ["retry budget"]
fields_add is how a contract field your own fork added gets covered without
touching the check. A vocabulary term may contain a space.
Two things to know before you tune it. An empty list means the shipped list, not
nothing: rules: [] carries no value, so it arrives as an absent key and the
default applies. Use enabled: false to check nothing. And a rule name the
check does not implement reports "did not run" rather than quietly checking less
than you asked for, as does a _remove that empties a list.
Whatever you set, the evidence names the rules and the fields that ran and flags a set that is not the default, so a narrowed check cannot be read as a full one.
What a pass does not mean
A pass means the sentence is checkable. It does not mean the sentence is true. Controlled language makes a wrong claim sound authoritative, which is the one failure mode this check adds. Truth stays with the skeptic.
The rules come from ASD-STE100, the controlled English written for aircraft maintenance manuals. Those manuals are not written to read well. They are written so a mechanic at 3am cannot misread them.