Curation v2: progress handoff and per-step agent briefs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A1rCLQeKBgnBo3oTgHuTMe
This commit is contained in:
2026-09-02 04:16:59 +00:00
co-authored by Claude Fable 5.1
parent 8b51454c20
commit 76527e3821
9 changed files with 653 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
---
# YOUR STEP: 1 — Feedback and profile (plan §21 step 1)
Scope (plan §6, §7, §8; tests in §20 under Vote, Rating events, Migration, and the prompt):
1. **Migration `migrations/0002_curation_v2.sql`** (§7). Create `rating_events`,
`article_embeddings`, `interest_embeddings`, `article_assessments`, `candidate_runs` with
the exact columns/indexes in §7; add `runs.config_json`, `runs.provider_costs_json`,
`issue_articles.why`. Copy `ratings` rows into `rating_events` per §6.2 (`vote=1 → 'loved',1.0`;
`vote=-1 → 'not_for_me',-1.0`; `source='migration'`; `kind='explicit'`; `event_at=rated_at`),
then `DROP TABLE ratings; DROP TABLE feed_priors;`.
**Orchestrator decision:** do NOT drop `scores` in this step. Stage A scoring
(`db::upsert_score`, `recently_low_scored_ids`) still writes/reads it until step 4 replaces
it with `article_assessments`; step 4 will add a `0003` migration dropping it. Do not edit
`0001_init.sql`. Migration is plain SQL, no Rust bootstrap.
2. **Three-way `Vote`** (§6.1) in `src/types.rs`: `Loved | Good | NotForMe`, `as_str`
`"loved" | "good" | "down"`, `parse` also accepts legacy `"up"``Loved`, `value(&FeedbackConfig)`
→ configured 1.0 / 0.35 / 1.0. Add `[curation.feedback]` config
(`loved_value`, `good_value`, `not_for_me_value`, `verdicts_in_prompt = 60`). Remove `Vote::as_i64`,
`Rating`, `FeedPrior`, and everything that depended on `ratings`/`feed_priors`
(`db::upsert_rating`, `ratings_with_feed`, `upsert_feed_prior`, `feed_priors`,
`profile::rebuild_feed_priors`, the feed-prior term in `prefilter.rs` and `ScoredArticle.feed_prior`
— set the prefilter's feed-prior contribution to nothing; the field can go). HMAC links
(`src/auth.rs`) must verify for all three votes; message stays `{issue_date}/{article_id}/{vote}`.
3. **Footer + confirmation page** (§6.1): `RatingLinks` in `src/epub/chapters.rs` and
`src/epub/templates/chapter.xhtml` render three links on one line sized for e-ink:
`Was this a good pick? [ Loved it ] [ Good ] [ Not for me ] Read online ↗`.
X4 edition still renders no links. `server::handle_rating` appends one `rating_events` row
(`kind='explicit'`, `source='epub'`, label `loved|good|not_for_me`, value from config) and
returns "Recorded: Loved it — thanks." (etc.) plus the other two links so a mis-tap can be
corrected. No feed-prior rebuild, no provider call.
4. **`rating_events` access** (§6.2) in `src/db.rs`: `append_rating_event(...)`,
`db::current_ratings(lookback_days) -> Vec<RatedArticle>` implementing the latest-explicit-event
rule (`ORDER BY event_at DESC, id DESC`, `cleared` removes the article from the learned set),
joining `articles`, the best entry's feed title, and the most recent `issue_articles.summary`.
Move `RatedArticle` to `src/types.rs` with `summary: Option<String>`, `facets: Option<Facets>`
(define `Facets` per §12.1 now, all `Option`s, unused until step 5), `note: Option<String>`,
plus `RatingEvent`. Also define `RatedArticle.value: f64` and `label`.
5. **Ratings CLI** (§6.3) in `src/main.rs`: `ratings list [--days 90] [--label loved|good|down|cleared]`,
`ratings set --article ID|--url URL --label loved|good|down [--note "..."]`,
`ratings clear --article ID|--url URL`. `set`/`clear` append rows with `source='cli'` and
`issue_date` from the latest `issue_articles` row for the article if any; `--url` canonicalizes
with `dedupe`'s canonical URL function then `db::article_id_for_url`. Print something useful.
6. **`data/profile.md`** (§8.2): create the file with the initial content shown in §8.2 verbatim;
add `profile_path` config (default `data/profile.md`); loader that parses any `## Interests`
section (one per line, leading `- ` stripped) and unions it case-insensitively with the OPML
interests, passing everything else through verbatim. Remove `STATED_PREFERENCES` and the
reader/wants/does-not-want/how-to-judge parts of `PROFILE_PREAMBLE` from code (they now live
in the file); the editor-in-chief framing paragraph stays in code. If the file is missing,
warn and continue with the OPML interests only.
7. **System prompt** (§8.4): rebuilt every run in this exact order: framing → profile.md verbatim
(minus Interests) → standing interests grouped by `profile::themes::group_into_themes`
learned adjustments → **recent verdicts**: up to `verdicts_in_prompt` most recent explicit
ratings, newest first, `LOVED | title | feed | one-line summary` (labels `LOVED`/`GOOD`/`NOT FOR ME`),
cleared and duplicate articles removed. Byte-identical within a run. `profile_version` bumps only
on the weekly rebuild, as today. `kv` keeps `ingest_watermark`, `taste_profile`,
`taste_profile_learned`, `profile_version` (§7.8).
8. **Weekly rebuild** (§8.3): still on the DeepSeek client this step (the Claude editor client
arrives in step 2). Each rated line carries
`LOVED | title | feed | summary | facets: format/depth/evidence/technicality/topic_group | note: …`
(omit empty parts), up to 200 most recent explicit ratings, cleared excluded; the softened
"strong prior, not a rule" instruction; the required "Diversity check:" bullet.
9. Pipeline: remove the `rebuild_feed_priors` call and every `feed_priors` reference; the paper
must still build. `handle_rating` and `profile rebuild` must not touch dropped tables.
Tests to add/adapt (§20): Vote parse/serialize incl. `up`→Loved; HMAC for all three; template
renders three links standard / none X4; latest explicit event wins; `cleared` removes from the
learned set; migration on a temp DB through 0001 then 0002 copies old ratings with the right
labels/values and `ratings`/`feed_priors` are gone; CLI `set`/`clear` append `source='cli'` rows
(test the db-level function); profile.md Interests parsing and union; system prompt section order
and verdict block content. Update `tests/m7_server.rs` and `tests/m4_epub.rs` as needed.