demonstration 2 of 11 · news.html

A feed that only ships what grounds

This page polls real news sources, but only on your say-so, and it only turns what comes back into facts it can point at. What it can't ground goes on a ranked list instead of into a made-up sentence. A worker running in AWS Lambda does the polling and the grounding; this page only renders what it reports.

What it demonstrates

news.html carries no engine of its own. Press start, enrich now, or teach it something, and the browser sends one request to a same-origin /api/ route. A news worker Lambda does the real work: it polls the sources, grounds what it can against your session's own graph, ranks what it can't, and writes a feed document the page reads back with GET /api/feed.

Five real feeds are on by default, and every one of them polls when you press start: a Wikimedia featured-articles feed, Hacker News, USGS earthquakes, NYT World News, and Wikinews. Untick a source's checkbox to leave it out of the next poll.

The page opens on the same seven-part dashboard as before: how many feed items it has built, how many terms it has met but can't yet ground, how many facts came from news, the graph's current size, how many sources reported in the last cycle, a ranked list of the ungrounded terms, and a per-source health strip.

Ground a term and the worker folds the new fact into the cards that mention it, the next time it materializes the feed. Every item still names its sources and shows a trust chip, the same tiers the ledger page uses.

news.html deliberately demos the hosted architecture a consumer would run. It uses the same row service, worker, and turn endpoint tmct ships, with the browser reduced to render-and-controls. The offline-in-your-browser claim stays with the other demo pages.

Next What you can try

What you can try

Before you press anything, the feed is empty and the page has made no request at all. That is the honest first-visit state, not a placeholder feed.

no news yet — the feed only shows named people, places and events the worker has reported. press start to fetch some.

Press start and the browser posts to the poll trigger, gets a 202 back with a cycle id, and waits for the worker to finish before it renders the cards. The button reads “start polling live sources” until you press it, then “poll now” on every visit after, because the consent preference sticks.

click: start polling live sources
POST /api/sessions/<uuid>/poll answers 202, a cycle id comes back
the page waits for feedVersion to move, then renders the materialized feed

Asserted in test/adapters/news-browser-entry.test.mjs and test-e2e/pages-news-feed.test.mjs: no request fires before any press; start() mints the session key, persists consent, and settles once its own poll cycle materializes; and the page never requests the old seed asset or an engine bundle, before or after.

Teach it something directly and the same ingest trigger grounds it on the spot. The teach panel still has two ready-made examples: a short paragraph and a two-line fact set.

teach: "A ceasefire is a formal agreement to stop fighting. A tariff is a tax on imported goods."
terms.ungrounded drops by one for each term the sentence grounds

If the service doesn't answer, the page says so instead of guessing: “The news service isn't answering right now, so there's nothing to show. Reload the page to try again.” Every button disables until a later request gets through.

Stop & forget deletes every row and the materialized feed under your session key, then forgets the key itself. A reload after that reads as a first visit, the same as if you had never pressed anything.

Asserted in test-e2e/pages-news-feed.test.mjs: stop & forget purges the session server-side, discards the local key, and a reload reads back as first-visit; and killing the row service leaves the page honestly idle, with every control disabled until the service answers again.

A chat area is landing next, right under the teach panel. It will carry the same session key to POST /api/sessions/<uuid>/turn, with replies coming back straight away instead of through a poll.

Next What it looks like

What it looks like

The dashboard strip, the ranked ungrounded-terms panel, and the newest three feed items on top.

The tmct news page showing the dashboard tiles, the ranked list of ungrounded terms, and the newest feed items
news.html. Feed items, the ranked ungrounded-terms panel, and a source health strip.
Next What it works out

What it works out

Grounded is two different questions. A term is vocab-grounded when the lexicon knows the word at all, and fact-grounded when the graph holds at least one fact about it. “Ceasefire” is a real English word the parser reads fine, and the graph might still hold nothing about it — the ranked list labels that case “parseable but knowledge-free”, and labels a word the parser can't read at all “unknown word”, so the two problems never look like one.

