Skip to content
R055LEPublic

About

Roger — a Discord chat bot on OpenRouter, and the spiritual successor to my first-ever programming project.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

roger

An OpenRouter-backed Discord bot — the spiritual successor to roger-bot, my first-ever programming project. Same character, rebuilt from scratch on hosted models and modern tooling.

Roger is a single-guild, owner-gated Discord assistant with four separate "brains":

  • Admin — an owner-only server concierge, reached through /roger in the guild. Ask in plain language ("a read-only podcast channel under Media that DJs can post in") and it creates channels/roles and sets permissions through a small, hand-rolled tool loop, with short per-channel conversation memory so follow-ups have context. No agent framework.
  • Ambient — a deadpan chat persona (via /chat, any @mention, or any DM). No tools, no authority.
  • Curated — a daily check of Scout's picks. It posts one source-grounded story when there is something worth sharing, and skips quiet days. Public posting stays off until CURATED_CHANNEL_ID is set. The owner can preview a draft through /roger.
  • Giga Brain — an owner-only, read-only strategic-analysis mode, reachable by /gigabrain. It reviews live server state and reasons about it — never acts. Optionally also checks in on its own on a configurable interval, DMing the owner unprompted suggestions.

Design details — the routing table, the tool loop, the store schema, and the security invariants the source cites as (§N) — are in ARCHITECTURE.md.

Security posture

Security is structural, not prompt-deep:

  • No privileged gateway intents. message_content, members, and presence stay off. Roger only sees content in DMs, @mentions, and its own messages — exactly what it needs.
  • Owner-gated. Admin actions require user.id == OWNER_ID, checked before a single token is spent. Everyone else gets a canned reply and an audit row.
  • Least privilege. Never requests Administrator. Roles it creates always have zero permissions; access is granted through channel overwrites. No delete, kick, ban, or purge tools exist — Roger creates and adjusts, never destroys, and every change to existing state is owner-confirmed.
  • Budgeted. Per-brain daily token caps and a hard cap on tool calls per request.
  • Untrusted item output is bounded. Curated drafts use bounded Scout article evidence, require supporting excerpts, accept only HTTP(S) item links, and suppress Discord mentions on delivery.
  • No secrets in git — ever, not even encrypted. Secrets live in a sops+age-encrypted roger.env on the host; the repo carries only .sops.yaml and roger.env.example.
  • Signed, scanned supply chain. CI audits deps (pip-audit) and scans the image (Trivy, build → scan → push); releases are signed keyless with cosign and the deploy host verifies the signature before it runs — a bad or unsigned image fails the deploy closed.

Stack

Python 3.12 · discord.py · the OpenAI SDK pointed at OpenRouter · pydantic · aiosqlite. Runs as a non-root, read-only-rootfs container. OPENROUTER_BASE_URL is config, so pointing Roger at a local inference host is an env change rather than a rewrite. That was tested and shelved on cost and hardware grounds, so hosted is the only supported setup today: see ADR-0009.

Configure

cp roger.env.example roger.env        # fill in tokens, owner/guild IDs, model chains

Each MODEL_* var is a comma-separated priority list (primary first, the rest are OpenRouter fallbacks). Every model in MODEL_ADMIN must support tool calling.

Deploy

CI/CD is pull-based: pushing to main runs the tests, builds the image, and publishes it to GHCR (ghcr.io/r055le/roger:main); the host polls that tag and redeploys itself. Secrets are injected at runtime by sops exec-env and never baked into the image. Full runbook (host bootstrap, age key, systemd timer) in deploy/.

Run it directly on any Docker host:

sops -e -i roger.env                                  # encrypt at rest (age key — see .sops.yaml)
mkdir -p data
sops exec-env roger.env 'docker compose up -d'        # pulls the published image

Observability

Roger exposes Prometheus metrics on port 9108 inside the container by default; set METRICS_PORT=0 to disable the server. Compose publishes it as 127.0.0.1:${METRICS_HOST_PORT:-9108} by default. Set METRICS_BIND_ADDRESS only when a scraper on a trusted private interface needs remote access. The endpoint has no authentication or TLS and must not be exposed to an untrusted network.

Event counters — LLM requests, errors, and budget rejections — are incremented in process; state gauges — token/dollar spend, caps, and audit tallies — are refreshed from SQLite so they survive restarts. Key series:

Metric Type Labels
roger_tokens_today / roger_tokens_cap gauge brain
roger_cost_usd_today / roger_cost_usd_cap gauge brain
roger_llm_requests_total / roger_llm_errors_total counter brain (type)
roger_llm_budget_exceeded_total counter brain, reason
roger_audit_events gauge tool, status
roger_build_info gauge — (version)

A ready-to-merge Prometheus scrape job and an importable Grafana dashboard live in deploy/observability/ — the bridge to the sre-observability-lab.

Development

python -m venv .venv && . .venv/bin/activate
pip install -e '.[dev]'
pytest
ruff check .

Status

Feature-complete across the planned phases:

  • Admin — owner-gated via /roger in the guild, with short per-channel conversation memory; a hand-rolled tool loop with list_structure, create_channel, create_role, and confirm-gated set_permissions / edit_channel / post_message / move_channel; per-request tool and daily token budgets; a full SQLite audit trail.
  • Ambient — deadpan chat via /chat, any @mention, or any DM, rate-limited per user + globally, with a short own-thread memory. No tools, ever.
  • Curated — defaults to checking Scout's output at 07:00 local time and may post one useful story with source detail and a light take. Set CURATED_CHECK_INTERVAL_MINUTES to enable an ongoing stream, bounded by CURATED_MAX_POSTS_PER_DAY, CURATED_MIN_SPACING_MINUTES, CURATED_MAX_OBSERVATIONS_PER_DAY, and the existing model budgets. There is no posting quota. Quiet or rejected input is remembered; changed evidence or model policy allows reconsideration. Transient failures get one retry after an hour; budget deferrals get one on the next local day. /status shows remaining allowances, spacing, uncertain deliveries, and the last decisions. The owner can preview the next decision with /roger preview the curated post; that spends from the curated model budget but does not post or change Curated state. Ask for preview_curated with developing_stories=true to inspect the bounded story-memory path. It follows confirmed sent reports across source updates, records explicit publish/update/combine/hold/skip decisions, and stays disabled until CURATED_DEVELOPING_STORIES=true. Story drafts and the one allowed revision have a fixed 4,096 token output ceiling; legacy Curated drafts and reviews keep the 900-token ceiling, and every call spends from the same Curated daily token and dollar budgets. Set CURATED_CHANNEL_ID only after reviewing preview quality. See ADR-0012 for the Scout boundary. Scout currently fetches only newly reported items; Scout #21 tracks same-page refresh evidence.

Runs as a non-root, read-only-rootfs container. More than 300 tests cover the guard rules, the tool loop (including channel creation with access presets and the confirm-gated edit, post, and reorder tools), the rate limiter, and the Scout ingestion path.

License

MIT — see LICENSE.

About

Roger — a Discord chat bot on OpenRouter, and the spiritual successor to my first-ever programming project.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages