Blog

Sixty Hedges Read Low, One Hedge Read Medium

· NanoParse team

Litmus returns twenty signal groups in the same response body as the markdown, and hedge_language is the one most likely to be read as a verdict, because it looks like one. It has a label. The label is low, medium, or high. A label invites a conclusion.

Underneath the label are two integers and a division. Once those are visible, a 26-page sample produces two rows that look like bugs and are not: a page carrying 60 hedge markers scores low, and a page carrying one hedge marker scores medium. Both are correct. Both were measured on 2026-11-01 against the live endpoint, and both are reproducible from the committed payloads.

The field as served

Two real objects, copied out of response bodies:

{"density": "low",    "markers_found": 60, "per_500_words": 1.7}
{"density": "medium", "markers_found": 1,  "per_500_words": 2}

The first is en.wikipedia.org/wiki/Conspiracy_theory, 17,240 words. The second is cancer.gov/about-cancer/treatment, 247 words. The shorter page has one sixtieth of the markers and a higher band.

The five patterns

hedge_language is a count of phrases. The whole vocabulary, quoted from computeHedgeDensity() in the deployed source:

const HEDGE_MARKERS: RegExp[] = [
  new RegExp("\\b(?:may\\s+be|might\\s+be|could\\s+be|appears\\s+to|seems\\s+to|likely|possibly|probably|potentially)\\b", "i"),
  new RegExp("\\b(?:suggests?|indicates?|implies?|reportedly|allegedly|purportedly)\\b", "i"),
  new RegExp("\\b(?:according\\s+to|sources?\\s+say|it\\s+is\\s+believed|it\\s+is\\s+thought)\\b", "i"),
  new RegExp("\\b(?:not\\s+fully\\s+understood|remains\\s+unclear|more\\s+research\\s+is\\s+needed)\\b", "i"),
  new RegExp("\\b(?:speculative|unconfirmed|unverified|preliminary|tentative)\\b", "i"),
];

Every occurrence counts, not every pattern that matched. That distinction was a bug until 2026-10-05: the patterns are non-global, so String.match returned at most one match per pattern and markers_found could not exceed 5 on any page. The fix clones each pattern with the g flag. The comment that records it is in the source:

// 2026-10-05 audit: the marker regexes are non-global, so String.match
// returned at most ONE match per pattern and capped markersFound at 5.
// Count every occurrence via a global clone of the pattern.

The denominator

The ratio needs a word count, and the word count is not the one a reader would guess:

