A pixel office you run with your voice. Barge in through the door, talk to Mo (the office manager), and watch him hire pixel worker pets that do real LLM work at their desks, live.
Inspired by Pixel Agents, flipped around: instead of watching your coding agents, you manage them by talking to the one character whose whole job is talking to you.
- Voice in: hold
SPACE(or the mic button), speak, release. Groqwhisper-large-v3-turbotranscribes. - Manager brain:
qwen/qwen3.6-27bon Groq via the Vercel AI SDK, with tool-calling (spawn_agents,dismiss_agent,dismiss_all,send_linkedin_message) and reasoning disabled for snappy voice turns. His replies are 1-2 spoken sentences. - Workers get realtime web search: Groq qwen is each worker's main brain, but a
cheap gate decides when a task needs current facts and reaches for Gemini 3.5 Flash
with Google Search grounding (
@ai-sdk/google). The pet visibly searches in the office, then grounds its writing with a Sources list. If the Gemini key has no quota, it falls back to Groq'scompoundweb-search model so research still works. - Voice out: Gemini native TTS (
gemini-3.1-flash-tts-preview, voice Achird), with automatic fallbacks to Groq Orpheus and then browser speech. - Observability: every LLM call is traced to Respan's Spans API via fire-and-forget scheduled actions, so tracing never adds latency to a voice turn.
- Real files: finished artifacts are written to
artifacts_out/on disk via a local Vite bridge; each artifact shows a file chip with reveal-in-Finder. - LinkedIn via MCP: research and sends run through the community LinkedIn MCP server (browser-session based) when you've logged into it, and fall back to a cookie bridge otherwise. See the setup section for the one-time login.
- LinkedIn outreach, autonomous: tell Mo "send Gioele a message about our project"
and his send_linkedin_message tool queues an outreach job; the browser tab executes
it end to end (research with your own
li_atsession, draft, send from your account) and Mo announces the result by voice. Your voice command is the authorization; there is no confirmation click. The outreach panel remains for manual drafting and editing. - Barge-in: speak (or
ESC) while Mo talks and he shuts up instantly. No sluggish turn-taking. - Workers are real agents: each hire runs a scheduler-driven Convex action pipeline (plan → write section by section) producing a markdown/code artifact you can open and copy from the desk work drawer, streaming in as it's written.
- Convex is the whole backend: agents, transcript, and artifacts are Convex tables;
the office UI is a live
useQuerysubscription; the agent runtime is Convex actions + the scheduler. No other server.
npm install
npx convex dev # terminal 1: pick "start without an account" (local) or log in
npm run dev # terminal 2: Vite on http://localhost:5173Set your keys (once):
npx convex env set GROQ_API_KEY gsk_your_key_here
npx convex env set GEMINI_API_KEY your_gemini_key # optional, best TTS voice
npx convex env set RESPAN_API_KEY respan:your_key_here # optional, enables tracingFor LinkedIn outreach there are two paths, and the bridge picks the best available one per request:
-
MCP (primary, recommended): the stickerdaniel/linkedin-mcp-server driven over stdio through the official MCP TypeScript SDK. It uses a real logged-in browser session, so research and sends are more robust than raw cookie scraping. It needs
uv(curl -LsSf https://astral.sh/uv/install.sh | sh) and a one-time login:uvx mcp-server-linkedin@latest --login # opens a browser; sign in once node scripts/mcp-verify.mjs # confirm it works (reads a public profile)
The app auto-detects the session (it looks for
~/.linkedin-mcp/profile) and routes through MCP once it exists. No login window ever pops up on its own: the path stays dormant until you log in. -
Cookie bridge (automatic fallback): set
LINKEDIN_LI_AT=...in.env.local(gitignored). Used whenever the MCP session is absent or a tool call fails, so the app works before you ever run the login. LinkedIn sometimes answers with status 999; resolved profiles and the session persist toartifacts_out/.li-cache.json.
Fresh demo state at any time:
npx convex run office:resetAllThe mic is the input: hold SPACE (or press the big mic) and talk. The persona orb
behind the mic mirrors the conversation state (listening, thinking, speaking). Typing
is a command palette: ⌘K or / opens it with free-text ask plus quick actions
(preset hires, status, dismiss all, desk work, LinkedIn outreach, reset office). While
workers write, the last few words they're typing stream live above their heads, and
new hires pick their task paper off the manager's tray before heading to a desk.
HTML reports: ask for a report, dashboard, or page and the manager hires the
worker with format: html; the finished self-contained page auto-opens in an 80%
screen preview (open in tab, copy html). Model selector: the brain · … header
button lists Groq's live model catalog with reasoning-effort levels; the choice
persists and drives every agent in the office.
- One hire: "Hire someone to write our landing page hero." One pet walks in, monitor lights up, words stream over its head, chime on done.
- Live status by voice: "How's everyone doing?" Mo answers from the live roster, correctly, mid-work.
- Parallel team: "Get two people on this: a README and a pricing table." Two pets work simultaneously; artifacts stream side by side in the drawer.
- Barge-in: interrupt Mo mid-sentence with a follow-up. His audio dies instantly; the conversation never stalls.
- Real code on a real desk: "Have an engineer write a Convex schema for a todo app in TypeScript." Open the artifact: language-tagged fenced code, copy it, then reveal the saved .md on disk in Finder.
- Visual report: "Build me a weekly KPI report page for the office." The pet streams a full HTML page that pops open across 80% of the screen when done.
- Real-world action, hands-free: "Send Gioele a LinkedIn message about what we built." Mo researches, drafts, sends from your account, and tells you when it's done. No clicks.
mic (MediaRecorder, hold SPACE)
└─ Convex action ai.transcribe ──► Groq whisper-large-v3-turbo
└─ Convex action ai.managerTurn
├─ Vercel AI SDK generateText + tools (qwen/qwen3.6-27b)
│ ├─ spawn_agent ──► office.spawnAgent + scheduler ► worker.tick
│ └─ dismiss_agent
├─ transcript + status written to Convex tables
├─ Respan span logged (scheduled, fire-and-forget)
└─ Gemini TTS (PCM ► wav) ► Groq orpheus ► browser speech (barge-in kills it)
worker.tick (scheduler-driven, one LLM step per tick)
walk in ► plan ► write sections ► done (all qwen/qwen3.6-27b)
every status change animates the pet sprite via the live agents query
The canvas renders the office at 60fps from a client-side spatial sim; the database holds only logical state (status, desk, progress). Sprites: the manager is a classic 4-direction walk sheet; workers are Petdex-format packs with canonical rows for idle, run, wave, jump, fail, wait, work, and review: agent status maps straight onto them.
Vite + React 19 + canvas 2D · Convex (DB, realtime, actions, scheduler) · Vercel AI SDK
(ai + @ai-sdk/groq) · Groq (qwen3.6-27b + Whisper + Orpheus TTS) · Respan (LLM
tracing)
Note: Respan dashboard keys look like respan:xxxx.yyyy; the API wants only the part
after respan: in the Bearer header. The app strips it for you.