Skip to content

status command: api-rs /api/status endpoint + discordbot channel-gated fast-path - #18

Draft
oponder wants to merge 10 commits into
mainfrom
feat/discord-status-command
Draft

status command: api-rs /api/status endpoint + discordbot channel-gated fast-path#18
oponder wants to merge 10 commits into
mainfrom
feat/discord-status-command

Conversation

@oponder

@oponder oponder commented Aug 12, 2026

Copy link
Copy Markdown

Summary

A bare @bot status (or health) mention gets an instant reply from the discordbot service — health tiles, a 24h tally, recent/in-flight turns (with requester and one-line errors), sandbox/warm-pool state, and a 7-day run histogram with failure rate — with no sandbox turn involved, so it keeps working when the agent pipeline itself is broken.

The reply data comes from a new read-only GET /api/status endpoint on api-rs (this PR adds it): api-rs owns the session schema, so the SQL lives there, and the ingress bot only speaks HTTP. The endpoint caches its report in-process for 10s, windows warm-pool claimed/failed rows to 24h (they're lifetime history), buckets the histogram by UTC calendar day, and a new migration gives session_executions(created_at) an index for the global recency scans.

Security posture (adversarially reviewed)

An independent review pass drove the current shape:

  • Per-channel opt-in, default OFF: the report exposes cross-platform activity (session titles, requester names, error snippets), so the fast-path only answers in channels listed in DISCORDBOT_STATUS_CHANNEL_IDS; unset disables the feature entirely.
  • No SQL from the ingress bot — HTTP only, with timeouts on every fetch (a hung dependency can no longer wedge the per-thread handler lock; there is no bot-side DB pool to exhaust).
  • Fence-safe rendering: backticks/newlines in interpolated titles/errors are neutralized so a hostile Linear/GitHub title can't break out of the code block (mention pings were already impossible — the adapter posts with allowed_mentions: []).
  • Generic failure reply: internals (hostnames, auth errors) stay in service logs, never in the channel.
  • DoS bounded: server-side 10s report cache + the new index cap the DB cost of scripted mentions.
  • No hardcoded deployment names: the header uses the bot's configured userName; no actor aliases.

Other review fixes: mention-created threads are subscribed before the fast-path replies (no orphaned threads); unreachable (❓) is distinguished from unhealthy (❌); histogram fetch window matches its calendar-day buckets exactly.

Testing

  • pnpm --filter discordbot test — 152 pass (keyword matcher incl. mention markup; collect under partial outages — api down / report down / malformed body; fence-injection neutralization; formatting + Discord 2000-char cap).
  • pnpm --filter discordbot run check:types — clean.
  • cargo check -p centaur-api-server + cargo fmt --check — clean (via rust:latest container, matching the Dockerfile's rust:1-bookworm).
  • Earlier iterations live-tested on a deployment via branch image builds; the endpoint-based shape needs one more live pass (api-rs + discordbot images together).

Deploy notes

  • Rebuild both images: deploy-centaur -f services="api-rs discordbot". The api-rs migration (0049, create index if not exists) runs on boot.
  • Set DISCORDBOT_STATUS_CHANNEL_IDS (comma/space-separated channel or thread ids) on the discordbot deployment to enable the command.
  • Generic feature — intended for upstreaming to paradigmxyz/centaur.

🤖 Generated with Claude Code

oponder and others added 10 commits August 12, 2026 16:54
A "@bot status" (or "health") mention now gets an instant reply built from
api-rs /healthz + /readyz and the shared session database (recent turns with
errors, 24h tally, in-flight executions, active sandboxes, warm pool) —
no sandbox turn involved, so the command keeps working when the agent
pipeline itself is what's broken.

Mentions with any other words ("status of the deploy") fall through to a
normal agent turn, so real questions are never hijacked. Every status source
is fetched independently and best-effort: api-rs down still reports DB data
and vice versa.

Co-Authored-By: Claude Fable 5 <[email protected]>
Discord has no table markup; a monospace code block with padded columns is
the idiomatic substitute. Header (bold + emoji health marks) stays outside
the block, rows use short ASCII tags (ok/FAIL/run) since emoji are
double-width in code blocks and wreck alignment. In-flight turns fold into
the same table as settled ones; errors get their own indented line so the
columns stay ~45 chars and portrait mobile doesn't wrap.

Co-Authored-By: Claude Fable 5 <[email protected]>
…abels

Timing columns were ambiguous — add a THREAD/WHO/AGE/TOOK heading row
(AGE = when requested, TOOK = runtime). Rows now prefer the session title
(the conversation name the bots already set) over raw thread keys, fall
back to a friendly rendering for untitled management turns
("GH PR splits-teams#1799"), and carry the requester from the execute
metadata's user_name. Truncation keeps both ends readable (middle
ellipsis) except names, which head-cut.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claimed/failed warm-sandbox rows are never deleted — an unfiltered count
reads like a leak ("868 claimed"). Window them to 24h and split the line
into current pool (ready/evicting) vs churn. Discord sessions store their
conversation name in metadata (discord_conversation_name), not title —
coalesce it in so Discord rows show thread names instead of raw ids. Widen
WHO to 10 and alias github-pr-manager to gerard.

Co-Authored-By: Claude Fable 5 <[email protected]>
Linear and Slack sessions store their conversation names under their own
metadata keys (linear_conversation_name / slack_conversation_name), same
pattern as discord — fold them into the title coalesce so those rows show
issue/channel names instead of raw UUIDs.

Co-Authored-By: Claude Fable 5 <[email protected]>
Unicode bar chart per UTC day (zero-filled week, bars scaled to the busiest
day), with failures as their own labeled column — one measure per bar, no
second scale, no color-alone signal — and a 7d totals/failure-rate stat
line beneath.

Co-Authored-By: Claude Fable 5 <[email protected]>
Keep the first block exactly as it was (tally, turn table, sandboxes) and
append the 7-day histogram as a second fenced block, budgeting the live
view's truncation around the fixed-size histogram.

Co-Authored-By: Claude Fable 5 <[email protected]>
"warm 24h: 3 failed" next to a histogram FAIL column reads like failed
turns; it's warm-sandbox provisioning failures (the next session just
cold-starts). Rename the display label so the two failure domains can't
be conflated.