const bodyText = markdown.replace(/^#+\s+.+$/gm, "");
const wordCount = bodyText.split(/\s+/).filter(w => w.length > 0).length;

Two consequences. Heading lines are removed completely, so a title like "Alleged fraud in the 2026 election" contributes no words and no markers. And markdown syntax counts as words: on a link list, every ](#cite_note-CSICOP-58) is a token.

That second effect is measurable. On the measured snapshot, the Hacker News front page returned 969 words on the endpoint's denominator, of which 659 were real prose — 32% of its "words" were link targets and footnote syntax. The front page rotates, so its word count moves between runs; the share of link syntax does not. Across the 26 completed pages, stripping link targets, footnote tokens and punctuation runs changed the band on zero of them, so the dilution is real but rarely decisive. Worth knowing before arguing that a link-heavy reference page was scored against a padded denominator.

The thresholds

Two comparisons, quoted from the same function:

if (per500 > 5) return { density: "high", ... };
if (per500 > 2) return { density: "medium", ... };
return { density: "low", ... };

Converted to markers: high needs more than one marker per 100 words. medium needs more than one per 250 words. For a 4,000-word page that is 41 markers for high, 17 for medium. For the 17,240-word conspiracy-theory page, high would need 173 markers. It carried 60.

That is the whole mechanism. No model, no scoring weights, two integers.

What 35 submissions returned

Twenty-six pages completed and nine were refused before any charge: 5 blocked, 3 not_found, 1 content_insufficient. Refusals are named and cost nothing. The completed set, sorted by absolute marker count:

page                                          words marks  /500  band
-------------------------------------------- ------ ----- -----  ------
en.wikipedia.org/wiki/Conspiracy_theory       17240    60   1.7  low
en.wikipedia.org/wiki/Speculation              3332    17   2.6  medium
nhs.uk/conditions/asthma/                      1699     8   2.4  medium
nhs.uk/conditions/asthma/symptoms/             1699     8   2.4  medium
en.wikipedia.org/wiki/Hearsay                  2406     7   1.5  low
en.wikipedia.org/wiki/Web_scraping             4095     6   0.7  low
en.wikipedia.org/wiki/Rumour                   1938     5   1.3  low
mayoclinic.org/diseases-conditions/asthma/sy   2055     5   1.2  low
who.int/news-room/fact-sheets/detail/asthma    1200     5   2.1  medium
bbc.com/news/world                             1054     4   1.9  low
law.cornell.edu/wex/defamation                 1687     4   1.2  low
bbc.com/news                                   1290     3   1.2  low
bbc.com/news/technology                        1263     3   1.2  low
en.wikipedia.org/wiki/Allegation                371     2   2.7  medium
kubernetes.io/docs/concepts/overview/          1469     2   0.7  low
law.cornell.edu/wex/hearsay                     527     2   1.9  low
developer.mozilla.org/en-US/docs/Web/API/Fet    459     1   1.1  low
cancer.gov/about-cancer/treatment               247     1   2.0  medium
usa.gov/                                        539     1   0.9  low
arxiv.org/abs/2610.29823                        645     0   0.0  low
docs.python.org/3/library/json.html            3791     0   0.0  low
en.wikipedia.org/wiki/List_of_common_misconc    395     0   0.0  low
example.com                                     132     0   0.0  low
news.ycombinator.com                            969     0   0.0  low
stripe.com                                     1339     0   0.0  low
vercel.com                                       99     0   0.0  low

The words column is the denominator the endpoint divides by — the markdown with heading lines removed, then whitespace-split. It is smaller than the token count in the body, because headings contribute nothing.

Band distribution: 20 low, 6 medium, 0 high. Total markers across the 26 pages: 144.

The counts are not taken on trust. Recomputing all five patterns over each served markdown, with the same heading strip and the same whitespace split, reproduced markers_found, per_500_words and the band on 26 of 26 pages.

The two ends of the sample

The conspiracy-theory article is 17,240 words and carries 60 markers: may be, likely, speculative, unverified, allegedly, spread across a very long document. Its ratio is 1.7, so it reads low even though it holds the largest marker count in the sample — 60 of the sample's 144, with the next page at 17.

The cancer.gov page is 247 words and carries one marker, and the sentence that fired it is a navigation caption:

Find cancer clinical trials—and learn how to locate other research studies—that may be right for you or a loved one.

One marker in 247 words is 2.024 per 500. That prints as 2.0, and it still clears the medium cut, because the comparison uses the unrounded ratio rather than the printed one. The trust score picks it up: the page's trust_factors include "Moderate hedge-language density — some claims are qualified."

The highest ratio in the sample belongs to en.wikipedia.org/wiki/Allegation at 2.7, and both of its markers come from one sentence about pleading procedure:

Such a pleading may be considered disjunctive and may be permissible.

That is a statement about what a court allows, not a statement of doubt. The field counted it as hedging, twice, on a 371-word page.

What the markers fire on

The phrase histogram across all 144 markers:

  44  may be
  26  likely
  12  suggests
  12  speculative
   8  could be
   8  according to
   6  suggest
   5  allegedly
   4  potentially
   4  probably
   3  unverified
   3  purportedly
   2  seems to
   2  indicate
   2  might be
   2  not fully understood
   1  implies

may be alone is 31% of the count, and the NHS asthma page shows what it is usually doing there: "you may be offered asthma medicines to see if they help your symptoms". That is a care pathway, not uncertainty.

according to is 8 markers, and on the Wikipedia pages all eight are attribution to a named author — "According to Barkun, the appeal of conspiracism is threefold", "According to Rothbard, a "shallow" theorist observes an event and asks". Attribution is the opposite of hedging: it is a page saying where the claim came from. The pattern cannot tell the two apart, and it is not trying to.

So the honest description of the signal is narrower than its name: it counts modal and attribution grammar. Modal grammar overlaps with uncertainty, which is why the signal is useful. It is not the same thing, which is why the label should not be read alone.

The band that did not fire

No page in the sample reached high. Part of that is the sample itself — a docs site, a storefront and a link list have nothing to hedge. Part of it is arithmetic: one marker per 100 words is roughly a qualified clause in every paragraph.

The band matters more than it looks, because structural_trust_score debits it:

if (hedgeDensity === "high") {
  score += 2;
  trust_factors.push("High hedge-language density — claims are heavily qualified");
} else if (hedgeDensity === "medium") {
  score += 1;
  trust_factors.push("Moderate hedge-language density — some claims are qualified");
}

Two points separate high from low, the same weight the score gives a user-generated source, and double what it gives a stale page. So the band on a 247-word navigation page and the band on a 17,240-word article are not cosmetic details of a report. They move a number.

Before the 2026-10-05 fix the ceiling made high nearly unreachable: with at most five markers countable, clearing the high cut required a document under 500 words that matched all five patterns. The fix removed the cap. The band is still rare in practice, and the sample above is the evidence: zero of 26.

How to read it

  1. markers_found is absolute, per_500_words is length-normalized. Compare the ratio only between pages of similar length; across lengths, read the absolute count and then read the sentences.
  2. low is not confidence. vercel.com returned 113 words with zero markers and scored low: a thin page, not a careful one.
  3. On a short page one marker sets the band. If markers_found is 1, the ratio is a fact about the page length, not about the prose.
  4. Treat medium and high as "go read the sentences", not "the page is unreliable". The cancer.gov and Wikipedia rows above are both examples of the field being right about the words and wrong about the meaning.
  5. Pair it with citations and outbound_authority, which measure where a claim points rather than how it is phrased. A page with strong outbound authority and a medium hedge band is a well-sourced page that qualifies its claims, which is what good writing does.

Reproduce it

Every number here comes from a stored response body. The field is three keys:

jq '.litmus.hedge_language' response.json
# {"density": "medium", "markers_found": 8, "per_500_words": 2.4}

Recompute it from the markdown in the same body, in Python, with no access to the worker:

import re

PATTERNS = [
    r"\b(?:may\s+be|might\s+be|could\s+be|appears\s+to|seems\s+to|likely|possibly|probably|potentially)\b",
    r"\b(?:suggests?|indicates?|implies?|reportedly|allegedly|purportedly)\b",
    r"\b(?:according\s+to|sources?\s+say|it\s+is\s+believed|it\s+is\s+thought)\b",
    r"\b(?:not\s+fully\s+understood|remains\s+unclear|more\s+research\s+is\s+needed)\b",
    r"\b(?:speculative|unconfirmed|unverified|preliminary|tentative)\b",
]

body = re.sub(r"(?m)^#+\s+.+$", "", markdown)
words = len(body.split())
markers = sum(len(re.findall(p, body, re.I)) for p in PATTERNS)
per500 = markers / words * 500 if words else 0
band = "high" if per500 > 5 else "medium" if per500 > 2 else "low"
print(markers, round(per500, 1), band)

On the 26 pages above this prints the same three values the endpoint returned, every time. That property is the useful part of a deterministic signal: a reader can audit it without a model, a key, or a second opinion.

The field is served by https://nanoparse.app/fetch at $0.005 per successful parse, and to MCP clients at https://nanoparse.app/mcp in the same envelope. If the label matters to a decision, the numbers behind it are in the body — the band is a summary, and the two integers are the claim.

Related: Nothing Fired reads the trust ledger the same way, and What boilerplate_ratio Counts takes the same approach to a different ratio.

← All posts NanoParse home