Drop a widget into your app. Your team flags bugs while testing — qa or prod. Otto reads each report, finds the relevant code, and opens a draft pull request on the right repo. Never auto-merges.
Live at ottoagent.app.
convex/— backend. Schema, HTTP endpoints, parser, router, Cursor invocation, audit log, GitHub App, team/auth.widget/— vanilla-JS feedback widget. Built with esbuild; otter sprites baked in as data URLs.app/— React + Vite admin app and the public marketing landing. Multi-page Vite setup:/is the landing,/dashboard/is the React admin.
- Backend: Convex (functions, scheduler, vector index, auth, cron).
- Auth:
@convex-dev/authpassword provider, gated by team membership. - Multi-tenant: every admin-scoped table carries
teamId; auto-create personal team on first sign-in. - Widget routing: each project owns its own widget secret. The
snippet's
data-secrettells the server which project (and team) the feedback belongs to. No URL-pattern matching, no team-wide secret. - Item routing: widget event → project's primary repo. If a project has no primary repo (or for any non-widget source), the semantic router picks one from repo embeddings. Below confidence threshold → Slack review queue.
- Outbound code: Cursor cloud agents. PRs always land as draft;
cursor.ts:verifyDraftfails closed if the opened PR isn't draft. - GitHub: per-team GitHub App install. PR verification mints a
short-lived installation token via
internal.github.getInstallToken, falling back toGITHUB_TOKENenv on teams without the app installed.
npm install
CONVEX_DEPLOYMENT=dev:your-deployment npx convex dev # syncs schema + functionsRequired Convex env vars on the prod deployment:
npx convex env set OPENAI_API_KEY sk-...
npx convex env set CURSOR_API_KEY cu-... # fallback when no team key set
npx convex env set GITHUB_TOKEN ghp-... # fallback PAT for PR verifyPer-team integrations (Cursor key, GitHub App install) are stored in the database — admins wire them up from Settings.
For the GitHub App (one-click install per team), the operator also sets:
npx convex env set GITHUB_APP_ID ...
npx convex env set GITHUB_APP_SLUG otto-agent-app
npx convex env set GITHUB_APP_CLIENT_ID ...
npx convex env set GITHUB_APP_CLIENT_SECRET ...
npx convex env set GITHUB_APP_PRIVATE_KEY "$(cat path/to/key.pem)"
npx convex env set GITHUB_APP_WEBHOOK_SECRET ...For Slack interactivity (low-confidence review queue):
npx convex env set SLACK_BOT_TOKEN xoxb-...
npx convex env set SLACK_SIGNING_SECRET ...
npx convex env set SLACK_REVIEW_CHANNEL C0123456789# from repo root
npm test # vitest, runs the convex-test pipeline suite
npm run widget:build # rebuild widget bundle into app/public/otto.js
# from app/
npm run dev # vite dev server (landing at /, admin at /dashboard/)
npm run build # static build into app/distDon't run npx convex dev from inside app/ — it'll repoint
app/.env.local at an anonymous local deployment. Always run convex
commands from the repo root.
- Frontend: Vercel auto-deploys
main(GitHub integration is wired viavercel git connect). Output dir isapp/dist. - Backend: GitHub Actions
deploy-convex-prodjob (in.github/workflows/ci.yml) runsnpx convex deployon every push tomainusing theCONVEX_DEPLOY_KEYrepo secret.
- Sign in to
http://localhost:5173/dashboard/(creates a personal team). - Settings → wire up the Cursor key + install the GitHub App.
- Projects → create a project → set its primary repo from the edit form.
- Project detail page → copy the snippet (it has the project's
data-secretbaked in) → paste ontolocalhost:5173/widget-demo.html→ file feedback. - Project detail's Activity table fills in. Watch status:
parsed → fired → pr_opened. Audit log captures every transition.
| Source | Auth | Cron | Where |
|---|---|---|---|
| Widget | per-project secret (auto-generated) | n/a | convex/http.ts /ingest/widget |
| GitHub | per-team App installation | n/a | convex/github.ts |
| Cursor | per-team API key (paste in UI) | n/a | convex/cursor.ts |
| Slack | workspace env vars | n/a | convex/slack.ts |
| Repos | n/a | daily 08:00 UTC | convex/repos.ts (reindex embeddings) |
convex/granola.ts and convex/zoom.ts are still in the tree but
aren't surfaced in the product or driven by any cron — leftovers from
an earlier meeting-led version of Otto. Safe to delete when nothing
references them anymore.
Encoded in convex/cursorPrompt.ts and convex/cursor.ts:verifyDraft:
- PRs are always draft.
- Scoped to the agent's branch.
- Original feedback + parser/router confidence land in the PR description.
- Existing tests cannot be weakened.
- Otto fails closed if it can't verify draft status (
failureReasonset, status flips tofailed, item ends up in the Slack queue for human review).