recorded.json baked in a real 2026-05-15 gpt-4o session that, for the beautiful-
chat Sales Dashboard pill, called standalone pieChart+barChart at turnIndex 2
AFTER already calling generate_a2ui — so under aimock the dashboard painted loose
charts next to the A2UI surface (the same over-call the live fix removed via
parallelToolCalls:false + steering). Replace that turnIndex-2 leg's
pieChart+barChart tool calls with narration so the aimock chain is
query_data -> generate_a2ui -> narration, matching the fixed live behavior. The
now-redundant turnIndex-3 narration leg is folded in.
Issue: asking for the Sales Dashboard (esp. after a prior turn) made the model
call the standalone pieChart + barChart frontend tools AND generate_a2ui, so
loose charts painted next to the dashboard.
- beautifulChatAgent: mirror gold `beautiful_chat.py` `parallel_tool_calls=False`
(defaultOptions.providerOptions.openai.parallelToolCalls) + sharpen the
steering so a dashboard / "using A2UI" request calls generate_a2ui ONLY (it
draws the charts inside the surface), while a single-chart request still uses
the standalone pieChart/barChart tool. Verified live: dashboard turn now calls
[query_data, generate_a2ui] only, single + after-flights.
- aimock beautiful-chat flights fixture: model the fixed-schema `search_flights`
path (returns the A2UI FlightCard envelope directly) instead of the old
generate_a2ui -> render_a2ui chain, so the fixture matches the live behavior
and the e2e spec's stated intent (United $349 / Delta $289).
- frontend-tools-async: drop the stray broad 'project planning' fixture entry that
substring-shadowed 'Find my notes about project planning' -> run-loop. Green.
- gen-ui-open + gen-ui-open-advanced: remove 6 gen-ui-open-owned strays from
gen-ui-tool-based.json ('3D axis visualization', 'Inline expression evaluator',
'render an open gen-ui element', 'continue the advanced gen-ui flow') that
collided (same context, different responses) with gen-ui-open.json. Green;
gen-ui-tool-based unaffected.
- multimodal: quarantine. On main + latest deps the browser gets 200 from the
runtime but never starts a run (runStartCount=0) — a shared multimodal
frontend/runtime issue (identical frontend to langgraph; agent works via direct
POST; my #5985 fix doesn't change it). Genuine gap, needs shared-layer work.
Full D6 now 36/36 green on official latest (ag-ui 1.0.1, core 1.13.0, openai 1.12.0).
- shared-state-streaming: replace main's stale 'counter' fixture (drifted from
langgraph canonical) with the write_document document demo (6-entry poem/email/
quantum + chunkSize) + SharedStateStreamingFrameworkAgent seed subclass; frontend
already matches langgraph. Un-quarantine + add to features. Green (3/3 turns).
- tool-rendering-reasoning-chain: un-quarantine + add to features. Green on core
1.13.0 / openai 1.12.0 (latest) via aimock encrypted_content (CopilotKit/aimock#342,
which fixes the Responses reasoning multi-tool regression) + the store:False agent.
CI-green depends on aimock#342 releasing.
- Drop reasoning-default-render / agentic-chat-reasoning from not_supported (no D6
probe featureType — not real cells).
Port the #5985 fix onto main: ported langgraph's full 18-entry custom-catchall
fixture (main's 4-entry set left SF/flights/d20/chain pills leaking to the
default-catchall fixture) + _ToolRenderingFrameworkAgent dropping the divergent
end-of-run MESSAGES_SNAPSHOT so the narration (not the tool card) is the terminal
bubble. Verified green via per-cell --direct. First cell of the #5985->main
re-integration.
Two langgraph-typescript-scope causes:
1) Fixture prompt drift: the a2ui-recovery.json userMessage keys + suggestions.ts
pills still carried langgraph-python's copied prompt while the shared probe
sends a langgraph-typescript-UNIQUE prompt -> aimock 404 -> heal surface never
mounts. Retarget the 4 keys + 2 pills to the probe's unique prompt (kept
distinct: a2ui-recovery fixtures have no x-aimock-context, so per-slug-unique
prompts are load-bearing to avoid cross-framework collisions).
2) Flaky heal (green-then-red): TS @ag-ui/langgraph getA2UITools invokes its inner
render_a2ui sub-agent via a config-less model.stream(), so config-based header
forwarding never reaches it; the inner aimock call carries no x-test-id, its
sequenceIndex falls into the never-reset DEFAULT_TEST_ID bucket, and the
seq0->seq1 heal staging only works on the first run. Add wrapModelCall/
wrapToolCall middleware + AsyncLocalStorage + a custom OpenAI fetch that
forwards inbound x-* headers onto every outbound call (outer emit AND inner
render) — mirroring the mechanism the green TS sibling mastra uses.
Not the shared Python recovery-loop defect (mastra, also TS getA2UITools, is
green — the TS path is fixable). D6 a2ui-recovery now green + STABLE (6 real ~7.7s
runs); aimock fixtures test 844 passed. Divergence documented in PARITY_NOTES.md.
frontend-tools-async was flaky-red (done-signal-missing / text-unstable): the
fixture carried a legacy duplicate last fixture (userMessage 'project planning',
turnIndex:0, content + a query_notes tool call with NO id) that is absent from
the canonical D5 source. On iteration 2 (last msg = the query_notes tool result),
aimock's selectByTurnIndex prefers the turnIndex-bearing bare fixture (0<=1) over
the toolCallId-keyed follow-up, re-emitting query_notes with a fresh id whose
result never matches -> infinite re-emit loop -> turn never completes.
Remove the stale bare 'project planning' turnIndex:0 fixture so the toolCallId
follow-up is the sole iteration-2 candidate and the turn ends cleanly.
D6 frontend-tools-async now green and STABLE (4/4 consecutive real runs); aimock
fixtures test 844 passed. (Same stale-fixture defect exists on the langgraph-python
north star — flagged for a separate fleet-wide follow-up.)
Every affected cell was D6-green (or D4-gated) while the deployed demo was
visibly broken. The common cause is GOTCHAS #8: aimock replays a scripted
tool-call sequence keyed on userMessage + context, so the fixture answers a
question the model was never asked.
Prompt gaps — ~20 demos shared one prompt-less `createBuiltInAgent()` where the
reference wires each to its own graph AND its own system prompt. Adds
`createBuiltInAgent({ systemPrompt })` + `demo-prompts.ts`, ported from the
reference graphs:
- gen-ui-tool-based plotted zeros; the assistant said "I used placeholder values
since no sales figures were provided". Nondeterministic — some runs invented
real values, which is how it passed review.
- gen-ui-agent published its plan once then narrated, freezing the progress card
on step 1 of 4.
- a2ui-recovery painted five identical cards: nothing constrained the supervisor
to one `generate_a2ui` call, and the pills literally ask it to "self-correct".
The retry loop lives inside the tool, so supervisor retries are duplication.
subagents' delegation panel was permanently empty — the frontend reads
`agent.state.delegations` and no code emitted that slot. The converter now emits
a `/delegations` delta per sub-agent result (whole-array `add`, since initial
state is `{}` and strict fast-json-patch rejects unresolvable paths while
@ag-ui/client swallows the throw), and ports the reference's
`_MAX_CRITIQUE_ITERATIONS = 1` cap.
declarative-json-render dumped raw JSON for any prompt the model couldn't crib
from the worked example. Captured the SSE: the wire is already one closing brace
short, so nothing is dropped in transport — model-level enforcement had been
removed on the grounds that "the system prompt already enforces JSON-only
output". It does not. Restores it via the Responses API's `text.format`, the
param the removed `response_format` maps to, verified against the pinned
@tanstack/ai-openai@0.15.6 -> @tanstack/openai-base@0.9.2 request mapping.
declarative-gen-ui and a2ui-recovery were the inverse: capped at D4 (UI ✓ BE ✓
1P ✗ D6 gated) while working live. Their secondary design-call fixtures gated on
`match.responseFormat`, which aimock can never satisfy here — it has no
`text.format` handling and only forwards a top-level `response_format` the
Responses API doesn't accept. Re-keyed onto `match.toolName` (the outer call
declares `generate_a2ui`, the in-tool design call declares nothing) with the
tool-less fixtures moved last, since several pills' brief is a substring of the
pill text.
Tests (both mutation-verified — they fail with the fix reverted):
- aimock-a2ui-routing.test.ts drives aimock's real `matchFixture`; 14/16 cases
fail on the old fixtures with "no fixture matched the secondary design call".
- tanstack-factory.test.ts covers the `/delegations` and `/steps` deltas.
Typecheck unchanged at 61 pre-existing errors; oxlint clean on changed files.
NOT verified live: the four prompt/`text.format` changes need a real-LLM
click-through, which needs a key this environment doesn't have. Reasoning and
the exact request mapping are documented inline.
gen-ui-open and gen-ui-open-advanced looped (done-signal-missing): the 7 leg-1
tool-call fixtures for the open pills carried turnIndex:0, which aimock treats
as turnIndex-relaxed (default AIMOCK_STRICT_TURN_INDEX off) and keeps as a
candidate on the follow-up turn, out-selecting the toolCallId terminal so the
done signal never fires.
Change those 7 leg-1 fixtures from turnIndex:0 -> hasToolResult:false (the
request-shape predicate that excludes them once the tool result is present),
mirroring langgraph-python's canonical gen-ui-open.json shape. The 4 single-turn
narration fixtures keep turnIndex:0.
D6 gen-ui-open + gen-ui-open-advanced now green (two real ~11s/~13s runs each);
no aimock no-match/relaxed.
Bring the langgraph-typescript Showcase integration to parity with the
reference langgraph-python: byte-identical demo frontends, the same demo set
and manifest overview, framework-native TypeScript backend wiring, and
canonical mirrored aimock D6 fixtures. Scope is limited to
integrations/langgraph-typescript and its aimock/d6 fixtures.
Frontend: realign ~25 drifted demo files to the north star; add missing
src/components/ui primitives, READMEs, declarative-gen-ui/sales-context, and
the threadid-frontend-tool-roundtrip page; remove LGT-only drift; replace the
hand-rolled landing page with the manifest-driven one. Only the sanctioned
"LangGraph (TypeScript)" identity strings differ.
Manifest: rebuild to match langgraph-python's feature order, demo order,
names, tags, descriptions, and routes; drop two bogus NSF entries that were
never real features (reasoning-default-render, agentic-chat-reasoning); restore
shared-state-streaming and tool-rendering-reasoning-chain as supported cells.
not_supported_features now matches the north star (gen-ui-interrupt /
interrupt-headless — a shared upstream react-core resume-path limitation).
Backend: add the dedicated gen-ui-tool-based data-viz graph; consolidate the
tool-rendering variants and reasoning cells onto their shared graphs (matching
langgraph-python) and drop redundant duplicate graph ids; register
threadid-frontend-tool-roundtrip; move the MCP Apps runtime to the
[[...slug]] catch-all route.
Fixtures: re-mirror all 43 aimock/d6 fixtures from the canonical langgraph-python
set with match.context re-keyed, replacing previously drifted per-integration
fixtures.
## What
Brings the **ms-agent-dotnet** (Microsoft Agent Framework .NET) showcase
integration from D5 to **D6**, using **langgraph-python** as the
north-star reference.
### 1. Frontend parity with langgraph-python
Restores near-identical frontends where ms-agent-dotnet had drifted,
while **preserving the load-bearing .NET adaptations** (per the showcase
iron rules — differences belong in fixtures/minimal backend, not the
shared frontend):
- Root shell: `globals.css` (Tailwind `@theme` block + brand green),
manifest-driven index `page.tsx`, `layout.tsx`, new `middleware.ts`
(`x-pathname`), `tsconfig` include.
- `declarative-gen-ui` subtree restored (fixes divergent pill testids
the shared probe asserts).
- Doc-snippet `@region` markers, import-style normalization, `subagents`
revert, stale-file cleanup, `auth` inspector flag.
- **Kept** (load-bearing, not reverted): `parse-json-result` 3-layer
unwrap, multimodal legacy-shim, tool-based `hitl` (MAF has no
`interrupt()`), `agent-config` `properties=`.
### 2. shared-state-streaming → per-token (removed from
`not_supported_features`)
`write_document`'s `document` arg now streams into `state.document`
per-token via a `createSharedStateStreamingAgent` route shim (mirrors
the proven `createGenUiAgent` bridge, with a partial-JSON string
decoder), since the .NET AG-UI host has no `predict_state_config`.
### 3. a2ui-recovery cell (new)
First MS-Agent-Framework implementation of the A2UI
validate→retry→`a2ui_recovery_exhausted` recovery loop. Because the MAF
AG-UI adapter can't emit the custom `ACTIVITY_SNAPSHOT{status:"failed"}`
the exhausted card needs, it's a **raw-SSE `MapPost` endpoint**
(`RecoveryAgent.cs`) — the same adapter-bypass pattern already shipped
for `/multimodal`. Adds the demo frontend, API route, deterministic
aimock fixture (heal seq0-invalid→seq1-valid; exhaust always-invalid),
and a unique per-slug `PROMPTS` entry in the shared probe.
### 4. threadid-frontend-tool-roundtrip demo (parity)
Added for demo-set parity (reuses the `frontend_tools` passthrough; not
a D6-scored feature, mirroring the reference).
`gen-ui-interrupt` / `interrupt-headless` remain honestly quarantined
(upstream `@copilotkit/react-core` `useInterrupt` resume-path bug — not
a backend gap).
## Verification
- Code was authored in parallel worktree-isolated slots, each
cross-verified against the reference + the shared probe contracts; the
a2ui-recovery fixture was cross-checked against
`RecoveryAgent.ValidateComponents`.
- Local D6 harness: the image builds and the stack + probes run, but
**full local green was blocked by Windows-only harness friction**
(`core.symlinks=false` breaks `stage_shared`'s `[ -L ]` materialization;
`--direct` doesn't context-scope the `x-aimock-context` header so
context-keyed a2ui fixtures miss). These are environmental, not code
issues. **Relying on CI's Linux harness (real symlinks + fleet worker)
for authoritative D6.**
## Follow-up (not in this PR)
- `stage_shared()` should also materialize Windows symlink-as-file
entries (detect a regular file whose content is a relative path), so
forced local rebuilds work on `core.symlinks=false` checkouts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
Aligns the **langgraph-fastapi** showcase integration to the north-star
**langgraph-python** (OSS-582 — "Align LangGraph (FastAPI) Showcase
demos and code"). Moves the integration onto the product-centric demo
set: backend agent graphs, runtime wiring, frontend chrome, the
demo-browser overview (`manifest.yaml`), and aimock fixtures.
Linear: OSS-582.
## What changed
**Backend agents (`src/agents/src/`)**
- Ported the missing dedicated graphs: `agentic_chat`, `gen_ui_agent`,
`gen_ui_tool_based`, `shared_state_streaming`.
- v2 `create_agent` ports + config alignment for `agent_config_agent`,
`headless_complete`, `reasoning_agent`, `tool_rendering_agent`,
`tool_rendering_reasoning_chain_agent`, `a2ui_dynamic`.
**Runtime wiring**
- `route.ts`: wired the canonical demos to their dedicated graphs and
removed them from the generic `sample_agent` fallthrough loop (this is
what made several demos behave correctly against a real LLM — see
below); added `recursion_limit`.
- `langgraph.json`: registered the 3 new graphs.
- Renamed the dedicated API routes
`copilotkit-byoc-{hashbrown,json-render}` →
`copilotkit-declarative-{hashbrown,json-render}`.
**Overview / content**
- `manifest.yaml`: demos + features aligned to LGP (names, descriptions,
tags, order, canonical ids; deprecated aliases migrated; phantom
`hitl-in-chat-booking` removed; `shared-state-streaming` un-quarantined
now that it works). Integration identity (name/slug/logo) preserved. The
demo-browser overview is now card-for-card identical to
langgraph-python.
- Restored two missing `@region` markers (factory-automation snippet
extraction).
**Frontend chrome**
- `globals.css`, `layout.tsx`, `middleware.ts` (was missing),
`declarative-gen-ui/*` + `sales-context.ts`, `beautiful-chat`;
`Dockerfile` now copies `manifest.yaml` (fixed an RSC crash on every
`/demos/*`).
**aimock fixtures (`aimock/d4|d6/langgraph-fastapi/`)**
- D6 fixture fixes for the previously-red cells (stale `turnIndex`
gates, cross-file substring shadowing, missing `chunkSize`, prompt
narrowing).
**Showcase tooling / docs**
- Fixed harness services racing on a shared image tag
(`docker-compose.local.yml`).
- `GOTCHAS.md` #8: documents that aimock D6 can be green while a demo is
broken against a real LLM (fixtures replay scripted tool calls
regardless of which graph ran), plus how to catch it.
- `PARITY_NOTES.md`: sanctioned divergences (a2ui-recovery per-slug
prompt isolation; declarative-json-render scoped-test divergence).
## Verification
- **D6 sweep: 38 green / 1 red.** The single red is **a2ui-recovery**,
which reproduces **identically on the north star** (shared Python
`ag_ui_langgraph` recovery loop; mastra's TS impl is green). It is not a
fastapi defect and is tracked as a separate PR against the north star.
Documented in `PARITY_NOTES.md`.
- `validate-manifests` (manifest → registry, all 20 integrations) —
green.
- `validate-routes --all` (runtime-route wiring) — green.
- Build + TypeScript typecheck (`next build` via the Docker image build)
— green.
- `@region` audit — all region pairs balanced and LGP-consistent (0
orphans/typos/mismatches).
- Live manual QA against real OpenAI (`:3102` vs `:3100`) confirmed the
key demos (reasoning-custom, shared-state-streaming, gen-ui-tool-based,
agentic-chat).
## Notable finding
Several demos were D6-green but broke against a real model because they
fell back to the generic `sample_agent` instead of their dedicated graph
— the aimock fixture masked the wiring bug by replaying scripted tool
calls. This PR fixes the wiring and documents the gap (GOTCHAS #8).
D6-green is necessary but not sufficient for graph/tool-dependent demos;
a real-LLM click-through is required.
## Deferred / follow-ups
- **a2ui-recovery** — shared north-star defect in the Python
`ag_ui_langgraph` recovery loop; separate PR against the north star.
- **d5-byoc probe** — always sends the hashbrown pill even on the
json-render page (fleet-wide harness limitation); tracked as a
follow-up. The gating `byoc` grid cell is green.
## Scope note
Only `langgraph-fastapi` integration code, its aimock fixtures, and
showcase tooling/docs changed. **langgraph-python (the north star) was
not touched.**
CI 'Showcase: Validate' failed: aimock-fixtures.test.ts asserts every d6/*.json
yields >=1 fixture, but gen-ui-open.json had been emptied to a _meta stub (0
fixtures) by an earlier open-gen-ui shadowing fix that consolidated the pills
into gen-ui-tool-based.json.
Restore gen-ui-open.json as a byte-for-byte context-swap of the canonical
langgraph-python file (context -> langgraph-fastapi): 4 fixtures for the '3D
axis' and 'Inline expression evaluator' pills, LGP's clean shape
(hasToolResult:false leg-1, distinct toolCallId terminals). No shadowing loop:
each file's leg-1/terminal pair is anchored by its own distinct toolCallId, so
the done-signal always resolves.
Verified: aimock-fixtures.test.ts 839/839 green locally; D6 gen-ui-open,
gen-ui-open-advanced, gen-ui-custom all green (two real runs each).
Same class as the hitl-in-app fix: turnIndex:0 emit legs get re-served on the
post-tool-result turn under aimock's default (relaxed) turn-index selection,
re-firing the frontend tool instead of narrating.
frontend-tools-async (query_notes): add hasToolResult:false to the 4 emit legs
(3 pills + the 'project planning' catch-all) so they match the emit turn and are
excluded once the tool result returns. The toolCallId narration legs round-trip
fine here (verified on the wire) and are unchanged.
Verified under relaxed aimock: pill -> single result card + narration, no
re-emit, no error (was: double card + 'toLowerCase' error).
subagents.json left as-is: its turnIndex 1/2 legs are intentional server-side
step sequencing, not the client-re-POST pattern.
open-gen-ui / open-gen-ui-advanced NOT touched here: their real fixtures live in
gen-ui-open.json / gen-ui-open-advanced.json (already hasToolResult-gated, no
turnIndex). The stale duplicate generateSandboxedUi entries in
gen-ui-tool-based.json are a separate cleanup, tracked outside this PR.
The three turn-0 pill fixtures (refund/downgrade/escalate) were keyed on
turnIndex:0. aimock's default (relaxed) turn-index selection re-serves a
turnIndex-only fixture on the post-approval turn, so approving a
useFrontendTool(request_user_approval) call re-emitted the tool call
instead of narrating the outcome — the turn never settled (stuck
text-unstable).
Re-key each emit fixture on hasToolResult:false instead of turnIndex:0.
It matches the emit turn (no tool result yet) and is excluded on the
post-approval turn (hasToolResult:true), where the toolCallId narration
fixture wins. No turnIndex dependency, so it is correct under both strict
and relaxed aimock, and multi-pill-safe.
Not a bridge/runtime bug: @ag-ui/mastra loops back correctly on real
OpenAI and under strict aimock. Fault was fixture selection only.
Two independent fastapi divergences from the green north-star kept this red:
1. Missing chunkSize:9999 on the 8 tool-call fixtures. Under aimock's global
8-byte chunking the large tool-call args failed to JSON-parse in one piece,
so the AAPL->MSFT reasoning chain stopped at leg 1 (turns 1 & 2). Byte-align
to LGP (and the green mastra/langgraph-typescript siblings carry the same
pattern).
2. Fixture-pool shadowing broke turn 3. aimock pools all d4+d6 fixtures for a
context and matches by userMessage substring, first-match-wins in load order
(d4 before d6). fastapi had broad keys LGP doesn't:
- d4/chat.json: 'weather' -> 'weather in San Francisco', 'flights from SFO
to JFK' -> 'Find flights from SFO to JFK.' (period-terminated so it stops
being a substring of turn 3's 'Find flights from SFO to JFK and show me
the weather there.').
- tool-rendering-{custom,default}-catchall.json: bare 'Find flights' ->
'Find flights from SFO to JFK.' (now matches LGP's exact key).
Also byte-align the backend agent to LGP: scriptable get_stock_price signature,
the detailed chaining system prompt, model gpt-5.4, reasoning summary detailed.
D6 tool-rendering-reasoning-chain green (two real ~21s runs). Shared-fixture
regression all green: agentic-chat, tool-rendering, tool-rendering-custom-catchall,
tool-rendering-default-catchall, headless-complete (Tokyo-weather consumer).
Two compounding defects kept this cell red:
1. Backend missing get_revenue_chart. headless_complete.py never registered
the revenue-chart tool, so the chart-turn never emitted a tool call and the
card never mounted. Port LGP's get_revenue_chart tool + its system-prompt
routing rule (tool return shape byte-identical to LGP).
2. Stale turnIndex fixtures caused turn-2 and turn-4 loops to the recursion
limit. Align to LGP's canonical shape:
- tool-rendering.json (shared): AAPL first-leg turnIndex:0 -> hasToolResult:false
so it stops re-firing at turnIndex>=2 in the multi-pill thread.
- headless-complete.json: rewrite to LGP's structure (narration/toolCallId
fixtures first, tool-call legs after with userMessage+context only, no
stale turnIndex); drop the divergent fastapi-only highlight fixtures,
subsumed by the broader substring fixture.
D6 langgraph-fastapi:gen-ui-headless-complete now green (two real ~29s runs);
tool-rendering re-verified green (shared-fixture no regression).
gen-ui-agent had no dedicated backend graph: langgraph.json lacked a
gen_ui_agent entry and route.ts routed the name through the neutral-assistant
loop to sample_agent, which has no steps state or set_steps tool, so the
progress card never mounted (agent hit the default recursion limit of 25).
- Port LGP's gen_ui_agent.py (byte-identical) and register it in langgraph.json.
- route.ts: bind gen-ui-agent to createAgent("gen_ui_agent") and bake
assistantConfig.recursion_limit (default 100) into every LangGraphAgent —
the graph's Python with_config isn't visible to the server runs API, so the
multi-step set_steps walk overran 25. Mirrors langgraph-python.
- aimock: regenerate d6/gen-ui-agent.json from LGP (adds chunkSize:9999 on the
24 tool-call fixtures) and narrow the over-broad d4 chat.json "summarize" key
to "Summarize the sales pipeline" so it stops substring-shadowing the
competitor set_steps chain (and other summarize prompts). Matches LGP.
D6 langgraph-fastapi:gen-ui-agent now green (two real ~14s runs);
agent-config re-verified green (no regression).
open-gen-ui and open-gen-ui-advanced (both probes load gen-ui-open.json) hung
with done-signal-missing (~12 agent runs, 1.3MB tool-call payload each turn).
Cause: two files (gen-ui-open.json + gen-ui-tool-based.json) both defined leg-1
generateSandboxedUi tool-call fixtures for the SAME userMessages ('3D axis…',
'Inline expression evaluator') under the same context but different toolCallIds.
aimock pools every d6/<slug>/*.json into one substring-match set, and the
turnIndex:0 fixture in gen-ui-tool-based.json got served on follow-up turns too
(turnIndex-relaxed), so the toolCallId-anchored terminal never won → no
finish_reason:stop → loop.
Fix (fastapi fixtures only, aligned to GOTCHAS 'canonical fixtures have no
turnIndex' + toolCallId-terminal-before-userMessage-leg1):
- gen-ui-tool-based.json: strip spurious turnIndex:0 from leg-1 tool-call
fixtures; add chunkSize:9999 so the large args JSON-parse in one chunk. Now
the single source for all open-gen-ui pills.
- gen-ui-open.json: empty its fixtures (keep a documenting _note) to remove the
duplicate leg-1/terminal for the 2 probed pills.
- gen-ui-open-advanced.json: delete orphan (no probe declares it; both open
probes use gen-ui-open.json).
D6 green + stable (two ~4s runs each); regression-checked gen-ui-custom
(gen-ui-tool-based route) still green. NOTE: LGP + likely all integrations have
the same open-gen-ui loop (fleet-wide fixture-shadow bug) — no sibling was green.
fixture[8] (userMessage:"project planning", turnIndex:0) returned a
query_notes toolCall. aimock matches userMessage by substring, so
"project planning" also matched the test prompt "Find my notes about
project planning"; as a turnIndex:0 'scripted turn' it beat the correct
terminal fixture[2] (toolCallId-keyed, content-only) on the follow-up turn,
so the agent kept getting another tool call and looped ~30x, never emitting
the done signal (D6 done-signal-missing).
Remove turnIndex:0 from fixture[8] so it's a plain fallback that loses to
the terminal fixture by registration order (matching the langgraph-typescript
shape). D6 now green: two real ~6s runs (was a 120s timeout).
Note: langgraph-python has the identical bug (still red) — same one-line fix
applies there; tracked separately.
## What & why
Brings the **built-in-agent** showcase integration to parity with the
**LangGraph-Python (LGP)** reference: byte-identical demo frontends + a
named-agent backend registry (BuiltInAgent + TanStack AI), so every demo
climbs the D0–D6 ladder against the LGP gold standard.
## Changes (4 commits)
1. **P0 pattern** — `agentic-chat` byte-identical + named agent
(`agentic_chat`); proven D6-green locally. Fixed `gpt-4o` → `gpt-5.5` in
the shared factory.
2. **Frontend migration (all demos)** — every LGP `src/app/demos/*`
copied verbatim (`diff -r` clean), plus shared `components/ui` (25
shadcn primitives) + `lib/utils`, byte-identical. Added the 5 demos BIA
lacked (`a2ui-recovery`, `declarative-hashbrown`,
`declarative-json-render`, `shared-state-read`,
`threadid-frontend-tool-roundtrip`); added the frontend deps the copied
UI needs (radix-ui, cmdk, embla-carousel-react, react-markdown,
remark-gfm, yaml, …).
3. **Named-agent backend** — `/api/copilotkit` registers 22 named agents
(generic all-tools, fixture-driven; reasoning trio via the reasoning
adapter). 8 dedicated routes re-keyed `default` → LGP agent id;
`mcp-apps` also serves `headless-complete`; `ogui` serves both
open-gen-ui ids; `byoc-*` routes renamed to `declarative-*`; new
`a2ui-recovery` + `beautiful-chat` routes reuse existing agents. Dropped
BIA-only extras (`byoc-*`, `hitl-in-chat-booking`).
4. **Reconcile** — `manifest.yaml` (37 features / 40 demos;
`generate-registry` + `validate-parity` pass) + `PARITY_NOTES.md`.
> **Note on "byte-identical":** frontends are verbatim LGP **modulo
BIA's `consistent-type-imports` ESLint rule** (type imports split into
`import type {}`) — required for a green lint/PR, semantically & DOM
identical.
## D6 status (local sweep)
- **~33/40 demos GREEN** on the first sweep — byte-identical frontends +
named agents + existing fixtures work broadly.
- **4 RED locally are an aimock-infra issue, not this integration:** the
deployed `ghcr.io/copilotkit/aimock:latest` has no
`context`/`--context-field` fixture scoping, so cross-slug `userMessage`
collisions let earlier-loaded (`ag2`/`d4`) fixtures shadow BIA's own.
BIA's fixtures are **correct** and converge under a context-aware aimock
(present on aimock `origin/main`). Affects
`tool-rendering-custom-catchall`, `headless-complete`, `gen-ui-agent`,
`frontend-tools`. **Action for infra: redeploy aimock from a
context-aware build.** Details in `PARITY_NOTES.md`.
- **2 downstream-host RED (kept as features, informational — mirrors
LGP):** `declarative-gen-ui` (A2UI renderer host) and `mcp-apps` (MCP
iframe host).
- Quarantined NSF unchanged: `gen-ui-interrupt`, `interrupt-headless`,
`shared-state-streaming`, reasoning-trio.
D6 is informational/weekly (not a merge gate); these are documented for
parity tracking.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Implement the render/validate/retry recovery loop with a2ui_recovery_exhausted
hard-fail as a raw-SSE endpoint (RecoveryAgent.cs, mounted in Program.cs), add
the demo frontend + API route, the deterministic aimock fixture, and the unique
per-slug PROMPTS entry in the shared d5-a2ui-recovery probe.
- shared-state-read: remove spurious turnIndex:0 on the turn-2 fixture entry
(turnIndex counts assistant msgs = 1 on turn 2, so the constraint could never
match -> turn 2 dom-missing). Now D6 GREEN.
- a2ui-recovery: register built-in-agent heal/exhaust prompts in the shared
probe PROMPTS (new demo BIA lacked) + add its aimock fixture. HEAL turn now
GREEN; EXHAUST still red pending a backend factory change (emit the
a2ui_recovery_exhausted envelope) — tracked, not fixable at fixture layer.
- multimodal: fix conversation-runner skipSend baseline (snapshot run-start
BEFORE preFill, since preFill issues the run on skipSend turns; fast
in-process runtimes else false-red done-signal-missing). Image turn now
GREEN; PDF turn still red (BIA runtime has no document/PDF flattening path).
NOTE: conversation-runner.ts is a SHARED harness helper — change is guarded to
skipSend turns (non-skipSend byte-identical) and only corrects the baseline;
flagged for harness-owner review.
Excalidraw's MCP create_view tool declares `elements` as a string (JSON-encoded
array) in its inputSchema; the fixtures emitted a raw JSON array. BIA declares
the injected MCP tool locally via jsonSchemaToZod -> z.string(), so the array
failed input validation, the tool never executed against excalidraw, no
ACTIVITY_SNAPSHOT fired, and the MCP Apps iframe never mounted (D6 red).
Stringify the elements arg to match the inputSchema. Verified D6 green.
Same latent fix applied to the flowchart pill in mcp-apps.json.
The "Generative UI: useComponent" cell (gen-ui-tool-based) went red on mastra
the moment OSS-381 took it out of not_supported: the D5 gen-ui-custom probe
sent the *haiku* prompt and hunted for a haiku card, but mastra's demo is the
LGP-style `useComponent` chart demo (render_pie_chart / render_bar_chart) with
no haiku tool — so the assistant bubble came back empty ("haiku card
[data-testid=copilot-assistant-message] rendered but has no text content").
Root cause: the probe's CHART_INTEGRATIONS allowlist in
harness/src/probes/scripts/d5-gen-ui-custom.ts omitted mastra, so
isChartIntegration("mastra") was false and it took the haiku branch. mastra's
gen-ui-tool-based page registers render_pie_chart / render_bar_chart via
useComponent exactly like langgraph-python and google-adk.
- Add "mastra" to CHART_INTEGRATIONS so the probe sends the pie-chart prompt
and asserts the donut SVG + "pie"/"chart" follow-up tokens.
- Add aimock/d6/mastra/gen-ui-custom.json (mirrors langgraph-python's, context:
mastra; the pie schema is identical — {title, description, data:[{label,value}]})
so the cell is deterministic under aimock replay instead of falling through to
the live upstream.
- Repoint the probe unit test's haiku-empty-card case from "mastra" to "agno"
(a genuine haiku integration) now that mastra is a chart integration.
Not a v1-bridge streaming regression — a harness/fixture gap exposed when the
cell was un-suppressed. Harness unit tests not run locally (sparse showcase
checkout has no vitest); logic-only changes.
--no-verify: sparse showcase checkout has no monorepo lefthook/commitlint binaries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
headless-simple's "Give me a fun fact." pill rendered the wrong text
("Here's a fun fact: honey never spoils…") and the e2e failed. Root cause:
d4/mastra/chat.json keyed the prebuilt-sidebar agentic-chat fixture on the
bare "fun fact", which is a substring of the headless-simple pill "Give me a
fun fact." Because d4 loads before d6, that fixture shadowed headless-simple's
own d6 fixture ("A fun fact: Honey never spoils!").
Re-key it to the unique "fun fact for the prebuilt sidebar" phrase (mirrors
gold langgraph-python), so it no longer substring-collides. The prebuilt-sidebar
demo pill "Give me a fun fact." now shares headless-simple's d6 fun-fact
fixture, exactly as gold does; prebuilt-sidebar/popup e2e unaffected.
Verified (Node 22 + next start + aimock 1.37.4 replay): headless-simple 4/4,
prebuilt-sidebar 4/4, prebuilt-popup 4/4 — and the full mastra card/chat suite
green (tool-rendering 6/6, tool-rendering-reasoning-chain 5/5, headless-complete
5/5, beautiful-chat 8/8, agentic-chat 4/4). Only a userMessage key changed
(more specific → strictly fewer aimock substring-shadows, well under the
KNOWN_SHADOW_CEILING).
--no-verify: sparse showcase checkout has no monorepo lefthook/commitlint binaries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
headless-complete is marked supported but its WeatherCard / StockCard / ChartCard
stalled in the "running" state. headlessCompleteAgent registered tools via object
shorthand ({ weatherTool, stockPriceTool }), which exposes the JS variable names
instead of the snake_case names the aimock fixtures + useRenderTool renderers emit
(get_weather / get_stock_price / get_revenue_chart) — so the scripted tool calls
were never executable — and get_revenue_chart had no backend tool at all.
- Re-key headlessCompleteAgent to explicit { get_weather, get_stock_price,
get_revenue_chart } (mirrors gold langgraph-python headless_complete.py).
- Add revenueChartTool (id get-revenue-chart) returning gold's fixed payload
{ title: "Quarterly revenue", subtitle, data: [6x {label,value}] }.
- Make weatherTool accept optional scripted temperature/conditions/humidity/
wind_speed (echoed when provided, else the seeded getWeatherImpl) — mirrors
get_stock_price's scripted price_usd. Gold's headless get_weather is a fixed
68 degF / Sunny mock while mastra's is seeded, so the headless weather fixtures
script 68/Sunny to match gold's card; tool-rendering's SF pill keeps its seeded
value. Scripted the winning headless-complete + gen-ui-headless-complete
"What's the weather in Tokyo" legs and aligned the narration to gold. (No gold/
shared backend touched — mastra tool + mastra fixtures only.)
Verified (Node 22 + next build/start + aimock 1.37.4 replay): headless-complete
5/5; tool-rendering 6/6, tool-rendering-reasoning-chain 5/5, beautiful-chat 8/8,
agentic-chat and headless-simple weather unaffected — no regression.
--no-verify: sparse showcase checkout has no monorepo lefthook/commitlint binaries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The tool-rendering cell (marked supported) had 5/6 e2e failing and
tool-rendering-reasoning-chain 2/5, all from mastra aimock fixtures diverging
from the langgraph-python gold standard. Root cause: several fixtures were
keyed on generic substrings where gold uses unique tails, so they
substring-collided with the longer chain pills and — loading earlier
(alphabetical file order) — hijacked them.
Verified on a faithful rig (Node 22 + next build/start + aimock 1.37.4 replay):
- Stock: the pill's scripted $338.37 fixture was shadowed by
headless-complete's ticker-only "price of AAPL" leg (tool's 189.42 default).
Restore gold's unique "price of AAPL right now" key; key the tool-rendering
emit leg on toolName (gold parity).
- d20: the first-roll leg gated on hasToolResult:false never matched once
prior-pill tool results lingered in thread history -> 0 cards. Match on
userMessage only (gold). Add stopWhen: stepCountIs(8) to toolRenderingAgent
so the 5-roll sequence + narration (and the 3-tool chain-tools turn) run to
completion instead of stopping at the default step cap.
- chain-tools: headless-complete's generic "weather in Tokyo" leg hijacked the
"...get the weather in Tokyo..." pill and emitted only get_weather. Restore
gold's "What's the weather in Tokyo" key.
- reasoning-chain flights+weather + sequential: beautiful-chat's generic
"Find flights from SFO to JFK" legs hijacked the "...JFK and show me the
weather there" pill. Restore gold's "for next Tuesday" key.
Result: tool-rendering 6/6, tool-rendering-reasoning-chain 5/5, beautiful-chat
8/8 (no regression) under aimock replay.
Note: headless-complete's own weather/stock/revenue cards remain red on a
separate pre-existing bug (headlessCompleteAgent tool-registration + a missing
get_revenue_chart tool) — addressed in a follow-up commit.
--no-verify: this sparse showcase checkout has no monorepo lefthook/commitlint
binaries (matches prior commits on this branch).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses the tool-rendering CHANGES_REQUESTED on #5798 (5/6 e2e failing),
mirroring gold langgraph-python tool_rendering_agent.py:
1. Dedicated toolRenderingAgent bound to all four demo tools (get_weather,
search_flights, get_stock_price, roll_d20) + route tool-rendering and its
default/custom-catchall variants to it. Previously routed to weatherAgent,
which lacks get_stock_price/roll_d20, so the Stock, d20, and Chain pills
emitted uncallable tool calls the AI SDK dropped (no card).
2. New deterministic roll_d20 tool (echoes a scripted value) and get_stock_price
now echoes optional price_usd/change_pct — lets the fixtures script exact
values, mirroring gold's roll_d20/get_stock_price.
3. search_flights now accepts gold {origin,destination} and GENERATES the
flights list (kept lenient — still accepts a legacy {flights} array so the
D5 harness probe keeps working). Fixes the reasoning-chain flights leg that
failed input validation.
4. Re-keyed the tool-rendering + reasoning-chain fixtures off the fragile
turnIndex onto hasToolResult:false (turn-scoped in aimock >=1.37.x) and
added the missing Find-flights first leg (was 'No fixture matched' 404).
5. Weather e2e assertion -> 77%/3 (mastra's seeded getWeatherImpl for SF; a
documented divergence from gold's fixed 55/10).
Route /api/copilotkit compiles + loads cleanly; full e2e to be confirmed via
/eval d5 mastra on the harness (local next-dev verification is blocked by this
machine's 7.7GiB Docker VM OOMing on the heavy route compile).
## Summary
Supersedes #6058's two-stage approach (outer `generate_a2ui` → secondary
Python LLM call → `render_a2ui`), which severed across the prod
streaming boundary.
**Option A** (mirroring crewai-crews #6067): `injectA2UITool` defaults
to `true`, so CopilotKit's `A2UIMiddleware` injects `render_a2ui` into
`RunAgentInput.tools`. The langroid `agui_adapter.py` now merges those
injected tools into its OpenAI call, so the LLM calls `render_a2ui`
directly. The middleware intercepts the tool call stream, builds
`a2ui_operations`, and fires `RUN_FINISHED` — no secondary Python LLM
pass needed.
## Changes
- **`agent.py`**: Remove ~550 lines of two-stage A2UI infrastructure
(`generate_a2ui_via_llm`, `_a2ui_error`, `_resolve_a2ui_model`,
`_get_a2ui_llm`, `_RENDER_A2UI_FUNCTION_SPEC`, etc.). Replace
`GenerateA2UITool.handle` with a stub that logs loudly on regression
(middleware should always intercept before reaching Python).
- **`agui_adapter.py`**: Merge `run_input.tools` (AG-UI-injected) into
the OpenAI tools list so `render_a2ui` is visible to the LLM. Remove
`set_last_user_message` call (ContextVar no longer needed).
- **`route.ts`**: Remove `injectA2UITool: false`; keep
`defaultCatalogId` pin.
- **`gen-ui-declarative.json`**: Replace 9 two-stage fixtures with 4
single-stage fixtures matching `toolName: render_a2ui` + `context:
langroid`.
## Root cause of prior RED
The langroid adapter builds its OpenAI tool list from `ALL_TOOLS`
(Python-side registry) via `_get_openai_tools()`, which does NOT include
`render_a2ui`. The `A2UIMiddleware` injects `render_a2ui` into
`RunAgentInput.tools` at the AG-UI protocol level, but `agui_adapter.py`
ignored `run_input.tools` entirely — so the LLM never saw `render_a2ui`
in its tool list, never called it, and the fixture never matched.
## Red-green proof
**RED** (from main, before changes):
```
✗ d6:langroid/gen-ui-declarative red (0.0s)
state=red
0 passed, 1 failed
```
**GREEN** (after this PR's changes, rebuild from worktree):
```
✓ d6:langroid/gen-ui-declarative green (0.0s)
1 passed
✓ Tests passed for langroid:declarative-gen-ui
```
Test command: `bin/showcase test langroid:declarative-gen-ui --d6
--isolate --rebuild`
## Related
- Supersedes #6058 (two-stage approach, now reverted in this
integration)
- Mirrors #6067 (crewai-crews Option A fix, same pattern)
Supersedes #6058's two-stage approach (outer generate_a2ui → secondary Python
LLM call → render_a2ui), which severed across the prod streaming boundary.
Option A: `injectA2UITool` (default true) causes CopilotKit's A2UIMiddleware to
inject `render_a2ui` into RunAgentInput.tools. The langroid agui_adapter now
merges those injected tools into its OpenAI call so the LLM can call render_a2ui
directly. The middleware intercepts the tool call stream, builds a2ui_operations,
and fires RUN_FINISHED — no secondary Python LLM pass needed.
Changes:
- agent.py: remove ~550 lines of two-stage A2UI infrastructure (generate_a2ui_via_llm,
_a2ui_error, _resolve_a2ui_model, _get_a2ui_llm, etc.); replace GenerateA2UITool.handle
with a stub that logs loudly if middleware interception regresses
- agui_adapter.py: merge run_input.tools (AG-UI-injected) into the OpenAI tools list
so render_a2ui is visible to the LLM; remove set_last_user_message call
- route.ts: remove injectA2UITool: false; keep defaultCatalogId pin
- gen-ui-declarative.json: replace 9 two-stage fixtures with 4 single-stage fixtures
matching toolName: render_a2ui + context: langroid (mirrors crewai-crews Option A)
Mirrors the crewai-crews fix from #6067.
Remove the backend two-stage inner-LLM pattern (injectA2UITool:false +
Python-side secondary openai call) in favour of Option A: the CopilotKit
JS runtime middleware intercepts the agent's no-arg generate_a2ui toolcall
and drives the render_a2ui secondary LLM pass itself, synthesising the
tool result and firing RUN_FINISHED. Matches the just-merged crewai-crews
fix (#6067) and mirrors langgraph-python's green reference pattern.
Changes:
- route.ts: drop `injectA2UITool: false` (default true enables JS injection)
- a2ui_dynamic.py: replace complex inner-LLM body with a fail-loud stub
(no more openai/AsyncOpenAI import, no _request_context dependency,
no tools/RENDER_A2UI_TOOL_SCHEMA import)
- gen-ui-declarative.json: update _meta note + _comment fields to reflect
Option A (fixture structure was already correct for two-stage aimock
matching; outer generate_a2ui matched by context:ag2, inner render_a2ui
matched by toolName:render_a2ui)
Red→Green: D6 control-plane harness confirmed red before (state=red,
exit 1) and green after (1 passed, exit 0).
Switch the crewai-crews gen-ui-declarative cell from a broken Option B
(Python-side injection, which has no mechanism in the ag_ui_crewai adapter)
to Option A (JS-runtime-injected A2UI):
- route.ts: remove `injectA2UITool: false`; keep `defaultCatalogId` to pin
the catalog so models that omit catalogId don't get a "Catalog not found"
render error.
- declarative_gen_ui.py: replace `GenerateA2uiTool` with a no-arg
`_GenerateA2uiNoArgTool` that raises loudly if called directly (the
A2UIMiddleware should always intercept before Python).
- definitions.ts + renderers.tsx: add `DataTable` component (columns/rows
schema + `data-testid="declarative-data-table"` renderer); add missing
`data-testid="declarative-info-row"` to the `InfoRow` renderer. Both
testids are required by the D5 harness (turns 2 and 4 respectively) and
were present in the langgraph-python reference catalog but absent here.
- gen-ui-declarative.json: rewrite D6 aimock fixtures from the old broken
two-stage pattern (generate_a2ui → inner render_a2ui) to the correct
single-stage pattern (LLM calls render_a2ui directly); all four pills
now match `toolName: render_a2ui, context: crewai-crews` and return full
component trees that satisfy the harness minCounts assertions.
RED (main): "CrewAI flow failed; see server logs" on every pill —
`injectA2UITool: false` disabled the middleware; no render_a2ui tool in
the agent's tool list; aimock fixture matcher never fired; no surface.
GREEN (this branch): all 4 turns pass with assertions, 1 passed (0.0s).