## Problem
A managed bot with **both** a Slack and a Teams adapter attached only
ever received its **Slack** deliveries. Teams deliveries stayed `queued`
forever — never claimed, never sent.
## Root cause
`channels-intelligence`'s runtime claim loop (`http-transports.ts` →
`claimOnce()`) posted a per-provider filter to
`/api/bots/listener/claim`:
```ts
{
runtimeInstanceId: this.cfg.runtimeInstanceId,
adapters: [this.cfg.adapter], // defaults to "slack"
}
```
app-api filters claimable deliveries by that list (`$adapters IS NULL OR
bie.provider = ANY($adapters)`), so a runtime declaring only `"slack"`
is never handed the same bot's Teams deliveries.
But the managed runtime is **provider-agnostic**: it emits abstract
render frames and Intelligence renders each reply per the delivery's own
reply target. There is no reason for the runtime to constrain claims by
provider — one `intelligenceAdapter()` should serve every channel its
bot has attached.
## Fix
Drop the `adapters` field from the claim body. `adapters` is already
optional on the app-api side (absent → `NULL` → no provider filter → all
providers), so this needs no coordinated backend change.
`this.cfg.adapter` is still used for the heartbeat's declared bots and
for egress, both unaffected.
## Testing
Verified end-to-end locally against a managed Teams bot: inbound Bot
Framework JWT → claim → agent run → render → Bot Connector egress all
`succeed` with this change. Slack continues to work unchanged.
Follow-up to the provider-agnostic claim change on this branch. Now that the
runtime claims deliveries for every provider its bot has attached, Teams
deliveries flow through the same bridge — and their reply target is a distinct
shape (serviceUrl/conversationId/tenantId, no teamId/channel/threadTs). Deriving
conversationKey from Slack-only fields collapsed every Teams conversation onto
one degenerate key, and conversationKey keys the agent/session
(getOrCreate -> makeAgent), so distinct Teams conversations would share
state/memory.
Make replyTarget a discriminated union (slack|teams) and derive conversationKey
per provider: teams:{tenantId}:{conversationId}, matching Intelligence app-api's
thread_key (OSS-441 slice 2, Intelligence #511) so client and server agree on
conversation identity. Unknown adapters fail loud (the claim loop's existing
catch nacks, not wedges).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
**OSS-406 Phase 1** — the missing composition that runs a managed bot
over the **Phoenix realtime path**, plus a real consumer of it.
The realtime foundation is live on main (gateway
`hosted_bots:project:<id>` channel, Redis fan-out, app-api durable
authority + lease fencing), and the SDK primitives — `startManagedBots`,
`connectPhoenixHostedBotChannel`, `PhoenixRealtimeTransport` — exist and
are unit-tested. But **nothing composed them**, so the managed adapter
fell back to its HTTP default and Phoenix was never exercised
end-to-end. This adds the launcher **and wires an example to it** so it
isn't an unused export.
## Launcher (`channels-intelligence/phoenix-launcher.ts`)
- **`startManagedBotsOnChannel(bots, { channel, scope,
runtimeInstanceId, log? })`** — wraps an already-connected channel in a
`PhoenixRealtimeTransport` (delivery source + render sink) and starts
the bots via `startManagedBots`. Split out so the *behavior* is
unit-testable against a fake channel.
- **`startManagedBotsOverPhoenix(bots, config)`** — thin glue:
`connectPhoenixHostedBotChannel` → delegate → `disconnect()` on
`stop()`.
- **`phoenixEgress`** — fail-loud `EgressSink`. `intelligenceAdapter` is
exclusive and, with a render sink wired, routes every
`post`/`update`/run-render through it — the generic `EgressSink` must
never be hit.
## Consumer (`examples/slack/app/managed.ts`)
A **real caller** of the launcher: the *same* Slack bot as
`examples/slack/app/index.ts` — identical agent, tools, context,
commands, and turn handlers — run in **managed mode over Phoenix**
instead of the native `slack()` adapter. `index.ts` (native/self-hosted)
is left untouched; `managed.ts` holds no Slack creds and no public
endpoint, just a runtime key + a Phoenix connection. Demonstrates the
"same bot, swap the transport" thesis concretely:
```
native: createBot({ adapters: [ slack({ botToken, appToken }) ] }) // index.ts
managed: startManagedBotsOverPhoenix([ createBot({ … }) ], { … }) // managed.ts
```
The managed `onMention` handler passes the current message as `prompt`
to `runAgent` — see the validation note below for why this is required
on the managed path (and not on native).
## Tests
Drive a **real `createBot`** through the **full managed path** over a
fake channel:
- a Phoenix-delivered turn → handler runs → `render_event` frame →
`complete_requested` (completion **intent**, never a self-ack);
- a throwing handler → `fail` intent (no completion, no ack).
Build + 97 package tests green; `examples/slack` `managed.ts`
type-clean.
## Validation — live E2E over the real Phoenix path
This didn't just pass unit tests against a fake channel; the whole loop
was driven end-to-end on a real local stack with a **real OpenAI
backend**, and I verified the message actually traveled the websocket
path (not the HTTP fallback).
**Stack:** Intelligence `main` via docker-compose (postgres,
postgres-ops, redis, keycloak, minio, tei, realtime-gateway on `:4401`)
+ app-api on `:7050` (graphile migrations applied). Managed-bots
entitlement was granted via the managed-service path (a stub ops
entitlement endpoint + `FF_MANAGED_BOTS=true` on both app-api and the
gateway; gateway join otherwise rejects with
`disabled_by_feature_flag`).
**Provisioning:** created a managed Slack bot + attached a Slack adapter
(fixture workspace/creds) against app-api, then triggered a real
`app-mention` event through a fake-Slack provider into app-api's signed
ingress.
**What was proven:**
- app-api ingress → Redis publish → gateway leases the delivery and
pushes `delivery.available` over Phoenix → `managed.ts` (via the
launcher) receives it, runs the **real OpenAI** agent, and streams
`render_event` frames back → durable render acceptances written
(`run_started`, `text_delta`, `text_end`, `finalize`) →
`complete_requested` intent → app-api commits the ack. Delivery ended
`succeeded`.
- **It genuinely used the websocket path.** With gateway debug logging
bumped, frames showed `HANDLED hosted_bot.render_event.v1 INCOMING ON
hosted_bots:project:<id> (SdkChannel)`. All app-api delivery-side calls
originated from the gateway's HTTP client (`hackney`), with **zero
launcher-originated HTTP delivery calls**, and the launcher contains no
HTTP delivery code at all. The render frames went bot → gateway over the
Phoenix socket.
**Bug found and fixed during validation (the reason `managed.ts` passes
`prompt`):**
The first real run failed with an OpenAI `400 input.messages=0` — the
agent received **zero messages**. Root cause: `runAgent`
(packages/channels `thread.ts`) only injects a user message when
`extra.prompt` is set; otherwise it relies on the adapter's
reconstructed history. The **native** path gets away with omitting
`prompt` because the native adapter's `getHistory` rebuilds the live
thread *including* the triggering message. The **managed** path does
not: app-api's `GET /api/bots/history`
(`reconstructManagedThreadHistory`) rebuilds only *prior committed*
deliveries and structurally excludes the in-flight turn (the current
delivery is handed to the SDK as the delivery envelope, not via
history). So a managed handler that omits `prompt` drops the newest user
message — turn 1 → 0 messages → 400; turn 2+ → the agent answers a
*stale* prompt. FakeAgent unit tests never caught it because they don't
exercise the history round-trip.
Fixed here by having the managed `onMention` pass `message.contentParts
?? message.text` as `prompt`. This is a **systemic** footgun for every
managed entrypoint; it's tracked with a durable-fix recommendation
(adapter auto-injects the current message so managed handlers match
native) in **OSS-459**.
## Scope
- **This PR:** the launcher composition + its first consumer +
unit/contract coverage, **plus the live-stack E2E above** which closes
OSS-406's "realtime path is unproven" gap and surfaced/fixed the managed
`prompt` bug.
- **Deferred (OSS-459):** Teams managed entrypoint, multi-tenant
multiplexing, BYO, promote to a deployable Intelligence managed-bot
runtime app, HTTP-path deprecation, shared bot-def extraction, and the
durable managed-`prompt` fix.
Refs OSS-406.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The managed runtime's claimOnce() declared `adapters: [this.cfg.adapter]`
(defaulting to "slack"), which Intelligence used to filter claimable
deliveries by provider. A runtime serving a bot with both Slack and Teams
adapters would therefore never receive the bot's Teams deliveries — they
stayed queued forever while Slack worked.
The managed runtime is provider-agnostic: it emits abstract render frames
and Intelligence renders per the delivery's own reply target. So the claim
must not filter by provider. Drop the adapter filter from the claim body;
one config-free `intelligenceAdapter()` now serves every channel its bot
has attached.
Verified end-to-end locally against managed Teams: inbound JWT -> claim ->
agent -> render -> Bot Connector egress all succeed with this change.
Managed getHistory (app-api /api/bots/history) doesn't include the in-flight
turn — unlike native adapters whose getHistory rebuilds the live thread — so
runAgent({context}) alone runs the agent with zero messages (→ provider 400).
Pass the current message (contentParts ?? text) as `prompt`, the sanctioned
mechanism for input not in the adapter's reconstructed history. Verified live:
the managed Slack bot now returns a real answer over the Phoenix loop.
The realtime primitives (startManagedBots, connectPhoenixHostedBotChannel,
PhoenixRealtimeTransport) existed but nothing composed them into a launcher, so
the managed path defaulted to HTTP and Phoenix was never actually used.
- startManagedBotsOnChannel(bots, { channel, scope, runtimeInstanceId }) — wraps
an already-connected channel in a PhoenixRealtimeTransport (delivery source +
render sink) and starts the bots via startManagedBots. Split out so the
behavior is unit-testable against a fake channel.
- startManagedBotsOverPhoenix(bots, config) — thin glue: connect the gateway
bot-IO channel, delegate, disconnect on stop().
- phoenixEgress: fail-loud EgressSink (Phoenix routes all egress through the
render sink).
- examples/slack/app/managed.ts — a REAL consumer of the launcher: the same
Slack bot as index.ts (agent/tools/context/commands/handlers identical) run in
managed mode over Phoenix instead of the native slack() adapter. No native
index.ts changes.
Tests drive a real createBot through the full managed path over a fake channel:
delivered turn → render frame → completion INTENT (never self-ack); throwing
handler → fail intent. Live-stack E2E + manual validation are the OSS-406 proof;
scale-out (Teams, etc.) is OSS-459.
## What
Adds a pre-merge guard that prevents the
[OSS-451](https://linear.app/copilotkit/issue/OSS-451) **dead-on-load**
failure class from recurring:
- `showcase/scripts/validate-runtime-routes.ts` — static validator +
`validate-routes` npm script
- `showcase/scripts/__tests__/validate-runtime-routes.test.ts` (+
fixture integration) — regression coverage
- `.github/workflows/showcase_validate-wiring.yml` — runs it on every
`showcase/integrations/**` PR
> **Stacked on #5881** (the fix). Base branch is
`mark/oss-451-mastra-demos-404`; retarget to `main` once #5881 merges.
The guard is green *because* #5881 is present.
## Why
OSS-451 shipped because **nothing linked a demo page's `runtimeUrl`
string to the existence of the `/api` route it names.** The only
automatic pre-merge gate for `showcase/**` is `showcase_build_check.yml`
— a Docker *build* — and a page that references a non-existent route
compiles perfectly cleanly. So three demos went dead-on-load in
production undetected (the e2e specs that *would* have caught it run
on-demand only, post-deploy, and exclude TS-only Mastra).
## How it works
For every **shipped** demo — a demo directory whose slug is in its
integration's `manifest.yaml` `features` — the validator asserts every
`runtimeUrl` it declares resolves to a real route dir under
`src/app/api/`.
- Unshipped / experimental demos (not in `features`) and
`not_supported_features` are **skipped** — they aren't claimed to work,
so they don't fail the gate. Promoting one into `features` immediately
starts enforcing it (this is the safety net for the still-unshipped
`declarative-hashbrown` / `declarative-json-render`).
- A `validate-runtime-routes.baseline.json` can grandfather pre-existing
violations (same pattern as the pin-drift `fail-baseline.json`). **The
fleet is currently clean — 0 violations, no baseline needed.**
- Handles the shared `/api/copilotkit` route and catch-all routes
(`copilotkit-auth`, `copilotkit-voice`) by asserting the top-level route
dir exists.
## Verification
- `npm run validate-routes` → clean across all 21 integrations.
- Removing the 3 OSS-451 routes locally → the guard flags **exactly
those 3** and exits 1; restoring → exits 0.
- Regression test (5 cases): flags the shipped-demo-with-missing-route
shape, passes shared + dedicated routes, skips unshipped.
- Full `showcase/scripts` vitest suite: **2151 tests green**.
- Workflow YAML validated; actions pinned to SHA; `contents: read` only;
no secrets.
## Follow-up for a maintainer
Add **"Showcase: Runtime-Route Wiring (PR)"** to the branch-protection
required checks so it blocks merge like the build check does.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The main @copilotkit/react-core/v2 entry re-exports from one monolithic shared
chunk, so importing any symbol links the built-in chat-message rendering stack
(streamdown -> shiki, mermaid, cytoscape, katex) into the consumer's bundle —
~3MB gzip / ~15MB raw — with no way to tree-shake it. Custom-UI consumers who
only use hooks pay the full cost (issue #4893).
The separate /v2/headless build entry ships those hooks in their own small chunk
without that stack, but it was missing two hooks a custom UI needs and leaked
tailwind-merge.
- headless: export useCopilotKit and useRenderToolCall (both DOM-free, pull no
chat-UI rendering stack). useDefaultRenderTool / useRenderCustomMessages /
useRenderActivityMessage stay in /v2 — web-only markup or a2ui-renderer.
- headless: fix UseAgentUpdate (a runtime enum) being re-exported via
`export type` — under isolatedModules that stripped its runtime binding, so
useAgent's `updates` option was unusable from /v2/headless.
- Stop headless pulling tailwind-merge: extract the tailwind-free ref helpers
(shallowEqual / useShallowStableRef) into lib/shallow-stable-ref and point
CopilotChatConfigurationProvider at them.
- Add a vitest export-surface test asserting the documented hooks are runtime
functions and UseAgentUpdate is a runtime value (the `export type` bug is
invisible to tsc).
react-core check-types, vitest (1427), and react-native check-types pass.
Shipped dist/v2/headless.mjs imports only react, @ag-ui/client, @copilotkit/core,
@copilotkit/shared, @copilotkit/react-core/v2/context, zod — no rendering stack.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## What
Adds the three missing per-demo CopilotKit runtime routes in the Mastra
showcase integration:
- `src/app/api/copilotkit-a2ui-fixed-schema/route.ts`
- `src/app/api/copilotkit-declarative-gen-ui/route.ts`
- `src/app/api/copilotkit-agent-config/route.ts`
Fixes [OSS-451](https://linear.app/copilotkit/issue/OSS-451) — three
demos are **dead on load** in production (`showcase-mastra-production`).
## Why (root cause)
Each of these demo pages sets `<CopilotKit
runtimeUrl="/api/copilotkit-<demo>">`, but those route handlers were
**never created** in the Mastra integration — the pages were mirrored
verbatim from the `langgraph-python` north-star (which *does* have
per-demo routes) without porting the backend routes. The client's
runtime-info fetch 404'd → `runtime_info_fetch_failed` → the page never
mounted. Deterministic, reproduces on every plain page load.
`git log --all` confirms these route dirs have **0 commits ever** in
Mastra; they arrived via the omnibus parity PRs #4280 / #5127.
## Approach
Dedicated routes, mirroring the proven `copilotkit-beautiful-chat`
pattern rather than repointing the pages at the shared
`/api/copilotkit`. This is required for the two A2UI demos: they pass a
client-side catalog, which makes the runtime default `injectA2UITool` to
`true`, but `weatherAgent` already owns its own `generate_a2ui` tool —
serving them from the shared route would **double-bind** the tool. So
the A2UI routes set `injectA2UITool: false` and pin `defaultCatalogId`
to the catalog the page registers (avoids "Catalog not found").
`agent-config` is a plain runtime that registers the exact agent id the
page requests (`agent-config-demo`).
Zero page edits — the pages already point at these routes.
## Scope
Page-load / 404 only, matching the ticket's explicit scope. These agents
currently alias `weatherAgent` (consistent with how every other Mastra
demo currently runs); **full behavioral parity** (dedicated Mastra
agents that emit real A2UI operations / honor agent-config context) is
tracked under OSS-381.
## Verification
- `next build` compiles all three into the route manifest
(`.next/server/app/api/copilotkit-*/route.js`).
- Runtime smoke against the built server: `POST` to each new route
returns **400** (route resolves, handler runs) — identical to the
flagship `copilotkit-beautiful-chat` — vs **404** for a nonexistent
route. No `getLocalAgent returned null` init error.
- Recommend a click-through in an aimock/real-LLM env before locking D5
regression coverage.
## Follow-ups (not in this PR)
- `declarative-hashbrown` / `declarative-json-render` are broken the
same way but are `status: unshipped` (non-navigable); they + the
`manifest.yaml` `byoc-*` drift need reconciling before they can flip to
`wired`.
- Preventing recurrence (a page→route/agent invariant in the pre-merge
gate) ships as a **separate PR**.
## Summary
Upgrades `react-syntax-highlighter` in `@copilotkit/react-ui` from
`^15.6.1` to `^16.1.1`.
The v16 line pulls `refractor@5` and `prismjs@^1.30.0`, keeping
react-ui's syntax-highlighting dependency chain current for downstream
consumers. The public API used by `CodeBlock` (the `Prism` / `Light`
exports) is unchanged.
Closes#2823.
## Changes
- `packages/react-ui/package.json` — `react-syntax-highlighter`
`^15.6.1` → `^16.1.1`
- `pnpm-lock.yaml` — regenerated
## Verification
- `nx run @copilotkit/react-ui:build` ✅
- `nx run @copilotkit/react-ui:check-types` ✅
- `nx run @copilotkit/react-ui:test` ✅ (53/53)
- Syntax highlighting renders the same across common languages (JS,
TS/TSX, CSS, JSON, Rust, shell, …)
- `pnpm install --frozen-lockfile` ✅🤖 Generated with [Claude Code](https://claude.com/claude-code)
Bumps react-syntax-highlighter from ^15.6.1 to ^16.1.1. The v16 line
pulls refractor 5 and prismjs ^1.30.0, keeping react-ui's syntax
highlighting dependency chain current for downstream consumers.
The public API used by CodeBlock (the Prism/Light exports) is unchanged,
and highlighting renders the same across common languages.
Closes#2823.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## What does this PR do?
Adds two clonable starter templates —
`examples/integrations/claude-sdk-python` and
`examples/integrations/claude-sdk-typescript` — that show CopilotKit
driving a Claude Agent SDK agent over AG-UI, mirroring the
`langgraph-python` showcase (todos canvas, charts, A2UI flight cards +
dynamic dashboards, human-in-the-loop, theme toggle, threads drawer).
Each agent is a thin, idiomatic layer on the official `ag-ui-claude-sdk`
/ `@ag-ui/claude-agent-sdk` adapters: three backend tools (`query_data`,
`search_flights`, `generate_a2ui`) live in per-tool modules and are
wired into `ClaudeAgentAdapter`, while the shared todo board is driven
by the adapter's built-in `ag_ui_update_state` tool. The default model
is `claude-sonnet-5` and local dev uses a real `ANTHROPIC_API_KEY`
(matching the official AG-UI dojo). Both instances are registered in the
`_parity` manifest so their frontends stay synced with the north-star.
## Related PRs and Issues
- Complements the Claude Agent SDK quickstart docs (#5840)
## Checklist
- [ ] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [ ] "Allow edits by maintainers" is checked
🤖 Generated with [Claude Code](https://claude.com/claude-code)
CodeRabbit flagged that the form onSubmit added to headless-chat could submit
empty/whitespace messages. The handler now returns early on blank input and the
submit button is disabled when the message is empty. Applied to the claude-sdk
starters (where the form lives); headless-chat.tsx is already declared
allowedDivergence for these instances, so parity stays green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Revert the cross-demo parity sync from b34a9a348: langgraph-python (north-star)
and the langgraph-js / langgraph-fastapi / strands-python instances are restored
to their origin/main state — this PR should not mutate the canonical demo or its
siblings.
Instead, keep the CodeRabbit fixes on the claude-sdk-* starters and declare the
affected shared files as per-instance `allowedDivergence` in the parity manifest,
so `pnpm parity:check` passes without touching the other demos. The starters
carry fixes the north-star has not caught up to yet:
- border-3 -> border-[3px]; bg-[--x] / text-[--x] -> [var(--x)] (Tailwind v4)
- JSX.IntrinsicElements -> React.JSX.IntrinsicElements; Recharts <Bar shape> type
- tool-rendering args?: unknown; mode-toggle a11y; headless-chat <form>
- docker-route-override AGENT_URL trailing-slash normalization
next.config.ts is left matching the north-star template (its ignoreBuildErrors is
a shared build-config concern and the Docker build re-adds it regardless).
parity:check green (5/5 instances, 0 errors); claude-sdk tsc + oxfmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The CodeRabbit fixes in d4ef84f2c edited verbatim parity-tracked files
directly in the claude-sdk-* instances, diverging them from the north-star
(langgraph-python) and failing `pnpm parity:check`.
Move the shared-surface fixes to the north-star and propagate to every
tracked instance via `pnpm parity:sync --all`:
- border-3 -> border-[3px] (border-3 is not a Tailwind utility)
- bg-[--x] / text-[--x] -> [var(--x)] (Tailwind v4 CSS-variable syntax)
- JSX.IntrinsicElements -> React.JSX.IntrinsicElements (@types/react 19)
- Recharts <Bar shape> callback typing
- tool-rendering args?: unknown
- mode-toggle aria-pressed/type/role, headless-chat <form> + aria-label
- docker-route-override AGENT_URL trailing-slash normalization
Also revert the next.config.ts `ignoreBuildErrors` removal: next.config.ts is
a north-star template file shared across all integration demos, so the
suppression can't be dropped on a subset without breaking parity, and dropping
it template-wide would need every demo verified to build clean without it. The
two real type errors it was masking are now fixed at the north-star, so the
shared surface is type-clean regardless.
parity:check green (5/5 instances, 0 errors); claude-sdk tsc + oxfmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve the real issues surfaced by CodeRabbit on the new claude-sdk-python /
claude-sdk-typescript starters (frontend files are shared, so most fixes apply
to both):
- Fix two type errors that `ignoreBuildErrors` was masking: `JSX.IntrinsicElements`
-> `React.JSX.IntrinsicElements` (@types/react 19) and the Recharts `<Bar shape>`
callback type; then drop the blanket `typescript.ignoreBuildErrors` from
next.config.ts so source type-checks. The Dockerfile's build-time patch still
re-adds it for the `next@latest` Docker build, so deploy behavior is unchanged.
- Fix Tailwind v4 CSS-variable syntax: `bg-[--x]` -> `bg-[var(--x)]`, and
`border-3` -> `border-[3px]` (border-3 is not a utility -> invisible spinner).
- Harden the agent tools: omit the empty ANTHROPIC_API_KEY, wrap the Anthropic
call in try/catch (TS + Python), normalize the AGENT_URL trailing slash, and
make the Flight schema's id/airlineLogo/statusIcon required to match the
"must have" tool description.
- Minor a11y: mode-toggle `aria-pressed`/`type`/`role`, headless-chat `<form>`
+ `aria-label` (Enter-to-submit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add claude-sdk-python and claude-sdk-typescript to the Integrations table
in examples/README.md (17 → 19; total 48 → 50). The two starters were added
to examples/integrations/ but were missing from the index.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two clonable starter templates showing CopilotKit driving a Claude Agent SDK
agent over AG-UI, mirroring the langgraph-python showcase (todos canvas, charts,
flight cards, dynamic dashboards, HITL, theme, threads drawer).
Each agent is a thin, idiomatic layer on the official ag-ui-claude-sdk /
@ag-ui/claude-agent-sdk adapters: three backend tools (query_data, search_flights,
generate_a2ui) live in per-tool modules and are wired into ClaudeAgentAdapter,
while the shared todo board is driven by the adapter's built-in ag_ui_update_state
tool. The default model is claude-sonnet-5 and local dev uses a real
ANTHROPIC_API_KEY (matching the official AG-UI dojo). Both instances are
registered in the _parity manifest so their frontends stay synced with the
langgraph-python north-star.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>