Skip to main content

AI integration

The Claude API is a first-class component of the Hub — not a chatbot bolted on, but a query layer that operators reach for when the UI doesn't have a button for what they need.

Drafted from planning · v0.1

The Claude adapter (src/lib/claude.js) and the AI Support bubble (slice 11) are specified but not yet wired. The schema for ai_conversations + ai_messages is in place.

The promise​

An operator should be able to ask the Hub any question whose answer exists in the system — the bible, the shop's D1 database, the operator's own conversation history — and get an answer in seconds. With citations. Without context-switching to a separate app.

Examples:

  • "What did this customer buy last spring?"
  • "Why isn't my deposit refunding for ticket T-N0042?"
  • "How do I onboard a second cash register?"
  • "Show me the receipts from yesterday with no customer attached."
  • "Translate this customer's note to French."

Components​

The C4 — Component diagram shows the AI Support bubble talking to the Claude adapter. Zoomed in:

AI bubble UI (button bottom-right + conversation pane)
│
▼
POST /api/ai/query
│
├── Grounding builder (Worker)
│ ├── Bible chunks (lexical search; vector when slice 11 ships)
│ ├── D1 structured context (current customer, ticket, transaction)
│ └── Recent conversation messages (rolling window)
│
├── Claude adapter (Worker)
│ ├── Prompt-cache the bible chunks (long, stable)
│ ├── Stream response
│ └── Tool-use loop (if Claude requests d1_query)
│
└── Persist (Worker → D1)
├── ai_conversations (one per AI bubble session)
└── ai_messages (one row per message, role: user|assistant|tool)

RAG against the bible​

The bible itself is corpus material — markdown files in this Docusaurus site, all canonical truth about the Hub. The build pipeline produces a JSON index of every doc chunked by heading. The Worker ships this index as a Cloudflare KV value (~1MB compressed).

On query:

  1. Lexical search (BM25 across heading + body) returns top-20 chunks
  2. Each chunk has its file path + heading + body
  3. Claude system prompt includes the top-20 with citations

Vector search is the planned upgrade (slice 11) — semantic relevance beats lexical for "translate this to French"-style queries. Until then, lexical works for the operator-question shape.

The system prompt is prompt-cached at the Claude API: bible content rarely changes, so the cache hit rate is ~95%. That's a ~70% cost reduction on RAG-heavy queries.

Structured grounding against shop D1​

Lexical search against the bible is one prong. The other is structured: when the operator is on a customer's screen and asks a question, the Worker pre-fetches that customer's record + recent transactions + open tickets and includes them in the user message. This is RAG-style grounding against the structured operational data.

// Pseudo
const grounding = {
current_screen: 'customer',
customer: await fetchCustomer(customer_id),
recent_transactions: await fetchRecentTxns(customer_id, 10),
open_tickets: await fetchOpenTickets(customer_id),
bible_chunks: await searchBible(question, 20),
};

This makes Claude's job to reason over the data, not find the data. Hugely improves answer quality and reduces hallucination risk.

Tool use​

For questions whose answer requires an arbitrary D1 query, Claude can request it:

{
"name": "d1_query",
"input": { "sql": "SELECT * FROM transactions WHERE staff_id = ? AND at > ?", "params": [3, "2026-04-01"] }
}

The Worker validates the SQL (no DDL, no DML, parameterized only, capped at 1000 rows) before executing. If validation fails, the tool returns an error and Claude reformulates.

Tool-use loops are bounded to 5 iterations per query to prevent runaway loops.

"Ask the Hub" — natural-language reporting (mockup live 2026-05-18)​

A second AI surface, distinct from the Support bubble: an ad-hoc reporting card where the operator types a question in plain English ("how many bikes did we sell to first-time customers last month?") and gets a table or chart back.

The pattern (mockup card in public/index.html describes the eventual real flow):

Operator question
↓
LLM (Claude or GPT-4-class) + read-only D1 schema for this shop
↓
LLM writes SQL
↓
SQL runs against this shop's D1 (read-only, validated, parameterized)
↓
Results render as table or chart in the card

PII protection. The LLM never sees raw customer data — only aggregated or sampled rows when needed for context-building. The schema itself is included in the prompt; the data is not.

Audit. Every Ask-the-Hub query writes an audit row: question text, generated SQL, row count, staff, timestamp. The owner can review what was asked, what got run, and what was returned at any point.

Cost. ~$0.003 per question for the typical Hub-sized schema on Claude. ~3 questions/day × 30 days = ~$0.30/month per shop. Negligible.

