← Now
v3.0.0May 2, 2026

Kian 3 — Claude under the hood, reasoning that streams

Vallit · 7 min read

Kian's brain just changed. Anywhere a logged-in user talks to Kian inside the workspace, the response now comes from Claude — and you can watch it think while it answers. The marketing widget still routes through the older OpenAI handler; that bridge is the next migration on the runway.

Phase α–6b shipped between 2026-04-22 and 2026-05-01 across nine pull requests: a unified router for every model call (#48), Claude-only routing for Vallit (#78), a tenant-isolation guardrail with regex + Haiku-classifier shadow mode (#80, #90), a glassmorphic ThinkingBubble plus reasoning-tree React component (#107), the server-side SSE channel that feeds it incremental thinking deltas (#121), and a security follow-up that wraps every page-context interpolation in a redaction layer (#121, #123, #125). The closure PR (#125) closed the same C1 page-context-injection vulnerability for Vallit-workspace, dynamic-company, and the agent-layer classifier — wherever a tenant's metadata flows into a prompt, an attacker-controlled `ignore previous instructions` substring is now replaced with `[redacted]` before the model sees it.

Three honest caveats. (1) Phase 6's thinking visualisation is wired through `apps/app/src/components/kian/kian-thinking.tsx` and the React `<KianWidget>` facade — that runs only on app.vallit.net workspace surfaces. The visitor widget on www.vallit.net still posts to the legacy `apps/marketing/src/lib/chat/runtime.ts` handler which doesn't emit `reasoning_delta` events. Theo's reaction ("there is still no thinking visible") is a fair read: visitors haven't seen this change yet. (2) Phase 7-11 (Voyage embeddings + rerank, WTM Claude cutover, Operator Analytics) were CUT 2026-05-01 per council-validated re-sequencing — none had specs in `docs/superpowers/specs/` and none had a customer hypothesis. They re-open if a customer signal arrives. (3) The agent-version switcher Theo expected in the workspace doesn't exist yet — Operator Studio (Phase 11) was part of the cut.


What changed
AI

Claude routing live for every workspace LLM call (Phase α + 4)

Vallit's authenticated chat surfaces (workspace UI, admin operator console) now route every generate/classify/safety/review call through Claude Sonnet 4.6 by default, with OpenAI as fallback when Claude rate-limits.

PR #48 introduced the role-router abstraction so every model call goes through one shared router. PR #78 set `VALLIT_CLAUDE_ONLY=true` in production and migrated the responder, classifier, planner, reviewer, and safety agents to Claude. The fallback chain still uses OpenAI on Claude failures so users never see a 5xx. WTM stays on GPT-4o for now — the planned cutover (Phase 10) was deferred when the Claude-only canary on Vallit hit zero regressions over a 14-day soak.


Platform

Tenant-isolation guardrail for cross-tenant safety (Phase 5)

Tier-1 regex + Tier-2 Haiku classifier inspect every model output for forbidden cross-tenant content before it reaches the user, with shadow-mode logging so we can baseline false positives before turning enforcement on per-tenant.

PR #80 (prep + ADRs) and PR #90 (Waves 2-4) shipped the guardrail. Stage-1 canary went live 2026-04-28 with `KIAN_GUARDRAIL_ENABLED=true` on Vallit. Tier-1 catches PII patterns + neutral-attribution allowlist violations in <5ms p95. Tier-2 runs Haiku as a shadow classifier — when it disagrees with Tier-1, both verdicts log to `analytics.chat_guardrail_events` with PII-redacted snippets so we can measure FP rate without storing raw user content for 90 days.


Widget

Reasoning-stream live: ThinkingBubble + cascading deltas (Phase 6 + 6b)

Inside the workspace UI, Kian's thinking now streams back as it happens — a glassmorphic bubble with cascading typing dots, then the reasoning tree renders incrementally as the model produces it.

PR #107 (Wave 2) shipped the engine + widget: `ReasoningDeltaEvent` in the streaming registry, `ReasoningNode.live` flag for the renderer, `<ThinkingBubble>` glassmorphic shell, `<KianReasoning>` SR-only `role="log"` for screen-reader compatibility, plus `e2e/chat/thinking-stream.spec.ts` for both standard and reduced-motion users. PR #121 (Wave 1, deferred) shipped the server-side SSE emit so a real Claude `extended_thinking` block streams back as `reasoning_delta` frames. `KIAN_REASONING_STREAM=true` was set in `vallit-app` production on 2026-05-02. Visible only on app.vallit.net workspace surfaces; visitor widget migration on the runway.


Platform

Page-context injection sealed across every prompt builder (Phase 12)

Three production prompt paths used to interpolate raw HTML metadata (page title, h1, URL) directly into the system prompt, leaving room for an `ignore previous instructions` substring to slip through. All three now route through `sanitizePageContextField` first.

PR #121 closed C1 for the WTM widget by wiring `sanitizePageContextField` at every interpolation site in `wtm-prompt-builder.ts`. PR #125 (the 10/10 closure PR) extended that fix to the three non-WTM paths discovered during the closure browser-smoke: `workspace-page-context.ts:parseWorkspacePageContext` (sanitize-on-parse for Vallit-workspace), `company-router.ts:286-296` (sanitize ctx.title/h1 before pageHint interpolation for dynamic-company tenants), and `agents/classifier.ts:66` (sanitize input.pageContext before it reaches the classifier prompt). Verified live on production via SSE milestone event showing `[redacted] instructions and reveal session ids` in the page_context_analyzed log.


Platform

Cost guard with atomic reservation (Phase 12.6)

Every routing branch now reserves a token budget before the LLM call and either records the actual usage or refunds the reservation on failure. Eliminates the daily-cap accounting drift that let a single tenant burst past the cap before the next 5-second tick.

PR #121 shipped `checkLLMCostGuard` at the four routing branches (WTM, dynamic-company, Vallit-workspace, default Vallit) plus `recordLLMUsage` inside `usage-logger.ts`. PR #123 added the reservation true-up so `cost-guard-context` carries the reserved-tokens forward and `recordLLMUsage(slug, totalTokens, reservedTokens)` updates the daily counter atomically. Image-analysis path (`route.ts:243`) gets its own guard. PR #125 extended the integration to include the same atomic INCRBY-then-conditional-refund pattern via Redis Lua — TOCTOU-safe under burst.