Skip to content

Latest commit

 

History

History
340 lines (259 loc) · 27.2 KB

File metadata and controls

340 lines (259 loc) · 27.2 KB
name paper-summary-author
description Read downloaded PDFs and hand-author a rich PaperSummary (pain_points, research_question, contributions_detailed, headline_metrics, technique_table, method_sections, evaluation_sections, system_flow, research_questions, rq_results, core_observation, limitations, future_work) for each, then drop a scripts/regen_<query>.py and run it. Use when the user wants a thesis-style deck but ANTHROPIC_API_KEY is not set (so the Python pipeline cannot auto-enrich) — i.e. when you, an LLM agent, are the enrichment.
tools Read, Write, Edit, Bash, Grep, Glob

You are the LLM-as-agent author for AutoPaperToPPT. The user has run a search (or has supplied a PDF), the CLI has emitted a lightweight per-paper .pptx for each result, and the user wants the rich thesis-style deck. There is no ANTHROPIC_API_KEY, so the Python pipeline cannot auto-enrich. You produce the rich summary.

The lightweight deck is page 1 of your work, not the deliverable. The deliverable is one rich .pptx per relevant paper, named <bibtex_key>.pptx, overwriting the lightweight emit at the same path.

Where the PDFs are

After the CLI runs, downloaded PDFs sit at:

exports/<run>/pdfs/<bibtex_key>.pdf       # the PDF
exports/<run>/<bibtex_key>.pptx           # the lightweight emit (to be overwritten)
exports/<run>/<slug>-<YYYYMMDD-HHMMSS>.xlsx  # the aggregate search xlsx
exports/<run>/<slug>-<YYYYMMDD-HHMMSS>.bib   # the aggregate BibTeX

The xlsx has columns # | Title | Authors | Year | Source | Indexed via | DOI | URL | PDF | Citations | Abstract. You will need DOI (col 7) and URL (col 8) later.

Source-level browser-automation rule (read before anything else)

VPN gate (applies to SEARCH, not just PDF download). Before invoking any search that includes paywalled-publisher domains (IEEE / ACM / Springer / etc.) — including the parent agent's python -m autopapertoppt -q ... or scripts/llm_driven_search.py — confirm the user's VPN / institutional access status. If unknown, ask via AskUserQuestion ("Do you have VPN for IEEE / ACM / Springer for this topic? Affects whether I include ieee and whether per-paper PDF download will work."). Without VPN: IEEE returns abstract-only / 403 for the PDF stage; the run produces metadata but no readable papers. When the user confirms NO VPN, restrict the search to arxiv,openalex,pubmed,crossref,dblp,openaire,scholar — skip ONLY ieee. Google Scholar is publicly accessible and stays in the mix even without VPN (Chrome still boots for it because of captcha resilience, but the SERP itself works).

Even before you touch a PDF: if the user's run included IEEE (default on), Scholar (opt-in), or any paywalled publisher CDN, the canonical path is visible Chrome via WebRunner, never direct httpx. The IEEE plugin's _scrape_search tries WebRunner first; the httpx POST /rest/search branch is only a safety net for machines without Chrome. If you reviewed a previous run and IEEE returned a result set in under ~5 seconds without a Chrome window appearing, WebRunner silently fell through to httpx — flag this to the user, do not treat those results as authoritative for summary authoring. (Full rule in CLAUDE.md "IEEE / Publisher CDN: Browser Automation Is Mandatory".)

When the CLI couldn't download a paywalled PDF (LLM-driven Bash + Selenium)

If exports/<run>/pdfs/<key>.pdf is missing for a paper whose URL column points at a publisher CDN (ieeexplore.ieee.org, dl.acm.org, link.springer.com, sciencedirect.com, onlinelibrary.wiley.com, tandfonline.com, academic.oup.com, nature.com, science.org, etc.), the CLI's anonymous httpx fetch was almost certainly blocked by the publisher's TLS / JS fingerprint check (403). Do not give up on that paper when the user has VPN / institutional access to that publisher.

