Signals: index the judge's query, six calls at once, one call per item on the server's key, tokens counted, a week's look-back an admin sets, edits that ask again only what was picked out (#274, #275, #276)

The judge's query read all of entries (605 MB, 566 ms) on every wake; an
index on each feed's items by date, as the query asks, and the rechecks
in a half of their own. Calls ran one at a time, about 150 ms each, so a
new Signal's month of about 8,000 items took twenty minutes: six in flight
now. On the server's key everyone's Signals on an item share one call.
TypeSafe's token counts go to /metrics and each jev span.

An item's text is cut at 1,000 characters instead of 2,000: on 55 real
items of eight podcast feeds and six Signals, on the development key,
1,000 changed none of 330 verdicts (nor did the same request twice) and
saved 12% of the input tokens, 1,266 to 1,112 a call; 500 changed one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 14:13:25 +00:00
parent 026fbd1622
commit 2077ea5810
16 changed files with 293 additions and 106 deletions

View File

@@ -77,24 +77,33 @@ scan worker, so a slow TypeSafe never holds up a scan.
`Db::unjudged` finds, newest first, up to 50 items that are:
- in a feed the Signal's owner subscribes to,
- published (or, with no date, first seen) in the last 30 days (`SIGNAL_DAYS`),
- not hidden from them by their block lists,
- not yet in `signal_verdicts` for that Signal,
- for someone with a key that TypeSafe has not refused since the daemon started.
- for someone with a key that TypeSafe has not refused since the daemon started,
- and either published (or, with no date, first seen) in the look-back window and not yet in
`signal_verdicts` for that Signal, or picked out by it before it was edited (`recheck`), however
old.
So a new Signal looks back a month, and after that sees each new item. A show subscribed to today
has its back catalogue skipped, apart from its last month.
The window is the admin's, Admin, Server, **Signals look back (days)** (`signal_days`, 7 by
default, 1 to 365; #276): each item in it is a call, and a month of production's feeds was about
8,000. So a new Signal looks back that far, and after that sees each new item. A show subscribed
to today has its back catalogue skipped, apart from the window.
The recent half reads the index `entries_recent`, on each feed's items by date, the expression it
is asked by; without it each wake read all of `entries`, 605 MB, 566 ms to find nothing (#274).
Rows for one item and one person are grouped, so an item is one call whatever the number of
Signals. A full batch means there may be more, and the next pass starts at once. Otherwise the
judge sleeps until woken or for 5 minutes.
Signals. On the server's key, everyone's Signals on the same item go in one call too, its text
sent once (#275). Six calls are in flight at once (`IN_FLIGHT`): one at a time, about 150 ms each,
a month of items took twenty minutes. A full batch means there may be more, and the next pass
starts at once. Otherwise the judge sleeps until woken or for 5 minutes.
### Every answer is kept
`signal_verdicts` records each answer, fitting or not. An item with no row has not been asked yet;
an item with a row is never asked again. So the cost is one call per item per person, once.
Changing a Signal's name, words or sentence deletes its verdicts, and everything in the window is
asked again. The first start with sentences (#259) deleted every verdict, since they answered the
Changing a Signal's name, words or sentence marks what it picked out to be asked again
(`recheck`), and those stay in its list until answered; what it passed over stays passed over, and
new items are asked the new question as they come (#276). The first start with sentences (#259) deleted every verdict, since they answered the
old question.
## TypeSafe
@@ -134,7 +143,9 @@ Sent with `Authorization: Bearer <the key>`, the server's or the person's, for a
```
- `state` is text: the feed's title, the item's title, and its show notes with the HTML taken out,
cut to 2,000 characters (`TEXT_CHARS`).
cut to 1,000 characters (`TEXT_CHARS`). It was 2,000 until #275: on 55 real items and six
Signals, 1,000 changed none of 330 verdicts and saved 12% of the input tokens (1,266 to 1,112 a
call); 500 changed one. Most of the input is the questions themselves, not the item.
- Each question is keyed `signal_<id>`, and its answer comes back under the same key.
- `criteria` is an object saying what true and false mean. A sentence there is refused with a 422
(#257).
@@ -373,10 +384,10 @@ erDiagram
- **Refused keys are remembered in memory**, so a restart tries each once again.
- **Keywords changed while an item is being judged** can leave that one item's verdict from the old
words.
- **There is no meter** of how many calls a key has made. It is one call per item in the person's
feeds in the window: a person with 1,500 feeds busy enough to publish 3,000 items a month spends
about 3,000 calls the first month and then as many as come in.
- **Only the last 30 days** are ever judged, by publish date.
- **The meter is the server's**, `ipx_jev_tokens_total` and `ipx_jev_requests_total` on
`/metrics`, and the Signals row of the dashboard: tokens as TypeSafe's answers count them. It is
one call per item in the person's feeds as they come, and per item in the window for a new Signal.
- **Only the window** is judged by publish date, apart from what an edit asks again.
## Testing
@@ -387,4 +398,6 @@ erDiagram
- `npx playwright test -g Signal`: makes a Signal from the folder's +, and checks that the matching
item shows and that reading it reads it in its feed and clears the badge. TypeSafe is stood in for
by the fixture server's `/jev` (`IPX_TYPESAFE_URL`), which refuses the shape TypeSafe refuses, and
`/signal.xml` is dated today, since only the last 30 days are judged.
`/signal.xml` is dated today, since only the window is judged. They spend the TypeSafe key, so
each runs only when the Signals code or the test has changed since it last passed (`signalsTest`;
`IPX_SIGNALS_TESTS=1` runs them anyway).