Design principles
Aparture's job is to let a researcher stop skimming the arXiv without worrying about what they're missing. Every design decision serves one of three requirements that job imposes: the briefing must be worth reading, it must be checkable, and the user must stay in control.
The principles below are how those requirements show up in practice. Each one points at the place in the codebase where it is enforced, so you can verify the claim rather than take it on faith.
1. The profile is the tool's memory
Your profile is a plain-English document you wrote, and it is the only representation of your interests the pipeline has. Every stage reads it. It grows the way memory should: each star, dismissal, and comment either becomes a proposed edit to the profile — shown to you as a diff you accept or reject — or it gets discarded. Nothing about your preferences is stored anywhere you can't read and edit.
In the code: hooks/useProfile.js (every pipeline stage reads profile.content), pages/api/suggest-profile.js (feedback → proposed edits with per-change rationales).
2. You decide what gets read in full
The pipeline pauses at three points: after filtering, after scoring (before any PDF is read), and before the briefing is written. Each gate shows what the next stage will work on and lets you change it. You can move a paper between filter buckets, star a paper into the PDF set or dismiss it, and comment before synthesis. The score-review and pre-briefing gates also show the projected cost of the stage they hold back. A "Skip remaining gates this run" link exists for days you don't need them, and each gate can be turned off in Settings.
In the code: the three gates in lib/analyzer/stages/startProcessing.js; components/run/ReviewGateBanner.jsx.
3. Spending follows relevance
Cheap models read everything; stronger models read only what survives. Each pipeline stage has its own model slot. The defaults are chosen at each registry refresh from the registry's pricing and capability data: a lighter model on the high-volume slots (the filter and quick summaries) and a stronger one on scoring, PDF reading, and the briefing. You can move any slot up or down. Prompt caching and cross-run duplicate detection exist for the same reason, to avoid paying twice for the same reading.
In the code: utils/models.js (registry with per-model pricing), lib/analyzer/applyDedupe.js, the caching paths in lib/llm/.
4. Synthesis is the product, and claims must be checkable
The briefing argues across papers. That editorial connection is the point, and it is allowed. Claims about individual papers are not given the same freedom: numbers, methods, and attributions are audited against the same abstracts and full reports the writer was given, and every cited arXiv ID, in a card or in the prose, must exist in the set of papers the model was actually given. Code also puts each paper's real title and score on its card and checks that every final-round paper has a card in a theme. A briefing that fails these checks gets one repair from the model, then a repair in code that labels what it added or removed. When the audit flags unsupported claims, synthesis is retried once by default, and the verdict and flagged claims are saved with the briefing.
In the code: lib/synthesis/validator.js (corrections, citation and coverage checks, salvage), pages/api/check-briefing.js, lib/synthesis/checkCorpus.js, and prompts/check-briefing.md (the audit pass).
5. Filter permissively, judge strictly
Early stages err toward letting papers through; later stages re-judge alignment from scratch instead of trusting the stage before. A paper that scraped through the filter still has to earn its score, and a high score still has to earn its place in the briefing.
In the code: prompts/rubric-scoring.md (the scoring rubric explicitly does not treat filter survival as evidence of relevance).
6. Be a good arXiv citizen
Requests to arXiv are serialized, spaced with jitter, cached, and identified with a contact address when configured. Metadata requests are 3–5 seconds apart; PDF and page downloads are 15 seconds apart, the crawl delay arXiv's robots.txt sets. Harvesting uses OAI-PMH with careful date-window handling rather than hammering the search API.
In the code: lib/arxiv/spacing.js, lib/arxiv/ingest.js, lib/analyzer/rateLimit.js (the PDF download throttle).
7. Local-first
Your API keys, profile, feedback, and briefing history live on your machine, and the server listens on 127.0.0.1 unless you open it up. The current architecture is single-tenant — one researcher per instance — and hosted multi-user operation is an explicit non-goal. Future packaging (a desktop app) can add export or sync without moving your data off your machine.
In the code: .env.local for keys, browser localStorage plus repo-local reports/ for state; see Environment for the deployment model.