Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1rCLQeKBgnBo3oTgHuTMe
8.5 KiB
Personalized Curation v2 — implementation progress and handoff
Updated: 2026-09-02 (end of session 2)
Plan: docs/plans/2026-09-02-personalized-curation-v2.md (§21 is the step sequence)
Branch: curation-v2 (branched from main at a599745; not merged, not pushed)
Briefs: docs/plans/curation-v2-briefs/ — 00-preamble.md + one stepN.md per step. Each
brief was handed to an implementation agent as cat 00-preamble.md stepN.md.
Where things stand
| Step (§21) | Status | Commit |
|---|---|---|
| 1. Feedback and profile | done, reviewed (codex review: no actionable issues) | 3a9f4b9 |
| 2. Claude editor and editorial | done | 57efbb4 |
| 3. Embeddings, signals, telemetry | done (branch curation-v2-step3, merged) |
ea3b141 + merge commit |
| 4. Triage replaces the gate | done (Codex plugin, high effort), reviewed | 10f4afd |
| 5. Deep assessment, utility, diversity | done (Codex started, cut off by quota; Claude agent finished), reviewed | 05a74a0 |
| 6. Paper telemetry, stats, lock | done (Claude agent), reviewed | d261cd4 |
| 7. Cleanup + implementation notes | done (Claude agent), reviewed | d403c51 |
All seven plan steps are implemented. cargo fmt --check, cargo clippy --all-targets
(including -W dead_code) and cargo test (329 lib tests + 6 bin tests + the 7 integration suites) are green at HEAD.
Nothing has been run against the production database or the real providers. No local
config.toml, database, or API keys exist on the dev box, so verification so far is tests only.
Decisions and deviations made while implementing
Steps 1–3 (session 1):
- Legacy
uplinks.Vote::parse("up")→Loved, andauth::verify_tokenalso accepts tokens signed over the literalupsegment so already-published issues keep working. Keep both. - Same-date regeneration no longer excludes its own picks (
published_beforeuses issue dates strictly before the run date), per plan §8.1. VoyageConfig.api_keyexists as a field (figment mapsDAILY_EPUB_VOYAGE__API_KEYinto it;deny_unknown_fieldswould otherwise reject the env var). Never document it in TOML.anthropic.max_concurrent_requestsis validated but not consumed; summary concurrency is the constantSUMMARY_CONCURRENCY = 4ineditorial.rs.RATINGS_LOOKBACK_DAYSinprofile/mod.rsis effectively unbounded (36,500) for the prompt verdict block and the weekly rebuild; the knn/feed preference state usescuration.ranking.rating_lookback_days(180) as the plan says.- Footer CSS:
.ratinghas nowhite-space: nowrap(it would clip on narrow e-ink screens).
Step 4:
- Hygiene order is blocked → published_before → recently_rejected, all skipped for auto-includes.
admitted_byrecords every retriever whose own top-N would have taken the article (first entry is the admitting one); exploration is flagged only whenexplorationadmitted it.- Under the pool cap (
triage_max) the surplus getsstage='eligible',excluded_reason='not_admitted'. - Migration
0003_drop_scores.sqldropsscores; the churn rule readsarticle_assessments.
Step 5:
Candidateis the only flow type;ScoredArticle,combined_score,score.rs,select.rsare gone.- The editor omits the
facets:line when every facet is unknown (plan renders it unconditionally). - Articles cut at the shortlist keep the stage they reached (
admittedif never deep-assessed, elseassessed) withexcluded_reasoncluster_suppressed(hit the base cap) orshortlist_cap. Curator::assessis a no-op under--skip-llm; cached deep rows are still reused when DeepSeek is merely down.--near-missesand the Behind-the-paper list order by utility, falling back to the preliminary blend per row.- Fixtures:
tests/fixtures/deepseek_deep_batch{,_messy}.jsonreplace the old score fixtures;deepseek_triage_batch.jsonadded in step 4.
Step 6:
BehindThePaperhangs offIssue.behind(filled afterbuild_issue), not abuild_issueargument. Generation time and cost shown in the chapter are measured at issue assembly (they exclude the EPUB build and publish, a few seconds); the cost includes Voyage.- Voyage spend is recorded under
provider_costs["voyage"];finish()counts it once and keeps its tokens out of the LLM aggregate. Voyage spend is not preloaded into its meter for the UTC day. src/lock.rsuseslibc::flock(libcwas already inCargo.lock; now a direct dependency). The holder's command name is written into the lock file for the "X is already running" message.- The mid-run
admission:line isdebug; the four-line §15.4 block is logged once afterfinish()and printed byprint_report. Timingssummariesandbriefreplaceeditorial.
Step 7:
features prunesweepsarticle_embeddings,candidate_runsand nowarticle_assessments;generateruns it once after a published (non-dry) issue, best effort.- The Brief chapter's TOC/title string is now "The Brief" (was still "From the Editor").
- Kept on purpose:
UsageMeter::new(&DeepseekConfig, ..)(a convenience overwith_prices, 12 call sites);StageCounts.candidates(feedsruns.candidatesand the colophon); theprefilter_keep/score_batch_sizeremoved-key startup errors (operator guards); the SQL comment "filled in by step 4/5" inside the applied migration0002(sqlx checksums); six v1-era unreferencedpubitems (enrich_one,issues_before,today_in_tz,to_json_pretty,with_retry_policy,db::issues_before) as pre-v2 API surface. config::tests::shipped_example_config_matches_the_defaults_key_for_keycompares the example againstConfig::default()in both directions (numbers within 1e-6).
Operator to-dos before the first real run
- Set
DAILY_EPUB_ANTHROPIC__API_KEYandDAILY_EPUB_VOYAGE__API_KEYin the systemd env file. - Set hard spend limits in the DeepSeek, Anthropic and Voyage dashboards (the meters are runaway guards, not accounting).
- Copy
data/profile.mdto whereverprofile_pathpoints on the server (default is relative toWorkingDirectory=/var/lib/daily-epub, likedata/scour-interests.opml). - Back up the DB, then
daily-epub db migrate(0002 dropsratings/feed_priors, 0003 dropsscores). daily-epub features backfill --rated-onlythen--days 30to warm the embedding cache.daily-epub generate --dry-run, read the paper (including the new Behind-the-paper chapter), thenexplain --date … --near-missesandstats.- Watch the first few real runs'
providers:log line against the ~$0.80/day estimate (plan §3).
How the work was run (so the next session can repeat it)
- Orchestrator: Claude Code (this repo), one implementation agent per step, review + commit by the
orchestrator after independent
cargo fmt/clippy/test. - Codex via the
openai-codexClaude Code plugin (v1.0.6) works on this host as of session 2 (the operator fixed bubblewrap). Launch:node ~/.claude/plugins/cache/openai-codex/codex/1.0.6/scripts/codex-companion.mjs task --background --write --effort high "$(cat brief.md)", poll… status <job-id> --json(.job.status), read… result <job-id>. Inside its sandboxbind()on 127.0.0.1 is forbidden, so exactly ten pre-existing loopback tests fail there (curate::llm::tests::anthropic_*×4,extract::tests::relative_urls_resolve_against_the_url_we_landed_on,server::tests::*×5, plus the twom7_serverintegration tests); the brief must tell the agent to ignore those and the orchestrator runs the full suite outside. The old "host quirk" paragraph in00-preamble.mdis obsolete. - Codex usage budget is roughly one large step per ChatGPT usage window: in session 2 it finished
step 4 (~26 min) and was cut off 21 minutes into step 5 ("try again at 7:44 PM"). The job status
becomes
failedwith the partial work left in the tree; a Claudegeneral-purposeagent with a "finish, don't restart" brief (known failures, scope audit checklist) completed it. Steps 6 and 7 ran on Claude agents directly. - Steps 4 → 5 → 6 → 7 were sequential, each reviewed against the plan sections named in its brief before committing.
Next session: what remains
git checkout curation-v2 && cargo test(expect green).- Optional: a Codex
review --background --scope branch --base mainpass over the whole branch. git merge --no-ff curation-v2intomain, build, deploy, and do the operator to-dos above.- After a week of real runs: read
stats, tune[curation.ranking]from whatexplainshows, and revisit the deferred items in plan §23.