The report's third column was budget-usage bars: the same three team totals the
donut beside it already showed, redrawn with limits added. Two charts, one fact.
It now ranks the largest individual charges, which changes the unit of analysis
from team to transaction. A team aggregate cannot distinguish one $15,000
charge from thirty $500 ones, so this is information the donut genuinely cannot
carry. Bars stay coloured by owning team, so a row still ties back to its slice
without the two charts duplicating each other.
Budget-vs-limit is not lost: it remains the "Over policy limit" KPI above and
the "Needs a decision" rows below.
Invoice-derived line items now carry the policy id of the team they belong to,
so they colour like any other charge. Without it they fell back to a generic
swatch and a Marketing charge did not match Marketing's slice.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five presentation fixes to the Northwind Finance demo, all from running the
beats live.
PIN change now resolves into a card rather than the sentence "New PIN saved."
It shows the card face, brand and last4, a masked new-PIN row and an active
badge. Digits are never rendered: they are never sent to the agent, so the mask
is the honest representation.
Reopening a thread replays setCardPin with status "inProgress" and no result,
so the answered card sat on "Loading..." forever. Other human-in-the-loop tools
here (showCharges) do replay their result, so this is specific to that call.
The outcome is now remembered per tool call id for the session and consulted
ahead of the replayed status. Both this card and the charges card key their
resolved state on the RESULT rather than the status, so an answered call can
never replay with live buttons.
setCardPin also registers once via a ref instead of depending on `cards`:
useFrontendTool re-registers whenever JSON.stringify(deps) changes and
re-registration removes the tool, so the PIN write tore down the very tool that
was servicing it.
showCharges becomes human-in-the-loop. Opening a filtered list is safe, but it
replaces the whole screen, and an agent that does that unasked reads as the
agent being in charge. The confirm card names the sort and filters before the
page changes, and on arrival the Sort and Show controls carry the brand tint
whenever they are non-default, so what the agent set is what lights up.
The Q2 report shows three different chart forms (share-of-total pie, time
series, budget bars) instead of three bar charts, and the seed is rebalanced so
team shares read 42/28/30 instead of 98/2/2 while all three pending charges
still exceed their limits. This drops the income-vs-expenses chart that was
showing $0.00.
Notes about reported charges carry a leading alert emoji so they cannot be
skimmed past. The seeded procedure asks for it and the handler applies it
regardless, because a model is not a reliable emoji emitter.
Finally, prose answers get a house style. The agent was formatting the first
few bullets of a list and then lapsing into plain text, which reads as a
rendering bug rather than a style choice.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The approval loop could never pass, independent of the demo's behavior:
for (let step = 0; step < 4; step++) {
const approve = page.getByRole("button", { name: /^approve$/i });
await approve.first().click({ timeout: 30_000 }).catch(...)
}
Two compounding problems. The recall path emits at most three HITL cards
(openPolicyException -> finalizePolicyException -> approveTransaction) but the
loop demanded four, so the last iteration always waited on a card that never
arrives. And the per-click timeout (30s) equalled the whole test budget
(playwright.config.ts sets no `timeout`, so Playwright's 30s default applied),
so the first miss consumed the entire test and the `.catch()` fallback to the
other APPROVE_LABELS was unreachable dead code.
Now: poll every label for the next visible approve control, treat "no card
left" as flow completion rather than a timeout, keep a runaway guard that is
explicitly not the expected card count, and give the test a budget that can
actually hold one agent turn plus the closing server poll.
Confirmed against a local run (aimock + the docker Intelligence stack): the
agent recalls the procedure, files the EXC-BOARD-APPROVED exception, and
approves the charge, and the "Record a workflow?" card never appears. The
header's "verify on first green run" notes are updated to record what that
run settled and what it did not.
The spec still fails at its closing assertion: PUT /api/v1/transactions/t-3
{status:"approved"} keeps returning 422, i.e. store.hasApprovedException() is
false, so the over-limit gate is never lifted even though the (fixture-scripted)
agent text claims it was. That is pre-existing and out of scope here — this PR
touches no part of the approval path (lib/store, the exceptions/transactions
routes, copilot-context, components/wow are all untouched). Left failing rather
than skipped so it stays visible.
Co-Authored-By: Claude <noreply@anthropic.com>
Realigns the inspector/memory work onto the banking demo as it shipped in
#6136 (ChatGPT-style shell, gen-UI beats, durable-memory self-learning) and
#6202 (README refresh).
All six conflicts were the same collision: this branch removes the bespoke
Glass Engine inspector, while #6136 kept and rebuilt around it.
- run-handler.ts: kept both sides (our CopilotKitCoreCatalogComponent and
main's MAX_FOLLOW_UP_DEPTH landed at the same spot).
- wrapper.tsx / layout.tsx: took main's rewritten provider tree and
right-hand icon rail, minus the Glass Engine providers, pane, and
telescope toggle. Also dropped main's `padClass` (it reserved space for
the Glass pane and referenced a now-removed `glassActive`) and
`<ProactiveNotice />` (main removed it; the import is already gone).
- memory-tab.tsx, lib/intelligence/memory.ts: confirmed the deletions.
Their only remaining importers were the bespoke inspector and the
banking-local /api/memories routes, all removed here. seed-memories.ts
is unaffected: it POSTs to INTELLIGENCE_API_URL, not the local route.
- README.md: kept our product-inspector section over main's Glass Engine
availability/activation prose, and documented the Capabilities tab.
Drive-by fixes to comment rot the migration created: user-id.ts and the
copilotkit route doc comments referenced the deleted Memory-panel proxies,
and the README pointed the presenter-reset control at the removed
telescope toggle.
Also replaces a literal NUL byte in capabilityKey() with a unicode escape.
The raw control character made tsc/grep/diff treat run-handler.ts as a
binary file, which hid this very merge's conflict markers from grep.
Behavior is unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
#6136 changed the demo substantially and left the README describing an older
build. Corrections:
- It documented CopilotPopup; the chat is a left-docked CopilotSidebar rendering
two columns (thread rail + conversation) with the app's nav rail on the right.
- It called the learned over-limit memory `procedural`; the code writes
`operational`.
- It said the Glass Engine Memory tab was "top-k semantic recall, not a full
enumeration". It is now a complete enumeration ("Stored memories (N)"),
aggregated across the demo's identities, with semantic search as a separate
box.
- It listed per-run reset as a future follow-up. It ships, gated behind
PRESENTER_RESET_ENABLED, and also re-seeds memory and returns to the bare root.
- It described showTransactions as a useFrontendTool; it is a useComponent so it
persists in the transcript.
- The "change my Visa PIN from the team page" example described a navigation
fallback; PIN entry is now a component in the chat and the agent never sees the
digits.
Adds what was missing: the eight-pill demo script, the /charges page, the PDF/Q2
multimodal beat, seeded memory (and why the over-limit procedure is deliberately
NOT seeded), the components-not-markdown-tables rule, per-team chart colour, and
the two incompatible helper families in actions.ts that make destructuring `ok`
report a success as a failure.
All three screenshots are replaced — they showed the old right-docked chat, so
they contradicted the first paragraph.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Turns the Northwind Finance banking demo into a presentable end-to-end demo.
Every beat was driven live and verified against real app state, not just the
agent's own claims.
The demo is eight suggestion pills, in order: show a chart; change a card PIN in
an interactive in-chat component; ask what is on screen; open the 10 most
expensive charges; prep the Q2 report from an attached PDF invoice; summarize
spend using a remembered preference; handle an unrecognized charge using a
remembered procedure; and finally learn a brand-new procedure by watching the
officer clear an over-limit charge. The last three are sequenced deliberately:
already-knows-a-preference, already-knows-a-procedure, learns-a-new-one — and on
a fresh thread the newly learned procedure is recalled and applied to a
different charge unaided.
Shell and UI:
- Chat docks LEFT as two columns (thread rail + conversation) with the app's
icon rail moved right via flex `order`, so nav still precedes <main> in the
DOM. The chat surface runs ChatGPT's neutral palette while the app keeps its
violet identity.
- Tool activity renders as ChatGPT-style expandable lines; internal AG-UI
plumbing is filtered out of the transcript.
- Tabular data always renders as components, never markdown tables; a one-row
table is blocked structurally because a single-record table is narration.
- Filed reports are a dashboard (KPI tiles, three charts, decision rows) rather
than a memo, with one colour per team across every chart and ranked bars in
place of a donut that collapsed when an invoice pushed one team to 96%.
- PIN entry happens in the chat component; the agent never asks for or receives
the digits.
Fixes worth review attention:
- Anything a RECALLED procedure can call must be registered globally. Four
action tools were registered on the cards route, so a saved procedure only
worked from one page.
- An unanswered human-in-the-loop card poisons the thread: skipping it left an
unresolved tool call and the next message failed with "Tool result is missing
for tool call ...". The procedure's closing step no longer gates; approvals
that move money still do.
- actions.ts has two helper families that are not interchangeable —
openPolicyException / finalizePolicyException / changeTransactionStatus return
{ok, data/error}, while changePin / addNoteToTransaction resolve with the
updated record. Destructuring `ok` off the second family reports success as
failure.
- A stale canvas surface hid the page: it is derived from the last a2ui-surface
message and replaces the page body until dismissed, but the dismiss effect
keyed on pathname only, so a ?tab= switch left it covering the page.
- Suggestion pills ignore clicks while a run is in flight, since a double-click
orphaned the previous run's tool call.
Deliberately NOT included: the @copilotkit/license-verifier pnpm patch (local
workaround for the "Invalid license token" banner) stays uncommitted, because it
patches a dependency for every install and is not demo scope.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drop the stale doc-comment reference to the deleted src/lib/glass-engine.ts in
presenter.ts (final verification sweep).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the GLASS_ENGINE_AVAILABLE env block from .env.example and replace the
README's 'Advanced mode — Glass Engine' section with a note on the product
web-inspector (enabled via showDevConsole; Memory tab powered by
exposeMemoryRoutes in Intelligence mode).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Delete the final e2e test that drove the bespoke Glass Engine pane and remove
the GLASS_ENGINE_AVAILABLE env from both playwright configs. Self-learning
recall stays covered by the headless recall test; the product web-inspector is
covered by packages/web-inspector's own tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Delete the 13 Glass Engine inspector files (pane, tabs, event store, event-card
mapping, deployment gate, env gate) and their tests, the dead Next /api/memories
proxy routes, and the now-unused src/lib/intelligence/memory.ts helper module
(+ test). Memory now flows through the product path (core memory store →
runtime /api/copilotkit/memories) rather than the banking-local Next proxies.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop useGlassEngine(), the Telescope toggle button, and the glassActive-driven
body padding from the app layout. The product inspector launcher replaces the
telescope toggle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the GlassEngineProvider / InspectorStoreProvider wrappers and the
<InspectorPane/> mount from the client tree, and drop the glassAvailable prop
(and its server-side threading in layout.tsx). The product web-inspector
enabled in the prior commit replaces the bespoke pane.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Set showDevConsole={true} on the CopilotKitProvider to surface the product
web-inspector on every host, and set exposeMemoryRoutes: true on the
Intelligence-mode CopilotRuntime so the inspector's Memory tab can list and
recall memories in the demo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The .first() guard on the 'rendered on the canvas' handoff-pill assertion
was justified by an inaccurate comment (accumulation across exchanges). The
real cause is intra-turn: generateSandboxedUi has followUp:true, so aimock
re-serves the same fixture on the unchanged-userMessage follow-up turn in
replay -> a second identical pill. A terminating sequenceIndex follow-up
fixture was attempted but destabilized the suite (title-generation requests
substring-match the pill text and consume the sequence counter before the
real leg-1 turn), so the .first() guard remains. Test/fixtures only; no
source changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
run-demo.sh detaches everything except the Next.js dev server (docker
compose up -d, native Metal TEI via nohup/disown, then exec pnpm dev), so
Ctrl-C on the dev server leaves the docker stack and the host embedder
running. stop-demo.sh brings those leftovers down in one command.
Tears down, idempotently:
- the Next.js dev server on :3000 (defensive; usually gone via Ctrl-C)
- the docker compose stack (project banking-memory), containers only by
default so a re-run reuses the built image + seeded data
- the native Metal TEI on :7067 (Apple Silicon; the host process docker
doesn't manage), SIGTERM then SIGKILL
Flags: --purge also drops volumes for a clean slate; --keep-tei leaves the
slow-to-warm embedder running when only bouncing the stack.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add full StateGraph + Annotation setup with CopilotKitStateAnnotation.spec
- Show complete tool implementation with proper ToolMessage handling
- Include graph compilation with nodes, edges, and routing logic
- Pattern examples after working shared-state-streaming.ts reference
- Fix both Deep Agents and LangGraph docs versions
- Include formatter fixes for JSON files
Fixes FAC-101
The self-hosted `run-demo.sh` path launches a native Metal
`text-embeddings-router` on :7067 for the durable-memory demo. TEI's
default `--max-batch-tokens` (16384) can fault the Metal backend during
its warmup forward pass on some Apple Silicon machines. The process then
either deadlocks (every thread parked in a pthread cond wait at 0% CPU)
or dies silently with no panic — a GPU-level abort — so it never binds
:7067 and the 300s health wait times out. The demo appears to "crash"
with no actionable error.
Pass `--max-batch-tokens 512` so warmup uses a small forward pass, which
clears reliably. This only bounds per-request tokens (memory texts are
short), not the embedding vectors, so recall stays byte-identical to the
docker/CI embedder.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bundled tei embedder image is amd64-only; under arm64 emulation the Candle
backend is unavailable and TEI falls back to the ONNX/ORT backend, which needs
onnx/model.onnx files Qwen3-Embedding-0.6B doesn't publish (404) -> crash-loop.
A fresh clone on Apple Silicon therefore couldn't stand up the embedder, so
memory save/recall were dead. Ports the proven pattern from the Intelligence
repo's docker-compose.deps.yml + demos/splat-demo/run-demo.sh into this demo:
- docker-compose.yml: gate the bundled `tei` behind the `cpu-fallback` profile,
so a bare `docker compose up` skips the crash-looping emulated image. amd64/CI
opt back in with `--profile cpu-fallback`. (intelligence's tei dep is
required:false, so it starts fine without it, using MEMORY_EMBEDDINGS_URL.)
- run-demo.sh: one-command cold start. On Apple Silicon it runs a native Metal
TEI on :7067 (same 1.9.3 + Qwen3-Embedding-0.6B => byte-identical embeddings,
~20x faster) and points app-api at it; on amd64/CI it uses the docker tei via
the profile. Mints a dev license if .env lacks one, then starts `pnpm dev`.
- README: correct the failure description (emulation->ONNX crash-loop, not OOM),
document run-demo.sh as the recommended start, and the profile-gated manual path.
All CopilotKit-repo-only (banking's compose is standalone); no Intelligence
changes. Validated: shellcheck clean, compose valid, bare `up` skips tei and
keeps intelligence healthy, memory save/recall verified through the native TEI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 3 of the banking->Intelligence-main migration. Self-hosted Intelligence
gates the paid `memory` feature behind a signed offline license; a locally-built
(unbaked) app-api trusts a runtime BAKED_LICENSE_KEYS_JSON, so a throwaway
keypair can sign an enterprise license with features.memory=true.
- scripts/mint-dev-license.mjs: prints (or --write upserts into .env)
COPILOTKIT_LICENSE_TOKEN + BAKED_LICENSE_KEYS_JSON + INTELLIGENCE_DEPLOYMENT_MODE.
Drives the signer from the PRIVATE Intelligence source via INTELLIGENCE_REPO
(same coupling the docker-compose image build already has) rather than
vendoring any signing code into this public repo. No secret is embedded; the
script is dev-only and never imported by the app runtime.
- .env.example: documents BOTH the managed path (CopilotKit-issued token, no
baked key — the eventual hosting target) and the self-hosted dev path, so the
demo is not locked to the local stack.
- package.json: add `mint-dev-license` script.
Replaces the ephemeral Intelligence/tmp/mint-banking-license.ts. .env stays
gitignored; nothing sensitive is committed. Local-only until verified.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 2 of the banking->Intelligence-main migration. Main's memory lib
(libs/memory/src/types.ts) closes MemoryKind to topical|episodic|operational;
the demo was authored against the legacy semantic|procedural names, which the
backend now rejects/misfiles. Rename across the whole surface:
- agent prompt (route.ts CLASSIFY + SAVE-THE-PROCEDURE): semantic->topical,
procedural->operational
- recorder instruction (copilot-context.tsx), learning-tab dual-read dropped,
memory-tab KIND_COLORS, memory unit-test fixture
- smokes (facts + drift) and the e2e spec seed + fixtures comment
Only true kind: values renamed; "semantic recall"/"top-k semantic search"
mechanism descriptions left intact (recall is vector search regardless of enum).
aimock fixture re-record was a no-op: the one fixture pins the recall-and-apply
arc (no kind: values); the seed is REST-side in the spec.
Verified: pnpm test:unit 47/47, tsc --noEmit clean, eslint clean on touched files.
Local-only until the full migration is verified against the main stack.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1 of the banking->Intelligence-main migration (branch:
feat/banking-intelligence-main-migration). PROVEN GREEN against main:
- INTELLIGENCE_DEPLOYMENT_MODE=self_hosted (renamed from legacy DEPLOYMENT_MODE)
- dropped legacy DEFAULT_ORGANIZATION_ID (main's loadAuthEnv rejects it)
- BAKED_LICENSE_KEYS_JSON wired: main gates memory behind a signed license
carrying the "memory" feature (MEMORY_NOT_ENTITLED otherwise). A locally
minted dev enterprise license + baked public key unlocks it (recipe mirrors
Intelligence apps/app-api-e2e global-setup). Verified: /mcp attaches
recall/save/forget_memory and save_memory(kind=topical) round-trips via the
cpk key.
REMAINING (next session): (1) reproducible dev-license mint helper + .env wiring
(mint script currently at Intelligence/tmp/mint-banking-license.ts, ephemeral);
(2) kind rename semantic->topical, procedural->operational across prompt, memory
lib, smokes, e2e spec; (3) aimock fixture re-record for new kinds; (4) re-verify
e2e/smokes/manual arc. Working demo (PR #5763, demo branch) is untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>