Localpager is a local-first triage and paging tool for GitHub issues and pull requests.
It watches repo activity, queues new items, runs a classifier command, stores the structured result, and sends matching notifications to Discord.
This repository is an early extraction of a working triage prototype. The
current code is intentionally small: it provides the worker, queue, GitHub and
gitcrawl source adapters, classifier command boundary, SQLite state, Discord
delivery, a bundled local model runner in localpager-agent/, offline prompt
optimization tooling in prompt-optimizer/, and a small localpager
operations CLI.
localpager validate config, show status, install services, test Discord
localpager reposhell compatibility wrapper for the standalone reposhell CLI
localpager-enqueue-github enqueue GitHub issues and PRs once
localpager-ingest-json ingest one normalized item from JSON
localpager-watch poll source adapters and enqueue items
localpager-worker run classifier jobs and send notifications
reposhell standalone read-only repository shell service, exec, and shell
The worker runs a classifier command. By default it uses:
./scripts/localpager-classifier
That wrapper calls localpager-agent, which points Pi at a local
OpenAI-compatible model endpoint and forces the final answer through the
configured classifier schema.
The classifier command receives one target argument, usually a GitHub URL or
owner/repo#number, and must print one JSON object to stdout:
{
"topics_of_interest": ["bug", "build_release"],
"description": "Why this item matters.",
"caveats": []
}Notification policy is deployment config, not classifier logic:
{
"classifier": {
"schema": "~/.config/localpager/classification.schema.json",
"prompt_template": "~/.config/localpager/classifier.prompt.md",
"topic_taxonomy": "~/.config/localpager/topics.json",
"reposhell_default_repo": "localpager",
"reposhell_visible_repos": ["localpager"],
"context": {
"github": {
"include_body": true,
"include_labels": true,
"include_comments": true,
"include_changed_files": true,
"include_diff": true
}
}
},
"worker": {
"agent_base_url": "http://127.0.0.1:1234/v1",
"agent_context_window": 8192,
"agent_max_tokens": 768,
"agent_temperature": 0,
"agent_top_p": 1,
"agent_seed": 1234,
"agent_presence_penalty": 0,
"agent_frequency_penalty": 0,
"agent_timeout_ms": 5000,
"model_unavailable_retry_delay": "5m",
"notify_topics_any": ["local_models", "open_weight_models"]
}
}When reposhell is enabled with a socket, run reposhell serve with matching
reposhell config. localpager-agent owns tool exposure: it always provides
final_json and adds a read-only bash tool whenever a reposhell socket is
passed. The model sees a familiar bash-shaped tool, but Localpager enforces a
read-only command allowlist against pinned repository snapshots. See
Reposhell for standalone setup, config, direct
commands, allowed command shapes, service mode, and troubleshooting.
If notify_topics_any is set, at least one classifier topic must match before a
notification is created.
Classifier prompts, topic taxonomies, and schemas should be configured as a
deployment profile. See Classifier Profiles.
When classifier.topic_taxonomy is set, localpager-classifier generates a
runtime schema that rejects topics outside that taxonomy. For the full
worker → wrapper → renderer → agent chain, prompt placeholders, and schema-enum
generation, see
Classifier Pipeline and Profile Rendering.
For OpenClaw maintainer routing, this repo contains a self-contained v10 profile matching the evalstate benchmark label set:
examples/profiles/openclaw-routing.prompt.hbs
examples/profiles/openclaw-routing.schema.json
examples/profiles/openclaw-routing-topics.json
Those files are the default OpenClaw profile used by scripts/localpager-classifier
when no profile flags or environment overrides are provided. The GEPA-reviewed
candidate artifacts remain under prompt-optimizer/results/.
Before the classifier runs, Localpager renders GitHub context into the prompt:
stored title/body/labels plus optional comments, changed files, and selected PR
diff. Prompt templates can include that block with {{{github_context}}}.
For prompt provenance and dataset artifact ownership, see
Classifier Prompt Provenance.
To test a prompt profile on a small live GitHub sample, use
scripts/localpager-experiment.mjs; see
Classifier Experiment Runner
and Classifier Benchmark Metrics.
Machine-specific runtime values, such as the loaded model context window,
parallelism, context truncation budget, and loaded local model, belong in a
deployment setup document. See
Onur's Isengard Setup for
the current OpenClaw/Gemma deployment. For Qwen NVFP4 through vLLM, see
vLLM Qwen NVFP4 LocalPager Setup.
For the Gemma 4 26B-A4B NVFP4 profile and local benchmark, see
Gemma 4 26B-A4B NVFP4 on DGX Spark.
If the classifier writes lines like these to stderr, Localpager stores them with the result:
prompt: /path/to/prompt.md
session: /path/to/session-dir
Transient local model failures, such as the LM Studio endpoint being down, are
requeued with model_unavailable_retry_delay and do not burn classifier
attempts. To manually requeue matching dead jobs:
localpager requeue-jobs \
--config ~/.config/localpager/config.json \
--status dead \
--last-error-contains "fetch failed"npm install --prefix localpager-agent
make test
make install
localpager validate --config examples/config.example.json
localpager-worker --config examples/config.example.json --once --dry-run-discordCheck the bundled agent:
npm --prefix localpager-agent run localpager-agent -- --statusTo send Discord messages, pass a channel ID and set a bot token:
export DISCORD_BOT_TOKEN="<discord bot token>"
localpager-worker \
--config examples/config.example.json \
--discord-channel-id "$DISCORD_CHANNEL_ID" \
--send-discordDo not commit tokens, .env files, SQLite databases, classifier sessions, or
prompt logs.
localpager-agent/ is the absorbed local model runner formerly used as an
external command. It defaults to LM Studio's OpenAI-compatible endpoint:
http://127.0.0.1:1234/v1
The main environment variables are:
LOCALPAGER_AGENT_BASE_URL
LOCALPAGER_AGENT_MODEL
LOCALPAGER_AGENT_CONTEXT_WINDOW
LOCALPAGER_AGENT_MAX_TOKENS
LOCALPAGER_AGENT_TEMPERATURE
LOCALPAGER_AGENT_TOP_P
LOCALPAGER_AGENT_SEED
LOCALPAGER_AGENT_PRESENCE_PENALTY
LOCALPAGER_AGENT_FREQUENCY_PENALTY
LOCALPAGER_AGENT_TIMEOUT_MS
LOCALPAGER_AGENT_STATE_DIR
LOCALPAGER_AGENT_SESSION_DIR
LOCALPAGER_AGENT_PI_CMD
LOCALPAGER_AGENT_FINAL_SCHEMA
The default public source is the GitHub API:
export GITHUB_TOKEN="<github token>"
localpager-watch --config examples/config.example.json --source github --onceThe gitcrawl source remains available for machines that already maintain a gitcrawl SQLite database:
localpager-watch --config examples/config.example.json --source gitcrawl --oncelocalpager-watch can run continuously under systemd. localpager-enqueue-github
is the one-shot equivalent.
Install compiled binaries and write user systemd units. The service installer
writes localpager-worker.service, localpager-reposhell.service,
localpager-watch.service, localpager-enqueue-github.service, and
localpager-enqueue-github.timer.
make install
localpager install-service --config ~/.config/localpager/config.json --work-dir "$PWD"
systemctl --user daemon-reload
systemctl --user enable --now localpager-reposhell.service localpager-worker.service localpager-enqueue-github.timerThat setup runs the worker continuously and enqueues GitHub issues and pull
requests on the timer. Enable localpager-watch.service instead if you want
continuous source polling. If your classifier profile does not expose bash,
you can leave localpager-reposhell.service disabled.
Check state:
localpager status --config ~/.config/localpager/config.json
localpager test-discord --config ~/.config/localpager/config.json
reposhell status --config ~/.config/localpager/config.jsonThe default SQLite state path is:
~/.local/state/localpager/localpager.sqlite
Localpager creates and updates SQLite tables from its GORM models at startup.
Override it with --db.