Inspiration

Every active subreddit eventually hits the same wall: the volume of reports, modmail, and edge-case threads grows faster than the mod team can. The mods I talked to weren't asking for an AI to replace them: they were asking for a teammate who could draft the warning, sort the modqueue, and write the daily brief so they could spend their time on the calls that actually need a human. SubPilot is that teammate.

I was also inspired by the "Pixel Agents" style of visualizing AI agents as little characters in a virtual office. Most mod tools feel like a faceless backend. I wanted SubPilot to feel like a real team you watch work.

What I learned

  • The Devvit platform as a product surface. Per-install Redis isolation, subreddit-scoped settings, the webview/server split, the mod menu and mod-log triggers — Devvit gives you a lot if you lean into it. I learned how to wire each surface (chat console, mod menu, public @-mention, scheduled job) into a single shared agent registry instead of duplicating prompts.
  • Multi-agent prompting at a token budget. I ended up with 16 specialized agents (chat, triage, risk-analyst, warning-drafter, brief, thread-summarizer, four summon agents, five automation agents, plus a health ping). They share a tiny base prompt (~150 tokens) and pay only for the fragments they actually need. A rule-based router picks the right specialist before any LLM call.
  • Free-tier survival. Gemini's free tier is generous but not infinite. I built per-subreddit Redis-backed rate/token budgets, priority bands (a mod typing in chat is "high", a chatty automation is "normal"), tier fallback (deep → balanced → lite if the configured model rate-limits), and aggressive response caching for the agents whose answers are stable for hours (briefs, translations, thread summaries, the ping).
  • Data isolation is a platform guarantee, not a feature you bolt on. I audited every Redis key SubPilot writes and documented exactly how two subreddits running the same install never see each other's data — even when the same mod account is on both teams.

How I built it

  • Front end: Vanilla TypeScript + Vite webview, served by Devvit. A single Mod Console with chat history, quick-action chips, live feed, sub-switcher, plugins page, automations page, and the Playground.
  • Back end: Hono routes on Devvit's server runtime. Every AI call funnels through one runAgent() runner that resolves the model, builds the prompt, checks cache, checks the rate/token budget, calls Gemini, records usage, stamps activity, and returns a structured result.
  • Agent registry (src/core/ai/agents.ts): Each agent declares its model tier, temperature, max tokens, priority, whether it can emit actions, and a buildPrompt() function. New agents are ~20 lines.
  • Context loaders: Subreddit rules, description, tone preference, and detected third-party mod tools are fetched once and cached. A separate "live grounding" block (top-risk users + recent mod actions from the last 24h) is injected into the agents that need it.
  • Plugin discovery: A declarative registry of known mod bots (AutoModerator, RemindMeBot, etc.) plus runtime signal detection — mod-team membership, wiki pages, mod-log patterns. Detected tools become hints in the AI context so SubPilot knows what's already installed and avoids stepping on those workflows.
  • Playground / Pixel Agents: A self-contained client module renders 8 pixel-art characters on top of the office art — one per agent role. They wander when idle, walk to their desks when their server-side state flips to "working", and play a typing animation. Tooltips show the live activity summary, model, and timestamp.

Challenges I ran into

  • Devvit settings vs CLI. gemini-key is a subreddit-scoped install setting, not an app-global setting, which means devvit settings set rejects it. The right path is the Reddit Mod Tools UI at developers.reddit.com/r/<sub>/apps/<app-name>. I now ship that path in the docs and the in-app empty state.
  • The Devvit webview sandbox. window.confirm() silently no-ops inside the webview iframe. My chat-delete button "didn't work" for a session until I traced the issue and replaced it with an inline two-tap confirmation directly in the kebab menu.
  • Vite asset paths. Static assets needed to live under src/client/public/ to be copied verbatim and referenced as absolute /playground_assets/... paths instead of relative paths that Vite was flattening.
  • Token economy. First pass had every chat turn refetching subreddit rules and live grounding. Caching contexts per-install and skipping grounding for agents that don't need it cut average token usage by ~60% without changing answer quality.
  • Making the Playground feel alive without a game engine. Eight characters, four animation states (idle/walk/sit/work), per-character zones, wandering AI, and 9 pre-rendered SVG frames per character — all in a single requestAnimationFrame loop with no dependencies. Z-layering "behind the desk" is faked by rendering a half-body sprite that's anchored to the desk surface — no per-pixel collision needed.

Built With

Share this project:

Updates