New
Apps 1,895Makers 1,890This week 1,690
Promote your app−30%
1views in
the last 30 days

Intern

Community brain for AI agents: drafts go out from your own Gmail/Slack after approval; Slack...

Built with Claude Code

by mihirsahu14 · Unclaimed · from USUnited States · added Oct 29, 2026
no ratings0 comments

A public community brain for AI agents, at intern-brain.vercel.app. Sign in with GitHub and you're in the same brain as everyone else. Brief an intern in one sentence and it recalls what the community already knows — facts plus passages from a searchable archive — then drafts exactly one outbound email or Slack message and stops. It never asks questions; anything it can't settle becomes a [placeholder] you fill in before approving. Once you've connected your own Gmail or Slack (through Composio), approving a draft sends it from your own account. Edit a draft before approving and the edit becomes a fact the next intern reads first — that's the whole learning loop. Outside briefs, the community Slack's public channels, documents (a link, an upload or a PDF) and public GitHub repos all feed that same archive automatically; a 🧠 reaction, the promote button, or an approved draft that cited a passage turns it into a fact.

Origin and credit

Started at a hackathon on 9 Aug 2026 as a two-person team: Andrew Wang (AndrxwWxng/intern, the upstream this repo forked from) and Mihir Sahu. Mihir has continued it solo since. The hackathon build ran on a separate Python agent engine, built on Agno's Apache-2.0 agno-agi/scout example; that engine, VoiceOS and the original MCP server are gone from this codebase.

What it does today

A run. interns.spawn (a mutation) checks the day's caps, inserts a row and schedules run.go (an action). run.go recalls facts and archive passages for the task, makes one streamed call to an OpenAI-compatible model (lib/model.ts, plain fetch, no SDK), parses the reply for fact blocks and an action block, and calls interns.finish — facts and a draft, in one transaction. A run that throws lands in interns.fail instead, which still records what the provider billed. There's no separate agent service, no Python, no queue: it's one Convex action.

Recall. Two things feed the prompt: facts.recall (what the community has taught the brain, including past corrections) and facts.archive (passages from Slack, documents and GitHub that match the task). The intern cites both kinds by id in its sources, never in prose. Lessons (preferences and corrections) are picked for the task, not by age: the member's own that match it, then anyone's that match it, then the member's newest, since house style carries across topics. Someone else's lesson about another topic never takes a slot: search matches a lesson by its draft's subject and the changed text, not the fixed wording every lesson shares. The prompt lists lessons in their own section and tells the intern to apply them to new drafts, and an edit's lesson states only what changed, so it fits in the 400 characters of a fact that reach the prompt.

Sending. An approved draft goes through outbox.decide to send.ts, which calls Composio to send from the member's own connected Gmail or Slack. Without a Composio connection, or without COMPOSIO_API_KEY / COMPOSIO_VERIFIER_URL set on the deployment at all, approving still files the decision but sends nothing (sandbox). A draft that cited archive passages promotes them to facts on approval (outbox.decide → sources.promoteCited).

Ingestion. The community Slack's public channels arrive live through /slack/events and a 90-day backfill; documents (a pasted link, a markdown upload or a PDF) and public GitHub repos (README, issues, PRs) are read into the same sources/passages tables by convex/documents.ts and convex/ingest.ts. No model call is involved in ingestion — it's plain fetching and chunking.

Architecture

flowchart LR
    UI["Next.js cockpit"] <-->|"queries / mutations"| Convex

    subgraph Convex["Convex backend"]
        Run["run.go (action)"]
        Finish["interns.finish"]
        Outbox["outbox.decide"]
        Facts[("facts")]
        Passages[("passages / archive")]
        Sources[("sources")]
    end

    Facts -->|"recall"| Run
    Passages -->|"archive"| Run
    Run --> Model["OpenAI-compatible model\n(Claude Sonnet 5.5 on prod; Groq default)"]
    Model --> Run
    Run --> Finish --> Outbox
    Outbox -->|"approve"| Composio
    Outbox -->|"cited passages promote"| Facts
    Composio --> Gmail
    Composio --> Slack

    SlackEvents["Slack Events API"] -->|"/slack/events"| Passages
    Docs["Documents + public GitHub repos"] -->|"sources.ts / documents.ts / ingest.ts"| Sources --> Passages

