Paper Scoring Digest
paper-scoring-digest is the scheduling and delivery adapter for Paper
Scoring. It keeps arXiv discovery, local retention, Slack presentation, and
discussion state outside the provider-neutral core packages.
The daily workflow:
- queries
cs.AI,cs.DB, andcs.LGas one arXiv candidate pool; - removes cross-list duplicates before scoring;
- ranks the merged pool and publishes one combined Top 20 (not 20 per category);
- downloads and retains the selected PDFs;
- renders an email-like Slack card through OpenClaw; and
- deletes PDFs that have reached seven days of age.
Metadata, scores, and arXiv links remain after the PDFs expire, so an Agent can still explain an earlier ranking. A discussion that requires an expired PDF must fetch it again from arXiv explicitly.
Runtime contract
- Python 3.11, 3.12, or 3.13 on Ubuntu and Windows.
- Docker Engine or Docker Desktop with Docker Compose v2.
- Paper Scoring package-to-package dependencies come only from PyPI. Git, local-path, and workspace dependency overrides are not used in production.
- Secrets are injected at process start. They are not copied into wheels, container images, reports, GitHub Actions, or OpenClaw cron arguments.
- Scheduling and retention use the configured IANA timezone. Automatic arXiv
discovery selects the latest completed UTC submission date. The production
schedule below uses
Asia/Taipei. - Slack delivery uses a stable channel ID and a configured OpenClaw Slack account. Slack tokens remain in OpenClaw's runtime secret store.
- On native Windows, collection, scoring, state inspection, and retention are
supported, but Slack delivery rejects npm
.cmd/.batshims because Windows may parse them throughcmd.exedespiteshell=False. Run delivery from a trusted Linux/WSL/Docker OpenClaw host or a native OpenClaw.exelauncher.
Install from PyPI
Ubuntu:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --index-url https://pypi.org/simple "paper-scoring-digest==0.1.0"
Windows PowerShell:
py -3.13 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --index-url https://pypi.org/simple "paper-scoring-digest==0.1.0"
Copy .env.example to a location outside the repository, fill only the
provider values you need, and restrict access to that file. On Ubuntu:
install -d -m 700 "${HOME}/.config/paper-scoring"
install -m 600 .env.example "${HOME}/.config/paper-scoring/digest.env"
On Windows, store the file under a user-only directory and use the file's
Security properties or icacls to remove access for other users. Do not place
the file in the repository. Put the digest state directory under the same
user-only ACL: POSIX 0700/0600 mode changes do not configure Windows DACLs.
For an OpenAI-backed run, set PAPER_SCORING_PROVIDER,
PAPER_SCORING_MODEL, and OPENAI_API_KEY. For a local Ollama-backed run,
set the provider to ollama, select an installed model, and optionally set
OLLAMA_BASE_URL; no cloud API key is needed.
Run once
The following is an illustrative production-shaped invocation. Replace every angle-bracket placeholder; never paste a credential into the command line.
paper-scoring-digest run \
--env-file <ABSOLUTE_RUNTIME_ENV_FILE> \
--state-dir <ABSOLUTE_PRIVATE_STATE_DIRECTORY> \
--category cs.AI \
--category cs.DB \
--category cs.LG \
--top-n 20 \
--retention-days 7 \
--timezone Asia/Taipei \
--slack-channel <SLACK_CHANNEL_ID> \
--slack-account <OPENCLAW_SLACK_ACCOUNT>
By default the service pages through the complete daily category union before
ranking, so "Top 20" covers the full discovered day. The optional
--candidate-limit 60 changes the meaning to "Top 20 from the latest 60
de-duplicated candidates" and bounds model cost; the Slack context labels that
scope explicitly. Re-running is idempotent: a pending delivery is handled
before a new daily run is created.
Useful read-only commands:
paper-scoring-digest show --run-id <YYYY-MM-DD> --rank 3
paper-scoring-digest show --run-id latest --paper-id <ARXIV_ID>
paper-scoring-digest presentation --run-id latest
paper-scoring-digest prune --state-dir <ABSOLUTE_PRIVATE_STATE_DIRECTORY> --retention-days 7 --timezone Asia/Taipei
Each successful run writes a private manifest plus text, HTML, and PDF reports
under the state directory. Only PDFs are removed by retention. Each manifest
stores pdf_expires_at, calculated from the instant when the digest was
created; an old arXiv submission downloaded during catch-up therefore still
receives the full 168-hour retention period.
OpenClaw and Slack
Install the canonical cross-Agent Skill from PyPI, then install it into the dedicated OpenClaw workspace:
python -m pip install --index-url https://pypi.org/simple "paper-scoring-skills[openai]>=0.2,<0.3"
paper-scoring-skills install --client openclaw --scope project
openclaw agents add paper-research \
--workspace <ABSOLUTE_OPENCLAW_AGENT_WORKSPACE> \
--model <PROVIDER/MODEL> \
--non-interactive
Configure the target Slack channel with replyToMode: "all" and add an exact
peer binding to paper-research. The fragment below is illustrative:
{
"channels": {
"slack": {
"channels": {
"<SLACK_CHANNEL_ID>": { "replyToMode": "all" }
}
}
},
"bindings": [
{
"agentId": "paper-research",
"match": {
"channel": "slack",
"accountId": "<OPENCLAW_SLACK_ACCOUNT>",
"peer": { "kind": "channel", "id": "<SLACK_CHANNEL_ID>" }
}
}
]
}
bindings is an array and a config patch replaces arrays. Export and back up
the current OpenClaw config, merge this item with every existing binding, run
openclaw config patch --dry-run, and only then apply it. Do not paste the
fragment directly over a live bindings array.
Create the job disabled, test it, and enable it after the card and fallback delivery have both been verified:
openclaw cron add \
--name paper-scoring-daily-digest \
--declaration-key paper-scoring-daily-digest-v1 \
--cron "15 19 * * *" \
--tz Asia/Taipei \
--exact \
--command-argv '["<VENV_BIN>/paper-scoring-digest","run","--env-file","<ABSOLUTE_RUNTIME_ENV_FILE>","--state-dir","<ABSOLUTE_PRIVATE_STATE_DIRECTORY>","--category","cs.AI","--category","cs.DB","--category","cs.LG","--top-n","20","--retention-days","7","--timezone","Asia/Taipei","--slack-channel","<SLACK_CHANNEL_ID>","--slack-account","<OPENCLAW_SLACK_ACCOUNT>","--cron-mode"]' \
--command-cwd <ABSOLUTE_OPENCLAW_AGENT_WORKSPACE> \
--timeout-seconds 14400 \
--announce \
--channel slack \
--account <OPENCLAW_SLACK_ACCOUNT> \
--to channel:<SLACK_CHANNEL_ID> \
--disabled
The digest sends a portable OpenClaw presentation that is rendered as Slack
Block Kit: a title, run context, one section per ranked paper, dividers, and a
thread instruction. The command's normal success output is suppressed in cron
mode; --announce is only a fallback if the command fails before direct card
delivery.
After the card arrives, reply in its thread, for example:
3, 7:比較這兩篇的研究問題、方法與實驗結果
The exact channel binding routes the reply to paper-research. The Skill maps
the displayed rank to that card's stored run, reads a retained PDF when
available, and answers in the same thread. It does not silently re-score a
paper.
Docker
The image builds the current package locally while resolving every Paper
Scoring dependency from the locked PyPI graph. The Dockerfile copies only the
package inputs; it never copies .env or the state directory.
Set the runtime env-file path in the shell that launches Compose:
export PAPER_SCORING_ENV_FILE="${HOME}/.config/paper-scoring/digest.env"
docker compose build
docker compose run --rm paper-scoring-digest --help
PowerShell:
$env:PAPER_SCORING_ENV_FILE = "$HOME\.config\paper-scoring\digest.env"
docker compose build
docker compose run --rm paper-scoring-digest --help
The Compose service uses a named volume for /data, which avoids host
ownership differences between Ubuntu and Docker Desktop. Provider credentials
are loaded only through env_file when the container starts.
To collect, score, and persist a digest in the container without attempting to
access the host's OpenClaw CLI, use --no-deliver:
docker compose run --rm paper-scoring-digest run \
--no-deliver \
--state-dir /data \
--category cs.AI --category cs.DB --category cs.LG \
--top-n 20 --retention-days 7
The manifest remains pending; a trusted OpenClaw host with access to the
same state directory can later run the normal delivery command without
rescoring.
The Python image intentionally does not bundle OpenClaw or a Slack token. Use it for collection, ranking, report/state inspection, and retention inside a container; run Slack delivery from the trusted OpenClaw host (or extend the official OpenClaw image with this PyPI package). This keeps the Gateway's credentials and config out of the digest image.
Verification and release evidence
Pull requests and main run the following gates:
- Ubuntu and Windows tests on Python 3.11-3.13, with branch coverage and JUnit reports;
- Ruff, formatting, strict mypy, wheel/sdist build, and Twine validation;
- a non-root, read-only Docker smoke test;
- Bandit,
pip-audit, anddetect-secrets; and - a redacted Gitleaks scan of the complete Git history plus a noreply-author policy check.
The workflows upload test, package, and security reports as GitHub Actions artifacts even when a gate fails. Reports must be reviewed before merging, tagging, or publishing to PyPI. Artifacts are evidence, not a place to store runtime credentials.
For a local equivalent using only the locked PyPI dependency graph:
uv sync --locked --no-sources --extra test --extra security
uv run --frozen ruff check .
uv run --frozen ruff format --check .
uv run --frozen mypy
uv run --frozen coverage run --branch -m pytest
uv run --frozen coverage report --show-missing
uv run --frozen bandit -c pyproject.toml -r src
uv run --frozen pip-audit
uv build --no-sources --default-index https://pypi.org/simple
uv run --frozen twine check dist/*
Before a release, also scan untracked files and the complete Git history. Never publish when any report contains an unexplained secret candidate, personal path, private email address, channel ID, token, or credential.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file paper_scoring_digest-0.1.0.tar.gz.
File metadata
- Download URL: paper_scoring_digest-0.1.0.tar.gz
- Upload date:
- Size: 35.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca7921806f5c03a26ffa2e26d1aca66b3f7f363aaeff2c810fad74cd71884d99
|
|
| MD5 |
f0762d22029eaa2c8bb217a6dc68b4e9
|
|
| BLAKE2b-256 |
09bf72ecc8bd0ef89c6705242840cd53c1a08c2b98eb2e84012a8bc287103dfc
|
File details
Details for the file paper_scoring_digest-0.1.0-py3-none-any.whl.
File metadata
- Download URL: paper_scoring_digest-0.1.0-py3-none-any.whl
- Upload date:
- Size: 26.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b756db3e6b4d9416bdf83750d6e216f33b9691c373cb59bfb66d1c445e4625a
|
|
| MD5 |
a295fe109111dbc08febbdf3a02786e9
|
|
| BLAKE2b-256 |
848093a25d9713633524847781d1895ad7b7c84402ff615e15e14e71fab5f234
|