Built in public · Changelog

How Supernova learned the job.

The product story, newest first: what actually became possible in each stretch, not a list of tweaks. Generated live from the engineering journal; the log is the changelog.

99

improvements shipped

9

days of building

11

shipped per day, avg

9
6 Jul
8
7 Jul
1
8 Jul
2
10 Jul
5
11 Jul
25
12 Jul
6
13 Jul
peak37
14 Jul
6
15 Jul

Product panorama

One deal, one continuous operating loop.

Six agents live in the product today, propose-only, a named human at every gate. Everything below ships; nothing is a mock.

01every desk, daily

Brief

The Daily Brief opens each cockpit: meetings with exact times, deals needing a touch, the one move.

02critic-checked

Draft

The Email Drafter investigates the thread through scoped tools, then a fresh-context critic verifies every claim.

038 dimensions

Qualify

Deal Check scores MEDDPICC with the evidence that earned each score, never vibes.

04pre-call

Prep

Meeting Prep briefs every call: who is in the room, where it stands, what to ask.

05with provenance

Track

The Promise Tracker extracts what each side committed to, source attached, resolved in place.

06propose-only

Audit

Every run costed and receipted: model, tokens, verdict, the audit trail behind every artifact.

15

Jul 2026

Wednesday

2 shipped

Agents

The Rohit walk: the org chart stops decorating, the briefing stops wrapping names

Walking the admin suite as Rohit (Command / Users / Audit / AI usage / Tech leads / Org chart), two real design-language breaches:

Walking the admin suite as Rohit (Command / Users / Audit / AI usage / Tech leads / Org chart), two real design-language breaches:

  • The org chart was the last page decorating with colour. Its L1-L4 depth ramp was neon indigo / blue / emerald / purple on bordered boxes, the exact thing Daylight bans (hierarchy comes from scale and weight, never a rainbow; the entity hues are warm and muted). Depth is now a ladder of prominence: clay at the root, fading down the ink ramp, elevation carrying the box, borders gone. The legend reads from the same LEVEL_STYLES, so it re-tuned itself.
  • The briefing's lead column is a MEASURE column (times "12:30", amounts, ages) and aligns beautifully at w-16 with tabular-nums. The Command Board's rep names are not measures: "Nitish Kumar Rana" wrapped to three lines. Names and company labels now get w-28 + truncate, and drop tabular.

Everything else on the admin suite held up: the team briefing reads with real intelligence ("Himanshu carries the biggest load: Rs 19.9 Cr with 20 slipping deals and 58 overdue tasks"), the audit trail shows agent runs with actor glyphs, the AI ledger costs every call.

Agents

Enrich (agent 7): live web research on any company, including ones not in the CRM yet