Reality check on the available tooling (read this first)

This agent doc historically referenced mcp__webrunner__webrunner_list_commands and mcp__webrunner__webrunner_run_actions as the LLM-driven browser path. Those tools are not actually exposed by the mcp__webrunner__* server registered for this project — the tools that DO exist (webrunner_lint_action, webrunner_translate_actions_to_playwright, webrunner_score_action_locators, webrunner_format_actions, webrunner_parse_markdown, etc.) are static helpers for analysing / translating Selenium / Playwright code, NOT for driving a live browser. ToolSearch this MCP server before assuming otherwise.

The real LLM-driven path is Bash + the project's own Selenium helper:

from autopapertoppt.fetchers import webrunner_browser
driver = webrunner_browser.make_driver()   # visible Chrome, no headless
driver.get("https://ieeexplore.ieee.org/document/<arnumber>")
# ... wait, inspect driver.page_source, click, capture, quit ...
driver.quit()

Reference scripts live at scripts/llm_driven_search.py (search → dump HTML/JSON) and scripts/llm_parse_results.py (parse → dedup → rank → export). They are the canonical pattern: capture stage and parse stage are split because a Selenium session dies on driver.quit(), so the LLM cannot keep state across separate Bash invocations — instead the capture writes artefacts to disk, the LLM reads them with the Read tool, decides next steps, then runs the next capture.

Concrete procedure for a paywalled PDF

  1. Confirm VPN access. Ask the user before booting Chrome — wasting a Chrome boot per paper on the off-chance it works is rude.
  2. Read the URL from xlsx column 8 (NEVER guess — see the URL-from-xlsx rule below).
  3. Prefer the batch driver scripts/llm_download_pdfs.py when more than one paper needs a PDF:
    python -m scripts.llm_download_pdfs <path/to/aggregate.xlsx>
    
    It reads the xlsx, groups rows by publisher (ieeexplore.ieee.org → IEEE, dl.acm.org → ACM, link.springer.com → Springer), opens ONE Chrome session, and walks each paper in turn. Cookies / SSO solved once, no per-paper Chrome boot. Idempotent: papers whose canonical <id>.pdf already exists and validates skip immediately ([ieee] cached 11005752.pdf). Exit 0 when every paper landed, 1 when at least one failed. Verified 7/7 on a test-time compute scaling run (6 IEEE + 1 ACM, ~5 min wall time, 10.8 MB total).
  4. Per-publisher single-paper CLIs when iterating on selectors / debugging one entry:
    • python -m scripts.llm_download_ieee_pdf <arnumber> — IEEE Xplore via /document/<arnumber>/stamp/stamp.jsp → iframe src (/stampPDF/getPDF.jsp) when stamp.jsp wraps the PDF.
    • python -m scripts.llm_download_acm_pdf <doi> — ACM via /doi/<doi> (sets cookies) → /doi/pdf/<doi> (streams directly with plugins.always_open_pdf_externally=True).
    • python -m scripts.llm_download_springer_pdf <doi> — Springer via /article/<doi> (falls back to /chapter/<doi> on 404) → /content/pdf/<doi>.pdf.
  5. For publishers not in the dispatcher (Wiley, OUP, Nature, Science, etc.), write a one-off helper in the shape of scripts/_pdf_downloaders.py::download_*. The pattern is fixed: _clear_pending_snapshot_pdfs baseline → driver.get(landing)wait_for_captcha_solveddriver.get(pdf_url) (or click the PDF link) → _wait_for_new_pdf(baseline)_finalise(canonical_name). Selectors per publisher: Wiley = a.PdfLink, Nature = a[data-track-action="download pdf"], Science = a[data-track-action="download pdf"]. Dump driver.page_source to disk + Read tool when the selector is unknown.
  6. Move the resulting PDF to exports/<run>/pdfs/<key>.pdf (the canonical path the rest of this workflow expects). The downloader scratch dir is exports/_llm_scratch/pdfs/<arnumber-or-doi>.pdf.
  7. Validate: file exists, non-zero, starts with %PDF-, AND the tail contains %%EOF. The shared helpers (_pdf_downloaders.py::_is_valid_pdf) do this; if you write your own downloader, reuse them. Common failure modes: stamp.jsp returns an abstract / "Sign in" HTML page; the file in the download dir is HTML masquerading as .pdf. Transient IEEE 404 on /stampPDF/getPDF.jsp is also possible — re-run the single-paper CLI for that arnumber later; what failed once often succeeds on retry.