Co-Authored-By: Claude Fable 5 <[email protected]>
Rework of the status fast-path after an adversarial review:
- Data now comes from api-rs's read-only /api/status over HTTP with
  timeouts — no SQL from the ingress bot, no bot-side DB pool to hang the
  per-thread handler lock, and the service-ownership boundary holds.
- The feature is opt-in per channel (DISCORDBOT_STATUS_CHANNEL_IDS; unset
  = disabled): the report exposes cross-platform activity, so where it may
  be seen is a deployment decision, not a default.
- Header names the configured bot userName (no hardcoded deployment name);
  the actor alias map is gone.
- Backticks/newlines in interpolated titles/errors are neutralized so a
  hostile title can't break out of the code block; failures post a generic
  line with internals kept to logs.
- Mention-created threads are subscribed before the fast-path replies;
  unreachable (❓) is distinguished from unhealthy (❌).

Co-Authored-By: Claude Fable 5 <[email protected]>
GET /api/status returns a JSON snapshot for status surfaces (chat-bot
status commands, dashboards): recent and in-flight executions (with
session title, requester, truncated error), a 24h status tally, active
session sandboxes, warm-pool state (current ready/evicting plus 24h
claimed/failed churn — those rows are lifetime history), and a 7-calendar-
day UTC run histogram. api-rs owns the session schema, so the SQL lives
here instead of in every ingress service that wants a status view.

The report is cached in-process for 10s so scripted callers cost at most
one scan set per TTL, and a new migration indexes
session_executions(created_at) for the global recency scans.

Co-Authored-By: Claude Fable 5 <[email protected]>
@oponder oponder changed the title discordbot: instant "@gerard status" replies from the control plane status command: api-rs /api/status endpoint + discordbot channel-gated fast-path Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant