Conductor Chat
The member's chat bar. Free-form messages go to the live Conductor agent (Claude) when configured, otherwise to a deterministic on-brand demo responder — same response shape either way, and every reply (live or demo) passes the deterministic no-jargon guard.
| Endpoint | Auth | Rate limit | Body cap |
|---|---|---|---|
POST /api/conductor |
session cookie when platform configured; public in keyless demo | 20/min per IP | 1 MB |
POST /api/conductor
Request (JSON)
| Field | Type | Notes |
|---|---|---|
message |
string | max 6,000 chars (raw length, before trim). Non-string values and malformed JSON are silently treated as an empty message. |
Empty/whitespace-only messages short-circuit to a canned 200 reply — not an error.
Responses
| Status | Body |
|---|---|
| 200 (live) | { "reply": "<conductor message>", "mode": "live" } |
| 200 (demo) | { "reply": "<demo responder>", "mode": "demo" } |
| 200 (empty message) | { "reply": "Tell me what you'd like to know and I'll help.", "mode": "demo" } |
| 400 (too long) | { "error": "That message is too long — please shorten it." } |
| 401 (configured, no session) | { "error": "Please sign in to chat." } |
| 411 / 413 / 429 | shared guard envelopes (conventions §4-5) |
⚠️ Envelope quirk: the guard errors (411/413/429) carry "ok": false; this route's own 400/401 use bare { "error": ... }; success carries neither. Parse defensively.
Order of checks
rate limit → body limit → parse → 6,000-char cap → empty-message short-circuit → auth (live only) → LLM.
Demo vs live
- Gate:
ANTHROPIC_API_KEY(isLLMConfigured). Live calls run ateffort: 'low'on thechatmodel role (ANTHROPIC_MODEL_CHAToverride, defaultclaude-opus-4-8). - Tenant-scoped facts (#112): with a configured platform, a signed-in member's chat answers against their instance's assembled facts — business, pipeline stage, live angle, angle-bank count, funnel counts to date (
funnel_to_date— cumulative snapshot, not a trailing week), latest diagnosis, pending approvals — via their RLS client (oldest-instance pick, matching v1). The demo business's facts feed the live model only on keyless deployments (the ANTHROPIC-key-without-Supabase combo, where no tenants exist). Fact assembly is all-or-nothing: any failed read throws — the member gets an honest try-again reply, never a plausible wrong fact ("brand new account", "0 angles banked") and never the sample business's numbers. - Auth exists only when the platform is configured — "anonymous LLM spend is never allowed". Keyless deployments are fully public (demo replies are free and deterministic).
- Live-failure semantics split by who's asking (review fix): for an authenticated member (configured platform), any live failure — API error, fact-assembly failure, or the reply failing the jargon guard twice — returns an honest retry line (
"I hit a snag pulling up your numbers just now — please ask me again in a moment.") with"mode": "live"and status 200; the member never receives the sample business's numbers. Only keyless paths fall back to the demo responder with"mode": "demo". Checkmode, not the status code, to know which path answered. - The demo responder is an ordered regex intent list (greeting, leak, cost, angles, …) with a fixed fallback; its output also runs through the jargon guard.
Notes for callers
- Stateless — nothing is persisted per message; retries are safe (they burn rate-limit quota and, live, LLM spend).
- Live calls record
llm_usagetelemetry (task/model/token counts only — never message content). - A client-side bug sending a non-string
messagelooks like a working chat (canned demo replies) — watch for an all-demo response pattern while live.
Source:
app/api/conductor/route.ts,src/agents/conductor/index.ts(conductorChat),src/agents/conductor/jargon.ts,lib/conductor-demo.ts