Status. The card is a mockup today — UI is in place, the LLM-to-SQL flow is not yet wired. The eventual implementation reuses the Support bubble's Claude adapter + tool-use loop (above), plus the read-only-schema-as-context prompt construction.

Why two surfaces, not one?

Support bubbleAsk the Hub
Question shape"Why did this customer's deposit not refund?" — operational"How many of X did we sell last month?" — analytical
GroundingBible + customer record + ticket contextSchema only; LLM constructs the query
OutputProse answer with citationsTable or chart
When usedMid-operation, on a specific recordWhen wondering about shop-wide patterns

They share the Claude adapter + audit-log + cost budget; they differ in prompt construction and output rendering.

"Does it do this?" feature-list modal (live 2026-05-18)​

Not AI, but adjacent — clicking the brand-mark SVG (top-left of every operator screen) opens a centered modal with the canonical "Does it do this?" feature list: every capability of the Hub, single line per item, grouped by area (Sign in & access, Sales, Customers, Service, Inventory, Reports, Audit, Integrations, AI, Beta feedback, Offline behaviour, Migration, Platform). A live filter input lets you type a keyword (e.g. "tax," "GBP," "audit") to narrow.

The list is maintained verbatim in public/index.html under SECTIONS = [...] so the source-of-truth for "what does the Hub do" lives in the same file as the operator UI it describes. Customer-facing sales conversations cite it directly.

Conversation memory​

Each AI bubble session is an ai_conversations row. Each turn is an ai_messages row:

  • role: 'user' | 'assistant' | 'tool'
  • content_json: the message body (text + tool calls + tool results)
  • tokens_in / tokens_out / cost_cents
  • at

The conversation is scoped to the operator's session. On the next sign-in, the operator sees their recent conversations in the bubble dropdown.

Per-customer AI opt-out​

Each customers row has ai_optout (default 0). When set to 1:

  • Structured grounding payloads skip that customer's identifying fields (name, email, phone, address)
  • Conversations that reference that customer are logged with customer_id = NULL
  • Bible queries that don't touch customer data are unaffected

The opt-out is exposed in the customer card via a small toggle pill, written to customers.ai_optout, audited via audit_events. See data ownership.

Budget controls​

Per-shop monthly cap (default $25/month, configurable in shop_config.ai_budget_cents). When the running total hits the cap:

  • New queries return a polite "AI is paused for this billing cycle" message
  • Owner gets an email (and a UI banner) once on cap-hit

Tracking: every API response writes the input/output tokens + computed cost to ai_messages.cost_cents. A nightly cron sums + writes to shop_config.ai_spend_cents_mtd.

Latency budget​

Operator-facing latency budget for an AI answer: 2 seconds for first token, 8 seconds for completion. Mostly Claude's TTFT, but the grounding step has to be fast:

  • Lexical bible search: target < 50ms
  • D1 grounding queries: target < 200ms
  • Claude TTFT: ~500-1500ms (Anthropic's number, varies)

If grounding takes > 500ms total, Claude doesn't get the data and quality drops. The grounding step is parallelized.

What this is not​

  • Not a customer chatbot. The bubble is for operators only.
  • Not autonomous. Every action that touches data (creating tickets, refunding, etc.) is suggested in the chat and confirmed by the operator before the Worker executes.
  • Not a code generator inside the Hub. Code-generation tooling is Claude Code on the dev side; the in-app bubble is a query-and-act assistant.
  • Not third-party LLMs. Anthropic only. See ADR-0009: Anthropic over OpenAI.

Interpretation Engine (dormant)​

The first place a real Claude call landed in the codebase is not the Support bubble — it's the Process Library's Interpretation Engine (migration 081, v0.6.253). The intent: feed Claude a process's "Store action" + "Software process" narratives and have it judge whether the code already covers the workflow (verdict ∈ programmed / partial / needs-code / uncertain), caching every call in process_interpretations.

The schema and POST /api/processes/:id/interpret shipped, and the handler contains a genuine fetch to the Anthropic Messages API. But in v0.6.254 the Anthropic-facing UI was removed, and the endpoint now returns 503 unless ANTHROPIC_API_KEY is set — and it deliberately is not. So no AI call fires in production today. The live path is manual: 📋 Copy-for-review (clipboard prompt → paste into Claude Code) and 📤 Export-prompt (a deterministic codegen template, no model call).

This is consistent with the rest of this page being "specified, not yet wired": the Interpretation Engine is wiring that exists but is gated off. Re-enabling is a ~3-line edit (re-add the buttons) plus setting the secret. When the AI budget + adapter described above land, this endpoint is the natural first consumer.

See also​