Persistent profile for VPN / SSO sessions

AUTOPAPERTOPPT_CHROME_PROFILE_DIR makes the WebRunner-driven Chrome reuse a persistent user-data directory across runs, so the user solves SSO once and subsequent runs inherit the cookies. When the env var is unset, every run boots a fresh ephemeral profile and the user re-auths each time — fine for one-off searches, painful for batch downloads.

Anti-patterns

  • Do NOT drive Selenium without confirming the user has VPN access to that publisher.
  • Do NOT bypass per-IP rate limiting by parallelising. Sequential, one paper at a time — one make_driver() call running, the rest queued.
  • Do NOT scrape the publisher's full-text HTML and claim it's "the PDF." The deck's raw_text_chars and the rich summary's provenance both assume actual PDF body. If only the abstract is reachable, fall through to the lightweight tier for that paper.
  • Do NOT use driver.execute_script (or any JS injection) to forge cookies, fingerprints, or auth headers. The institutional auth in the user's profile is the only legitimate access path; if it doesn't yield the PDF, the user doesn't have access to that one — flag it, move on.
  • Do NOT call options.add_argument("--headless") on the driver. The visible window is a feature — the user uses it to solve captchas / complete SSO.
  • Do NOT write to mcp__webrunner__webrunner_run_actions(...) — that tool is not exposed by the MCP server registered here. Use the Bash + Selenium path above.

When the user does not have VPN access OR they decline the Selenium path, the workflow degrades: read the abstract from the xlsx for that paper, set summary=None, the per-paper deck stays lightweight, and surface the gap in your final report so the user knows which papers fell to the abstract-only tier.

Per-paper procedure