The seventh agent, Enrich, answers "who is this company, really?" with the live web instead of the CRM's stale fields. Built on Anthropic's server-side web_search tool (verified on the…

The seventh agent, Enrich, answers "who is this company, really?" with the live web instead of the CRM's stale fields. Built on Anthropic's server-side web_search tool (verified on the user's key; ≤5 searches per run, each metered at $0.01 into llm_calls.web_searches and the cost ledger):

  • `src/lib/ai/agent-enrich.ts`: one Haiku call with web search → structured dossier: what they do / size / HQ, recent news (every item must carry its source URL — uncited news is dropped deterministically), key people, an honest ICP-fit read (strong/medium/weak) against Antino's ICP, ≤3 talking angles, and proposed CRM updates (current → proposed, only where the web contradicts or fills a listed field — propose-not-act, they land in Zoho with write-back). Verdict PASS/THIN/NOT_FOUND; run summary + trace-true totals; upsert into new enrichments table (unique per entity_kind + entity_id).
  • Three entity kinds: lead (company or corporate-email domain), account (name + website), and `prospect` — research-from-nothing: the company name typed into accounts search, stored lowercase. The /ae/accounts?q=LifeWise empty state now offers "Research LifeWise on the web"; the dossier renders right there, with a Propose as account button that audits ACCOUNT_ADD_PROPOSED (PREVIEW) with the dossier fields in after.
  • Surfaces (src/components/enrich-panel.tsx): accounts-search empty state, account hub, AE lead hub, SDR lead hub. Toast on start + finish (principle 13), receipt line (verdict · N web searches · ₹). Server actions in src/lib/enrichment/actions.ts audit AGENT_ENRICHED.
  • Eval added (5th agent eval): /api/evals/run case + run-evals.mjs asserts company identified, all news cited, valid ICP score, totals == ledger, cost < $0.15 (search-metered ceiling).
  • Verified live: Bajaj Finserv account (PASS, 4 cited news, ICP strong, $0.088); LifeWise prospect (found LifeWise Academy, honestly scored ICP weak — the openers even warn the name is ambiguous across companies); ANSCER Robotics lead (ICP strong, the real $5.4M Series A + US expansion in news, key people matching the actual email thread, 4 proposed field updates: industry, 73 employees, anscer.com, Bengaluru).

14

Jul 2026

Tuesday

10 shipped

Agents

The Chaser: promises tracked, kept, chased (agent #6) + the lead thread gets the matcher

People commit to things on threads and calls — "we'll send the proposal by Friday", "Vikrant will share it with the US team" — and those promises evaporate. Two ships close #13 completely:

People commit to things on threads and calls — "we'll send the proposal by Friday", "Vikrant will share it with the US team" — and those promises evaporate. Two ships close #13 completely:

The Chaser (src/lib/ai/agent-chaser.ts): reads an entity's deterministic sources — the matched email thread (full bodies) + the recorded calls' action items — and extracts each concrete commitment: crisp text, who made it, which side (WE/THEY), the due date only when one was actually stated (resolved against that source's date, never invented), and the exact source it came from. A fresh-context critic drops anything the sources don't support (extraction filters, it never repairs). Rows land in `commitments` with full provenance (source email/recording + agent run id; a dedupe index makes re-scans idempotent). The Promises panel (promises-panel.tsx) sits on the AE deal hub and both lead hubs: WE/THEY typeset rows, due chips (amber → rose when overdue), source links into the recording page, and in-place kept / dismiss — REAL writes to our table, audited as COMMITMENT_FULFILLED/COMMITMENT_DISMISSED (not previews). Verified live on centaurihs: 7 promises from 3 emails + 2 calls (6 ours, 1 theirs — Vikrant taking Antino to his US counterparts), all due dates correctly resolved to the Jul-14 proposal deadline, critic verdict FILTERED (it dropped an unsupported one), ₹0.79. 6 of 8 agents live.

The matcher reaches the lead thread (#13's leftover): getLeadTimeline now uses the same 4-branch corporate-domain matcher as the deal thread — exact + company-domain mail in ONE indexed pass, with a per-row exact flag driving "with this lead" vs "team thread @domain" (the old code only fell back to domain when exact matched nothing, hiding team threads entirely — SwiffyLabs' finance-team invoices now show on Ashish's lead). And a hazard found before it shipped: a lead at microsoft.com would have dragged 400+ dmarcreport@/no-reply@ notifications into their timeline — new NOISE_LOCALPARTS filter on the from-domain branch (431→15 real rows), exact-address matches untouched, deal side inherits the fix.

Agents

Test infrastructure: route smoke suite + agent evals + CI

The honest audit said it: agents were verified live but had no eval suite, and the early Playwright pass was a one-off, not maintained. Built the first two tiers:

The honest audit said it: agents were verified live but had no eval suite, and the early Playwright pass was a one-off, not maintained. Built the first two tiers:

  • Route smoke suite (tests/e2e/, npm run test:e2e) — Playwright signs in one seeded user per role (global-setup samples live entity ids from the DB), then hits 37 routes across admin/AE/SDR/public — every static page plus real dynamic instances (deal hub, lead hubs, account, contact, recording, meeting event, a peek URL). Each must return <500, render a landmark, show no Next error state (the dev overlay text is checked through the shadow DOM — <nextjs-portal> itself always exists in dev), and log no serious console errors. 37/37 green in ~39s — and the suite caught a real bug on its maiden run: /admin/ai 500'd because as w(window) used a reserved SQL word. Fixed.
  • Agent evals (scripts/evals/run-evals.mjs, npm run test:evals) — a token-guarded eval endpoint (/api/evals/run) runs each live agent against a real fixture entity; the runner asserts hard invariants: valid artifact structure (8 MEDDPICC dims, scores from the enum, every non-unknown score carries evidence; brief sections within bounds; extraction caps), sane verdict, run totals == the llm_calls ledger exactly, and a per-run cost ceiling ($0.10). All 4 evals passed — Meeting Prep even exercised the REPAIRED path live.
  • CI (.github/workflows/ci.yml) — typecheck + lint on every push; e2e/evals run locally (need the dev DB + key + server).

Next tiers (planned): golden-fixture grounding snapshots per agent (deterministic, no-API), the 8–10 money-flow tests (peek, link-recording, run-agent with a mocked client, close-on-activity, handoff accept), nightly evals with trend tracking off agent_run_summary.

Agents

The agent team gets plain-English names

"Witness" and "Qualifier" mean nothing to a salesperson — the name should say what the agent does. Renamed the whole team and propagated everywhere: Daily Brief (Briefer) · Email Drafter…

"Witness" and "Qualifier" mean nothing to a salesperson — the name should say what the agent does. Renamed the whole team and propagated everywhere: Daily Brief (Briefer) · Email Drafter (Writer) · Call Notes (Witness) · Deal Check (Qualifier) · Meeting Prep (Researcher) · Promise Tracker (Chaser); planned: Meeting Minutes (MoM), What's Next (Sorter). Single source of truth: src/lib/ai/agents.ts. Propagation: every agent writes the new name into agent_run_summary.agent_name + audit actorLabel; all UI copy (panel toasts, buttons, receipts, StubButton hints, "Copilot draft" → "Draft email"); agent system prompts; 115 telemetry rows migrated to the new names. One deliberate exception: historical audit_events rows keep the labels they were written with — the audit trail is append-only and history is never rewritten.

Agents

The Researcher: the pre-call brief (agent #5)

Walk into every meeting prepared. On the meeting-event page, "Brief me" → the Researcher gathers deterministic grounding (who the external attendees are in the CRM — lead/contact → their…

Walk into every meeting prepared. On the meeting-event page, "Brief me" → the Researcher gathers deterministic grounding (who the external attendees are in the CRM — lead/contact → their deal + account; the recent email thread both directions; the last recorded calls' recap + action items/risks) and writes the brief: headline goal · who's in the room · where it stands · still open · lead with · ask them. Same guarantees as the rest of the team: a fresh-context critic flags any fact not in the grounding (one repair round), meeting_briefs stores one row per (mailbox, meeting), agent_run_summary + AGENT_RESEARCHED audit event, receipt on the panel.

Verified live on the Right Horizons negotiation meeting — the brief nailed the deal's actual shape from 5 recorded calls + the thread: "Bridge the 150% budget gap… reworked two-phase proposal that fits their 25-30L constraint"; who: "Sunil Kawariya — decision-maker; firm on 25-30L, rejected three-phase model"; still open: Anil's internal greenlight; ask: "Has Anil signed off on the Phase 1 scope?". Files: src/lib/ai/agent-researcher.ts, scripts/agents/create-researcher-table.mjs, event-page actions.ts + researcher-panel.tsx. 5 of 8 agents live.

Agents

The Qualifier: MEDDPICC scored from real evidence (agent #4)

The deal hub's empty MEDDPICC scorecard is now filled by a real agent. Same harness as the agentic Writer (tool loop → fresh-context critic → repair), new job: score the 8 MEDDPICC…

The deal hub's empty MEDDPICC scorecard is now filled by a real agent. Same harness as the agentic Writer (tool loop → fresh-context critic → repair), new job: score the 8 MEDDPICC dimensions with the evidence that earned each score.

  • `src/lib/ai/agent-qualifier.ts` — INVESTIGATE via the deal tool belt, then emit strict JSON (per-dimension score unknown|weak|partial|strong + evidence, headline, gap, suggestedFocus). Guardrails: a non-unknown score with empty evidence demotes to unknown; JSON extracted substring-wise; a re-emit round recovers truncated output (the first live run failed exactly here — too-small max_tokens + a strict parse; fixed). The critic audits that scores are EARNED ("strong needs explicit confirmation, not a passing mention"), one repair round.
  • Stored in deal_qualifications (one row per deal, latest wins; scripts/agents/create-qualifier-table.mjs) + agent_run_summary + an AGENT_QUALIFIED audit event; RACI-gated runQualifier action.
  • `qualifier-panel.tsx` — headline read, Biggest gap, 8 score rows (dot-colored, evidence expands per row), a clay Next move, Run/Re-qualify, and the auditable-AI receipt (verdict · tokens · ₹).

Verified live on the centaurihs deal (the very deal linked via the new in-place connect — full circle): 14 tool calls (bundle×1, email×3, call-intel×2, transcript-search×8), verdict PASS, ₹1.47. The read is blunt and grounded: "Early-stage prospect with implicit need but no urgency, incumbent entanglement, and US-based economic buyers — poor fit for short-cycle deal"; Competition scored strong quoting the Jul-1 call (two incumbent EOR vendors, 3 offers extended); next move: "Before sending the proposal due Jul-14, confirm who at Centauri HQ will evaluate it and secure a warm introduction — otherwise proposal dies in inbox."

Agents

#10: the Writer is AGENTIC — tool loop + critic, live on the AE inbox

The Copilot draft is no longer a pre-stuffed prompt: it's a real agent. Built on the official SDK's Tool Runner (client.beta.messages.toolRunner, betaTool) — deliberately NOT the "Claude…

The Copilot draft is no longer a pre-stuffed prompt: it's a real agent. Built on the official SDK's Tool Runner (client.beta.messages.toolRunner, betaTool) — deliberately NOT the "Claude Agent SDK" package, which is Claude-Code-as-a-library (subprocess + filesystem tools; wrong fit inside a Next.js server action). The Tool Runner is the SDK's model-driven tool loop over tools we define — genuinely agentic, in-process.

  • `src/lib/ai/tools.ts` — the Writer's tool belt, READ-ONLY and deal-scoped by construction (ids resolved at build time; get_email refuses ids off this thread): get_deal_bundle (facts + thread INDEX), get_email (full body + attachments), get_call_intelligence (recap/action-items/risks), search_transcripts (FTS over the deal's linked call transcripts — the GIN index earning its keep). Every call is logged for the receipt.
  • `src/lib/ai/agent-writer.ts` — INVESTIGATE→DRAFT (toolRunner, Haiku per the model dial, max 8 iterations) → CRITIC (fresh context, judges the draft against the evidence the tools actually returned: no invented facts/numbers/commitments, exactly one ask) → one repair round on failure. Telemetry row per run in agent_run_summary (model/tokens/cost/verdict).
  • Receipt in the UI — the toast now says what the agent did: "Writer investigated (deal bundle×1 · email×3) · critic flagged & repaired".
  • Verified live on the GasTech deal: agent chose to open 3 emails, critic flagged, repair fixed it; grounded draft with one clear ask. ~9.9k tokens in / 344 out ≈ ₹1 per draft on Haiku.

Also this stretch: grounding.ts (deep thread grounding — full email bodies, meetings, call intel, attachments + receipt) now powers the SDR lead Writer; the deal Writer's fetchers became the agent's tools, as planned.

Agents

Markdown renderer for user/AI-authored text (+ transcript backfill complete)

"Markdown to html is not working" — traced to two real homes: (1) lead descriptions carry AI-enriched markdown ( bullets, bold — e.g. the Target/Bandhan leads), and (2) one transcript turn…

"Markdown to html is not working" — traced to two real homes: (1) lead descriptions carry AI-enriched markdown (* bullets, **bold** — e.g. the Target/Bandhan leads), and (2) one transcript turn where the prospect pasted formatted requirements into the meeting chat and Proshort merged it into the transcript. (The Proshort long fields/insights have zero literal markdown — checked all 548.) Built src/components/markdown.tsx: a tiny dependency-free renderer (bold/italic/code/links/headings/bullet+numbered lists) that outputs React nodes, never injected HTML (XSS-safe by construction), styled on the DESIGN text scale (clay-dot bullets, mono numerals, semibold ink emphasis). Wired into: transcript turns (recording page), AE + SDR lead descriptions, and the deal "What they want" panel. Verified on the Target lead + the InfraKeys transcript.

Also: the transcript backfill finished — 498/548 recordings now have speaker/timestamped transcripts (50 genuinely empty, 0 failed).

Agents

The Witness: notes write themselves from the call recording

"Add note" was a hollow placeholder string. Replaced it with the Witness — on a deal, ✨ Draft from last call composes a real minute-of-meeting from the deal's most recent Proshort…

"Add note" was a hollow placeholder string. Replaced it with the Witness — on a deal, ✨ Draft from last call composes a real minute-of-meeting from the deal's most recent Proshort recording: recap + action items + risks + pain points + competition. Deterministic (src/lib/notes/recording-note.ts composes from the already-extracted proshort_recording_insights — no LLM, so it never invents; every line traces to the transcript). Matched to the deal via its buying-committee contacts or the account name. draftDealNote action + a proper note-compose panel in the deal rail (editable textarea, toast acknowledgement, Save). Preview until Zoho write-back. Verified live on centaurihs (Jul 10 call → recap + 4 action items + 3 risks + competitor). Next: same for leads + auto-fill on "Log call".

Agents

Follow-up cleanup: clear what's already done (+ briefing below the greeting)

The overdue-task mountain (19.5k team-wide) is mostly uncleared, not undone — reps did the work and never ticked the box. Built an evidence-based cleanup that recognises done/moot…

The overdue-task mountain (19.5k team-wide) is mostly uncleared, not undone — reps did the work and never ticked the box. Built an evidence-based cleanup that recognises done/moot follow-ups and clears them for the rep, reversibly.

  • `task_completions` overlay — Zoho write scope isn't granted, so we don't fake the mirror; a completion lives app-side and every open-task query LEFT-excludes it (cleared tasks vanish immediately). Becomes the write-back outbox when scope lands.
  • `getTaskCleanup(zid)` (deterministic — no LLM decides a close): matches each overdue task to its lead (subject-based, like getTasks) AND deal (what_id), then buckets: Moot (deal closed / lead lost/converted), Looks already done (lead or deal moved after the task was due, incl. an email off the CRM — the exact "did it, never logged it" case), Stale (>90d — suggest, don't auto). Each row carries its evidence.
  • `completeTasks` action — bulk clear → overlay + ONE audit event (reversible, PREVIEW).
  • `CleanupPanel` on /sdr/tasks: three bucket cards with evidence + "Clear N". Honest by design — it clears only what it can prove (~4-5% here; the CRM lacks evidence for the rest, which correctly stay). The durable fix remains close-on-activity (prevention), noted for next.
  • Layout: the daily briefing now sits below each cockpit's greeting/KPI header (AE Desk, SDR My Day, Command) — greeting first, then the brief.
Agents

The Briefer: the daily briefing agent (first of the agent team) is LIVE

The API key landed (valid — verified), so the agent program is unblocked. Built the Briefer end-to-end and put it as the first thing on the Desk / My Day / Command board.

The API key landed (valid — verified), so the agent program is unblocked. Built the Briefer end-to-end and put it as the first thing on the Desk / My Day / Command board.

  • Storage — two app-owned tables (src/db/schema.ts + scripts/briefings/create-tables.mjs): agent_run_summary (per-run telemetry every agent writes — model/tokens/cost/verdict; the audit agent_run_id points here) and briefings (one row per user per day; sections JSONB; unique(user_id, briefing_date)).
  • Section ownership done rightsrc/lib/briefing/facts.ts computes every NUMBER in SQL, role-scoped (AE/SDR = their book; HEAD_OF_SALES/ADMIN = team roll-up). briefer.ts (Haiku) writes prose only; the schedule (today's meetings, exact IST times), at-risk deals, and rep lists are rendered deterministically from facts — the model never touches a time or amount. This fixed the first-cut bug where the model narrated meetings (wrong/empty bullets).
  • SurfaceDailyBriefing component: headline + aligned mono lists (time/amount lead) + prose sections + a highlighted "Your one move" + an auditable-AI receipt (agent · model · "numbers computed from your live CRM"). Lazy-generates on first view; getAnthropic() now prefers process.env.ANTHROPIC_API_KEY.
  • BatchPOST /api/briefings/generate (token-guarded, the cron target). Generated 17/17 live briefings: Rohit (team: "₹42.3 Cr slipping… urgency starts with Himanshu and Harshad") + every mapped SDR/AE. Reads like a real chief-of-staff.

Format: bullets everywhere. Every section is now short bullet points (the model writes 2-4 one-sentence bullets per section, never paragraphs); data lists lead with a mono time/amount, narrative bullets with a clay dot. Regenerated all 17.

Enrichment + clickable links. Three data sources added to the facts layer (all numbers/names still code-computed): (1) meeting prep — each today's meeting is matched to a deal via an external attendee → shows "— FedX · Qualification · no next step"; (2) "Waiting on your reply" — the actual unanswered threads (lead + subject + days-waiting), not just a count; (3) "Follow up from your recent calls" — Proshort call intelligence (open action items + ⚠ risks from the last 10 days of recorded calls the user was on). Every list item now carries a ref and renders as a link (role-aware): meetings → the event page, deals → the deal hub, threads → the lead, calls → the recording. Verified: clicking a meeting opens its detail (correct 16:00 IST, real subject).

Still to do: the pre-dawn cron trigger + the rest of the agent team (MoM, Sorter, Qualifier, Researcher, Chaser) + the critic pass. #11 is now in progress (Briefer shipped).

12

Jul 2026

Sunday

2 shipped

Agents

Audit + notify spine v2: actor-kind (agents!), outcome, severity, the wrapper

Diagnosed the "fucked up" audit/notify: the code was well-built but the spine couldn't log agents (it required a User actor; null just meant "system"), had no outcome/severity/summary, and…

Diagnosed the "fucked up" audit/notify: the code was well-built but the spine couldn't log agents (it required a User actor; null just meant "system"), had no outcome/severity/summary, and covered only the identity/admin half — every sales + agent action wrote nothing. Rebuilt the foundation:

  • Schema (audit_events + notifications, via ALTER TABLE, never db:push): actor_kind (USER|AGENT|SYSTEM), actor_label (person or agent name), outcome (SUCCESS|FAILURE|PARTIAL|PREVIEW — for propose-not-act), summary (a rendered human sentence — what the trail shows), agent_run_id; notifications gain severity (info|warning|critical) + cta_label/cta_href.
  • `recordAudit` now takes actorKind/label/outcome/summary/agentRunId and is fire-and-forget (swallows errors, returns number | null) so a logging failure never breaks the action. notify gains severity + CTA and is fire-and-forget too.
  • `recordAuditAndNotify()` — THE spine wrapper (antino3's SyncAudit pattern): one call = one audit row + notifications with a shared vocabulary (outcome→severity). `recordAgentRun()` — logs an AGENT action attributed to the user it ran for.
  • Wired the Writer agent: every draft (success/empty/error) now writes an AGENT_DRAFTED_REPLY audit row (actorKind AGENT, label "Writer", outcome, char-count). Registry (audit-actions.ts) gained the sales + agent action codes + ActorKind/Outcome/Severity enums. Verified end-to-end (agent event + severity/CTA notification insert). Then made it visible + gated: /admin/audit now surfaces the new detail — an actor-kind glyph (agent = violet sparkle, system = cpu, user = person), a colored outcome chip (shown when not SUCCESS), and the summary sentence on its own line. Built the RACI access helper (src/lib/auth/access.ts): canActOnOwner/requireActOnOwner gate write-actions by admin-tier · ownership (Zoho mapping) · manager hierarchy (recursive report tree) — unauthorized → notFound(); reading stays open. Then wired the deal action rail to log every user action: src/app/ae/deals/[id]/actions.ts (logStageAdvance / logDealNote / logCall) — each requireRole('AE') → looks up the deal owner → requireActOnOwner (404 if not owner/manager/admin) → recordAuditAndNotify({ outcome: 'PREVIEW', … }) (audit row + a manager notification with a CTA). The deal-hub Stage-gate Advance button + the Advance / Log call / Add note rail buttons now call these server actions (client toast reflects the logged result) instead of a bare client toast. Typecheck-verified; the spine itself was DB-smoke-tested. Live click-through pending a fresh sign-in (I can't authenticate). Then wired the SDR lead action rail the same way (src/app/sdr/leads/[id]/actions.ts): Send email / Log call / Book meeting / Qualify / Add note now call RACI-gated server actions logging PREVIEW events + notifying the SDR's manager. Qualifying fires the SAL handoff (SAL_HANDOFF_SENT) — the seam antino3 calls the most important — and Unqualified logs LEAD_DISQUALIFIED with the reason code. #9 complete: every user action (deal + lead) and every agent run is now logged in detail, RACI-gated, with severity/CTA notifications. (SAL accept/reject on the AE side + the SLA engine belong to #8.)
Agents

First AI: the "Writer" agent + task accordion + hover cards

First real Claude call in the app. @anthropic-ai/sdk installed; src/lib/ai/anthropic.ts builds the client from the encrypted ANTHROPIC_API_KEY setting (cached until the key changes);…

  • First real Claude call in the app. @anthropic-ai/sdk installed; src/lib/ai/anthropic.ts builds the client from the encrypted ANTHROPIC_API_KEY setting (cached until the key changes); src/lib/ai/draft.ts = Writer, which drafts a reply grounded in the lead's real email thread + Zoho record (Haiku 4.5, read-only). Server action draftReply(leadId) in src/app/sdr/ai-actions.ts; the Inbox "Copilot draft" button now calls it (loading state, toast, graceful error) instead of a template.
  • Verified the path is real: clicking it fires a genuine API call and returns a proper Anthropic response. The stored key is currently an invalid 24-char placeholder (sk-ant-tes…, updated 2026-07-07) so it 401s — a valid key in Admin → Settings unblocks it, no code change.
  • Agent framing: the SDR gets a team of simple-named agents — Writer (live), Briefer / Sorter / Qualifier / Researcher / Chaser (planned) — all Haiku, all propose-not-act, user in the center. Write-back (send via Graph, CRM update via Zoho) is a separate, deliberate switch.
  • Follow-ups → accordion. getTasks now resolves each task to its lead via a lateral join (subject parse, since who_id is null), so tasks carry a hoverable/linkable lead. New src/lib/sdr/task-types.ts classifies each task (follow-up / status / future / notes / data / linkedin — grounded in a scan of the real subjects, ~8k are "Priority Action" bot tasks); the row expands to the right "how to finish it" actions per type. All preview + toast.
  • Universal `LeadHoverCard` (src/components/sdr/lead-hover-card.tsx) — hover any lead name to preview contact/status in the lead entity hue + jump to the record; the pattern extends to deal/contact on the AE surface.