An iMessage-based personal agent you can run with either your Claude Code subscription or your Codex / ChatGPT subscription.
Choose your runtime during setup:
- Claude — powered by the Claude Agent SDK and your local Claude Code login.
- Codex — powered by the local Codex app-server runtime and your local
codex login.
No Anthropic or OpenAI API key is required for the agent runtime when using subscription auth.
📺 Watch the walkthrough: YouTube — How I built Boop
Boop in action — text it like a person, get back an answer with full context.
This is a starting point, not a finished product. It's the architecture I built for my own personal agent, opened up as a template so you can take it, text-enable your own Claude or Codex-backed agent, and extend it however you want. Integrations are plugged in via Composio — drop in an API key and connect Gmail, Slack, GitHub, Linear, Notion, and ~1000 others straight from the debug dashboard.
iMessage → Sendblue webhook → Interaction agent → Sub-agents (per task)
│ │
▼ ▼
Memory store ←── Integrations (your MCP tools)
Built on:
- Claude Agent SDK or local Codex runtime — choose your provider during setup
- Composio — integrations layer. One API key = Gmail, Slack, GitHub, Linear, Notion, Stripe, Supabase, + ~1000 more with hosted OAuth
- Sendblue — iMessage in/out (free on their agent plan)
- Convex — real-time database for memory, agents, drafts
- Your Claude Code or Codex/ChatGPT subscription — no separate provider API key required
- iMessage in / iMessage out via Sendblue (with typing indicators and webhook dedup).
- Sendblue CLI integration —
npm run devauto-registers the inbound webhook for you every restart (no re-pasting into the dashboard when free ngrok rotates your URL). - Dispatcher + workers pattern: a lean interaction agent decides what to do, spawns focused sub-agents that actually do the work.
- Pure dispatcher — the interaction agent has only memory + spawn + automation + draft tools. Web access, files, and integrations are explicitly denied to it; sub-agents get
WebSearch/WebFetch/ the integrations. - Tiered memory (short / long / permanent) with post-turn extraction, decay, and cleaning.
- Vector search for recall when you add an embeddings key (Voyage or OpenAI) — falls back to substring.
- Memory consolidation — a daily 3-phase adversarial pipeline (proposer → adversary → judge) that merges duplicates, resolves contradictions, and prunes noise. Uses the configured runtime, with provider-specific model defaults. Runs every 24h by default, also triggerable manually via
POST /consolidate. - Automations — the agent can schedule recurring work from a text ("every morning at 8 summarize my calendar") and push results back to iMessage.
- Draft-and-send — any external action stages a draft first; the agent only commits when the user confirms.
- Heartbeat + retry — stuck agents auto-fail, debug dashboard can retry.
- Composio-powered integrations — one API key unlocks 1000+ toolkits. Connect Gmail, Slack, GitHub, Linear, Notion, Drive, HubSpot, etc. with a click from the debug dashboard. Composio handles OAuth + token refresh.
- Optional local browser use — when enabled in Settings, spawned agents can use a Patchright-backed Chrome profile for login-required services, visual workflows, or pages that reject ordinary automation.
- Debug dashboard (React + Vite) with a Boop mascot — Dashboard (usage, known cost, tokens, agent status), Agents (timeline + integration logos), Automations, Memory (table + force-directed graph), Events, Connections.
- Convex for persistence — real-time, typed, free tier.
- Uses your Claude Code or Codex/ChatGPT subscription — choose during setup, with no separate provider API key required.
Agents tab — every spawned sub-agent with status, usage/cost, tokens, turns, runtime, and the integrations it touched.
Automations tab — schedule recurring jobs from a text ("every morning at 8 summarize my calendar") and watch them run.
Memory tab — force-directed graph of clustered memories across short, long, and permanent tiers. Tabular view also available.
Connections tab — Composio toolkits with OAuth handled for you. Click Connect and the agent can use it on the next message.
- This was never meant to be open-sourced. I built it for personal use and decided to share the architecture after enough people asked. It's not a product.
- Not optimized for cost or security. Use at your own risk. Review the code, set your own budgets, and don't trust it with anything you wouldn't trust yourself with.
- I'm open to PRs for optimizations — performance, bug fixes, DX improvements, new example integrations, better docs.
Boop is meant to be a proactive agent — one that nudges you over iMessage with reminders, drafts, and little follow-ups. A small "boop" whenever it has something for you.
And it's named after my dog, Luna, who gives plenty of them.
I'm working on open-sourcing the native iOS app I originally built for this. The rewrite is taking much longer to get right than I'd hoped, but it will happen. I don't personally use it anymore — but enough people have asked, and I want to make it happen.
If you want to see what it looked like before I transitioned to an iMessage-based agent, here's the walkthrough on YouTube.
You need accounts for these. Keep the tabs open — setup will ask for credentials from each.
You should be able to get away with the free plan for each service except your chosen agent subscription, and I'm working to secure discounts for you guys on the pro plans. If you work at any of these companies, please reach out!
| Service | Why | Free? | Discount code |
|---|---|---|---|
| Claude Code or Codex / ChatGPT | Powers the agent. Install the matching CLI, sign in once, Boop uses your local session. | Subscription required | Working on getting one (if you work here, please reach out!) |
| Sendblue | iMessage bridge. Get a number, grab API keys. | Free on their agent plan | RAROQUE20 — 20% off for 6 months (helpful if you plan to commercialize) |
| Convex | Database + realtime. | Free tier is plenty | Working on getting one (in touch with them 👀) |
| Composio | Integrations — one API key unlocks ~1000 toolkits. Optional if you just want chat + memory + automations without third-party access. | Free tier covers personal use | CHRISXCOMPOSIO — 1 month free on starter plan |
| ngrok or similar | Expose your local port so Sendblue can reach it. | Free tier works | Working on getting one (if you work here, please reach out!) |
Custom integrations welcome. Composio covers the common catalog, but you're free to add your own MCP servers under server/integrations/ and register them in server/integrations/registry.ts — the dispatcher treats them the same as Composio-backed ones (just named toolkits the execution agent can spawn against). Useful for in-house APIs, local tools, or anything Composio doesn't ship.
Local browser use is fully optional. Boop can expose a local Chrome profile to spawned agents, but it is off by default. Enable it from the debug dashboard under Settings → Local browser use when you want browser automation for login-only services, visual workflows, or bot-wall-sensitive pages. The Patchright Chrome binary is installed only if you opt in during setup or click the install button in Settings.
# 1. Clone + install
git clone https://github.com/raroque/boop-agent.git
cd boop-agent
npm install
# 2. Install one agent runtime (one-time, global) and sign in
npm install -g @anthropic-ai/claude-code
claude # sign in, then Ctrl-C to exit
# or:
npm install -g @openai/codex
codex login
# 3. Interactive setup — writes .env.local, creates Convex deployment, offers optional local browser use
npm run setup
# 4. Install ngrok (one-time) and authorize it
brew install ngrok
# or grab from https://ngrok.com/download
ngrok config add-authtoken <your-token> # free at https://dashboard.ngrok.com
# 5. Start everything with one command — server, Convex, debug UI, and ngrok
npm run devnpm run dev prints color-prefixed output from all four processes and shows a banner with your ngrok webhook URL once the tunnel is live.
Public URL: https://<abc123>.ngrok.app
Sendblue webhook: https://<abc123>.ngrok.app/sendblue/webhook
On free ngrok, the webhook auto-registers with Sendblue every boot — no manual paste needed. For stable URLs (ngrok reserved or Cloudflare Tunnel), set the webhook once in the dashboard.
Text your Sendblue-provisioned number from a different phone. The agent replies.
⚠ ngrok free plan gives you a new URL every time. That means every time you restart
npm run dev, your Sendblue webhook URL is dead until you paste the new one in.If you're going to run this for more than a quick demo, strongly recommend one of:
- ngrok paid plan — gives you a reserved domain that stays the same forever
- Cloudflare Tunnel — free, stable subdomain, a bit more setup
- Any other tunnel with a static URL (Tailscale Funnel, localtunnel reserved, etc.)
If you use a non-ngrok tunnel, point it at
localhost:3456yourself —npm run devwill still run the rest, just ignore its ngrok output and use your tunnel's URL.
Gotcha:
SENDBLUE_FROM_NUMBERmust be your Sendblue-provisioned number (the one people text TO), not your personal cell. Sendblue's API requires it, and misconfiguring it returns either "missing required parameter: from_number" or "Cannot send messages to self".Fix in one command:
npm run sendblue:syncpulls the right number from the Sendblue CLI and writes it to.env.local.
Boop uses the Sendblue CLI (@sendblue/cli) to eliminate almost all manual dashboard work. Three NPM scripts wrap it:
| Command | What it does |
|---|---|
npm run setup |
Interactive. Offers to run sendblue login / sendblue setup and pulls api_key_id + api_secret_key from sendblue show-keys into .env.local. |
npm run sendblue:sync |
Runs sendblue lines, parses your provisioned phone number, and writes SENDBLUE_FROM_NUMBER to .env.local in E.164 format. Run this anytime your number changes or got set wrong. |
npm run sendblue:webhook -- <url> |
Runs sendblue webhooks list, removes stale ngrok/tunnel hooks, and adds <url> as a type=receive inbound webhook. Called automatically by npm run dev. |
1. Preflight: confirm convex/_generated/ exists (else prompt to run setup).
2. Spawn four children in parallel, each with a prefixed log stream:
server │ (tsx watch server/index.ts)
convex │ (npx convex dev — pushes schema + functions)
debug │ (vite dev server on :5173)
ngrok │ (if installed AND no static URL) exposes :PORT
3. Wait for all four readiness signals:
server → "listening on :PORT"
convex → "Convex functions ready"
debug → "Local: http://localhost:5173/"
ngrok → tunnel URL visible at http://127.0.0.1:4040
4. Auto-register the webhook (FREE ngrok only, not reserved domains):
webhook │ [webhook] removed stale https://old.ngrok-free.app/sendblue/webhook
webhook │ [webhook] registered https://new.ngrok-free.app/sendblue/webhook (type=receive)
5. Show the banner with dashboard + public URL + your Sendblue number.
The banner will look like:
════════════════════════════════════════════════════════════════════
Boop is ready — ngrok tunnel is live (webhook auto-registered).
🐶 Debug dashboard (click me): http://localhost:5173
🌐 Public URL: https://abc123.ngrok-free.app
📮 Sendblue webhook (inbound): https://abc123.ngrok-free.app/sendblue/webhook
📱 Text this Sendblue number: <sendblue-number> (from a DIFFERENT phone)
════════════════════════════════════════════════════════════════════
| Setup | Auto-register fires? | Why |
|---|---|---|
| Free ngrok (default) | Yes, every boot | URL rotates; dashboard would be stale otherwise |
Reserved NGROK_DOMAIN |
No | URL is stable; configure once in Sendblue dashboard |
Static PUBLIC_URL (Cloudflare Tunnel etc.) |
No | Same reason |
SENDBLUE_AUTO_WEBHOOK=false |
No | Manual opt-out |
When someone texts your Sendblue number, expect this sequence in your terminal:
server │ [turn a3f21d] ← <sender-number>: "what's on my calendar today?"
server │ [turn a3f21d] tool: recall({"query":"calendar today"})
server │ [turn a3f21d] tool: spawn_agent({"integrations":["google-calendar"],"task":"Pull today's events"})
server │ [agent 9e82c1] spawn: google-calendar [google-calendar] — "Pull today's events"
server │ [agent 9e82c1] tool: list_events
server │ [agent 9e82c1] done (completed, 2.1s, in/out tokens 1234/567)
server │ [turn a3f21d] → reply (3.4s, 140 chars): "Light day — just your 2pm with Sarah..."
server │ [sendblue] → sent 140 chars to <sender-number>
Per-line anatomy:
[turn xxxxxx]— one iMessage round trip. Same id across←(incoming) → tool calls →→ reply→[sendblue] sent.[agent xxxxxx]— a spawned execution agent. Showsspawn, eachtool:it invokes, anddonewith timing + token counts.[sendblue]— outbound send results. If Sendblue rejects, the error body is logged with a hint about the likely cause (from_number mismatch, self-send, etc.).
The same events are written to Convex (messages, executionAgents, agentLogs, memoryEvents tables) and streamed to the debug dashboard in real time.
- First time / after losing
.env.local→npm run setup(walks through Sendblue + Convex together) - Phone number looks wrong in the banner →
npm run sendblue:sync - Webhook went stale in the dashboard and auto-register is off →
npm run sendblue:webhook -- https://your-url.example.com/sendblue/webhook
Add to .env.local:
SENDBLUE_AUTO_WEBHOOK=false
npm run dev will still show you the webhook URL in the banner so you can paste it yourself.
Visit http://localhost:5173 for the debug dashboard (chat, agents, memory, events). You can also chat from the dashboard's Chat tab without Sendblue.
This is the full first-run. You now have a working agent that chats, remembers, and schedules reminders. Enable integrations (Gmail, Calendar, Notion, Slack) when you want more — see the next section.
┌─────────────┐ webhook ┌─────────────────────┐
│ iMessage │ ─────────────► │ Sendblue → /webhook │
└─────────────┘ └──────────┬──────────┘
│
▼
┌────────────────────────────┐
│ Interaction agent │
│ (dispatcher only) │
│ • recall / write_memory │
│ • spawn_agent(...) │
└────────┬────────┬──────────┘
│ │
┌───────────────┘ └──────────────┐
▼ ▼
┌───────────────┐ ┌──────────────┐
│ Memory │ │ Execution │
│ (Convex) │ │ agent(s) │
│ + cleaning │ │ + integrations│
└───────────────┘ └──────────────┘
- Interaction agent (
server/interaction-agent.ts) is the front door. It reads the user's message + recent history, optionally callsrecall, writes memories, creates automations, and decides whether to answer directly or spawn a sub-agent. - Execution agent (
server/execution-agent.ts) is spawned per task. It loads only the integrations named in the spawn call and returns a tight answer. - Memory (
server/memory/) handles writes, recall, post-turn extraction, and daily cleaning. Stored in Convex. - Automations (
server/automations.ts) poll every 30s for due jobs, spawn an execution agent to run them, and push results back to the user. - Integrations are provided by Composio. The dispatcher names toolkits by slug (
spawn_agent(integrations: ["gmail"]));server/composio.tsopens a toolkit-scoped Composio session per spawn and wraps its tools as an MCP server. No per-integration code to write. - Local browser use is a separate optional integration named
browser. It appears to the dispatcher only after you enable it in Settings, and it controls a persistent local Chrome profile through Patchright.
Deep dive: ARCHITECTURE.md. Adding your own tools: INTEGRATIONS.md.
Skills are reusable playbooks — SKILL.md files that teach execution agents how to do a specific kind of task (write a YouTube script, draft a cold email, plan a trip, etc.).
Boop now has two runtime paths, so keep this distinction in mind:
- Claude runtime: the Claude Agent SDK loads project skills from
.claude/skills/when the execution agent boots. - Codex runtime: Boop keeps Codex-facing skills under
.agents/skills/, while the core sub-agent loop, memory tools, draft tools, and integration tools are provided through Boop's runtime adapter.
For capabilities that must work under both providers, keep the skill instructions mirrored in both directories or move the behavior into Boop's runtime tools/prompts. This applies to both runtime skills and upgrade/migration skills referenced from CHANGELOG.md. The dispatcher never loads skills directly; only spawned execution agents should do real work.
Wiring (in server/execution-agent.ts):
- Claude runs with
settingSources: ["project"]and"Skill"inallowedTools. - Codex runs through
codex app-serverwith Boop's dynamic runtime tools.
To add a cross-runtime skill or migration: create matching files:
.claude/skills/<kebab-name>/SKILL.md.agents/skills/<kebab-name>/SKILL.md
Example:
---
name: youtube-script-writer
description: Write a tight, retention-focused YouTube script from a topic or outline. Use when the user asks for a video script, wants to turn research into a video, or needs a hook rewritten.
---
<instructions the agent follows when this skill is invoked>There's a soft budget (~15k chars by default, via SLASH_COMMAND_TOOL_CHAR_BUDGET) for the combined skill-description block in context — if you end up with many skills, keep descriptions sharp so none get truncated.
Examples included: .claude/skills/youtube-script-writer/, .agents/skills/youtube-script-writer/, and mirrored /upgrade-boop skills for agent-assisted updates.
npm run setup asks which subscription-backed runtime Boop should use:
- Claude Code subscription: uses the Claude Agent SDK and the credentials Claude Code writes to your machine when you sign in. You do not need an
ANTHROPIC_API_KEY. - Codex / ChatGPT subscription: uses the local Codex app-server runtime and the credentials
codex loginwrites to your machine. You do not need anOPENAI_API_KEYfor the agent runtime.
For Claude:
- Install once:
npm install -g @anthropic-ai/claude-code - Run
claudein a terminal, sign in. - That's it — the SDK finds the session automatically.
For Codex:
- Install once:
npm install -g @openai/codex - Run
codex loginin a terminal, sign in. - Boop reads that local auth. Set
BOOP_CODEX_AUTH_HOMEonly if you need a custom Codex home.
If you'd prefer Claude API-key billing (e.g. for a deployed server), set ANTHROPIC_API_KEY in .env.local and the Claude SDK will use it instead. The Codex runtime path uses local Codex subscription auth.
Everything lives in .env.local (auto-created by npm run setup). See .env.example for the full list.
| Var | Required | Notes |
|---|---|---|
VITE_CONVEX_URL |
yes | Convex deployment URL for the Vite debug UI. Written by npx convex dev; the server falls back to this value locally. |
CONVEX_URL |
optional | Server-only Convex URL override for non-Vite deployments. Leave unset locally to avoid Convex CLI ambiguity warnings. |
SENDBLUE_API_KEY / SENDBLUE_API_SECRET |
yes | From your Sendblue dashboard. |
SENDBLUE_FROM_NUMBER |
yes | Your Sendblue-provisioned number. |
BOOP_RUNTIME |
no | claude by default. Set codex to use local codex app-server with the ChatGPT/Codex account from codex login. |
BOOP_MODEL |
no | Default claude-sonnet-4-6. Used as the fallback when no runtime override is set. The user can switch the model at runtime from iMessage ("use opus", "switch to sonnet") via the set_model self-tool — that override is stored in the Convex settings table and takes precedence over this env var. |
BOOP_CODEX_MODEL / BOOP_CODEX_REASONING_EFFORT |
no | Codex defaults when BOOP_RUNTIME=codex. Defaults: gpt-5.5 and medium. |
BOOP_CODEX_AUTH_HOME |
no | Optional path to a Codex home containing auth.json; otherwise Boop uses the current codex login auth. |
BOOP_BROWSER_ENABLED |
no | Fallback for Local browser use. Default false. Runtime settings in Convex take precedence once changed from the dashboard. |
BOOP_BROWSER_PROFILE_DIR |
no | Persistent Chrome profile directory. Default ~/.boop/browser-profile. |
BOOP_BROWSER_SHOW_UI |
no | true opens a visible Chrome window; false runs hidden/headless. Default true. |
BOOP_BROWSER_LOGIN_HANDOFF |
no | Enables the agent's login handoff tool. Default false. |
BOOP_BROWSER_START_URL |
no | Optional URL to open when launching the local browser without an explicit URL. |
BOOP_BROWSER_CHANNEL / BOOP_BROWSER_EXECUTABLE_PATH |
no | Chrome channel or explicit browser binary path for Patchright. Default channel chrome. |
BOOP_BROWSER_EXTRA_ARGS |
no | Optional newline-separated Chrome flags. Only --flag lines are used. |
BOOP_UPSTREAM_CHECK |
no | Set to false to disable the new-version banner on npm run dev. Default: on. |
PORT |
no | Default 3456. |
PUBLIC_URL |
no | Base URL used in the Sendblue webhook. Composio handles its own OAuth callbacks on platform.composio.dev, so this is just for inbound iMessage. |
VOYAGE_API_KEY or OPENAI_API_KEY |
optional | Unlocks vector recall. Falls back to substring. |
COMPOSIO_API_KEY |
optional | Enables integrations. Without it, plain chat + memory + automations still work. Get one at app.composio.dev/developers. |
COMPOSIO_USER_ID |
optional | Stable user id Composio keys connections under. Defaults to boop-default. |
ANTHROPIC_API_KEY |
optional | Bypass the Claude Code subscription for the Claude runtime. |
Local browser use is for cases where a normal API integration or web fetch is the wrong tool: login-required portals, visual browser workflows, JavaScript-heavy apps, or services that may detect bot-like automation. It is deliberately opt-in.
How it works:
- Open the debug dashboard → Settings → Local browser use.
- Turn on Local browser use. Until this is enabled, agents do not see the
browserintegration at all. - Choose whether Chrome should be visible with Show browser UI. On means a Chrome window opens on your machine; off runs hidden/headless.
- Turn on Spawn login instance only when you want the agent to hand control to you for login or MFA. The agent will say: "I need you to log in first. I’ve spawned an instance on your machine."
- Use Install Patchright Chrome if Patchright has not installed its browser binary yet.
The browser uses a persistent Chrome profile, so cookies and login state can carry across runs. Boop does not store third-party service passwords or OAuth tokens for this feature; those live in the local Chrome profile you choose. The browser_fill tool redacts typed values before agent tool-use logs are stored. Settings are stored in Convex under the settings table, with .env.local values used only as fallbacks.
Browser control HTTP routes are local-only. Requests forwarded through a public tunnel are rejected, so your ngrok/Sendblue URL cannot launch, close, or install a local browser.
For Codex runtime, local browser tools are exposed internally under the local_browser namespace to avoid Codex's reserved browser namespace. The user-facing integration name remains browser.
Boop outsources 3rd-party service integrations to Composio. One API key unlocks ~1000 toolkits (Gmail, Slack, GitHub, Linear, Notion, Drive, Stripe, Supabase, HubSpot, Salesforce, Granola, and so on). Composio hosts the OAuth apps, manages token refresh, and exposes every toolkit as tools Boop can adapt for either runtime. Boop never sees an access token.
- Grab an API key at app.composio.dev/developers.
- Add it to
.env.local:COMPOSIO_API_KEY=sk-comp-... npm run dev.- Open the debug dashboard → Connections tab. You'll see a curated list of ~20 cards. For each one: click Connect, authenticate on Composio's hosted page, done — Composio ships managed OAuth for every curated toolkit. (If you add a custom toolkit that needs your own OAuth app, the card flips to a "Set up →" state pointing at
platform.composio.dev/auth-configs— rare, but supported.)
After a successful connect, the agent can use that toolkit immediately — no restart.
Boop keeps the dispatcher / executor split intact. Composio sits under the executor:
interaction-agent: spawn_agent(task, integrations: ["gmail", "slack"])
│
▼
execution-agent: for each slug, open a Composio session scoped to that toolkit:
composio.create(BOOP_USER, { toolkits: ["gmail"] })
session.tools() ← returns only Gmail tools
│
▼
Claude: createSdkMcpServer({ name: "gmail", tools })
Codex: dynamic runtime tools
│
▼
Sub-agent sees mcp__gmail__GMAIL_* — nothing else.
Key properties:
- Per-spawn tool scope. The dispatcher picks which toolkits the sub-agent sees. Tens of tools per spawn, not thousands, so context stays tight and the agent stays fast.
- Toolkit slug = integration name.
spawn_agent(integrations: ["linear"])works for any toolkit you've connected. Unknown slugs just log a warning and are skipped. - No tokens on our side. Every tool call runs through Composio's proxy. If Composio goes down, integrations go down — but your server never holds user OAuth tokens.
- Multi-account per toolkit. Connect a second Gmail (work + personal) — each gets its own connection row you can alias. The dispatcher picks up all active connections for the slug.
- Identity resolution. Connection cards show the real account email (e.g.
user@example.com) resolved by calling the toolkit's own "who am I" tool through Composio (GMAIL_GET_PROFILE, etc.). Alias per connection if you want a friendlier label.
The ~20 toolkit catalog is hand-picked in server/composio.ts:CURATED_TOOLKITS. To surface another:
// server/composio.ts
export const CURATED_TOOLKITS: CuratedToolkit[] = [
// …existing entries…
{ slug: "airtable", displayName: "Airtable", authMode: "managed" },
];authMode: "managed" is correct for virtually every toolkit Composio ships today. Use "byo" only if Composio doesn't have a hosted OAuth app for that toolkit. If you guess wrong, the UI's auth-config fallback banner catches it and points you at the right dashboard page.
Every LLM call — dispatcher turn, execution-agent run, memory extraction, proactive email classification, and consolidation (proposer / adversary / judge) — writes a row to the usageRecords table with runtime, billing mode, requested model, token counts, cache counts when available, and cost when the runtime exposes it.
Claude runtime: total_cost_usd comes from the Claude Agent SDK's result message, so Dashboard cost tiles and per-agent cards show real dollar amounts that should match Anthropic billing.
Codex runtime: codex app-server exposes token counts but not your actual subscription bill. Boop records billingMode=codex-subscription, stores the token counts, and estimates costUsd from OpenAI's published standard API token prices. Treat Codex dashboard spend as an API-equivalent usage proxy, not a bill.
Boop's Claude SDK query() calls don't currently set maxTurns or maxBudgetUsd. Those are hard stops the Claude SDK exposes — set them and the agent aborts once the threshold hits, with whatever partial result it has. Codex subscription runs do not currently have the same dollar-budget stop because the app-server path exposes token counts, and Boop's Codex dollar amounts are estimates derived from those counts.
Kept as-is intentionally for a single-user personal agent: every task is scoped tight (spawned by the dispatcher with a specific task string + a small integration list), integrations are Composio-scoped per spawn so the tool surface stays small, and the existing 15-minute heartbeat (server/heartbeat.ts) marks any long-running agent as failed and aborts it. In practice execution agents complete in under 60 seconds.
If you deploy Boop in a higher-throughput setting, or hand it integrations that allow looping (webhooks, scrapers), add runtime-specific caps before opening it up to more users.
Deeper dive — auth modes, toolkit scoping internals, multi-account flow, per-connection identity: INTEGRATIONS.md.
Upgrade path when upstream ships changes: open Codex or Claude in the repo and run /upgrade-boop. The mirrored skills under .agents/skills/upgrade-boop/ and .claude/skills/upgrade-boop/ preview diffs, back up, merge, validate, and surface [BREAKING] CHANGELOG entries. See CONTRIBUTING.md for contribution rules + the CHANGELOG / migration-skill conventions.
boop-agent/
├── server/
│ ├── index.ts # Express + WS + HTTP routes
│ ├── sendblue.ts # iMessage webhook, reply, typing indicator
│ ├── interaction-agent.ts # Dispatcher
│ ├── execution-agent.ts # Sub-agent runner
│ ├── runtime-config.ts # Claude/Codex runtime selection + model defaults
│ ├── automations.ts # Cron loop
│ ├── automation-tools.ts # create/list/toggle/delete MCP
│ ├── draft-tools.ts # save_draft / send_draft / reject_draft MCP
│ ├── heartbeat.ts # Stale-agent sweep
│ ├── consolidation.ts # 3-phase adversarial pipeline (proposer → adversary → judge)
│ ├── usage.ts # aggregateUsageFromResult helper (shared cost aggregation)
│ ├── embeddings.ts # Voyage / OpenAI wrapper
│ ├── composio.ts # Composio SDK wrapper (session + toolkit scoping)
│ ├── composio-routes.ts # /composio/* HTTP routes for the Debug UI
│ ├── browser-routes.ts # /browser/* HTTP routes for Local browser use
│ ├── broadcast.ts # WS fanout
│ ├── convex-client.ts # Convex HTTP client
│ ├── browser/
│ │ ├── launcher.ts # Patchright Chrome launch/status/actions
│ │ └── tools.ts # Local browser runtime/MCP tools
│ ├── runtimes/
│ │ ├── claude.ts # Claude Agent SDK adapter
│ │ ├── codex-app-server.ts # Codex app-server adapter
│ │ └── types.ts # Shared runtime/tool contracts
│ ├── memory/
│ │ ├── types.ts
│ │ ├── tools.ts # write_memory / recall (vector + substring)
│ │ ├── extract.ts # Post-turn extraction
│ │ └── clean.ts # Decay + archive + prune
│ └── integrations/
│ ├── registry.ts # Integration loader
│ ├── browser-loader.ts # Registers optional Local browser use
│ └── composio-loader.ts # Registers each connected Composio toolkit
├── convex/
│ ├── schema.ts
│ ├── messages.ts
│ ├── memoryRecords.ts
│ ├── agents.ts
│ ├── automations.ts
│ ├── consolidation.ts
│ ├── conversations.ts
│ ├── drafts.ts
│ ├── memoryEvents.ts
│ ├── usageRecords.ts # Append-only per-call cost log
│ └── sendblueDedup.ts
├── debug/ # Dashboard: Dashboard / Agents / Automations / Memory / Events / Connections
├── scripts/
│ ├── setup.ts # Interactive setup CLI
│ ├── dev.mjs # One-command orchestrator (server + convex + vite + ngrok)
│ ├── preflight.mjs # Checks convex/_generated exists before booting
│ ├── sendblue-sync.mjs # Pulls phone number from `sendblue lines`
│ └── sendblue-webhook.mjs # Registers inbound webhook via Sendblue CLI
├── README.md ← you are here
├── ARCHITECTURE.md
└── INTEGRATIONS.md
Boop is a fork-and-own template. You customize your copy freely — system prompts, memory thresholds, extra tools — and pull upstream fixes in on your own schedule.
The intended path is agent CLI-driven:
codex # inside your repo
/upgrade-boop
# or:
claude
/upgrade-boop/upgrade-boop is mirrored in .agents/skills/upgrade-boop/SKILL.md and .claude/skills/upgrade-boop/SKILL.md. It:
- Refuses to run with a dirty working tree.
- Creates a timestamped rollback tag.
- Previews upstream changes bucketed by area (core / integrations / UI / schema / scripts / docs).
- Merges (or cherry-picks, or rebases — your choice).
- Runs
npm install+npm run typecheck. - Parses
CHANGELOG.mdfor[BREAKING]entries and offers to run the referenced migration skills. - Prints a rollback hash + any env-var additions you should copy into
.env.local.
/upgrade-boop is for your local agent CLI operating on the repo. It is not exposed to the Boop SMS/web dispatcher. The Codex runtime used by Boop conversations runs with read-only sandboxing and no shell/file-write tools, so a text-message conversation cannot update the server.
Every time you run npm run dev, a small background check (scripts/check-upstream.mjs) asks your upstream remote if there are new commits. If there are, you'll see a banner up top with the count and a reminder to run /upgrade-boop from Codex or Claude. If you're up to date, or the check fails for any reason (offline, no upstream remote, timeout), it stays silent.
Behavior at a glance:
upstreamset, new commits → banner with the countupstreamset, up to date → silent- No
upstreamremote, on a fork → one-line hint on adding it - No
upstreamremote, on the canonical repo → silent (you are upstream)
To turn it off:
- Env var: add
BOOP_UPSTREAM_CHECK=falseto.env.local - Or comment it out: the call lives in
scripts/dev.mjs— thespawn("node", ["scripts/check-upstream.mjs"], ...)block. Delete or comment that block and the check never runs.
Every release lists additions under CHANGELOG.md, with [BREAKING] prefixes for anything that requires action. /upgrade-boop parses that format automatically.
Agent doesn't reply.
- Check the server is running:
curl http://localhost:3456/health - Check the Sendblue webhook is pointed at
<public-url>/sendblue/webhook - Watch server logs. Look for
[sendblue]and[interaction]messages.
Convex errors / VITE_CONVEX_URL is not set.
- Run
npx convex devmanually. Ensure.env.localhasVITE_CONVEX_URL; the server can use that locally.
"Could not find public function for X:Y".
CONVEX_DEPLOYMENTandVITE_CONVEX_URLin.env.localare pointing at different projects.convex devpushes functions toCONVEX_DEPLOYMENTbut the client reads fromVITE_CONVEX_URL. Fix: make sure the URL has the same name as the deployment —CONVEX_DEPLOYMENT=dev:foo-bar-123→VITE_CONVEX_URL=https://foo-bar-123.convex.cloud. Re-runningnpm run setupnow auto-syncs these.
Agent replies but can't use my integration.
- Check
COMPOSIO_API_KEYis set in.env.local. - Check the toolkit shows as Connected in the Connections tab.
- Watch server logs for
[composio] registered …at boot and[integrations] unknown integration: …on spawn attempts.
Agent says Local browser use is off.
- Open the debug dashboard → Settings → Local browser use and turn it on. Agents cannot see or use the
browserintegration while it is disabled. - If launch fails, click Install Patchright Chrome in that same section, then try Launch again.
- If you need to log in manually, also turn on Spawn login instance so the agent can open a visible handoff window.
I want to skip Sendblue for now.
- The server exposes
POST /chatwith{ conversationId, content }— curl or a tiny client can drive the agent directly, no iMessage required.
Claude SDK says no credentials.
- Run
claudeonce and sign in, or setANTHROPIC_API_KEYin.env.local.
Codex says no credentials.
- Run
codex loginonce, or setBOOP_CODEX_AUTH_HOMEto a Codex home containingauth.json.
"Cannot send messages to self" / "missing required parameter: from_number".
SENDBLUE_FROM_NUMBERis set to your personal cell instead of your Sendblue-provisioned number. Runnpm run sendblue:syncto pull the correct number fromsendblue linesand write it to.env.local.
"Dashboard crashed" in the debug UI.
- The ErrorBoundary caught something. Check the server logs (
server │stream) and the browser console — both will have the real error. Most common cause: a new Convex function hasn't been deployed yet. Restartnpm run devsoconvex devre-pushes.
MIT. Build whatever you want on top of this.