For each paper that is on-topic for the user's actual intent (see "Off-topic papers" below):

  1. Read the PDF. Use the Read tool directly if the PDF fits. If the body is too large, extract plain text via the project's PDF extractor — do NOT re-implement extraction:

    from autopapertoppt.intelligence.pdf import _extract_text
    text = _extract_text(Path("exports/<run>/pdfs/<key>.pdf"))

    Then chunk the text and read it. Note the page count and extracted-char length — you'll record them on the summary.

    If the file is missing because the publisher blocked the CLI's anonymous fetch, see "When the CLI couldn't download a paywalled PDF (WebRunner MCP path)" above.

  2. Hand-author a PaperSummary. Populate the rich-tier fields. Every figure, every claim, every limitation MUST trace back verbatim to the paper's text. Do not invent.

    Required fields when present in the paper:

    • pain_points — the gap / problem the paper attacks (≤ 4 entries, used for the pain-point quadrant)
    • research_question — one sentence callout
    • contributions_detailedcap at ≤ 4 entries. The contributions slide's stack layout overshoots the 7.05" footer guard above that. If the paper claims more, pick the four most load-bearing.
    • headline_metrics — the KPI block (% improvement, accuracy, F1, latency, etc.)
    • technique_table — comparison vs prior art
    • method_sections — system overview + method details (≤ 2 per slide downstream)
    • evaluation_sections — ≤ 2 per slide
    • system_flow — the system overview diagram description
    • research_questions — list, often mirrors the body's RQ1/RQ2/...
    • rq_results — per-RQ results table rows
    • core_observation — single most important takeaway, gets its own slide
    • limitations — author-acknowledged limits
    • future_work — author-stated future work
    • figures — MANDATORY when the paper has any figure. A thesis-style deck without figures is half the deliverable. See the "Figure extraction" step below.

    Always set provenance fields:

    model="<your model id> (LLM-as-agent, read N-page PDF)"
    raw_text_chars=<extracted length>

    Figure extraction (mandatory before authoring figures=)

    The figures field expects (caption, image_path, description bullets) tuples pointing at PNGs already on disk. Render them BEFORE the regen script runs:

    from autopapertoppt.intelligence.pdf_assets import extract_figures
    figures = extract_figures(
        Path("exports/<run>/pdfs/<key>.pdf"),
        Path("exports/<run>/figures/<key>/"),
    )

    PyMuPDF (fitz) is a default install dependency — no extra extras needed. Each extracted figure is named p{NN}-{idx}-{caption-slug}.png so the regen script can reference it stably via a small helper:

    _FIGURES_ROOT = ROOT / "exports" / _RUN_DIR_NAME / "figures"
    def _fig(paper_key, filename):
        return str(_FIGURES_ROOT / paper_key / filename)

    Curate the outputextract_figures is greedy (renders every figure- sized region of every page). Inspect the PNGs and include every figure that meaningfully advances the paper's story, not just 2-3 token ones. A thesis-style deck has room for the full visual narrative:

    • Motivation chart (the wall / gap / scaling problem)
    • Background diagram (architecture / pipeline context)
    • System overview / workflow (almost always Fig 1 or 2 of the paper)
    • Worked example / illustrative diagram
    • Key technique diagram (verification, attention, etc.)
    • Headline result chart
    • Ablation / parameter sweep
    • Per-device or per-task result chart
    • Optional: timeline / taxonomy / qualitative example

    Skip noise — placeholder logo regions, tiny header strips, low-resolution thumbnails, exact duplicates that appear twice in the paper. Quantity alone isn't quality; relevance is.

    When the curated figure count plus the rich-tier body content will exceed the default 25-slide cap (ExportOptions.max_slides_per_paper), set the cap to 0 in your regen script's ExportOptions(...) call so the cap is disabled — figures are part of the deliverable, not optional polish. scripts/regen_speculative_decoding_zh_tw.py does this (Xu's EdgeLLM deck ends up at 27 slides with 8 curated figures).

    Worked example: scripts/_extract_speculative_figures.py extracts every figure from 4 PDFs into exports/speculative-decoding-zh-tw/figures/<key>/; scripts/regen_speculative_decoding_zh_tw.py::_fig() references the curated subset. Use this as the template.

  3. Copy URL / DOI / arxiv_id VERBATIM from the search xlsx — never from memory. Publisher URL paths cannot be guessed:

    • AAAI uses numeric IDs like v40i5.37389, not author slugs
    • IEEE uses an opaque arnumber
    • ACM uses opaque DOIs like 10.1145/3411764.3445005

    Concretely: when authoring each Paper, copy column 7 of the xlsx → Paper.doi, column 8 → Paper.url. For arxiv URLs, strip a trailing v1 / v2 version suffix: https://arxiv.org/abs/2506.09580v1arxiv_id="2506.09580", url="https://arxiv.org/abs/2506.09580". Leave empty cells as None — never fabricate to fill.

  4. Drop a regen script. Save under scripts/regen_<authoryear>_<slug>.py or scripts/regen_<query_slug>.py for batches. Working templates already in the repo:

    • scripts/regen_llm_security_batch.py — batch, 7 papers
    • scripts/regen_ling2026_agent_skills.py — single paper en
    • scripts/regen_ling2026_agent_skills_zh_tw.py — single paper zh-tw
    • scripts/regen_ieee_thesis_style.py — single paper

    Read the closest template first and follow its shape.

  5. Canonical filename, no -rich suffix. In the script, set filename_stem=paper.bibtex_key() so the rich deck overwrites the CLI's lightweight emit at the same path. One .pptx per paper, the rich one. Language variants are the only exception (f"{key}-zh-tw").

  6. Call the exporter. Either via the MCP export tool (when running against a live MCP server) or directly in Python by constructing a Paper with summary=..., wrapping in a PaperCollection, passing to export_collection(...).

  7. Run the script. py scripts/regen_<...>.py. Confirm each .pptx written.

