Skip to content

AI assistants

Since v1.4.0, Flagpost ships an optional AI assistants module: an organiser assistant for operational questions over a competition, and a guard-railed competitor assistant organisers can offer as a controlled hint channel. Nothing runs anywhere until an administrator configures a provider and flips the site-wide master switch (off by default). Once it’s on, the organiser assistant is live in competitions that haven’t toggled the module off (per-competition module toggles default enabled, like every optional module) — while the competitor assistant stays off per competition until explicitly enabled. The design is recorded in ADR-0023.

Flagpost ships no model, no API key, and no vendor SDK. You point it at any OpenAI-compatible chat-completions endpoint (POST {base_url}/chat/completions): OpenAI, Azure OpenAI, OpenRouter, Anthropic’s compatibility endpoint — or a self-hosted server like Ollama, vLLM, LM Studio, or LiteLLM, in which case nothing leaves your infrastructure. The one hard requirement: the model must support tool calling, because that’s how the assistants read competition data.

Configuration lives at Admin → Settings → AI, gated on the dedicated global permission manage_ai — separate from manage_site_settings, because this surface holds an API key and enables outbound calls to an operator-chosen endpoint.

Field Meaning
Base URL The endpoint’s API root; deliberately exempt from the webhook SSRF blocklist — it’s operator-configured, never competitor-controlled, and a loopback/private inference server is the intended self-hosted setup
Model The model name your endpoint serves
API key Write-only, encrypted at rest (ADR-0020), excluded from platform exports; may be blank for keyless local endpoints
Max output tokens Server-enforced reply caps (defaults 1024 organiser / 512 competitor)
Request timeout Default 60 s, with a whole-turn wall-clock deadline on top
Admin prompt override Optional replacement for the organiser assistant’s shipped system prompt — setting it discards the default operating instructions entirely, so start from the shipped text if you only want to add to it

Enabling is refused until the base URL and model are set — “enabled but unconfigured” is unreachable. A Test connection button probes the saved config with two separately-reported checks: a trivial completion, and a forced tool call — so you learn before an event whether the model can tool-call at all.

A docked chat for staff — anyone holding at least one staff permission in the competition (analytics, ticket assignment, feedback, or announcements) — that answers operational questions in plain language: standings, ticket load, which challenges are hurting. It reads through seven read-only tools: competition overview, scoreboard, per-challenge stats, ticket search and detail, feedback summaries, and announcements. Answers stream live.

Every tool call executes as the asking user under their own RBAC permissions — no service account. The assistant can never fetch what that user couldn’t read themselves, and it can mutate nothing.

A separate, opt-in channel competitors can ask for help — off by default, enabled per competition under Competition Settings → Assistant (gated edit_competition; the tab only appears while the AI module is enabled for that competition), and available only while the competition is running.

  • Guidance levelplatform only (the default: how-do-I-use-the-site help), conceptual (nudge with ideas, never steps), or guided (walkthrough-style coaching). A competition inherits the site-level default and may override it. This is a behavioural prompt control — the data guarantees below hold at every level.
  • Challenge metadata access — a separate hard toggle (default off) deciding whether the challenge list/detail tools exist at all.
  • Structural guarantees — competitor tools serialize through the same competitor-facing schemas the UI uses, so flags, flag hashes, correct multiple-choice options, hidden hints, and unpublished challenges are structurally unreachable — there is no field for them. The scoreboard tool respects a freeze like every other read.
  • Belt and braces — competitor replies are buffered (never token-streamed), stripped of any leaked tool-call JSON, and scanned to redact flag-shaped strings before delivery.
  • Disclosure — before first use, each competitor accepts a one-time disclosure: messages go to the operator-configured model endpoint, and transcripts are staff-reviewable. Acceptance is enforced server-side and recorded (emits ai.disclosure_accepted).

Every competitor conversation is reviewable on the per-competition AI transcripts page, gated on the new competition-scoped permission ai_view_transcripts — held by Judge by default, and deliberately its own grant: transcripts are competitor content of a different sensitivity than analytics. A usage endpoint totals input/output tokens per competition, and every exchange emits ai.query (token counts and tool names — never message content) to the event catalogue and audit log.

Per-user message rates (60/hour organiser, 20/hour competitor), one in-flight generation per user, a 20-exchange cap per conversation (then the thread closes and a fresh one starts), at most 6 tool rounds per turn, server-enforced output caps, and request plus whole-turn timeouts.

Nothing, until an administrator configures a provider and enables the module. After that: the content of assistant conversations (and the competition data the tools read into context) goes to the endpoint you configured — and only there. Point it at a local model and nothing leaves the box. The platform’s PRIVACY.md carries the full statement. There are no new environment variables — all AI configuration lives in the database.