The scrub and #4084 touched the same surface: #4084 re-added an `open:`
generative_ui profile listing `open-gen-ui`/`open-gen-ui-advanced`, and
re-added both features to `constrained-explicit.allowed`. Extending the
branch's scrub to both re-additions keeps the semantic consistent with
the schema (which already dropped `open` from the approaches enum).
- `showcase/shared/constraints.yaml`: drop `open-gen-ui` +
`open-gen-ui-advanced` from `constrained-explicit.allowed`; drop main's
re-added `open:` profile entirely.
- `showcase/packages/langgraph-python/manifest.yaml`: drop the now-orphan
`open-gen-ui` + `open-gen-ui-advanced` feature and demo entries
(validator confirmed they had no allowed approach left).
- Regenerated `showcase/shell/src/data/registry.json` + sibling
`shell-docs`/`shell-dojo` registries and `constraints.json` via
`pnpm --dir showcase/scripts generate-registry`. All 17 integrations
validate.
`feature-registry.json` intentionally still defines both features — the
original scrub commits (2b996c54d, 27f886e59) left it untouched, so the
demo source files on disk also stay. Follow-up deletion if desired is
out of scope for this merge.
PR #4140 placed the liveness probe inside server.mjs under the belief it
would bind "synchronously before any await." That is not how ES modules
work — all top-level `import` statements resolve before any module-body
code runs. `@langchain/langgraph-api/server` takes ~4m30s to cold-import
on Railway (graph eval + tsx transpile), but the watchdog's 180s grace +
3x30s strikes = 270s kills the container ~4s before the probe would have
bound. Kill-loop forever.
Split into:
- `src/agent/liveness.mjs` (new entry): binds :8124/ok immediately using
only `node:http`, then dynamic-imports server.mjs so the heavy langgraph
bootstrap runs AFTER :8124 is already serving 200.
- `src/agent/server.mjs`: removed the now-dead sibling-probe code; comment
block explains why.
- `src/agent/package.json`: `start` script now runs `node --import tsx
liveness.mjs` instead of `server.mjs`.
- `entrypoint.sh`: comments updated to describe the new ordering.
Local smoke (docker build + run on linux/amd64): /ok returns 200 within
1.4s of container launch vs the previous ~4m34s.
## Summary
- Rewrite starter Dockerfile templates (`typescript`, `python`, `java`,
`dotnet`) as true multi-stage: builder has dev tools, runner is minimal.
No `pip`/`npm`/`pnpm`/`tsx`/`*-cli dev` in runtime stages.
- Encode prod-mode behaviors for `claude-sdk-typescript`, `mastra`,
`langgraph-typescript` directly in the TS template + generator
conditionals (folds in the pattern from #4132 for langgraph-ts; adds
parallel migration for claude-sdk-ts + mastra).
- Regenerate all 17 starter Dockerfiles.
- Rewrite all 17 package Dockerfiles to match the template shape
(multi-stage, venv builder for Python, 3-stage Python runtime, `USER
app`, etc.).
- Cold-start instrumentation on `claude-sdk-typescript` package
(absorbed from closed PR #4133).
- Pin `platforms: linux/amd64` in the showcase deploy workflow
(`docker/build-push-action` step) to match what Railway + GHCR require.
- Template `README.md` documenting multi-stage conventions, size target,
platform mandate, and the `dockerfile_hygiene` probe.
- `generate-starters.ts` strips the `langgraph-typescript` starter's
dead `server.mjs` + its `start` script (both reference deps only needed
in the package's prod-mode path).
## Why
Railway cold starts exceed the 180s watchdog grace when the runtime
stage boots via `langgraph-cli dev` / `npx mastra dev` / `npx tsx` — the
dev-mode work (TS compile, Rollup bundle, native libsql init) blocks the
health probe. #4132 proved the fix is to move this to image build time
for langgraph-ts. This PR generalizes that shape to all TS starters that
need it and reshapes every Dockerfile in the tree so runners no longer
carry dev toolchains.
Secondary outcomes:
- Smaller runtime images (pip/npm toolchain moved to builder only).
- Consistent `--platform linux/amd64` pinning end-to-end.
- No `pnpm install --frozen-lockfile` in the TS starter template
(starters are no-lockfile by design).
- `validate-pins` ratchet re-baselined to match the regenerated spec
strings.
## Test plan
- [ ] `docker build --platform linux/amd64` green for every starter (17)
and package (17) locally
- [ ] `pnpm -C showcase/scripts` vitest: 1085/1085 pass
- [ ] `tsc --noEmit -p showcase/scripts/tsconfig.json` clean
- [ ] `oxfmt --check` + `oxlint` clean on touched TS files
- [ ] validate-pins: count 110, hash matches updated baseline
- [ ] CI Showcase Validate green on PR HEAD
- [ ] Post-merge: showcase deploy for mastra + claude-sdk-typescript
goes green (currently flapping)
Completes the NODE_ENV scope cleanup started in the prior Python
packages commit. Image-scope ENV NODE_ENV=production leaks into every
child process (agent subprocesses, healthchecks, shell scripts) —
several frameworks change behavior under NODE_ENV and should NOT see
'production' when running as an agent backend.
- mastra: Dockerfile drops image ENV; entrypoint.sh now invokes
'env NODE_ENV=production npx next start' (was relying on image ENV).
- spring-ai: Dockerfile drops image ENV; entrypoint.sh now scopes
NODE_ENV=production to the Next.js child only (Java agent never
interpreted NODE_ENV but the scoping matches every other package).
- langgraph-python entrypoint: 'npx next start' now inherits
NODE_ENV=production via 'env' wrapper (Dockerfile already removed
image ENV in prior commit).
All 11 Python runners previously executed as root. Bring them into parity
with langroid (the one Python package that already had the pattern):
- Create uid/gid 1001 'app' user/group before any COPY
- Add --chown=app:app to runner-stage COPY directives
- USER app before EXPOSE/CMD
- langgraph-python: add the missing COPY --from=frontend /app/public line
(every other Python package has it) plus an empty public/.gitkeep so
the copy resolves
Packages touched: ag2, agno, claude-sdk-python, crewai-crews, google-adk,
langgraph-fastapi, langgraph-python, llamaindex, ms-agent-python,
pydantic-ai, strands.
Package-level service claims to listen on :8000 but Railway health probes
fail. Narrow the cold-start window by timestamping each phase from
entrypoint exec to first /health-serving moment:
- entrypoint.sh: print `[entrypoint] pre-node <ISO>` immediately before
`node /app/agent_server.js`, pairing with the agent_server.ts prints
below to chain timestamps across the exec boundary.
- agent_server.ts: print `[agent_server] module loaded <ISO>` before any
side-effect imports (so import-time hangs surface distinctly from
init-time hangs), `[agent_server] pre-Anthropic <ISO>` right before
`new Anthropic(...)`, and include an ISO timestamp in the existing
app.listen() callback so we can read the delta directly.
No behavior change — only logs added. Next deploy cycle should reveal
which phase consumes the budget and whether the failure mode is
listen-but-unreachable vs. listen-never-reached.
Split the runner image into three stages:
- frontend (node:22-slim) builds Next.js artifacts
- agent-builder (python:3.12.13) builds /opt/venv from requirements.txt
- runner (python:3.12.13-slim) installs Node.js + copies artifacts, with no
pip and no build-essential
Mirrors the pattern already used by showcase/starters/strands/Dockerfile.
Split the runner image into three stages:
- frontend (node:22-slim) builds Next.js artifacts
- agent-builder (python:3.12.13) builds /opt/venv from requirements.txt
- runner (python:3.12.13-slim) installs Node.js + copies artifacts, with no
pip and no build-essential
Mirrors the pattern already used by showcase/starters/pydantic-ai/Dockerfile.
Split the runner image into three stages:
- frontend (node:22-slim) builds Next.js artifacts
- agent-builder (python:3.12.13) builds /opt/venv from requirements.txt
- runner (python:3.12.13-slim) installs Node.js + copies artifacts, with no
pip and no build-essential
Mirrors the pattern already used by showcase/starters/ms-agent-python/Dockerfile.
Split the runner image into three stages:
- frontend (node:22-slim) builds Next.js artifacts
- agent-builder (python:3.12.13) builds /opt/venv from requirements.txt
- runner (python:3.12.13-slim) installs Node.js + copies artifacts, with no
pip and no build-essential
Mirrors the pattern already used by showcase/starters/llamaindex/Dockerfile.
Split Python install into a dedicated agent-builder stage (python:3.12.13)
that produces /opt/venv, which the slim runner COPYs in. The runner no
longer runs pip install, so no compiler toolchain or build-essential bloats
the final image.
Preserves behavior: entrypoint.sh, port 10000, src/aimock_toggle.py,
shared_python/ -> /app/shared/python/ + PYTHONPATH. No image-level
NODE_ENV (entrypoint.sh scopes it to Next.js only).
Split Python install into a dedicated agent-builder stage (python:3.12.13)
that produces /opt/venv, which the slim runner COPYs in. The runner no
longer runs pip install, so no compiler toolchain or build-essential bloats
the final image.
Preserves behavior: entrypoint.sh, port 10000, ENV NODE_ENV=production,
shared_python/ -> /app/shared/python/ + PYTHONPATH.
Split Python install into a dedicated agent-builder stage (python:3.12.13)
that produces /opt/venv, which the slim runner COPYs in. The runner no
longer runs pip install, so no compiler toolchain or build-essential bloats
the final image.
Preserves behavior: entrypoint.sh, port 10000, ENV NODE_ENV=production,
shared_python/ -> /app/shared/python/ + PYTHONPATH.
Split Python install into a dedicated agent-builder stage (python:3.12.13)
that produces /opt/venv, which the slim runner COPYs in. The runner no
longer runs pip install, so no compiler toolchain or build-essential bloats
the final image.
Preserves behavior: entrypoint.sh, port 10000, ENV NODE_ENV=production,
shared_python/ -> /app/shared/python/ + PYTHONPATH.
PR #4132 migrated away from `langgraph-cli dev` to the @langchain/langgraph-api
`startServer` Hono server. That server does bind `/ok` via its meta routes, but
only AFTER `registerFromEnv` imports the graph and spawns the N workers — on
Railway that window has been exceeding the watchdog's 180s startup grace plus
the 90s strike budget, producing an infinite restart loop (deploy ddc88ef9 is
currently flapping ~every 4m30s).
Move liveness off the heavy Hono path entirely:
- server.mjs now spins up a bare node:http server on port 8124 (configurable
via HEALTH_PORT) *synchronously before any await*, answering GET /ok with
{"status":"ok"}. Always live within ms of `node` boot.
- entrypoint.sh watchdog now probes 127.0.0.1:8124/ok instead of :8123/ok.
The liveness port is independent of whether :8123 has finished graph import,
so the probe reflects what the watchdog actually cares about: is the Node
process alive and its event loop responsive. A true hang (event loop pinned)
still fails the probe and triggers container restart, so the existing
hang-detection behavior is preserved.
Local Docker smoke (amd64): curl /ok returned 200 within 20s of container
start; LangGraph API logged "listening on 0.0.0.0:8123" ~15s later as normal.
Root cause of the 4+ day langgraph-typescript Railway flap: `npx @langchain/langgraph-cli dev` is a dev-mode CLI that compiles TypeScript on-demand per HTTP request, blocking the single-thread event loop ~5s per /schemas call. On Railway, Studio IPC handshake + langgraph-api JIT spawn + graph compile routinely exceeded the 90s watchdog budget; PR #4123's 180s grace papered over cold-start but didn't fix steady-state event-loop blocking.
Replace the CLI with a direct import of startServer from @langchain/langgraph-api/server (the same Hono server dev wraps, minus the tsx watcher, Studio handshake, and LangSmith tenant lookup). Schema extraction runs in a worker_thread and is pre-warmed in parallel with server startup.
Container-tested on node:22-slim:
- /ok live in <3s (vs. 5-30s dev)
- /ok steady-state <10ms
- /assistants/search returns the registered graph
- Pre-warm log: [server] Pre-warmed schemas in ~1.3-3.4s
Caveats:
- Uses --legacy-peer-deps for @langchain/langgraph-sdk version skew.
- @langchain/langgraph-api exports ./server but it's not officially public API — exact version pin (1.1.17) required.
- /runs streaming not end-to-end tested (dummy key); /assistants/search and schema extraction verified.
Two more starters are restart-looping with the same pattern #4123 fixed
for langgraph-*: the 90s (3-strike) watchdog budget is shorter than the
cold-start path on a fresh Railway container, so the agent gets killed
before it ever reports healthy, and the loop never breaks.
Railway evidence:
- claude-sdk-typescript (package, :8000): restart-looping since 04-20
16:54 UTC. Runs compiled `node /app/agent_server.js` which spins up
the full @anthropic-ai/claude-agent-sdk. Package entrypoint is hand-
written (not generator-emitted), so add the same grace block inline
matching #4123's shape.
- mastra (starter, :8123/api): restart-looping since 04-20 18:18 UTC.
Runs `mastra dev` on :8123 alongside Next.js on ${PORT:-10000}.
`mastra dev` performs a tsx build + Mastra server boot on first
request — legitimate supervised process, grace is the right fix.
Package-level mastra has no watchdog (the entire package IS the Next.js
app; no separate agent to watchdog) — PR #4116's classification of
"N/A" was correct for the package. The starter has a separate Mastra
dev server, so the generator-emitted watchdog is legitimate there.
Per-framework grace (not universal) preserves the #4123 design: uvicorn
and express agents are responsive within the 2-3s sleep before
AGENT_HEALTH_CHECK, so adding grace elsewhere would only delay
legitimate restart on true hangs.
This commit covers the generator mapping + the hand-written package
entrypoint. The regenerated starter entrypoints land in the next commit.
PR #4116 introduced a generalized silent-hang watchdog that polls the
agent health endpoint every 30s and kills the agent after 3 consecutive
failures (~90s). This worked for fast-starting agents (uvicorn,
express) but regressed langgraph-typescript on Railway: `langgraph-cli
dev` does a heavy cold-start (Studio IPC setup + @langchain/langgraph-api
JIT spawn + graph compile) that routinely exceeds 90s on a fresh
container, so the watchdog was killing the process before /ok ever
became reachable — producing the 04-20 17:05 UTC restart loop on
deployment 58bbebe8-7a94-4f99-b6e4-ffcbb4eb78b9.
Phase 1 verification:
- `@langchain/langgraph-cli@1.1.17/dist/cli/up.mjs:27` uses /ok as
its own health probe, and `@langchain/langgraph-api@1.1.17/dist/
api/meta.mjs:64` registers `api.get("/ok", ...)` — so the watchdog
path is correct. The regression is purely timing.
- Railway logs show no successful /ok probe before kill-loop starts.
Fix (Option B — startup grace): add a per-framework startup-grace
window in getWatchdogGraceSeconds(). For langgraph-* starters, the
watchdog now waits up to 180s for the first healthy /ok probe before
arming the strike counter. If /ok comes up sooner, fall through
immediately. If 180s elapses without success, arm the counter anyway —
the steady-state watchdog will then handle a true hang on the normal
90s schedule.
Non-langgraph starters (crewai-crews, ag2, agno, etc.) have grace=0 and
behave exactly as before PR #4116 — the 2–3s `sleep` before the health
check is sufficient for uvicorn-based agents.
Files changed:
- showcase/scripts/generate-starters.ts — new getWatchdogGraceSeconds()
+ grace block in getWatchdogBlock()
- showcase/starters/*/entrypoint.sh — regenerated (grace block for
langgraph-*, comment-only for all others)
- showcase/packages/langgraph-typescript/entrypoint.sh — hand-edited
grace block (this package uses its own hand-edited entrypoint, not
the template)
- showcase/packages/langgraph-fastapi/entrypoint.sh — same
- showcase/packages/langgraph-python/entrypoint.sh — same
Not a revert of #4116: the silent-hang vulnerability class remains
covered (the watchdog still kills after 90s of steady-state failures;
the grace only defers the first strike).
Validated:
- bash -n on all 19 edited entrypoint.sh files
- 1079/1079 showcase-scripts vitest pass
Hand-applied watchdog + unbuffered-stdout block for every Bucket-B package
entrypoint where the shape couldn't come from the starter generator:
* ag2, agno, claude-sdk-python, claude-sdk-typescript, google-adk,
langgraph-fastapi, langgraph-python, langgraph-typescript, langroid,
llamaindex, ms-agent-dotnet, ms-agent-python, pydantic-ai, strands
-> watchdog polls the framework-native health endpoint; Python variants
also get `python -u` + awk fflush log prefixing.
* spring-ai (Bucket C) -> watchdog extends the existing startup probe;
polls :8000/health every 30s and kills PID on 3 consecutive failures.
Added WATCHDOG_PID cleanup to the post-wait block.
* mastra is single-`exec next start` on the package side — no agent
process to probe, so no watchdog needed (N/A).
Reference shape: showcase/packages/crewai-crews/entrypoint.sh (unchanged
from origin/main; proven in production via PRs #4114 + #4115).
Root-cause fix for the 04-21 silent-hang incident on the crewai-crews
Railway deploy. Three tightly-coupled changes:
1. Bump ag-ui-crewai pin from `>=0.1.4,<0.1.6` to `>=0.2.0,<0.3.0`.
0.1.5 had three defects that wedged the agent: unguarded
`source.state.messages` access, an orphan `asyncio.create_task` with
no cancel ref, and a sync `completion()` call that pinned the event
loop. All three are fixed in ag-ui PR #1550, released as 0.2.0 on
2026-04-18. Our upper ceiling was blocking the fix.
2. Remove the pre-bind LLM crash hardening shim in `agent_server.py`.
The shim monkey-patched `crewai.cli.crew_chat.generate_*_description_with_ai`
to static strings so that `ChatWithCrewFlow.__init__` — which
ag-ui-crewai <= 0.1.5 invoked at endpoint-registration time, BEFORE
uvicorn bound its port — could not crash the process before the HTTP
server was listening. 0.2.0 defers `ChatWithCrewFlow` construction to
first request via a module-scoped `_cached_flow` + `asyncio.Lock`
inside `add_crewai_crew_fastapi_endpoint`. Any LLM hiccup now
surfaces as a 5xx on the first request instead of a startup crash,
which is what the shim was reaching for. The shim is dead code on
0.2.0 and has been removed (with `logging` import dropped as it was
only used by the shim).
3. Add `python -u` to the uvicorn invocation in `entrypoint.sh` as a
belt-and-suspenders complement to the existing `PYTHONUNBUFFERED=1`
export. The env var can in principle be un-exported by a child;
`-u` forces unbuffered stdout/stderr at the interpreter level and
is not overridable by user code. Combined with `awk '{...; fflush()}'`
in the pipe (already in place), this guarantees uvicorn request
lines reach Railway's log stream line-at-a-time. During the 04-21
incident Railway saw only ~15 log lines over 9h of uptime because
of buffering through a previous `sed` formulation.
Also updates `showcase/scripts/fail-baseline.json`'s `validatePinsFailHash`
to match the new `ag-ui-crewai` spec string. Pin-drift FAIL count is
unchanged (110); the hash changed only because the `ag-ui-crewai` line
in the FAIL set went from `>=0.1.4,<0.1.6` to `>=0.2.0,<0.3.0`.
Verified locally:
- `pip install -r requirements.txt` resolves `ag-ui-crewai-0.2.0` cleanly.
- `python -u -m uvicorn agent_server:app` starts; `/health` returns
200 `{"status":"ok"}`; request lines appear in real-time logs.
- `pytest tests/python/` — 94/94 pass.
- `pnpm -C showcase/scripts test` (vitest) — 1079/1079 pass.
- `validate-pins.ts` — count=110 matches baseline; hash updated.
Upstream refs:
- crewAI issue: https://github.com/crewAIInc/crewAI/issues/5510
- ag-ui PR #1550: https://github.com/ag-ui-protocol/ag-ui/pull/1550
Intentionally NOT in this PR:
- `showcase/starters/crewai-crews/` parity backport (the starter still
carries the 0.1.5 pin and the shim).
- The 14-starter watchdog generalisation.
Both belong to the silent-hang vulnerability-class work tracked
separately.
Production deploy of showcase-crewai-crews was stuck in agent:"down" for
~4-5h on 2026-04-21. Railway deployment logs show the FastAPI agent on
:8000 was reachable at startup and handled hundreds of requests, then
stopped responding at ~06:56-07:01 UTC after an
unhandledRejection: UND_ERR_BODY_TIMEOUT from the Next.js runtime. The
Python process did not exit — it hung — so bash's wait -n never fired,
the container never restarted, and every subsequent request stacked up
on undici's default 5-minute headers timeout (UND_ERR_HEADERS_TIMEOUT).
Two surgical fixes:
1. entrypoint.sh: add a watchdog goroutine that polls
http://127.0.0.1:8000/health every 30s with a 5s curl timeout. After
3 consecutive failures (~90s unreachable) it SIGKILLs the agent PID,
causing wait -n to return and the container to restart. This converts
a silent agent hang into a fast, loggable restart. Also adds structured
[entrypoint] / [agent] / [nextjs] / [watchdog] log prefixes, a startup
PID-is-alive check, and PYTHONUNBUFFERED=1 so crashes surface before
the log pipe closes — mirroring the starter entrypoint pattern.
2. src/app/api/smoke/route.ts: split the upstream fetch timeout
(UPSTREAM_TIMEOUT_MS = 25_000) from the route's overall budget
(maxDuration = 60). Previously the inner fetch had AbortSignal.timeout(45000)
which, on a hung agent, burned nearly all of the 60s before Next.js
killed the request — producing HTTP 000 at the caller instead of a
structured stage: "timeout" JSON response. With the shorter inner
timeout the route can report a clean timeout within ~25s even when
the agent is wedged.
Both fixes are defensive against upstream CrewAI ChatWithCrewFlow
deadlocks (which the logs show happening repeatedly via the pre-existing
"Cannot send 'RUN_FINISHED' while steps are still active" errors). The
watchdog is scoped to this package only; the starter already has a
richer entrypoint with similar intent.
Post-merge CI caught two regressions:
1. shared-state-read-write demo directories were on disk but never
committed — 16 packages' manifests reference the demo but the
page.tsx scaffolds weren't tracked. This failed validate-parity,
bundle-demo-content tests, and drift-check transitively.
2. langgraph-python starter templates drifted after main added
docstring region markers to weather-tool-backend; regenerating
via `npx tsx generate-starters.ts` syncs the 17 starters.
Bundle (showcase/shell/src/data/demo-content.json) regenerated to
pick up the new demo content.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the aspirational 185-line prompt (read_me preflight, multi-call
iteration, palette discipline, zone backgrounds, label/fixedPoint rules,
opening+mid+final cameraUpdates) in favor of a ~35-line shape that biases
the agent toward exactly one create_view call with 3-5 elements and a
single framing cameraUpdate. Downgrades gpt-4.1 → gpt-4o-mini since the
element schema is simple JSON and model latency dominates end-to-end time.
Measured end-to-end (agent/run SSE to RUN_FINISHED) on the showcase
runtime: 5-6s per prompt across two prompts × two runs, all producing a
single create_view call with 8 elements (clean 3-step flowchart;
client/server/db diagram). Previously 3-5 minutes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restructure the headless-interrupt cell so the app surface is empty by
default and the popup appears there (not inside the chat or as a global
modal) when the backend calls `interrupt()`. The user triggers the run
from a pre-built suggestion chip in the right-hand `CopilotChat`, which
mirrors the other in-app-action demos (hitl-in-app, gen-ui-interrupt).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reimplements the approach from the now-closed PR #4093 (which I closed
after #4095 merged the agno 2.5.17 upgrade, on the belief that the SDK
bump had fixed the underlying SSE termination bug).
Follow-up evidence collected 2026-04-19 showed the SDK upgrade only
addressed the error path — production /api/smoke with real API keys
still hangs because the agno SDK does not reliably emit TEXT_MESSAGE_END
/ RUN_FINISHED on the success path. Documented in Notion page
3473aa381852815b9765fd28fa53275a, item #5.
Change: instead of `await res.text()` (which only resolves when the
server closes the stream), read res.body incrementally with
getReader() + TextDecoder. Short-circuit as soon as the buffer contains
`"type":"TEXT_MESSAGE_CONTENT"` with `"OK"` — the agent has produced
the expected output, so we cancel the reader and return a 200 success
envelope without awaiting a terminal event that may never arrive. If
the stream closes cleanly without confirmation, return a diagnostic
502 with stage="response_incomplete" so regressions surface clearly.
Keep the existing 45s AbortSignal timeout as a final safety net.
Response envelope shape is preserved for the smoke monitor parser.
This is a client-side mitigation, not a fix. The real fix belongs
upstream in the agno SDK's AG-UI interface. Keep this PR open until
upstream is addressed and verified in production with real keys.
Rewrites the mcp-apps agent system prompt from a minimal "call a tool and
populate the canvas" instruction into a detailed Excalidraw-aware
playbook. Adoptions:
- Workflow: instructs the agent to call `read_me` first (the Excalidraw
MCP server's own format reference) before `create_view`, and to use
`restoreCheckpoint` for follow-up edits.
- Layout principles: left-to-right vs. top-down vs. hub-and-spoke,
3-7 node target, consistent sizing (160x70 rectangles, 120x80
diamonds) and spacing (40-80px gutters, 60-120px viewport padding).
- Camera framing: opening `cameraUpdate` with a 4:3 size (400x300 →
1600x1200), optional mid-diagram pans, and a REQUIRED final
cameraUpdate that re-frames the whole diagram (otherwise the Excalidraw
animation camera ends zoomed on the last-drawn element).
- Labels: prefer `label: {text, fontSize}` on shapes/arrows (auto-centers,
auto-resizes) over bare `text:` fields (which Excalidraw silently
ignores on rectangles).
- Arrow bindings via `fixedPoint` edge anchors rather than hand-placed
coordinates; dashed stroke for async/optional paths; short label
annotations on decision transitions.
- Palette discipline: draws from the read_me palette (pastel fills +
primary strokes), caps at 3-4 background colors per diagram, assigns
them semantically (blue=frontend, green=success, yellow=decision,
red=error).
- Progressive emission order so the draw-on animation tells a story
(zone → shape → its label → its outgoing arrows → next shape), with
decorations last.
Also upgrades the model from gpt-4o-mini to gpt-4.1 because Excalidraw
element emission is structured/coordinate-heavy and a smarter model
produces visibly cleaner layouts (aligned nodes, edge-bound arrows,
labeled shapes) in practice.
Before: 3 colored boxes with no text (bare `text:` field silently
dropped), arrows ending on shape centers, no camera framing, no title.
After: 3 labeled boxes with consistent palette, edge-bound arrows with
transition labels, title text, cameraUpdate framing the diagram, and
the agent correctly consults `read_me` once per conversation for the
authoritative format reference.
Progressive streaming (Part 2 of the ask) was investigated and flagged
as shared-package-bound: `@ag-ui/mcp-apps-middleware` emits a single
`ACTIVITY_SNAPSHOT` only AFTER `RUN_FINISHED`, so there are no partial
tool-input events to consume in the cell. The Excalidraw MCP server does
animate elements in order once the full input arrives, which gives the
visual impression of progressive drawing without runtime changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the generic greeting-card / Pomodoro / revenue-chart suggestions
on the Fully Open-Ended Generative UI (minimal) cell with a set tuned for
intricate, educational visualisations: 3D airplane pitch/yaw/roll, a
feed-forward neural network's forward pass, quicksort, and a Fourier
series square-wave build-up.
Uses the provider's `openGenerativeUI.designSkill` prop (which injects
agent context for the built-in `generateSandboxedUi` tool) to swap in a
visualisation-tuned authoring prompt in place of the default shadcn
design language — covering SVG/canvas geometry, CSS keyframe motion,
mandatory labels + legends + callouts, a semantic palette (indigo /
emerald / amber / rose / slate), typography scale, and the self-running
constraint for the minimal cell. Agent system prompt updated in parallel
so the LLM knows the cell's focus and contract up-front.
Advanced variant untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The per-kickoff `agent.setMessages([])` call was racing with the
subsequent `addMessage` + `runAgent` pair, causing the second run
attempt to silently not dispatch (connection-refused on resume in some
timings). Drop the reset — rely on the fresh langgraph thread that each
`runAgent` implicitly produces, and on the existing `runId` marker to
gate ResultCard visibility so the stale result doesn't flash under the
button mid-run.
Validated: initial → modal → result → re-click → modal appears again,
all without a page refresh.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`create_deep_agent` does not accept `state_schema=` as a kwarg (confirmed
against the installed signature; state schema is only pluggable via
middleware). The minimal state-extending middleware is kept but renamed
to `_GenUiStateMiddleware` with a comment pinning its one purpose, and
`CopilotKitMiddleware` is added so this cell matches every other
langgraph-python cell.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The sandboxed iframe runs with `sandbox="allow-scripts"` only, so a
`<form>` with `<button type="submit">` had its submit blocked by the
browser BEFORE the onsubmit handler could call `e.preventDefault()` —
which meant `Websandbox.connection.remote.evaluateExpression(...)`
never fired. The agent also read `res.result` while the host handler
returns `{ ok, value }`.
Tightened the system prompt and per-suggestion prompts to explicitly
forbid `<form>` / `type="submit"` and to steer the LLM toward the
handler's exact return shape (`res.value` / `res.error`). Validated
end-to-end via Playwright: both `evaluateExpression` and `notifyHost`
now round-trip with visible results in the sandbox UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The PieChart + BarChart renderers were added in 8be81821e as part of the
brand-polish pass, but the a2ui_dynamic agent's system prompt was still
only mentioning dashboards/status/KPIs. Expand the prompt to enumerate
the registered catalog (Card, StatusBadge, Metric, InfoRow, PrimaryButton,
PieChart, BarChart) and name when each chart fits — so the LLM reliably
reaches for PieChart on part-of-whole breakdowns and BarChart on
cross-category comparisons.
Validated on port 4100 with Playwright: "Show a pie chart of sales by
region" renders a brand-coloured donut with legend + percentages, and
"Render a bar chart of quarterly revenue" renders a polished Recharts bar
chart with tooltip. No "Unknown component" errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Apply the verified CopilotKit brand palette (lilac #BEC2FF, mint #85ECCE,
near-black #010507, neutral greys) across every custom-defined component in
the langgraph-python cells. Replaces ad-hoc indigo/purple/amber/slate
Tailwind classes with brand tokens, softens corners (rounded-xl/2xl),
switches shadow-lg to shadow-sm, and normalises pill/label styling to
10-11px uppercase tracking-wider.
Preserves all component props, data-testid attributes, and @region markers.
Beautiful-chat (reference) is untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gen-ui-interrupt and hitl-in-chat both render an interactive time-picker
inline in the chat, but via different API layers:
- gen-ui-interrupt -> useInterrupt (low-level primitive)
- hitl-in-chat -> useHumanInTheLoop (ergonomic wrapper)
Rename to make the dashboard row label reflect the API distinction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrites the gen-ui-agent demo as a minimal deepagents-powered agent that
defines its own `GenUiAgentState` schema (extending langchain's AgentState)
and exposes a custom `set_steps` tool. The tool returns Command(update=...)
to mutate the typed `steps: list[Step]` field, and a one-line middleware
registers the extended schema with deepagents. A last-wins reducer on
`steps` tolerates concurrent tool calls.
No StateGraph, no copilotkit_emit_state, no TodoListMiddleware - the pattern
mirrors how real applications extend deepagents with domain state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the synthetic _ReasoningEmitterModel + hand-rolled StateGraph
scaffolding in reasoning_agent.py and tool_rendering_reasoning_chain_agent.py
with minimal create_deep_agent graphs (mirrors gen_ui_agent). Drops ~340
lines of custom graph wrappers / synthetic reasoning emitters while
keeping the four mock tools (get_weather, search_flights, get_stock_price,
roll_dice) intact for the tool-rendering-reasoning-chain cell.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prior 4085 port blindly copied 4084 docs-links.json patterns, producing many
entries that pointed at wrong-framework docs (e.g. crewai-crews → crewai-flows),
nonexistent shell paths (e.g. /claude-sdk-typescript/... prefix doubled into
/claude-sdk-typescript/claude-sdk-typescript/...), or the wrong field name
(mastra used `shell_docs_url` with absolute http://localhost:3000 URLs).
Audit against live docs.copilotkit.ai and the 4085 shell at :4010 surfaced:
- 9 og_docs_url 404s (8 on claude-sdk-typescript under a nonexistent
/claude-sdk-typescript/* slug, 1 on /ag2/multi-agent/subagents)
- 24 shell_docs_path 404s (8 claude-sdk-typescript, 6 pydantic-ai, 4 strands,
4 mastra, 2 langgraph-python)
- crewai-crews pointing entirely at /crewai-flows/... (different product)
Fix: point each entry at a real docs.copilotkit.ai page scoped to the correct
framework OR null it out; normalize shell_docs_path to leading-slash form
that composes with the DocsRow `${shellUrl}/${slug}/unselected${path}` scheme;
null google-adk shell paths (no google-adk-scoped shell docs exist, better to
show ✗ than mislead). Regenerated registry + docs-status bundles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>