Ranking is an invitation, not a verdict. The ungrounded list is sorted by how often a term turned up, most first. A term at the top of the list isn't wrong or bad, it's just the biggest gap the graph has right now. Ground it and it drops off the list; the worker's own enrichment tries the top few terms itself, each cycle.

A source that fails is reported as a failure. Every fetch goes through one courtesy gate: a timeout, a rate limit or a dead source all read back as a failed poll, never as a fact. Three failures in a row and the source auto-disables itself, visibly, rather than quietly going stale.

A miss can expire. A term the enrichment cycle tried and got nothing back for sits in a negative cache for a day, so it doesn't keep re-asking a source that just said no. It comes back into play once the cache expires, or the moment you switch on a source that hasn't tried it yet.

What it retrieves. Each feed item is built around one hub term and everything within two hops of it in the graph, so it reads as a set of grounded sentences about one thing, with sources named underneath, never as a rewritten article.

Next How it is built

How it is built

src/services/news.mjs is still the one place every surface goes through: the CLI's tmct news verb, chat.html's own /news command, and now server/news-worker/handler.mjs, the Lambda this page's poll, enrich and ingest triggers invoke. No surface can drift from what the others do.

Feed parsing is string scanning over RSS, Atom and JSON Feed, plus the sources' own JSON shapes, in src/domain/feed-normalize.mjs — no XML library, because a domain module can only import other domain modules. Every fetch runs through createCourtesyGate, the same single-slot, rate-limited, timeout-bound gate every other live source in this repo already uses. It runs inside the worker now, not the browser, so the worker's own outbound HTTP is allow-listed to exactly the source roster and the knowledge-base hosts it looks terms up against, nothing else.

The row service, the worker, and the turn endpoint are the same stack a consumer would host: an anonymous, per-session partition in DynamoDB, a seven-day TTL, and one materialized feed document the worker rewrites after every cycle. server/row-service/README.md and archive/PLAN_MEMORY_BACKEND.md's own thin-client section carry the full design.

Grounding reuses the same ingest path chat.html's own teach turn uses: strict facts from sentences the grammar fully reads, a lower-confidence optimistic tier for the rest. A bounded syllogism round runs after each batch, so a handful of new facts can chain into a few more without anyone asking a follow-up question.

Next Related work

Related work

The refusal this page is built around, and the wire formats it reads, both follow published work rather than an invented shape.

  • Chow, “On optimum recognition error and reject tradeoff”, IEEE Transactions on Information Theory 16(1), 1970.The reject option: a classifier allowed to decline. An ungrounded term goes on the ranked list instead of into a guessed sentence.
  • Reiter, “On Closed World Data Bases”, in Logic and Data Bases, Plenum, 1978, pp. 55–76.The open world assumption. A term with no fact yet is a gap in the graph, not a claim that nothing exists to know.
  • Winer, “RSS 2.0 Specification”, Berkman Klein Center for Internet & Society, 2003 (revised 2009).The wire format behind the NYT World and Wikinews feeds.
  • Nottingham and Sayre, eds., RFC 4287, “The Atom Syndication Format”, IETF, December 2005.The second feed format the parser reads.
  • Ungerleider et al., “JSON Feed Version 1.1”, jsonfeed.org, 2020.The third feed format the parser reads.
Next Credits and further reading

Credits and further reading

The sources this page can read, and what each one costs to use.

  • Wikimedia featured feed, Hacker News and USGS earthquakes are on by default: no key, no cost.
  • NYT World News is on by default too. Its feed is published for personal, non-commercial use with attribution, so this page links every NYT item back to the story and names the outlet.
  • Wikinews is on by default as well — its own front page currently announces the Wikimedia Foundation closing the site, so the health row here is what tells you if that feed goes quiet.
  • Simple English Wikipedia, Wikidata and Wiktionary back the worker's knowledge-base lookups by default. DBpedia Lookup and English Wikipedia sit in the registry too, but this page has no control that switches them on.
  • README, “Standards and bibliography”, for the full reference list.
Next demo The sprite library