Bundles the CopilotKit-side work from **OSS-769**, **OSS-767** (partial), and the unshipped remainder of **OSS-609**. ## OSS-769 — `useCoAgent().nodeName` never updates `useAgentNodeName` tracked the current node in a ref and returned `nodeNameRef.current`. Mutating a ref schedules no render, so a component reading `useCoAgent().nodeName` kept showing whichever node was current at its last render and never updated on its own — it only appeared to work when something unrelated happened to re-render it. Backing the value with state fixes it. Five lines, no API change. ## OSS-767 (partial) — silent content-part drops (#1748) `normalizeMessageContent` handles only `text` and `binary` parts; anything else — the `{"type": "image", ...}` case from the report — maps to `""` and is filtered out with no signal, so an agent emitting structured content sees its output vanish silently. This makes the drop visible, once per unrecognised part type so streaming doesn't flood the log. Deliberately **not** the schema change: carrying structured assistant content needs an `AssistantMessage` decision upstream in `ag-ui`, which stays open on OSS-767. ## OSS-609 — the five docs gaps that never shipped Gap #2 shipped in #6403; gaps 1, 3, 4, 5 and 6 did not. | Gap | Where | Closes | | --- | --- | --- | | AWS Lambda self-hosting | `docs/deploy/aws-lambda.mdx` | #1151 | | Per-user thread authorization | `docs/auth.mdx` (new section) | #2241 | | Thread persistence without the platform | `docs/threads-self-managed.mdx` | #6090 | | DIY guardrails / DLP | `docs/integrations/langgraph/guardrails.mdx` | #3414 | | When you need an MCP App | `docs/agentic-protocols/mcp.mdx` (new section) | #5991 | The Lambda guide leads with the constraint that actually bites — streaming is opt-in on every front door, so a chat runtime deployed with the defaults appears to hang for the whole run and then dumps the reply at once. It documents the Function URL + `RESPONSE_STREAM` path as the default, and API Gateway REST + `responseTransferMode: STREAM` for anyone who needs a REST API in front. `threads-self-managed` follows the existing shared-snippet pattern with per-framework wrappers, because two nav contracts require it: every authored framework must publish the page, and every React destination must map to an Angular one (`ANGULAR_DOC_REDIRECTS`). All nine wrappers were confirmed necessary by deleting one and watching the suite fail. ## Review corrections Two blockers from @MikeRyanDev, both verified against primary sources before changing anything. **API Gateway REST APIs can stream** ([`91b3e632d5`](https://github.com/CopilotKit/CopilotKit/commit/91b3e632d5)). The guide was built on the pre-November-2025 limitation and claimed no API Gateway type supports response streaming, steering readers to a buffered `serverless-http` setup. REST gained it via `responseTransferMode: STREAM`, which also lifts the 10 MB cap and 29-second timeout. REST and HTTP are now split; REST is documented as a streaming front door (payload-format-1.0 event adapter, `AWS_PROXY` integration on the `2021-11-15/.../response-streaming-invocations` URI, CLI/CDK/SAM config), and the buffered fallback is scoped to HTTP APIs and ALB, which still have no streaming path. Added the constraints that actually matter for chat: the 30-second idle timeout on edge-optimized endpoints (5 min Regional), and the console Test tab always buffering so a working config looks broken. **`identifyUser` is the platform's thread-scoping binding** ([`91b3e632d5`](https://github.com/CopilotKit/CopilotKit/commit/91b3e632d5)). The section told every reader to build an ownership table and enforce it in `onBeforeHandler`. On the Intelligence path the runtime already resolves `identifyUser(request)` server-side and carries that id to the platform; `listThreads` is scoped by user *and* filtered by `agentId`, so the "every user of one project sees that project's threads" claim was wrong. `identifyUser` is now documented as the binding, and the DIY pattern is scoped to SSE runtimes, custom stores, and the local in-memory runner. **Follow-up correction — two routes are genuinely unscoped** ([`240672ff56`](https://github.com/CopilotKit/CopilotKit/commit/240672ff56)). My rewrite then over-claimed. `handleGetThreadEvents` and `handleGetThreadState` resolve the caller and discard it, and the platform client takes no `userId` on either method (`client.ts:1113`/`1135`) — unlike `getThreadMessages` at `1063`. Both hit project-authenticated `_inspect` endpoints, so any caller `identifyUser` accepts can read the event log and agent state of **any thread in the project** given its id. The blanket guarantee is replaced by a per-route table marking those two explicitly unscoped, plus an `onBeforeHandler` guard narrowed to them. That is a live gap in shipped runtime code, not a docs error, and it is tracked as **OSS-851** — a platform-side `_inspect` change plus matching runtime/client work and tests, out of scope for a docs PR. The interim callout in `auth.mdx` comes out when OSS-851 lands. ## Not in this PR **OSS-772** and **OSS-773** are already merged in `oss-path-to-production` (#237, #238). Both are telemetry-sink changes with no CopilotKit-side component. OSS-773's remaining half — re-keying runtime `distinct_id` from email to the Clerk subject — is recorded on the ticket as an open decision, not a task. ## Testing **OSS-769.** New `use-agent-nodename.test.tsx`, 5 tests. Against unmodified `origin/main`, **4 of 5 fail**: ``` × re-renders consumers on every node transition × reports 'end' when a run errors × resets to 'start' when a new run begins ✓ unsubscribes on unmount × carries the agent, thread, and current node Tests 4 failed | 1 passed (5) ``` With the fix: `Tests 5 passed (5)`. These assert only re-render behaviour under normal `act()` flushing — no manufactured intra-batch window. **Full react-core suite:** `Tests 7 failed | 1496 passed (1503)`. All 7 failures are **pre-existing** `ResizeObserver is not a constructor` under jsdom, confined to `CopilotChatView.pinToSend` and `use-pin-to-send` — neither of which this PR touches. **Typecheck:** `packages/react-core` → `tsc --noEmit` exit 0, no output. **OSS-767:** 3 new tests covering the warn, warn-once-per-type, and no-warn-for-supported-types. `src/graphql/message-conversion/` → `Tests 125 passed (125)`. **Docs:** `showcase/shell-docs` → `Tests 1 failed | 373 passed (374)`. The single failure (`channels-docs > publishes the Channels overview only through provider navigation`) is **pre-existing**; baselining with all changes stashed reproduces it and nothing else. Re-run unchanged after both review-correction commits. **Review corrections.** The AWS rewrite was checked against the AWS sources rather than written from memory — the REST streaming announcement, `configuration-response-streaming`, `response-transfer-mode` (endpoint-type idle timeouts, unsupported buffered-only features), `response-streaming-lambda-configure` (CLI/OpenAPI shapes), the CFN `Integration` reference, and the CDK `ResponseTransferMode` enum. Two details corrected in passing: `InvokeWithResponseStream` authorizes against plain `lambda:InvokeFunction` (no new grant, contrary to what the streaming URI suggests), and ALB still has no Lambda streaming path. The auth corrections were verified by reading the handlers and the platform client, not the tests — `resolve-intelligence-user.ts`, `intelligence/threads.ts` (every `resolveIntelligenceUser` call site), and `intelligence-platform/client.ts`. The existing tests assert the `threadId`-only call shape, so they pass under the defect and could not have surfaced it. Also corrected: there is no `threads/delete` route — delete is `DELETE` on `threads/update` (`fetch-handler.ts:606`). Both edited pages MDX-compile clean, and all inbound `#thread-authorization` anchors still resolve after the h3→h4 demotions. Two nav tests broke during this work and are fixed rather than papered over — adding a page to the Rich Threads group violated the cross-framework ordering contract and the React→Angular parity contract: ``` src/lib/__tests__/docs-render.test.ts src/lib/__tests__/angular-docs-content.test.ts Test Files 2 passed (2) Tests 33 passed (33) ``` All 15 internal links in the new pages resolve against the content tree. Closes #1151, #2241, #3414, #5991, #6090 Refs #1748, OSS-851
Showcase Platform
Tagline: agent entry point for the showcase docs tree and from-scratch local setup. The fanout block below routes you to the right procedural doc.
Per-framework demos of CopilotKit (LangGraph, CrewAI, Mastra, Claude Agent SDK,
etc.). Each package is a Next.js frontend + agent backend bundled in a Docker
image. Railway deploys those images from main on push.
Agent Fanout — when X, see Y
| When you need to... | Read |
|---|---|
Turn a red cell green (cell red→green SOP, bin/showcase test CLI) |
./TESTING.md |
| Debug a failure mode locally (debugging loop, strategies, prod ops) | ./DEBUGGING.md |
Look up a framework / fixture / --isolate edge case |
./GOTCHAS.md |
| Add a brand-new integration (per-package + external setup) | ./INTEGRATION-CHECKLIST.md |
| Style a demo page (Tailwind v4, CopilotKit overrides, layout patterns) | ./STYLING-GUIDE.md |
| Reason about which shell renders what / consolidate a new frontend | ./FRONTEND-STRATEGY.md |
| Deploy / promote / pin / roll back a Railway service | ./RAILWAY.md (fleet config) + ./bin/README.md (bin/railway CLI) |
| Understand aimock fixture semantics (fixtures + Railway reconstruction) | ./aimock/README.md + ./aimock/RAILWAY.md |
| Operate showcase-harness (alerts, probes, hot reload, build/deploy) | ./harness/README.md + ./harness/docs/rotation-drill.md |
| Track or check per-slug deviations from canonical | ./integrations/<slug>/PARITY_NOTES.md |
Anything below is from-scratch local setup — skip if your stack is already up.
Layout
showcase/
bin/showcase # unified CLI — run showcase/bin/showcase <command> for help
bin/railway # Ruby tool for Railway ops (snapshot/promote/pin) — see bin/README.md
integrations/<slug>/ # one per framework (17 total) — Dockerfile, src/app/demos/*/, src/agents/ or equivalent
shell/ # hub: home page, /matrix, canonical /integrations/[slug]/[demo]/{preview,code}
shell-dashboard/ # internal-only feature × integration grid (port 3002)
harness/ # showcase-harness service — see harness/README.md
aimock/ # aimock fixtures + Railway config — see aimock/README.md
shared/
feature-registry.json # canonical features + categories (feeds the grid rows)
constraints.yaml # allowlist for which demos a package can expose
local-ports.json # deterministic host ports per package for local Docker runs
python/ typescript/tools/ # shared agent utility code; CI stages these into each build context
scripts/
dev-local.sh # low-level Docker Compose wrapper (prefer bin/showcase)
cli/ # command modules for bin/showcase
generate-registry.ts # builds shell/src/data/registry.json from all manifest.yaml
bundle-demo-content.ts # bundles per-demo source + README into shell/src/data/demo-content.json
docker-compose.local.yml # one service per package; ports from local-ports.json; env from .env
.env.example # commit template — copy to .env and fill in
Generated data files
The shell apps consume JSON data files that are generated at build time by
scripts in scripts/. These files are gitignored — every build path (Docker,
CI, npm run build, npm run dev) regenerates them automatically.
| File | Generator | Shell apps | What it does |
|---|---|---|---|
registry.json |
generate-registry.ts |
shell, shell-docs, shell-dojo, shell-dashboard | Integration manifest — scans integrations/*/manifest.yaml, builds the full catalog with metadata, feature flags, categories |
demo-content.json |
bundle-demo-content.ts |
shell, shell-docs, shell-dojo | Bundled source code from every demo directory — powers the Code tab, Snippet components, dojo cell viewer |
constraints.json |
generate-registry.ts |
shell | Filter facets for the integration explorer (categories, frameworks, features) |
search-index.json |
generate-search-index.ts |
shell, shell-docs | Cmd-K search entries — scans MDX docs, AG-UI content, and registry data |
starter-content.json |
bundle-starter-content.ts |
shell | Starter template source bundles for the "Get Started" code viewer |
docs-status.json |
probe-docs.ts |
shell-dashboard | Per-feature docs reachability — HTTP HEAD on og_docs_url, file-exists check on shell-docs MDX |
Each generator writes to the src/data/ directory of every shell app that
consumes it. Shell apps are independent — no shell cross-imports another
shell's data directory.
Prerequisites
- macOS or Linux
- Homebrew
- Docker engine. Any of: Docker Desktop, Colima (recommended, no GUI / no sign-in), or OrbStack.
- Node 22+ and npm (for
shell/shell-dashboarddev servers — they're not in the compose)
Colima install (one time)
brew install colima docker docker-buildx docker-compose
# Tell the docker CLI where its plugins live
mkdir -p ~/.docker
cat > ~/.docker/config.json <<'JSON'
{
"cliPluginsExtraDirs": ["/opt/homebrew/lib/docker/cli-plugins"]
}
JSON
# Start the engine (adjust resources to taste; needed for building 17 images)
colima start --cpu 4 --memory 8 --disk 60
# Verify
docker compose version
Colima auto-starts with brew services start colima if you want it on login.
API keys
One .env file feeds every container. Not committed.
cp showcase/.env.example showcase/.env
# Edit showcase/.env and fill in:
# OPENAI_API_KEY=<required>
# ANTHROPIC_API_KEY=<optional; needed for Claude Agent SDK demos and a few others>
# LANGSMITH_API_KEY=<optional; enables LangSmith tracing for LangGraph demos>
Only OPENAI_API_KEY is strictly required. Missing optional keys fail
gracefully (per-package).
bin/showcase CLI — quick reference
For the full invocation table (control-plane vs --direct, per-demo scoping
matrix) and the cell red→green SOP, see
./TESTING.md. For
debugging workflows (aimock rebuild cycles, fixture validation, probe testing,
diagnostics), see ./DEBUGGING.md.
# from any directory — paths resolved relative to the script itself
./showcase/bin/showcase up langgraph-python # start infra + one integration
./showcase/bin/showcase test langgraph-python --d5 --isolate # run D5 probes (canonical)
./showcase/bin/showcase down # tear down
| Command | Description |
|---|---|
test <slug> |
Run probes against a running service (see TESTING.md for full flag table) |
up [slugs...] |
Start infra (aimock, pocketbase, dashboard) + named packages. No args = infra only |
down [slugs...] |
Stop services. No args = stop everything |
build [slugs...] |
Build Docker images |
rebuild <slug> |
Rebuild a slug (handles symlink deref that raw docker build cannot) |
recreate <slug> |
Force-recreate a service (picks up new image) |
restart <slug> |
Restart container (picks up src/ edits — see Iterating on a demo below) |
ps |
Show running services |
ports |
Print slug to host port mapping |
logs <slug> |
Follow container logs (supports --grep, --since, -n, --no-follow) |
doctor |
Check local environment and stack health |
Container exposes port 10000 internally → host port in
shared/local-ports.json. The image and entrypoint
are the same ones Railway runs.
Hooking local containers into the shell
The shell app's /preview route iframes integration.backend_url (Railway)
by default. Set SHOWCASE_LOCAL=1 when running shell to swap in the
localhost ports from local-ports.json instead — per-slug, falling back to
Railway for anything you don't have running.
cd showcase/shell
npm install # once
SHOWCASE_LOCAL=1 npm run dev # /preview iframes http://localhost:<port>/demos/...
In production the env var is unset → Railway URLs, unchanged.
shell-dashboard — feature × integration matrix
Internal overview of which packages support which features, linking to the
canonical /preview and /code routes on shell. Lives at
http://localhost:3002 and reads the same registry.json shell does.
cd showcase/shell-dashboard
npm install
npm run dev
Column ordering lives in shell-dashboard/src/lib/sort-order.ts — internal to
this app, not part of the public registry.
Iterating on a demo
- Edit the demo in
integrations/<slug>/src/app/demos/<demo-id>/page.tsx(and the backend undersrc/agents/if applicable). - Rebundle so
/codeinshellreflects the edit:cd showcase && npx tsx scripts/bundle-demo-content.ts. - If you changed
manifest.yamlor added a feature toshared/feature-registry.json:npx tsx scripts/generate-registry.ts. - Rebuild + restart the container:
showcase/bin/showcase up <slug>(orrestart <slug>for puresrc/edits — see DEBUGGING.md "Dev Iteration Speed"). - The grid in
shell-dashboardand/previewinshellnow show the new state.
Relationship to Railway
- Dockerfile,
entrypoint.sh, and build context (shared_python/,shared_typescript/) are shared between local and Railway. .github/workflows/showcase_deploy.ymlbuilds each image on push tomainand pushes it to Railway. Per-PR deploys are opt-in viagh workflow run showcase_deploy.yml -r <branch> -f service=<slug>.- The only real differences at runtime are env var values and the URL. If something works locally in Docker, it works on Railway (and vice versa).
Dashboard SOPs (catalog.json + PocketBase)
The dashboard at showcase.copilotkit.ai reads two data sources:
- Static
catalog.json— generated at build time bypnpm generate-registry. Contains the full 38-feature × 17-integration cell matrix with status (wired/stub/unshipped), parity tiers, and feature categories. Changes require a generator run + commit. - Live PocketBase probe results — streamed via SSE. Probes discover demo routes automatically and update the dashboard in real time. No manual intervention needed for probe data.
Known limitation — PocketBase fetch cap: useLiveStatus.ts fetches status
records with a hard INITIAL_CAP (currently 2000). PocketBase returns records
in rowid (creation) order. If the total record count exceeds the cap,
later-created dimensions (e.g. e2e:<slug>/<featureId> per-cell records from
the 6-hourly e2e-demos probe) get silently truncated, causing the dashboard to
show D2 instead of D4 across the board. If new probe types are added and the
dashboard regresses to D2, raise INITIAL_CAP in
shell-dashboard/src/hooks/useLiveStatus.ts. The correct long-term fix is
dimension-scoped fetching or sort=-updated so the cap never silently drops
functional records.
Key invariants:
- Parity tiers are never manually set. They are computed by comparing each integration's wired feature set against the reference integration's.
- The reference integration is auto-detected as the integration with the
most wired features (ties broken alphabetically). No
reference: trueflag exists. catalog.jsonis gitignored — the generator emits it into the shell apps'src/data/directories, which are already in.gitignore.- The
stubstatus means: feature declared in manifest, demo entry exists, but noroutefield. Today onlylanggraph-python/cli-startqualifies.
SOP 1: Wire a new demo on an existing integration
- Edit
showcase/integrations/<slug>/manifest.yaml— add the feature tofeatures[]and a correspondingdemos[]entry with aroute. - Run
pnpm generate-registry— updatesregistry.jsonANDcatalog.json. The cell flips fromunshippedtowired. Parity tiers auto-recompute. - Commit the manifest + both generated files. PR, merge.
- CI rebuilds the package image + dashboard image. Railway auto-deploys both.
- Ops probes discover the new demo route and begin probing. Dashboard updates live via PocketBase SSE — no further action needed.
SOP 2: Code fix on an existing demo (no manifest change)
- Edit code under
showcase/integrations/<slug>/src/.... - PR, merge. No generator run needed (manifest unchanged).
- CI rebuilds the package image. Railway auto-deploys.
- Probes re-probe on the next tick. If the fix turns a red cell green, the dashboard updates live. Zero manual steps beyond the normal PR workflow.
SOP 3: Add a brand-new integration
- Create
showcase/integrations/<new-slug>/manifest.yamlwithfeatures[]+demos[]. - Add
{"slug": "<new-slug>", "name": "<Display Name>"}toshowcase/shared/packages.json. - Provision a Railway service (manual:
railway service createor Dashboard UI). - Run
pnpm generate-registry— catalog gains 38 new cells (mostlyunshipped, somewired). Parity tier computed automatically. - Commit, PR, merge. CI + Railway deploy. Probes discover the new service automatically via the Railway discovery filter.
For the full per-package + external-setup checklist see
./INTEGRATION-CHECKLIST.md.
SOP 4: Reference migration (move the reference integration)
- No manual flag needed — the generator auto-detects the reference as the integration with the most wired features (ties broken alphabetically).
- If you want a different integration to be reference, wire more features on it until it leads the count.
- Run
pnpm generate-registry— all parity tiers recompute automatically. - Commit, PR, merge.