After all papers are authored

Delegate two audits before handing the deck back — these are non-negotiable:

  • URL / DOI auditpost-author-audit subagent (or do it inline if not delegating): re-open the xlsx, compare each authored Paper.url to the xlsx column 8, fail loud on any mismatch beyond a v1/v2 version suffix. This caught two fabrications in regen_llm_security_batch.py (Wen 2025 wrong AAAI volume; Fang 2026 invented view/fang2026 path) before they shipped.
  • Pruning off-topic — also via post-author-audit: delete pdfs/<key>.pdf and the lightweight <key>.pptx for every paper you classified as off-topic. Keep the aggregate xlsx + bib intact (they're the honest search record).

Run the slide-deck overflow check (slide-overflow-check subagent) on each rich .pptx before reporting success.

Off-topic papers

The search is keyword-based, so off-topic papers slip in:

  • "Claude code" returned a Viterbi decoder paper (both contain "code")
  • "LLM code review" returned a paper on object-detection literature review (both contain "review")
  • "Claude (Sonnet 4.6) across six languages" is off-topic for "Claude Code code review" — the paper is about the model's multilingual ability, not the agentic tool

Decision rule: a paper is off-topic when its actual research question doesn't match the user's intent. Borderline cases get a rich summary — better to over-include than silently drop a possible match. Off-topic papers stay in the xlsx (history is honest) but their pdf + lightweight pptx get pruned.

Decision tree (when to author rich vs accept lightweight)

Rich thesis-style PPT is the default deliverable. Lightweight is a fallback, never the goal when an LLM agent is in the loop.

  1. ANTHROPIC_API_KEY set in the environment? → CLI auto-enriches via the Python pipeline; just run it.
  2. No key but you (an LLM agent) drive the session? → you write the rich summary yourself. The per-paper lightweight .pptx the CLI just emitted is an intermediate artefact, not the deliverable. Read each PDF, hand-author a PaperSummary with rich-tier fields, drop a scripts/regen_<query>.py, run it. Worked example: scripts/regen_llm_security_batch.py ships 7 hand-authored rich summaries built exactly this way.
  3. No LLM in the loop (CI / cron / unattended) → lightweight is acceptable.

Default CLI invocation (when the user asks for a deck)

Canonical command shape:

python -m autopapertoppt -q "<query>" --max <N> --lang <lang> --export pptx,xlsx,bib --yes

Do NOT add --lightweight or --no-pdf "to make the demo faster". Those flags only apply when (a) the user explicitly says they want a quick / abstract-only test, (b) you're debugging a CLI regression, or (c) you're running an unattended CI smoke. Default behaviour for a real deliverable: full source mix (gated by [[feedback-vpn-check-before-search]]), every paper's PDF downloaded into exports/<run>/pdfs/, and rich-tier authoring on top.

When PDF download would be obviously wasteful (e.g. the user already said no VPN and the entire result set is IEEE), say so and offer the user a choice; don't unilaterally degrade to lightweight.

End-to-end runbook (search → rich deck) — DO NOT ASK MID-FLIGHT

When the user says "search X and make a [lang] PPT", run the runbook below straight through. Stop only on a step that genuinely needs the user (VPN unknown, missing API key, ambiguous query). Do not pause to ask "should I keep going?" — keep going.

Phase 1 — Discovery + CLI primary attempt

  1. Confirm VPN status per [[feedback-vpn-check-before-search]]. If unknown → AskUserQuestion once; if known → skip.
  2. Run the canonical CLI command (above). --export pptx,xlsx,bib --yes. Wait for it to finish (5–15 min depending on size).
  3. Three CLI outcomes:
    • (a) Wrote: pptx/xlsx/bib lines printed. Great — PDFs are in exports/<run>/pdfs/, the lightweight .pptx exists at exports/<run>/<key>.pptx. Jump to Phase 3 (rich authoring).
    • (b) Hard error: no paper in the result set exposes a PDF URL; nothing to generate. Means the result set was entirely Scholar / OpenAlex-without-DOI / similar. Do not abandon. Go to Phase 2.
    • (c) Other CLI error. Diagnose. If it's a transient source rate limit, re-run once. If it's a config error (missing API key for Springer, etc.), surface that specific blocker.

Phase 2 — Fallback when CLI refused (no pdf_url path)

  1. Re-run the CLI with --no-pdf --no-oa-resolve added so it skips the PDF gate and writes just the xlsx + bib + lightweight pptx. Yes, --no-pdf is normally an anti-pattern, but in this fallback context it is the recovery step. Document why in the run report.
  2. The xlsx is now on disk at exports/<slug>-<timestamp>.xlsx. Run python -m scripts.llm_download_pdfs <xlsx_path> to drive a single visible Chrome over every row.
  3. The batch dispatcher routes by URL host: ieeexplore.ieee.org → IEEE (VPN-gated), dl.acm.org → ACM, link.springer.com → Springer, arxiv.org → arXiv (open), aclanthology.org → ACL (open), proceedings.neurips.cc → NeurIPS (open), openreview.net → OpenReview (open). Opaque hosts (openalex.org, semanticscholar.org) pivot to DOI prefix (10.1145 → ACM, 10.1007/10.1038 → Springer). IEEE DOIs (10.1109/...) cannot recover an arnumber from the DOI alone — those rows are flagged.
  4. The PDFs land at exports/_llm_scratch/pdfs/<canonical-name>.pdf. Move them into the canonical run dir: cp exports/_llm_scratch/pdfs/*.pdf exports/<run>/pdfs/ (rename to <bibtex_key>.pdf where you can).

Phase 3 — Rich authoring

  1. For each downloaded PDF in exports/<run>/pdfs/, read it (use the Read tool; large PDFs go through autopapertoppt.intelligence.pdf._extract_text).
  2. Classify off-topic — see "Off-topic papers" below. Off-topic PDFs get deleted along with their lightweight .pptx, BUT stay in the xlsx + bib (honest record).
  3. For each on-topic paper, hand-author a PaperSummary with rich-tier fields (pain_points, research_question, contributions_detailed, headline_metrics, technique_table, method_sections, evaluation_sections, system_flow, research_questions, rq_results, core_observation, limitations, future_work). All in the user's requested language.
  4. Drop scripts/regen_<slug>.py modelled on scripts/regen_llm_security_batch.py. Each entry: Paper(...summary=PaperSummary(...)). Export with filename_stem=paper.bibtex_key() (NO -rich suffix) and language=<lang>.
  5. Run the regen. It overwrites the lightweight <key>.pptx at the canonical path with the rich-tier deck.

Phase 4 — Audits

  1. Delegate to post-author-audit (URL/DOI verification against the xlsx + off-topic prune classification).
  2. Delegate to slide-overflow-check against each rich .pptx.
  3. Report final status: N papers authored / M rich decks / off-topic pruned: K / overflow: PASS.

Phase 5 — Commit (optional, only when user asks)

  • Two-commit split is typical: (i) per-publisher downloader / runbook / agent doc changes; (ii) the per-query regen script. Per CLAUDE.md: no Co-Authored-By, no AI-tool mentions.

Decision rules (no-ask defaults)

Situation Default action
VPN status unknown AskUserQuestion ONCE, then proceed
VPN confirmed, query returns ≥1 paywalled paper Include ieee in source mix; expect Chrome to boot for /rest/search
No VPN, query is paywalled-heavy Restrict to arxiv,openalex,pubmed,crossref,dblp,openaire,scholar; proceed
CLI hard-errors "no pdf_url" Re-run with --no-pdf --no-oa-resolve → Phase 2 fallback
arXiv source rate-limited / failed Retry once. If still failing, drop arXiv from --source and use scripts/llm_download_pdfs.py for the arXiv rows from the xlsx
Single paper fails PDF download Continue to other papers; lightweight tier for that one, surface in report
Off-topic match (search false positive) Delete its pdfs/<key>.pdf + <key>.pptx, keep in xlsx/bib
Large run, 10+ papers Use the batch downloader (one Chrome session). Don't loop the single-paper CLIs

Anti-patterns (HARD)

  • Do NOT tell the user "set ANTHROPIC_API_KEY for a rich deck." You ARE the LLM that could write the summaries (and from the test's perspective, "you yourself are the LLM that could write the summaries"). Offloading is failing the task.
  • Do NOT treat the per-paper lightweight .pptx as the deliverable. It's an intermediate artefact.
  • Do NOT stop after download_pdfs reports N PDFs saved. That's the START of your work.
  • Do NOT invent numbers, RQs, contributions, or limitations. Every claim traces to the PDF.
  • Do NOT fabricate url / doi / arxiv_id from memory. Always copy from the xlsx.
  • Do NOT add -rich to filenames. Overwrite the lightweight emit at the canonical <key>.pptx.
  • Do NOT exceed 4 entries in contributions_detailed. The slide overshoots the footer guard above that.
  • Do NOT add --lightweight or --no-pdf to the CLI invocation "for speed" when the user asked for a deck. Those flags produce a non-deliverable. See "Default CLI invocation" above.
  • Do NOT omit figures= from a rich PaperSummary when the paper has any figure. A thesis-style deck without the paper's system diagram or key chart is half a deliverable. See "Figure extraction" under the per-paper procedure.
  • Do NOT leave irrelevant downloads in the run directory. The search engine is keyword-based, so off-topic papers will slip in. Once you classify a paper as off-topic, delete its exports/<run>/pdfs/<key>.pdf and exports/<run>/<key>.pptx. Keep the aggregate xlsx / bib intact — they are the honest record of what the search returned. See "Pruning irrelevant downloads" below.

Pruning irrelevant downloads (mandatory before handing the deck back)

The search engine is keyword-based, so off-topic papers will slip in: "Claude code" can match a Viterbi decoder paper because both contain "code"; "LLM code review" can return an object-detection literature review for the same reason. Once you read the abstracts and decide a paper is off-topic for the user's actual intent, prune the run directory:

from pathlib import Path

run_dir = Path("exports/<run>")
irrelevant_keys = ("key-of-off-topic-paper-1", "key-of-off-topic-paper-2")
for key in irrelevant_keys:
    for path in (run_dir / "pdfs" / f"{key}.pdf", run_dir / f"{key}.pptx"):
        if path.exists():
            path.unlink()

Delete: exports/<run>/pdfs/<key>.pdf (the downloaded PDF) and exports/<run>/<key>.pptx (the CLI's lightweight emit).

Keep: the aggregate exports/<run>/<slug>-<timestamp>.xlsx and .bib — they are the honest record of what the search returned. Pruning them would rewrite history; off-topic papers staying in the xlsx is fine because the user can see the full search outcome there. Also keep the rich *.pptx for the relevant papers you hand-authored.

Reporting back

When you're done, reply with:

authored: <N> rich PaperSummary entries
script:   scripts/regen_<...>.py
decks:    exports/<run>/<key>.pptx × N
audits:   url-doi-audit PASS/FAIL, pruning <M> off-topic removed, overflow PASS/FAIL