mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
d02f7e699e
## Why
An Intelligence integration lost a request-to-row correlation map
partway through a user interaction — no error, no warning. It surfaced
as "our response routing is flaky". OSS-979 filed it as
`CopilotKitProvider` remounting its children.
The provider does nothing of the kind. It renders `{children}`
unconditionally at `CopilotKitProvider.tsx:952` — unkeyed, no early
return, and there is no `Suspense` boundary anywhere in `v2`. Nothing in
the SDK silently re-points the active thread either; every mutation path
(`setActiveThreadId`, `startNewThread`, the drawer row click, the
inspector override) is caller-driven.
The remount was app-side, and it was app-side because this skill told it
to be:
- `references/threads.md:98` teaches `useThreads()` → select →
`<CopilotChat key={activeId}>`, and that recipe is only reachable once
Intelligence is wired.
- `references/switching-agents.md:123` teaches "`key={activeAgent}`
forces remount so thread state doesn't leak" without saying what else
that discards.
- `examples/showcases/reskinnable-demo/src/app/[skin]/layout.tsx:223`
models `<SubagentActivityProvider key={threadId}>` above `{children}`,
commented "Remounting is deliberate".
Follow all three and you key a layout-level provider on a thread id that
changes asynchronously after mount. Everything below it dies
mid-interaction.
Two properties made it invisible:
- Durable threads exist only in Intelligence mode, so with a plain SSE
runtime `useThreads` returns nothing, the selected thread never changes,
and the remount never fires. It appears the moment Intelligence is
wired.
- Whether state survives depends on whether the user acted before the
thread list resolved.
## What changed
Docs only — no library change. Both traps now carry their blast radius,
in the four places an agent actually reads:
| File | Change |
|---|---|
| `SKILL.md` | Two invariants in the load-once section, so they land
before any reference is opened |
| `references/threads.md` | New HIGH entry on keying above app state;
note that `activeId` in the switcher recipe settles asynchronously |
| `references/switching-agents.md` | Existing HIGH entry now states the
blast radius and cross-links the threads trap |
| `references/switching-agents-recipes.md` | Key rule amended — keep it
on `<CopilotChat>`, nowhere higher |
| `references/agent-access.md` | The second route to the same symptom:
`useAgent` swaps a provisional stand-in for the real agent when `/info`
resolves, so an effect keyed on `agent` re-runs once, mid-interaction.
Adds an `isReady` pattern and a HIGH entry |
`isReady` appeared in **zero** shipped skills before this — it was
documented only in `showcase/shell-docs/.../useAgent.mdx` and in JSDoc.
Same shape as OSS-888, where the root cause was the shipped skill rather
than the library.
Also corrects a factual error: the skill claimed `useAgent` returns `{
agent }` only. It returns `{ agent, isReady }`.
The 10-file diff is 5 source files under `packages/react-core/skills/`
plus their 5 mirrors under `skills/`, regenerated with `pnpm
sync:plugin-skills`.
## Verification
- `pnpm check:plugin-skills` — mirror in sync
- `pnpm exec vitest run scripts/__tests__/sync-plugin-skills.test.ts` —
12 passed
- `oxfmt --check` — clean over both skill trees
- Full pre-commit suite green, including `test-and-check-packages`
(`test`, `publint`, `attw` across 2 projects and 20 dependent tasks)
## Not in scope
Whether the run's app keyed on `threadId` or on `agent` is not
settleable from the repo — its source is not in any checkout, and there
is no `2026-08-25` strands run report under
`tools/one-prompt-development/evaluation/runs` on any branch. Both
variants produce the reported symptom and this covers both, so a
first-hand repro is a separate task. The `reskinnable-demo` layout is
left as-is deliberately: it is a legitimate use of the pattern, and it
is now the worked example the guidance warns about.
Scoping detail in the OSS-979 comment.
refs OSS-979
🤖 Generated with [Claude Code](https://claude.com/claude-code)
CopilotKit - React Core
✨ Why CopilotKit?
- Minutes to integrate - Get started quickly with our CLI
- Framework agnostic - Works with React, Next.js, AGUI and more
- Production-ready UI - Use customizable components or build with headless UI
- Built-in security - Prompt injection protection
- Open source - Full transparency and community-driven
🧑💻 Real life use cases
Deploy deeply-integrated AI assistants & agents that work alongside your users inside your applications.
🖥️ Code Samples
Drop in these building blocks and tailor them to your needs.
Build with Headless APIs and Pre-Built Components
// Headless UI with full control
const { visibleMessages, appendMessage, setMessages, ... } = useCopilotChat();
// Pre-built components with deep customization options (CSS + pass custom sub-components)
<CopilotPopup
instructions={"You are assisting the user as best as you can. Answer in the best way possible given the data you have."}
labels={{ title: "Popup Assistant", initial: "Need any help?" }}
/>
// Frontend actions + generative UI, with full streaming support
useCopilotAction({
name: "appendToSpreadsheet",
description: "Append rows to the current spreadsheet",
parameters: [
{ name: "rows", type: "object[]", attributes: [{ name: "cells", type: "object[]", attributes: [{ name: "value", type: "string" }] }] }
],
render: ({ status, args }) => <Spreadsheet data={canonicalSpreadsheetData(args.rows)} />,
handler: ({ rows }) => setSpreadsheet({ ...spreadsheet, rows: [...spreadsheet.rows, ...canonicalSpreadsheetData(rows)] }),
});
Integrate In-App CoAgents with LangGraph
// Share state between app and agent
const { agentState } = useCoAgent({
name: "basic_agent",
initialState: { input: "NYC" }
});
// agentic generative UI
useCoAgentStateRender({
name: "basic_agent",
render: ({ state }) => <WeatherDisplay {...state.final_response} />,
});
// Human in the Loop (Approval)
useCopilotAction({
name: "email_tool",
parameters: [
{
name: "email_draft",
type: "string",
description: "The email content",
required: true,
},
],
renderAndWaitForResponse: ({ args, status, respond }) => {
return (
<EmailConfirmation
emailContent={args.email_draft || ""}
isExecuting={status === "executing"}
onCancel={() => respond?.({ approved: false })}
onSend={() =>
respond?.({
approved: true,
metadata: { sentAt: new Date().toISOString() },
})
}
/>
);
},
});
// intermediate agent state streaming (supports both LangGraph.js + LangGraph python)
const modifiedConfig = copilotKitCustomizeConfig(config, {
emitIntermediateState: [
{
stateKey: "outline",
tool: "set_outline",
toolArgument: "outline",
},
],
});
const response = await ChatOpenAI({ model: "gpt-4o" }).invoke(
messages,
modifiedConfig,
);
🏆 Featured Examples
Documentation
To get started with CopilotKit, please check out the documentation.