Determinism and evals

Interns never ask questions — the prompt gives them no way to, and a missing detail becomes a [placeholder] instead. If a reply asks anyway, or an outbound-shaped brief comes back with no usable draft, run.go makes exactly one code-enforced rewrite call telling it to draft with placeholders. A live send is refused while any [placeholder] remains in the draft.

npm run eval runs 28 fixed briefs through the live prompt (EVAL_LIVE=1 runs them in "live" mode, as a fully connected member). On Claude Haiku 4.5, 2026-09-25, live-mode expectation match went from 15/28 to 28/28 with zero questions asked. It exits non-zero if the action-block parse rate drops under 90% or more than a quarter of the briefs error out, so a model outage can't read as a pass.

npm run eval:learning checks the learning loop: does an edit change the next draft? Each of 5 cases (a sign-off, a greeting, a subject prefix, an added link, a Slack channel) drafts a brief, applies the edit, files the lesson the way outbox.decide does and hands it to a related brief the way facts.recall does, next to a control run without it. A case passes when only the run with the lesson shows the edit. On Claude Haiku 4.5, 2026-10-23, it went from 2/5 (lessons quoted both drafts in full, so the cut dropped the change) to 5/5 on two runs. Which lessons reach a brief is covered deterministically in convex/engine.test.ts.

Tests

npm test          # unit tests: caps, prompt, parsers, redaction — 153 passing
npx vitest run     # Convex functions via convex-test — 242 passing

(Counts as of this write-up; run them yourself to check current numbers.)

Safety

Caps, all in lib/caps.ts, enforced inside mutations so two tabs can't race past a limit: 5 briefs per person per UTC day, 1 intern working at a time, 20 facts per person per day, 20 sends per person per day, 5 sources added per person per day, and a $5/day model-spend budget shared by everyone. CAP_EXEMPT_HANDLES (a Convex env var, comma-separated GitHub handles) skips the per-member caps for the deployment owner's own testing; it never skips the shared budget.

Visibility. Drafts and sends are owner-only. Public briefs and facts have email addresses redacted (lib/redact.ts) before anyone else sees them. Private documents stay out of the graph, the feed and other members' recall.

Inbound. /slack/events (the community Slack) and /composio/webhook (the 🧠 reaction and the opt-in Gmail label) both verify a signature before parsing anything — lib/slack.ts's verifySlack and lib/inbound.ts's verifyWebhook. An unsigned or mis-signed call gets a 401.

Fetching. Adding a document by URL goes through lib/ingest.ts's fetchDocument, which rejects private/loopback addresses and re-checks every redirect hop, so a link can't be used to reach an internal address.

Composio. Connections finish through Composio's callback identity verification (lib/composio.ts), so a Connect Link can't be redeemed for someone else's session.

Run it locally

npm install
npx convex dev   # backend — one watcher at a time
npm run dev      # cockpit → localhost:3000

Env vars: copy .env.local.example to .env.local and run npx convex dev to fill in the Convex URLs; everything else (the model key, GitHub sign-in, Composio, Slack) is a Convex env var, not a .env file — see .env.local.example and HANDOVER.md for what to set and where each value comes from. HANDOVER.md also covers the full community setup (Composio, the community Slack, Gmail, ingestion) end to end; this file only summarizes what's live.

docs/HLD.md is the older, larger design this was cut down from — read it as intent, not as a description of this code.

Description taken from the repository README.

Ratings

–
0 ratings
5 0
4 0
3 0
2 0
1 0

Rate this app

Sign in to rate

Comments 0

Sign in to comment

No comments yet.

Related apps

#agents · #social · #email