mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
tyler/workflow-observer-example
1657 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
40f4fdc25e |
feat(showcase): add travel workflow example
Add a minimal CopilotKit useAgent showcase backed by a Python LangGraph workflow. Stream attractions onto a Leaflet map with stable markers and Inspector support. |
||
|
|
0298616223 |
docs: add Claude Managed Agents cookbook (#6430)
## Summary - add an independently runnable Claude Managed Agents finance-assistant example - add a cookbook recipe that explains the CopilotKit runtime, managed-session mapping, and tool rendering flow - add the recipe to cookbook navigation, the overview grid, sidebar icon mapping, and render coverage - use the real Claude vector mark for the cookbook instead of the text-placeholder SDK asset - register the example's Vite configuration and managed-agent model with the repository CI allowlists - include a compact architecture diagram and links to the relevant rendering and CopilotKit Intelligence documentation - disable Claude's complete built-in toolset and expose only the scoped `show_growth_projection` runtime tool - make the provisioning model configurable through `ANTHROPIC_MODEL`, defaulting to `claude-fable-5` - bound CopilotKit request bodies to 256 KB and managed-agent turns to 90 seconds, while relying on the adapter's per-thread serialization - cap public run traffic at 20 provider-like attempts per client IP per minute and 2,000 successful starts per process per 24-hour window - restrict browser runtime requests with an exact Origin allowlist that supports same-origin or separately hosted frontends, and restrict iframe parents with CSP `frame-ancestors` - validate persisted managed-agent IDs at startup so malformed local configuration fails immediately - publish the interactive example on Railway and embed the live deployment in the cookbook - align the demo with the existing cookbook chat styling and show the `Project monthly investing` starter on first load ## Demo  ## Why This gives developers a focused example of connecting CopilotKit to Anthropic Claude Managed Agents without the extra surface area of a larger analyst application. The recipe follows the existing cookbook structure and keeps the live demo compact enough for the standard cookbook pane. Its managed environment has no outbound network or package-manager access, and its agent cannot use bash, filesystem, search, or fetch tools. The request, turn, per-IP, and process-wide limits bound public demo traffic without adding authentication or user friction. The traffic counters are intentionally in memory, reset on process restart, and are not shared across replicas, so the dedicated Anthropic workspace spend limit remains the durable cost backstop. The exact-Origin browser check reduces drive-by use but is explicitly documented as a control rather than authentication. The model override allows operators to select a lower-cost supported model during provisioning without editing source code. ## Validation - scoped formatting: passed - scoped lint: 0 warnings, 0 errors - shell-docs typecheck: passed - standalone example typecheck: passed - docs render tests: 26/26 passed - standalone example tests: 23/23 passed - shell-docs tests: 375/375 passed - shell-docs production build: passed (222/222 pages) - standalone example production build: passed - standalone npm lockfile validation: passed - build-config allowlist validator: passed - docs model-name validator: passed - exact-Origin regression coverage for run requests plus headerless same-origin runtime discovery: passed - malformed persisted agent-ID regression coverage: passed - live Railway root and iframe CSP: passed - live Railway runtime discovery, exact welcome copy, and first-load starter pill: passed - live three-turn AG-UI managed-agent run with `show_growth_projection`: passed - cookbook verified in the browser at desktop and narrow widths with no console errors or horizontal overflow |
||
|
|
b8b19834a2 |
fix(runtime): unify the Intelligence key name and publish the wiring (refs OSS-881) (#6595)
## What does this PR do? Closes the naming and documentation half of [OSS-881](https://linear.app/copilotkit/issue/OSS-881). Paired with CopilotKit/Intelligence#890, which adds `copilotkit verify` and tightens the evaluation rubric. ### 1. One name for the Intelligence key **Three** names for one value were live in CopilotKit's own documentation, and following the wrong one with a CLI-provisioned project yields an undefined key: | Name | Where | Code readers | | --- | --- | --- | | `INTELLIGENCE_API_KEY` | what `copilotkit project select` writes; all 34 integration examples; the docs site | 34 | | `COPILOTKIT_INTELLIGENCE_API_KEY` | 7 Channels package READMEs + packaged skills | **0** | | `COPILOTKIT_API_KEY` | `examples/slack`, `examples/teams`, and the TSDoc on `CopilotKitIntelligence` itself | 2 | `INTELLIGENCE_API_KEY` wins — it is the name the CLI provisions, and changing it would break every scaffolded project in the wild. - `COPILOTKIT_INTELLIGENCE_API_KEY` is **retired outright**. Nothing ever read it, so there is nothing to keep compatible. - `COPILOTKIT_API_KEY` stays **readable as a deprecated alias** in the two examples that consume it, so an existing `.env` keeps working, and is documented as deprecated everywhere it appears. The third name was the worst placed: it was in the TSDoc on `CopilotKitIntelligence`, which is what an IDE shows on hover. This was not only untidy. The CLI's own `channels-preflight` accepts `INTELLIGENCE_API_KEY` or `COPILOTKIT_API_KEY` — **not** `COPILOTKIT_INTELLIGENCE_API_KEY`, the name the Channels READMEs told people to set. So following a Channels README verbatim made `copilotkit channels` warn that no runtime API key was present while the key sat visibly in `.env`. After this PR the documented name is one preflight accepts. > [!NOTE] > `NEXT_PUBLIC_COPILOTKIT_API_KEY` is a **different value** — the legacy Copilot Cloud public key — and is deliberately left alone. ### 2. A real defect, not just naming skew `skills/runtime/references/intelligence-mode.md` documented `organizationId` as a `CopilotKitIntelligence` option, sourced from two further env names (`COPILOTKIT_INTELLIGENCE_ORG_ID`, `COPILOTKIT_ORG_ID`). `CopilotKitIntelligenceConfig` has no such field — the copy-pasteable sample it appeared in **would not compile**. Removed from the samples, and the prose telling readers to fetch a value for it corrected. That file is the only place those two names ever existed, which is very likely why the failing validation run reported that "the runtime reads `COPILOTKIT_INTELLIGENCE_API_KEY` and `COPILOTKIT_INTELLIGENCE_ORG_ID`". ### 3. Publish the Intelligence wiring The wiring instructions existed only inside `node_modules/@copilotkit/runtime/skills/`, and the only docs pages mentioning `CopilotKitIntelligence` at all were the two Channels frontends — so a developer on the plain web path had no page to reach it from. Adds **`/premium/connect-your-runtime`**: the wiring itself, how to confirm the credential is actually consumed, the self-hosted both-URLs-or-neither rule, and a troubleshooting table. Linked into both navs, and the skills reference now points at the published page. ### 4. A guard so it cannot drift back `scripts/validate-intelligence-env-names.ts` (`pnpm check:intelligence-env-names`), wired to lefthook and a new workflow. The workflow is **intentionally unfiltered**. The two workflows that would otherwise cover this both filter: `plugin-skills-check` by `paths:`, and `static/quality` by `paths-ignore: examples/**` — which is exactly where the deprecated alias lives. Scoping the job would re-open the hole it exists to close. Legitimate alias sites live in `ALIAS_ALLOWLIST`. ## Related PRs and Issues - [OSS-881](https://linear.app/copilotkit/issue/OSS-881) — needs **both** PRs; neither closes it alone - CopilotKit/Intelligence#890 — items 1 and 4 (`copilotkit verify` + rubric contract 1.3.0) ## Verification - Full lefthook pre-commit ran green: `check-plugin-skills`, `lint-fix`, the new `check-intelligence-env-names`, and `test`/`publint`/`attw` across **25 projects**. - `examples/slack` `managed.test.ts` extended to cover **both** the canonical name and the alias fallback, and proven non-vacuous — removing the fallback turns the new test red. - The drift guard proven non-vacuous the same way: reintroducing a retired name fails it, exit 1. - `oxfmt` and `oxlint` clean on every file touched (0 errors). ## Checklist - [x] 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
f36cb2b8ee | fix(showcase): validate persisted setup IDs | ||
|
|
65f07610a1 | fix(showcase): allow runtime discovery request | ||
|
|
d50e8d7e7c | Merge branch 'main' into codex/claude-managed-agents-cookbook | ||
|
|
ff060eb97b | feat(showcase): polish managed-agent cookbook demo | ||
|
|
7089b3cf53 | fix(showcase): harden managed-agent deployment setup | ||
|
|
35ea9eee36 |
feat(reskinnable-demo): run banking on a nested LangChain deep agent with a streaming CLI console (#6581)
Replaces the Codex CLI harness with **LangChain deep agents (Python)**:
sandboxed shell execution, parallel research subagents, no external
harness binary, our own API key.
All ten beats have been walked in a browser, and the demo now has its
own CI job. **Ready for review.**
## What banking's agent is now
```
banking gpt-5.4, temp 0
│ banking's own prompt · the browser's frontend tools · Intelligence
│ memory tools · render_report for the canvas
└─ expense-analyst gpt-5.6-sol, reasoning_effort=high
│ sandboxed shell (LocalShellBackend) · submit_expense_report
└─ merchant-researcher gpt-5.4, one per merchant
search_merchant (Tavily)
```
Six skins still run in-process as `BuiltInAgent`s. `banking` is an
`HttpAgent` pointed at `agent/`.
Each level exists because something about it must differ from its parent
— prompt, model, reasoning effort, tool set. A single agent had nowhere
to put any of that.
### Why the whole agent moved, not just the expense beat
The obvious design was a second agent id for the long-running beat,
leaving banking's `BuiltInAgent` alone. That was built first, and it
does not survive the requirement *"start the analysis, switch threads,
run other pills, come back to it."*
**Threads are scoped per agent.** `listThreads` takes `agentId` as a
required parameter, and measured against the running app the two lists
were disjoint — `banking` 46 threads, `banking-expenses` 10. The v2
runtime has no handoff mechanism, and `defineTool`'s `execute` takes
`(args)` with no emitter, so a tool cannot stream a multi-minute run
into the conversation. One conversation list means one agent.
### Why the beat is a subagent rather than prose in one prompt
It started as a section appended to banking's prompt. That left per-beat
configuration homeless (model, effort and recursion limit are all
agent-level, and there was one agent), and made banking's
~21,000-character rulebook ride every one of the ~20 model calls the run
makes — re-sending rules about markdown tables while the agent read a
CSV.
Reached as a `CompiledSubAgent`, because a raw `SubAgent` spec has no
`subagents` field and this one needs its own: the per-merchant fan-out
is a headline of the beat, and a flat subagent could only research
serially. A probe confirmed nesting survives — the analyst's `task`
dispatches, the researchers' `search_merchant` calls and the final
report tool all reach `astream_events`.
## The console
One CLI window in the transcript carries the whole run: narration, `$
execute`, `search "…"`, `→ merchant-researcher: …`, results, indented by
depth so a ten-way fan-out reads as a fan-out. Subagent narration is
suppressed from the conversation so the console is the single place the
harness is visible; banking's own replies still render normally.
`shell/subagents/subagent-activity.tsx` subscribes to the agent's
**event stream**. Reading `agent.messages` — the first design — was
wrong twice: messages materialise at the `MESSAGES_SNAPSHOT` (two per
run), so the pane sat still for minutes then filled at once; and
persisted messages carry no `subagentRunId`, so the harness's narration
could not be told from banking's reply. The fold is pure and idempotent,
so the same code serves the live subscription and a replay of the
thread's stored events when a conversation is reopened.
Three heuristics were deleted along the way, each replaced by identity
the protocol already carried:
| heuristic | replaced by |
|---|---|
| console anchored on "the first tool call" | the run's first `task`
call, from **message order** (durable; the event-derived version
rendered one console *per delegation* on a restored thread — six,
measured) |
| `CONSOLE_TOOL_NAMES` allowlist | presence of `subagentRunId` |
| `disable_streaming` on the researchers | the canary's per-lane state |
The message filter suppresses **prose** and keeps **tool calls**: an
agent routinely narrates and calls a tool in one message, and returning
`null` for the whole message hid the report card — the run looked
perfect and ended with nothing to show.
## Canary stack, contained to this app
The subagent surface only exists on the canary line, and a released
`@ag-ui/client` ≤ 0.0.57 rejects `SUBAGENT_*` events **in the HTTP
transport before any middleware runs**, killing the stream. So the demo
leaves the root pnpm workspace and ships its own lockfile:
- `@copilotkit/* 1.68.3-canary.1786716392`
- `@ag-ui/* 0.0.59-canary.1786716392.0`
- `ag-ui-langgraph 0.0.43.dev1786716392` → `ag-ui-protocol
0.1.20.dev1786716392`
That keeps an unreleased protocol out of every other package in the
monorepo.
A 1.62.2-based canary was tried first and **could not compile the app**:
`workspace:*` is not a version, so the demo had no recorded lower bound
on the CopilotKit API it needs, and pointing it at 1.62.2 silently
rewound that API five minors. `OpenGenerativeUIActivityRenderer` — a
public `/v2` export since ~1.63 — was the first thing to break, and
there was no reason to think it was the only one. Rebasing the canary on
1.68.3 collapsed that whole class of risk.
## Verified against the running app
Every row is a measurement, not a claim.
| | |
|---|---|
| Registry shape | `/info`: `banking` = HttpAgent, other six =
BuiltInAgent |
| Sandboxed execution | 8 `execute` calls; agent writes and runs its own
Python |
| Parallel subagents | 6 `SUBAGENT_STARTED`/`FINISHED` pairs, 10
`search_merchant` |
| Canvas beat | `render_report` → a2ui middleware emits `activityType:
a2ui-surface` |
| Frontend tools | given two, picks `showTransactions`, emits no result,
emits **no prose** — the prompt's restraint rule surviving the port |
| Memory (Intelligence) | run is handed `recall_memory`, `save_memory`,
`forget_memory`, `knowledge_base_shell` |
| HITL round-trip | tool call out, answer back in, agent continues |
| Durable background run | client disconnected at 8s; run finished
unattended; thread replayed |
| Thread restore | 61 messages persisted incl. the report tool (an
earlier flat-subagent attempt collapsed to 4) |
| Report correctness | 14 rows, 9 researched, 6 filed with ids read out
of real 201 bodies, totals reconciling against their own rows |
| Run duration | 86s reported vs 98s wall clock |
## Correctness bugs found by running it
Each produced a confident, complete-looking wrong answer rather than an
error — the characteristic failure of a multi-minute agentic beat.
- **Beat 3d was dead, and it took the whole thread with it.** The bet
against this one was right. `ag_ui_langgraph` routes *every* attachment
to the model as an `image_url` block, documents included, so the Q2
invoice was rejected before the first token: `400 Invalid MIME type.
Only image types are supported`. The exception is raised inside the
model node, which kills the SSE stream — the runtime sees `RUN_ERROR:
terminated` with no cause and the browser renders **nothing**: no error
bubble, no failed message. And the crashed run is still checkpointed, so
every later message on that thread replays the rejected content and dies
the same way. One click on the pill killed the conversation permanently;
only restarting the service cleared it. Fixed here by a
`wrap_model_call` middleware that rewrites those blocks into LangChain
standard `file` blocks, and upstream in
[ag-ui#2476](https://github.com/ag-ui-protocol/ag-ui/pull/2476) (both
adapters, plus the return leg so a non-image attachment survives
`MESSAGES_SNAPSHOT`). The middleware is a stopgap with its deletion
condition written into its header — this service installs the adapter
from PyPI, so the upstream fix cannot reach it until published.
- **Totals did not match their own rows.** Every per-row amount matched
the CSV while the headline totals came back $1.00 and $0.20 high: the
model authored them instead of adding them. The card prints the total
directly above the rows it is the sum of. Fixed structurally — derived
in `submit_expense_report`, removed as parameters.
- **`amount` arrived as a string** on all 14 rows, silently defeating
`toLocaleString(…currency…)` so it printed `842.10` with no currency.
- **`merchantKind` non-answers.** With no search tool the model wrote a
bare `"unclear"`; with Tavily live it hedges in prose (`"unknown (likely
bookbindery/bookshop retail, but not established for this exact
merchant)"`), which an exact-match filter passed into a 60-character
label glued to the merchant name. Now rejected on leading token, hedging
language, and a 40-character cap.
- **The run clock reported 333s for a two-minute run.** It took the
oldest open stamp across the process because the tool could not name its
own run; model calls *after* the report re-stamped it and the leftover
became the next run's start. Now keyed per run via an injected
`ToolRuntime`.
- **`graph.with_config({"recursion_limit"})` is silently dropped** by
the AG-UI adapter. The agent completed the entire analysis, streamed
every argument of the final report, then died at LangGraph's default of
25 supersteps.
- **A sync `wrap_model_call` under `astream`** surfaces as a bare
`RUN_ERROR: terminated`, cause only in the service log.
- **`emit_raw_events` defaults to `True`**, piggybacking LangChain's
internal events onto the stream: 27,950,261 bytes → 374,086 with it off,
identical report. Matters because the thread *persists* those events for
replay.
- **`gpt-5.6-sol` + `reasoning_effort` + function tools 400s** on
`/v1/chat/completions`; needs `use_responses_api=True`. The first model
probe missed it by binding no tools — a model probe for an agent has to
bind one.
## Upstream findings (reported separately, not fixed here)
1. **`@copilotkit/runtime` drops `subagentRunId` when persisting
messages.** 2888 of 3026 stream events carry it; 0 of 53 persisted
messages do. Reproduced with Intelligence removed entirely, so it is the
runtime's message shape rather than the platform store — and
`@copilotkit/runtime`'s dist contains no occurrence of the field, while
`@ag-ui/core`, `ag-ui-protocol` and `@copilotkit/core` all model it. One
field threaded through would let the console rebuild from message
history and delete the event-replay seeding here.
2. **`copilotkit` 0.1.95 × `ag-ui-langgraph` 0.0.43** — the FastAPI
endpoint calls `agent.clone()` per request; 0.0.43's base `clone()`
hard-passes three kwargs the SDK subclass does not accept, so **every
request 500s**. Verified with a minimal repro on stock classes and by
reading published wheels (0.0.41/0.0.42 are fine — the window is 0.0.43
only). `sdk-python` requires `>=0.0.42` unbounded, so fresh installs
break, and `examples/showcases/deep-agents{,-finance-erp,-job-search}`
are one `uv lock --upgrade` away. Now open as #6592 (`**kwargs`
passthrough + 9 regression tests incl. a forward-compat guard); it needs
a `0.1.96` bump to reach PyPI before `main.py`'s
`BankingAGUIAgent.clone()` workaround can go — and note `agent/uv.lock`
pins `copilotkit 0.1.95`, so removing the workaround is a re-lock as
well as a delete. That branch also fixes 4 pre-existing failures in the
sdk-python suite from the same root cause — **`test_unit-python-sdk` may
currently be red on `main`; worth checking independently.**
3. **Both LangGraph AG-UI adapters send non-image attachments as
`image_url`**, so a PDF, audio clip or video is rejected on the block
kind — see beat 3d in *Correctness bugs*. Fixed in
[ag-ui#2476](https://github.com/ag-ui-protocol/ag-ui/pull/2476); carried
here as a middleware until that publishes.
Also verified and **not** a problem: pnpm 10.33.4 still applies
`package.json` `pnpm.overrides` despite warning that it ignores them —
the ~70 root overrides including the security pins are live, nothing was
silently unpinned. (Migration to `pnpm-workspace.yaml` is worth doing
anyway, since the installed pnpm is 11.21.0 where the field genuinely is
dropped; branch exists, byte-identical lockfile.)
## What is not done
1. **Docs are stale.** `CLAUDE.md` still says `AgentRegistration` is `{
createAgent: () => BuiltInAgent }` and that the route "builds one
`BuiltInAgent` per registered skin"; the reskin skill says the same in
three places, and `templates.md` scaffolds it. The real type is `() =>
AbstractAgent`. This app has a standing rule that every change answers
whether the skill went stale. It did — the skill's launcher step is
fixed, the `BuiltInAgent` claims are not.
3. **Pre-release dependencies**, now on both halves: the JS canaries in
this app's own `pnpm-lock.yaml`, and `ag-ui-langgraph` /
`ag-ui-protocol` pinned `==` to the matching `.dev` canaries in
`agent/pyproject.toml`. Both move to stable when the subagent work
ships.
4. `pnpm test:e2e` has not been run.
### Closed since this PR was opened
- **The demo has its own CI job** —
`.github/workflows/test_reskinnable-demo.yml`. Nx discovers projects
*through* the pnpm workspace (no `workspaceLayout` in `nx.json`), so
leaving it also left the repo-wide `nx run-many -t build`
(`static_compat.yml`), `-t check-types` (`static_quality.yml`) and `-t
test` (`test_unit.yml`) sweeps, and both static workflows carry
`paths-ignore: ["examples/**"]` besides. One job runs the four gates
(lint, typecheck, 2460 unit tests, build); a second syncs
`agent/uv.lock` with `--frozen` and asserts the subagent *capability*
rather than a version string, so it keeps meaning something after the
pin moves.
The build gate is the one a developer can least run locally: `next
build` corrupts a concurrently running dev server's PostCSS/Turbopack
cache — measured, `globals.css` transforms to garbage, every route 500s,
and a dev-server restart does not clear it because the corruption is on
disk.
**Its first two runs failed, both usefully**, and both bugs were
pre-existing:
1. `pnpm/action-setup@v6.0.10` with `package_json_file:` pointed at this
app still installed the **root's** pnpm 10.33.4 rather than this app's
pinned 10.10.0 — the resolver that ignores `pnpm.overrides`, which is
the only place three of the five `@ag-ui/*` canaries are pinned. Caught
only because the job asserts `pnpm --version` against the pin; it would
otherwise have installed under the wrong resolver and stayed green. Now
activated through corepack, which reads the nearest `package.json`.
2. `pnpm install` in this directory **never installed this app**. pnpm
walks *up* for a workspace root, found the repo's, and installed all 70
monorepo projects (4645 packages) while leaving this directory with no
`node_modules` — the next command failing as `sh: 1: eslint: not found`.
So the app that ships its own lockfile was uninstallable by its own
documented instruction. Fixed by giving it its own
`pnpm-workspace.yaml`; `ignore-workspace=true` in an `.npmrc` does
**not** work (CLI-only in pnpm 10.10, measured).
- **The canary `overrides` now live in `pnpm-workspace.yaml`**, their
supported home, as a side effect of that fix. They no longer depend on
`packageManager: pnpm@10.10.0` being the version that still reads
`package.json` — which matters because `@ag-ui/core`, `@ag-ui/encoder`
and `@ag-ui/proto` are pinned nowhere else and a released `@ag-ui/core`
rejects `SUBAGENT_*` in the HTTP transport.
- **All ten beats walked in a browser**, including 3d — which was
broken, for the reason in *Correctness bugs* above.
- **`./run-demo.sh` starts a complete demo.** It now brings up the
Python agent between the compose wait and `pnpm dev`, guarded on
`/health` so a re-run reuses a live one, and dies with `(cd agent && uv
sync)` when the venv is missing. `./stop-demo.sh` had the mirror gap and
now stops it too — that one mattered more than it looks: the start
script *reuses* a live :8124, so an orphan surviving teardown is
silently adopted by the next cold start, serving whatever code it was
launched with.
- **The agent's Python deps are pinned and locked.** They were not, and
the JS half was — so a colleague's fresh `uv sync` resolved the
*release* `ag-ui-langgraph 0.0.43`, which does not accept
`emit_subagent_events` and ships no subagent symbols. That is this PR's
headline feature, and it would have failed silently: `main.py` sets the
flag as an attribute, so the assignment succeeds against an object
nobody reads and the service starts clean. Now `==` pins plus a
committed `agent/uv.lock`, verified by building a venv from only the
tracked files and replaying a captured Q2-with-PDF payload through it.
- **The README told people to install from the repo root** "as a
workspace package". It is deliberately not one, so a root install did
nothing for this app — and installing in the right place did nothing
either, until the `pnpm-workspace.yaml` above.
## Relationship to #6501
Based on `b94e4bfb5d`, the last commit before Codex appears, which is
**not on `main`**. So this PR carries **9 commits: the 4 here plus the 5
foundation commits it shares with #6501** — harness types + fixture,
prompt/workspace, the OFFSITE-to-fixture invariant guard, the
summary-shape/filing-contract fix, and `POST /transactions`. Whichever
merges first shrinks the other. #6501 and #6565 are deliberately
untouched.
## A demo-design question, not a bug
The fixture's merchant names are invented, so `Cardinal & Ash` — the
prompt's own worked example of *"could be a restaurant or a law firm,
find out"* — cannot be resolved by real web search and stays `unclear`
alongside `Bluebonnet Provisions`. The beat's headline claim is that the
agent researches every merchant; real merchant names in the CSV would
make that land harder.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01JRubZT6AS6LCGkcE2KzcfA
|
||
|
|
6623dcfd4f |
fix(reskinnable-demo): make the app its own pnpm root
`pnpm install` in this directory did not install this app. Leaving the root workspace's member list was only half of it: pnpm walks UP from the cwd for a workspace root, found the repo's, and installed THAT — measured in CI (run 32398378642), "Scope: all 70 workspace projects", 4645 packages resolved for the monorepo, and this app left with no node_modules. The next command then failed as `sh: 1: eslint: not found`, which reads as a broken toolchain rather than an install that went elsewhere. A `pnpm-workspace.yaml` here stops the walk. `ignore-workspace=true` in an `.npmrc` does not — it is CLI-only in pnpm 10.10 (measured: the Scope line was unchanged), so the alternative would have been a flag every human and job had to remember. The five canary `overrides` move into that file, their supported home. They stay duplicated in `package.json`'s `pnpm` field for now, which is read only because this app pins `packageManager: pnpm@10.10.0` — three of the five (`@ag-ui/core`, `@ag-ui/encoder`, `@ag-ui/proto`) are pinned nowhere else, and a released `@ag-ui/core` rejects SUBAGENT_* events in the HTTP transport, so a packageManager bump would have silently killed the harness console. Verified read-only: `pnpm install --frozen-lockfile --lockfile-only` in this directory resolves the single project, satisfies the committed lockfile, and leaves it byte-identical. |
||
|
|
cf9de905ac |
fix(reskinnable-demo): pin and lock the agent's Python canaries
A colleague cloning this branch could not reproduce the demo. The JS half
is pinned exactly — this app left the root pnpm workspace and ships its
own `pnpm-lock.yaml` with `@ag-ui/client 0.0.59-canary.1786716392.0` — but
the Python half pinned nothing: `ag-ui-langgraph>=0.0.43` and no
`uv.lock`, so a fresh `uv sync` resolved the RELEASE.
Measured, on the release that `>=0.0.43` actually selects:
ag-ui-langgraph==0.0.43
emit_subagent_events accepted by LangGraphAgent.__init__: False
subagent symbols in ag_ui.core: NONE
That is this branch's headline feature — the streaming CLI console — and
it would have failed SILENTLY. `main.py` sets `emit_subagent_events` as an
attribute (copilotkit's subclass takes only four kwargs), so on a release
without the feature the assignment succeeds, lands on an object nobody
reads, and the service starts clean. No `subagentRunId` reaches the
browser, the console cannot separate the harness's work from the parent's,
and a reopened thread collapses a multi-minute run to one tool message.
Every gate stays green.
So `ag-ui-langgraph` and `ag-ui-protocol` are now `==` pins, and
`agent/uv.lock` is committed. `ag-ui-protocol` is pinned as a DIRECT
dependency although nothing imports it by name: it carries the SUBAGENT_*
event types and the adapter asks only for `>=0.1.15`, so left transitive
it resolves the release and undoes the other pin.
README: the quick start said `pnpm install # from the repo root — this is a
workspace package`. It is not one — it is absent from
`pnpm-workspace.yaml`, deliberately, so the canary line cannot leak into
the rest of the monorepo. A root install therefore installs nothing for
this app, which is a confusing first five minutes for anyone who reads it
and follows it.
Verified by cold start rather than by inspection: copied ONLY the files
git tracks (the five .py modules, pyproject.toml, the new uv.lock) into an
empty directory, ran `uv sync --frozen`, and got
ag-ui-langgraph 0.0.43.dev1786716392 with `emit_subagent_events accepted:
True` and the five SUBAGENT symbols present. Then booted that venv on a
spare port and replayed the browser's captured Q2-with-PDF payload
through it: RUN_FINISHED, with createReport carrying the invoice's real
line items. The live stack was not touched.
Reskin skill: checked, no impact. Its install/verify step is `pnpm dev`
inside this app, which is correct either way; the root-vs-here distinction
is a README concern and the skill never mentions the workspace.
|
||
|
|
ffbb01be08 |
fix(reskinnable-demo): stop banking's agent in stop-demo.sh
The teardown mirror of the previous commit. `./stop-demo.sh` stopped the
dev server, the docker stack and the native TEI — never :8124 — so
banking's Python agent survived every teardown.
That leftover is not merely litter. `run-demo.sh` health-checks :8124
before starting (so a re-run reuses a live agent instead of colliding on
the port), which means the next cold start silently ADOPTS the orphan and
serves whatever code it was launched with. Edit `agent/`, re-run the
script, observe no change, conclude the edit did nothing.
No `--keep-agent` flag to match `--keep-tei`: TEI has that flag because it
is slow to warm, and the agent boots in seconds, so keeping it would only
reintroduce the failure above.
Also corrects two things in the same breath:
* The Ctrl-C claim I got backwards one commit ago. MEASURED this time,
with the same shell construct the script uses: SIGINT reaches the
foreground process group, which the backgrounded children are still in,
but a NON-INTERACTIVE shell sets background jobs to ignore SIGINT
(POSIX) — so only the exec'd dev server dies (exit=-2) and the stack,
TEI and agent all survive. `nohup` is not what saves them; that covers
SIGHUP, a different signal. Both scripts and the README now say this.
* `ok "docker stack down${PURGE:+ (volumes removed)}"` printed "(volumes
removed)" on EVERY teardown, because the flag holds the string "0" when
unset and `:+` expands on non-empty. The action was always right
(`--volumes` is gated on `-eq 1`) — verified: the postgres/redis/minio
volumes are still there after a flagless run — but the line told anyone
reading it that their seeded data had just been deleted.
Verified with a full cycle through both scripts: `./stop-demo.sh
--keep-tei` reported the agent stopped and left the volumes in place, then
`./run-demo.sh` came back with `banking agent ready (200)` and `stack
healthy`. The idempotency guard was exercised against the live service and
reports "already up" rather than starting a second uvicorn.
Reskin skill: checked, no impact. It documents authoring a skin, not
running the stack; its one launcher line got the note it needed in the
previous commit.
|
||
|
|
b96a9449de |
fix(reskinnable-demo): start banking's agent from run-demo.sh
`./run-demo.sh` brought up the embedder, the Intelligence stack and the dev server, then handed over an app whose DEFAULT skin could not answer a single message. Banking's agent is a Python service (`agent/`, :8124) and it is not a compose service, so it had to be launched by hand — and nothing said so: no line in the script, no line in the README, no line in any markdown in this tree. The failure mode is the expensive kind. Nothing errors at startup: the stack comes up healthy, the app boots, the dashboard renders off the REST ledger, every pill is present. Only sending a message fails, and the six in-process skins keep working, so the obvious reading is "my machine is fine, the demo is broken". The script now starts the agent between the compose wait and `pnpm dev`, guarded on `/health` so a re-run reuses a live one instead of colliding on the port, and dies with `(cd agent && uv sync)` when the venv is missing — the same shape as the native-TEI branch above it. Also corrects the Ctrl-C line, which claimed Ctrl-C "stops only the dev server". Measured: the docker stack survives, and the dev server, the native TEI and now the agent all go down with the script. README: the quick start said `pnpm dev` and described OSS mode as needing only `OPENAI_API_KEY`. True for six skins, false for the default one. It now starts the agent too and says why the whole agent lives out of process. Reskin skill: updated, one sentence in the Verification list's step 2. A skin author runs `pnpm dev`, gets redirected from `/` to banking, sends a test message to see if anything works, and gets silence — then debugs their own registration. The skill now points them at `/<their-id>` or `./run-demo.sh`. Verified by doing it: stopped the dev server and the agent, re-ran the script, and it reported `banking agent ready (200)` and `stack healthy` without touching the warm TEI. Then walked beat 3d in the browser — the Q2 pill filed a report citing "the Meridian Creative Agency invoice from page 1", i.e. the model read the attached PDF. |
||
|
|
11838db020 |
fix(reskinnable-demo): carry an attached PDF to the model as a file block
Beat 3d was dead on banking: clicking the Q2 pill staged the invoice,
sent the message, and then nothing happened at all — no report, no error,
no failed message in the transcript.
`ag_ui_langgraph` hands every attachment to the model as an `image_url`
block, documents included, so the PDF was rejected before the first
token:
openai.BadRequestError: 400 - Invalid MIME type. Only image types are
supported. (code: invalid_image_format)
The exception is raised inside the model node, which kills the SSE
stream. The runtime sees `RUN_ERROR: terminated` with no cause and the
browser renders nothing. Worse, the crashed run is still checkpointed, so
every LATER message on that thread replays the rejected content and dies
the same way: one click on the pill killed the whole conversation, and
only restarting this service cleared it (`MemorySaver` is in-process).
`_repair_document_attachments` rewrites those blocks into LangChain
standard `file` blocks before the model call. It walks every message, not
just the newest, because the offending content comes back from the
checkpoint on later turns.
STOPGAP, not a design. The real fix is open upstream as
ag-ui-protocol/ag-ui#2476 (both adapters, plus the return leg so a
non-image attachment survives MESSAGES_SNAPSHOT). This service installs
the adapter from PyPI, so that fix cannot reach this venv until it is
published; the middleware's header says when to delete it.
No test: `agent/` has no python test runner, and standing one up for code
whose deletion is already scheduled is the wrong trade. The durable
tests — PDF, audio, video, filename, round-trip, legacy binary — ship
with the upstream PR instead. Verified here by replaying the browser's
real captured run payload against the service: 400 before, RUN_FINISHED
with `createReport` carrying the invoice's line items after.
Reskin skill: checked, no impact. Its beat-3d guidance is entirely the
CLIENT half (staging into the composer, the `AttachmentFailureCause`
union, do not copy `@/shell/attach`), and it names no model-side
conversion. A skin authored from the skill gets a `BuiltInAgent`, whose
converter already maps documents onto file parts — banking is the only
skin whose agent is a LangGraph service, so this failure is unreachable
from the skill's path.
Gates: lint clean, typecheck clean, 2460 unit tests pass. `pnpm build`
deliberately not run — the diff touches no TypeScript, and `next build`
would clobber the `.next` state of the dev server currently serving the
demo.
|
||
|
|
0163beab8e |
feat(reskinnable-demo): stream the harness into a CLI console via AG-UI subagents
Makes the offsite-expenses beat legible while it runs, and gives it its own
model, by taking the AG-UI subagent surface from the canary line.
## The expense analyst is now a real subagent
banking gpt-5.4, temp 0
│ banking's prompt; browser frontend tools; Intelligence memory tools
└─ expense-analyst gpt-5.6-sol, reasoning_effort=high
│ sandboxed shell, submit_expense_report
└─ merchant-researcher gpt-5.4, one per merchant, Tavily
Previously the beat was a section of banking's prompt, which left nowhere to put
per-beat configuration: model, effort and recursion limit are all agent-level and
there was one agent. It also meant banking's ~21,000-character rulebook rode
every one of the ~20 model calls the run makes, re-sending rules about markdown
tables while the agent read a CSV.
Reached as a `CompiledSubAgent` because a raw `SubAgent` spec has no `subagents`
field and this one needs its own — the per-merchant fan-out is a headline of the
beat, and a flat subagent could only research serially. Verified nesting
survives: a probe showed the analyst's `task` dispatches, the researchers'
`search_merchant` calls and the final report tool all reaching `astream_events`.
`gpt-5.6-sol` additionally needs `use_responses_api=True`: with function tools
and `reasoning_effort` it 400s on /v1/chat/completions. The first probe missed
that by asking the model a question with NO tools bound — a model probe for an
agent has to bind one.
## The console: one CLI window, streaming
`shell/subagents/subagent-activity.tsx` subscribes to the agent's event stream
and folds it into console lines. Reading `agent.messages` (the previous design)
was wrong twice over: messages materialise at the `MESSAGES_SNAPSHOT`, two per
run, so the pane sat still for minutes and then filled at once; and persisted
messages carry no `subagentRunId`, so the harness's narration could not be told
from banking's own reply.
The fold is pure and idempotent — every line keyed by the id of the thing that
produced it — so the same code serves the live subscription and a replay of the
thread's stored events when a conversation is reopened.
Three heuristics are deleted, each replaced by identity the protocol already
carried:
- the console's "first tool call" anchor -> the run's first `task` call, from
MESSAGE order (durable; the
event-derived version rendered
one console per delegation on a
restored thread — six, measured)
- `CONSOLE_TOOL_NAMES` suppression list -> `subagentRunId` presence
- `disable_streaming` on the researchers -> the canary's per-lane state
`shell/subagents/subagent-message-filter.tsx` keeps subagent narration out of the
conversation. It suppresses the PROSE and keeps the TOOL CALLS: an agent
routinely narrates and calls a tool in one message, and returning null for the
whole message hid the REPORT CARD — the run looked perfect and ended with nothing
to show. Shell-level and inert for a skin whose agent has no subagents.
## Canary stack, contained to this app
The subagent surface only exists on the canary line, and a released
`@ag-ui/client` <= 0.0.57 rejects `SUBAGENT_*` events in the HTTP transport
before any middleware runs, killing the stream. So the demo leaves the root pnpm
workspace and ships its own lockfile, pinning `@copilotkit/* 1.68.3-canary` and
`@ag-ui/* 0.0.59-canary` locally instead of imposing an unreleased protocol on
every package in the monorepo.
A 1.62.2-based canary was tried first and could not compile the app: it silently
rewound the CopilotKit API five minors under a demo written against 1.67.1, and
`OpenGenerativeUIActivityRenderer` (a public `/v2` export since ~1.63) was the
first thing to break. `workspace:*` is not a version, so the app had no recorded
lower bound on the API it needs.
KNOWN GAP, deliberately not fixed here: Nx discovers projects THROUGH the pnpm
workspace (there is no `workspaceLayout` in `nx.json`), so leaving it also
removes the demo from the repo-wide `nx run-many -t build` and `-t check-types`
sweeps. Verified — `nx show project deep-agents` and the other standalone
showcases return "Could not find project". No workflow names this demo, so it is
currently unbuilt and untype-checked in CI and needs its own job. Run the four
gates locally until that lands. Documented in `pnpm-workspace.yaml`.
## Fixes
- The run clock is keyed per run and read through an injected `ToolRuntime`
instead of taking the oldest open stamp across the process. Model calls AFTER
the report re-stamped the clock and that leftover became the next run's start:
a two-minute run reported 333s. Now 86s reported against 98s wall clock — the
gap is thread-naming and delegation, before the analyst's first model call,
which is what the tile claims to measure.
- `merchantKind` non-answers are rejected on the leading token, on hedging
language, and over 40 characters. With no search tool the model wrote a bare
"unclear"; with Tavily live it hedges in prose ("unknown (likely
bookbindery/bookshop retail, but not established for this exact merchant)"),
which an exact-match filter passed into a 60-character label glued to the
merchant name.
- `vitest` no longer externalises `@copilotkit/*`. Installing them from npm moved
`src/app/layout.tsx`'s stylesheet import under `node_modules/.pnpm/`, where
Node's ESM loader threw `Unknown file extension ".css"` and took out 16 suites
while naming a stylesheet nobody had touched.
- `agent/main.py` reads the demo's `.env` as well as its own, so `TAVILY_API_KEY`
works wherever an operator puts it. Two env files to keep in sync is a trap
whose failure mode is "the agent ignores a key that is plainly sitting in .env".
## Upstream finding (reported separately, not fixed here)
`@copilotkit/runtime` drops `subagentRunId` when persisting messages: 2888 of
3026 stream events carry it, 0 of 53 persisted messages do. Reproduced with
Intelligence removed entirely, so it is the runtime's message shape rather than
the platform store — and `@copilotkit/runtime`'s dist contains no occurrence of
the field at all, while `@ag-ui/core`, `ag-ui-protocol` and `@copilotkit/core`
all model it. One field threaded through would let the console rebuild from
message history and delete the event-replay seeding added here.
`CLAUDE.md`'s appended block is generated by `next dev`
(`next/dist/server/lib/generate-agent-files.js`) and committed per its own
instruction to keep the tree clean.
Gates: lint 0, typecheck 0, test:unit 2460 passed across 216 files, build 0.
Measured end to end in Intelligence mode: 14 rows, 9 merchants researched, 6
charges filed with ids read out of real 201 bodies, totals reconciling against
their own rows, 3220 events with 6 SUBAGENT_STARTED/FINISHED pairs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JRubZT6AS6LCGkcE2KzcfA
|
||
|
|
a29bf0720a |
feat(reskinnable-demo): add the offsite-expenses pill and harden the report fields
THE PILL. The offsite-expenses beat had no entry point in the UI. The agent was
wired, the service was up, all four gates were green, and every verification I
ran POSTed directly at the runtime — so the beat was fully working and completely
unreachable for anyone opening the app. It is the ninth and last pill, after the
AWS charge, because it is the only one that does not answer in seconds.
`suggestions.test.ts` now pins it. That guard is not decoration: this pill has no
type referencing it, no matcher and no route, so nothing else in the tree
notices if it is dropped or retitled. It checks REACHABILITY, which is the
property nothing else was holding.
MERCHANT KIND HARDENING. `merchantKind` is a KIND ("hotel", "pharmacy") that the
report card prints inline beside the merchant name, where it has room for about
two words. The previous filter rejected exact matches of "unclear"/"unknown",
which was enough when there was no search tool and the model wrote a bare
"unclear". With Tavily live it hedges in prose instead — measured:
"unknown (likely wellness-related business)" and "unknown (likely
bookbindery/bookshop retail, but not established for this exact merchant)". Both
sailed through and would have rendered as a 60-character label glued to the
merchant name.
Now rejects on the leading token, on hedging language anywhere, and on anything
over 40 characters. The length cap is the backstop: it encodes the actual
constraint rather than a list of phrasings already observed, so it catches the
next wording nobody predicted. A measured run after the change leaked none, with
a longest surviving kind of 23 chars.
ONE ENV FILE. `agent/main.py` now loads the demo's `.env` in addition to its own,
with `agent/.env` winning on conflict. The app and this service need the same
keys, and asking an operator to keep two files in sync is a trap whose failure
mode is "the agent ignores a key that is plainly sitting in .env" — which is
exactly what happened when TAVILY_API_KEY was added to the demo's `.env` and the
service, reading only its own, never saw it.
`.env.example` documents TAVILY_API_KEY and BANKING_AGENT_URL, including what a
keyless run actually looks like: the research subagents are told plainly that no
search happened and instructed to report "could not establish" rather than
guess, so the run completes with several rows marked `unclear` and a "merchants
researched" tile reading 0. Nothing errors, which is why it needs writing down.
Measured with Tavily live: 14 rows, 10 merchants researched, 10 parallel
subagents, 31s, totals reconciling against their own rows, 7 charges filed with
ids read out of real 201 bodies, zero non-answer kinds. Research also CHANGES a
decision rather than only labelling one — The Copper Room resolved to
`restaurant_lounge` and moved from `unclear` to `expensable`.
Gates: lint 0, typecheck 0, test:unit 2460 passed across 216 files, build 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JRubZT6AS6LCGkcE2KzcfA
|
||
|
|
e4eb96f8d7 |
feat(reskinnable-demo): make banking's agent the Python deep agent
Banking's `agent.ts` stops returning a `BuiltInAgent` and returns an `HttpAgent` pointed at the Python deep agent instead. Six skins still run in-process; banking is the one that does not. WHY THE WHOLE AGENT, not just the offsite-expenses beat. Threads are scoped per agent — `listThreads` takes `agentId` as a REQUIRED parameter, and measured against the running app the two lists are disjoint (banking 46 threads, banking-expenses 10). The v2 runtime has no handoff mechanism, and `defineTool`'s `execute` takes `(args)` with no emitter, so a tool cannot stream a multi-minute run. Under a second agent id a presenter could start the analysis, switch threads, and have nothing to come back to. One conversation list means one agent. The expense task spec is appended to banking's own prompt as a CONDITIONAL job rather than a second identity — it opens by saying it applies only when the user hands over a statement, and to ignore it otherwise. Ported to Python: - `agent/prompt.py` — banking's system prompt, extracted from the TS template literal programmatically and asserted equal, not paraphrased. 21,208 chars carrying 56 distinct rules; a dropped rule silently breaks a beat that still looks like it works. - `agent/report.py` — `render_report` and the a2ui operations builder. Component order, ids and the `columns` arithmetic match the TS builder; the unique surface suffix uses uuid rather than a timestamp so two reports in the same millisecond cannot collide. Carried over from the TS `BuiltInAgent` because each had a reason written beside it: the non-mini model (the teach-and-recall arc routes unreliably on mini) and `temperature: 0` (tool routing must be deterministic). Also sets `emit_raw_events = False`. It defaults to True, which piggybacks LangChain's internal events onto the AG-UI stream: a measured run streamed 27,950,261 bytes, and the same run with it off streamed 374,086 — a 75x reduction with an identical report. This matters more than it looks because the thread PERSISTS those events for replay, and leaving a running thread and coming back to it is the point of this beat. Nothing downstream reads RAW. Verified against the running app, not reasoned about: - `/info` reports banking as HttpAgent and the other six as BuiltInAgent. - Canvas beat: `render_report` -> TOOL_CALL_RESULT -> the a2ui middleware emits `activityType: a2ui-surface` with createSurface + updateComponents. - Frontend-tool beat: given showTransactions + showPendingApprovals the agent picks showTransactions, emits no result (the client executes it) and emits no prose — the prompt's "the rendered list is the single source of truth" rule surviving the port. - Intelligence mode: the run is handed the browser's tool AND recall_memory / save_memory / forget_memory / copilotkit_knowledge_base_shell. - Durability: a run whose client disconnected at 8s finished unattended and left a replayable thread. Removes `expenses-agent.ts` and the non-skin `banking-expenses` registry key; banking's own id now serves both. `AgentRegistration.createAgent`'s comment is rewritten — it described a second remote key that no longer exists — and now gives a grep to derive the BuiltInAgent/HttpAgent split rather than asserting it. Gates: lint 0, typecheck 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JRubZT6AS6LCGkcE2KzcfA |
||
|
|
a6418bcc09 |
feat(reskinnable-demo): run the offsite-expenses beat on a LangChain deep agent
Replaces the Codex CLI subprocess with a Python LangChain deep agent
(`examples/showcases/reskinnable-demo/agent/`) reached over AG-UI: sandboxed
shell execution via deepagents' `LocalShellBackend`, parallel per-merchant
research subagents, no external harness binary, and our own API key.
Registered as `banking-expenses` in the server agent registry. That required
widening `AgentRegistration.createAgent` from `() => BuiltInAgent` to
`() => AbstractAgent` — the runtime's own `agents` option is
`Record<string, AbstractAgent>` and the v2 tree contains no
`instanceof BuiltInAgent` branch, so the demo's type was stricter than the
runtime it described.
Measured end to end against the real runtime: 58 `execute` calls, 7 concurrent
`task` subagents (peak concurrency 4/4 on a dedicated probe), RUN_FINISHED,
eight charges filed with ids read out of real 201 bodies, and 8221 replayable
AG-UI events persisted on the thread after the client disconnected 8s in.
Correctness fixes found by running it rather than reading it:
- `submit_expense_report` DERIVES the totals from the verdicts instead of
accepting them as arguments. A measured run had every per-row amount matching
the CSV while the headline totals came back $1.00 and $0.20 high — the model
authored them. The report card prints the total directly above the rows it is
the sum of, so tiles and rows must not be able to disagree.
- `amount` is coerced at the tool boundary; it arrived as a string on all 14
rows, which silently defeats the widget's currency formatting.
- `merchantKind` values that are non-answers ("unclear") are dropped rather than
rendered beside the merchant name as if they were findings.
- The recursion limit is set on the AGENT, not via `graph.with_config(...)`,
which the AG-UI adapter drops — the run completed the whole analysis and then
died at LangGraph's default of 25 supersteps.
- A `clone()` override works around `copilotkit` 0.1.95 being incompatible with
`ag-ui-langgraph` >= 0.0.42, whose base `clone()` (called per request) passes
kwargs the SDK subclass does not accept. Every request 500s without it.
Deletes the superseded Node-side harness scaffolding: `prompt.ts` (the task
specification now lives in the Python agent's system prompt), `workspace.ts`
(the sandbox owns the workspace) and their tests. `types.ts` stays — it is the
shared vocabulary the report card and the tool renderer both import.
Also drops the three unused `@tanstack/*` dependencies.
Reskin-skill impact: checked. `.claude/skills/reskin/` documents the `Skin`
contract, and no field of it changed — `banking-expenses` is NOT a skin and must
never look like one. It is absent from `registry.ts`, `skinIds`,
`skinIdentities` and `LINTED_SKIN_IDS`, and has no route, theme or identity. The
one documented thing that did change is `AgentRegistration.createAgent`'s type,
which SKILL.md describes in its registration section; that is updated in the
follow-up commit that makes banking itself a deep agent.
Gates: lint 0, typecheck 0, test:unit 2458 passed across 217 files, build 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JRubZT6AS6LCGkcE2KzcfA
|
||
|
|
6f58b2c6a4 |
fix(runtime): unify the Intelligence key name and publish the wiring (refs OSS-881)
Three names for one value were live in CopilotKit's own documentation, and following the wrong one with a CLI-provisioned project yields an undefined key: - `INTELLIGENCE_API_KEY` — what `copilotkit project select` writes, used by all 34 integration examples and the docs site. - `COPILOTKIT_INTELLIGENCE_API_KEY` — the seven Channels package READMEs and the packaged skills. Nothing ever read it. - `COPILOTKIT_API_KEY` — the Slack and Teams examples, and the TSDoc on `CopilotKitIntelligence` itself, which is what an IDE shows on hover. `INTELLIGENCE_API_KEY` wins, because it is the name the CLI provisions and changing it would break every scaffolded project in the wild. `COPILOTKIT_INTELLIGENCE_API_KEY` is retired outright — no code read it. `COPILOTKIT_API_KEY` stays readable as a deprecated alias in the two examples that consume it, so an existing `.env` keeps working, and is documented as deprecated everywhere it appears. The skills reference also documented `organizationId`, sourced from a fourth and fifth env name, as a `CopilotKitIntelligence` option. It is not one: `CopilotKitIntelligenceConfig` has no such field, so the copy-pasteable sample it appeared in would not compile. Removed from the samples, and the prose that told readers to fetch a value for it corrected. The Intelligence wiring itself was published only inside `node_modules/@copilotkit/runtime/skills/`, and the only docs pages showing `CopilotKitIntelligence` were the two Channels frontends — so a developer on the plain web path had no page to reach it from. Adds `/premium/connect-your-runtime`, which covers the wiring, how to confirm the credential is actually consumed, and the self-hosted two-URL rule. `scripts/validate-intelligence-env-names.ts` keeps this from drifting back. It runs unfiltered in CI on purpose: the two workflows that would otherwise cover it filter paths, and static/quality ignores `examples/**` — exactly where the deprecated alias lives. |
||
|
|
367e7bda15 | feat(react-core): add local message inspector links | ||
|
|
38b013c5a0 | fix(showcase): cap public Claude demo traffic | ||
|
|
c81c6e2535 | fix: harden Strands TypeScript request boundaries | ||
|
|
a1ca0150b8 | feat: configure Claude cookbook model | ||
|
|
20e481b749 | fix: make Strands TypeScript starter smokeable | ||
|
|
bd91313517 | feat: add AWS Strands TypeScript starter | ||
|
|
5b9776a1a0 | chore(examples): update CLI starter package versions | ||
|
|
b94e4bfb5d |
feat(reskinnable-demo): POST transactions so the harness can file charges
Checked the reskin skill: no impact. This adds one banking-only REST route and one store adder; it touches no Skin contract field, no shell file, no lint rule and no gate the skill names. |
||
|
|
ccfcc7e68a | fix(reskinnable-demo): validate harness summary shape and correct the filing contract | ||
|
|
14b0ee2bff | test(reskinnable-demo): guard the OFFSITE-to-fixture invariant | ||
|
|
f3b29d36fa | feat(reskinnable-demo): harness prompt and scratch workspace | ||
|
|
d0a3706a65 | feat(reskinnable-demo): harness types + offsite expense fixture | ||
|
|
12d315d09c | docs: publish Claude cookbook live demo | ||
|
|
73fce0b0cb | fix: harden Claude cookbook deployment | ||
|
|
fa13d52502 | Merge branch 'main' into codex/crewai-full-d6 | ||
|
|
335209b39a | Merge branch 'main' into feat/reskinnable-demo-beat-parity | ||
|
|
3bf6e30e9a |
feat(reskinnable-demo): open Aeronova's demo on a flight-cadence chart
Beat 1 is the demo's first move, and it was answering "how do my trips look?"
with a trip wall. It now answers "How often do I fly?" with a picture: every
trip on the account laid out on a day scale, a today divider, the disrupted
ones called out, and the average gap between trips.
WHY A STRIP AND NOT BARS. The account holds seven trips across about ten weeks.
Monthly bars collapse that to three columns, hide which trips are disrupted, and
read as a stub on a projector. The strip uses all seven, and the GAPS are the
actual answer to "how often" -- which is why the summary quotes the average gap
rather than a count.
MEASURED against the shipped seed and the app's own clock, pinned in
data/flight-cadence.test.ts:
7 markers - 0 flown - 7 ahead - 2 disrupted - average gap 11 days
Note the clock. This app runs on a FIXED demo clock (`store.ts` publishes
`now: SEED_NOW`, 2026-07-14), not the wall clock, so every seeded trip is AHEAD
and the strip is forward-looking. "About every 11 days" is therefore the honest
answer, and it is a better one than any count of flights behind us.
Structure:
- `data/flight-cadence.ts` -- pure, no React, no Date. Takes `now` as an
argument and reads days out of the ISO string by civil-day arithmetic.
Both rules are load-bearing here: a `Date.now()` would put the divider in
one place on the server and another in the browser (the hydration class
this branch already chased once), and `new Date(iso)` on a string carrying
an airport's UTC offset re-expresses a 23:00 Lima departure as the next
day. `components/local-clock.ts` makes the same argument for display; this
is its data-side counterpart.
- `components/flight-cadence-chart.tsx` -- paints only. Receives `position`
already normalised to 0..1, so there is no date maths in a component where
nothing could unit-test it.
- `showFlightCadence` registered with `useComponent`, NOT `useFrontendTool`:
only a component replays out of thread history, which is what beat 2 asks
the audience to reload and see.
Three details worth keeping:
- Only flights someone HOLDS a booking on are drawn. The ledger's `flights`
also carries the rebooking candidates, and counting offers would inflate
the answer to the question being asked.
- An unreadable departure is DROPPED and counted, never placed at day 0. A
marker at the wrong point asserts a cadence that is false while still
looking like data.
- The helper takes a structural `{ id, flightId }` rather than `Booking`, so
it accepts the client's `BookingDto` without a cast -- and therefore cannot
see `waiverGround`, beat 6's sixth leak channel.
Tests: 12 on the helper (including the offset case, the drop-don't-relocate
case, and the seed figures), 7 on the component (every marker by flight number,
the cancelled trip named in WORDS and not only as a coloured dot, summary and
picture derived from one object), and `beat-1.test.ts` pinning the contract --
pill wording, registration via useComponent rather than useFrontendTool, the
prompt naming the tool and demanding prose alongside the chart, and no `Date`
in either new file.
Also uses airline's existing amber/negative tones from `trip-list.tsx` rather
than inventing a `warn` design token -- there isn't one; the vocabulary is
brand / positive / negative.
Gates: lint clean, tsc 0 errors, 214 files / 2448 tests, build exit 0.
--no-verify for the reason recorded in
|
||
|
|
e3d9c911a1 |
chore(reskinnable-demo): add a typecheck script and point the docs at it
`tsc --noEmit` is the only command in this tree that type-checks the 211 test
files -- `next build` visits only what the app's module graph reaches, and
vitest does not type-check at all. The docs already said so and told readers to
run `pnpm exec tsc --noEmit`; this makes it a script, so the command people are
told to run is one word and shows up in `package.json` beside the others.
Note this is a NEW convention here, not a missing piece being restored: no
package in this monorepo defines a typecheck script, so build-time checking is
the house norm and test files fall outside it everywhere, not just in this app.
This closes the DISCOVERABILITY half of that gap for this app only.
It does NOT make the check enforced. Nothing runs it unless a person or an
agent chooses to. Wiring it into CI is a repo-wide decision with real CI cost
across 45 packages and is deliberately not taken here.
Earned: a slot reported three green gates (lint, test:unit, build) and still
shipped a TS2352 in a test file, because none of those three look at test
files.
8 doc references updated from `pnpm exec tsc --noEmit` to `pnpm typecheck`
across README.md, CLAUDE.md, SKILL.md and demo-beats.md. Verified the script
runs clean under the new name.
--no-verify for the reason recorded in
|
||
|
|
b7c144d94a |
fix(reskinnable-demo): make Rowan's queue pill move the user, not describe the move
Reported from the running demo: clicking "Oldest pending requests" often got a
prose reply --
Confirm the levers and I'll take you there: **pending** only, sorted by
**oldest first**, top **10**.
-- and nothing else. No tool call, no confirm card, no navigation. Beat 3c
failing while looking like it worked: the answer is correct and well formatted,
and "that was a maneuver, not a link" goes unproven.
ROOT CAUSE, and why the model was not disobeying. It was obeying a sentence
that reads two ways. `showRequestQueue`'s description said "Confirm the levers
with them first" without saying WHERE that happens. The HITL card IS the
confirmation -- it lists the levers and waits -- but nothing said so, so
confirming in chat satisfied the instruction as written. Two other things left
it with no reason to prefer the tool:
- `people/agent.ts` never mentioned `showRequestQueue`, or navigation at all.
Nothing connected "show me the oldest requests" to a tool call.
- `top` was `.optional()`, and an optional lever invites the model to go and
ask for the missing value first.
`logistics` hit this and was fixed; `people` never was, because nothing pinned
the fix. This applies logistics' shape:
- the description now says the card confirms, and says not to confirm in prose;
- the prompt gains MOVE THEM, DON'T DESCRIBE THE MOVE, naming the tool and the
"in front of ... rather than describe one" framing;
- every lever is REQUIRED, with 0 as the "no limit" sentinel. That needs no
page change: the render sets the `top` query param only `if (args?.top)`,
which is falsy at 0, so the page applies no limit.
`beat-3c.test.ts` pins all three. It is source-level on purpose -- what went
wrong is what the MODEL was told, which lives in `description` and the prompt,
and nothing else in this app checks either. Mutation-verified: reverting `top`
to `.optional()` turns it red.
NOT changed: commerce. Its `top` is `.int().positive().optional()` with a stated
reason -- omitting it is exactly what its `parseTopLever` honours -- so that is a
different, documented design rather than the same defect. Its prompt already
names its nav tool.
Reskin skill impact: YES, fixed here. demo-beats.md ss 3c now records the
two-readings failure, the quoted prose it produces, both halves of the close
(description AND prompt), and the note that commerce's optional `top` is
deliberate so nobody copies the wrong shape.
Gates: lint clean, 211 files / 2420 tests passing. Committed with --no-verify
for the reason recorded in
|
||
|
|
14f90410ff |
docs(examples): fix stale clone paths in v1 example READMEs (#6471)
<!-- Thank you for sending the PR! We appreciate you spending the time to work on these changes. Help us understand your motivation by explaining why you decided to make this change. **Please PLEASE reach out to us first before starting any significant work on new or existing features.** By the time you've gotten here, you're looking at creating a pull request so hopefully we're not too late. We love community contributions! That said, we want to make sure we're all on the same page before you start. Investing a lot of time and effort just to find out it doesn't align with the upstream project feels awful, and we don't want that to happen. It also helps to make sure the work you're planning isn't already in progress. As described in our contributing guide, please file an issue first: https://github.com/ag-ui-protocol/ag-ui/issues Or, reach out to us on Discord: https://discord.com/invite/6dffbvGU3D You can learn more about contributing to copilotkit here: https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md Happy contributing! --> ## What does this PR do? Fixes three `examples/v1/*` README files whose "Clone the repository" step `cd`s into a directory that no longer exists (leftover from when examples were reorganized under `examples/v1/`). Following the README as written fails at the first step with `cd: no such file or directory`. - `examples/v1/chat-with-your-data/README.md`: `cd CopilotKit/examples/copilot-chat-with-your-data` → `cd CopilotKit/examples/v1/chat-with-your-data` - `examples/v1/form-filling/README.md`: `cd CopilotKit/examples/copilot-form-filling` → `cd CopilotKit/examples/v1/form-filling` - `examples/v1/state-machine/README.md`: `cd CopilotKit/examples/copilot-state-machine` → `cd CopilotKit/examples/v1/state-machine` This matches the already-correct format in `examples/v1/travel/README.md`. Docs-only change, no code/behavior affected. ## Related PRs and Issues - N/A ## Checklist - [X] I have read the [Contribution Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md) - [ ] If the PR changes or adds functionality, I have updated the relevant documentation - [X] "Allow edits by maintainers" is checked (lets us help iterate on your PR directly — faster turnaround for everyone) |
||
|
|
bd09c3d790 |
chore(examples): drop grok showcase lockfile
A 13.9k-line new file trips the fork-PR supply-chain heuristic (security_fork-pr-alert flags any added file over 5000 lines), and the job cannot post its explanation because fork tokens are read-only. Several showcases ship no lockfile; this one is not a pnpm workspace member, so nothing depends on it. |
||
|
|
fe0e7cf28f |
feat(examples): add grok-generative-ui showcase
grok-4.6 runs xAI's X Search server-side, then composes the answer out of real React components through five CopilotKit frontend tools. Every post rendered is a real post the model found. Registers next.config.ts in the build-config allowlist and adds the row to the examples index. |
||
|
|
9ceb88c596 | fix: bound Claude cookbook requests | ||
|
|
cd64abd2fa | fix: restrict Claude cookbook agent tools | ||
|
|
d7dd1bcfbe | docs(examples): fix stale clone paths in v1 example READMEs | ||
|
|
a3ee26b424 | Merge remote-tracking branch 'origin/main' into codex/crewai-full-d6 | ||
|
|
784f2e7529 |
docs(reskinnable-demo): retire the last "all six skins" claims after bookstore
The merge of main brought a seventh skin. These are the surviving count claims
outside the conflicted files, all of which the seventh skin falsified:
- `airline` was described as "the one PASSENGER-FACING skin"; `bookstore` is
also customer-facing, so it now names the pair.
- demo-beats.md still told a skin author "every registered skin is
demo-complete, so there is no partial precedent to copy". Bookstore IS a
partial precedent, deliberately, so the sentence now says so.
- Eight in-skin comments said "all six skins" while describing something that
is true of the WHOLE roster (the shared PDF primitive's coverage, the dark
treatment, and — load-bearing — the project-scope warning in three
seed-memories.ts files, where undercounting understates the blast radius of
a project-scoped sweep). All now say "every skin", which cannot rot.
Reskin-skill staleness check (CLAUDE.md standing rule): yes, demo-beats.md is
part of the skill and is corrected here.
Verified from examples/showcases/reskinnable-demo: `pnpm lint` clean,
`pnpm exec tsc --noEmit` 0 errors, and the roster/config drift guards plus the
touched skin tests pass (110 tests).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
6473cdcf9d |
feat(reskinnable-demo): merge main, and reconcile the docs with a seventh skin
Brings in `bookstore` and 31 other commits from main.
WHY THIS MERGE CONFLICTED IN SEVEN FILES. Both sides hand-maintained the
same roster. This branch had just rewritten the docs around four
conclusions that were true when written:
- `useData` has zero implementors
- no in-memory skin remains
- every registered skin is demo-complete
- there are six skins
`bookstore` falsifies all four: it sets `useData: useBookstoreData`, so the
optional hook has a live implementor and an in-memory skin exists again; it
ships intelligence/{seed,forget}-memories.ts but no teach loop, so it is not
demo-complete; and it is the seventh.
Neither side was wrong. The resolution is the union, and where a list or a
count was load-bearing it is now the command that derives it -- which is the
convention this branch adopted precisely because two hand-maintained copies
of one roster is what produced these conflicts.
The de-narration this branch applied is preserved: main's older phrasings
carried retrospective prose that was deliberately removed, and it has not
been reintroduced.
Registration verified rather than assumed -- bookstore is present in
LINTED_SKIN_IDS, skinIds, skinIdentities, SkinRegistry and agentRegistry.
That last one has no drift guard at all, so a missing key there fails only
when someone sends a chat message.
Gates on the merged tree: lint clean, `tsc --noEmit` 0 errors, 210 test files
/ 2414 tests passing (up from 197/2227 -- bookstore's own, nothing dropped),
build exit 0.
COMMITTED WITH --no-verify, DELIBERATELY, WITH THE USER'S APPROVAL.
The pre-commit hook was bypassed. That is normally forbidden here, so the
reason is recorded rather than left to be guessed:
- This branch's ENTIRE diff against main is inside
examples/showcases/reskinnable-demo. `git diff --name-only origin/main...HEAD`
lists nothing outside it.
- The hook fails on `@copilotkit/vue` -> CopilotThreadsDrawer.ssr.test.ts,
"does not eagerly evaluate the Lit element module when the package entry is
imported". That test fails STANDALONE on this machine
(`npx nx test @copilotkit/vue` -> 1 failed | 1073 passed, exit 1), with no
merge in progress and nothing of ours involved. It asserts a lazy-import
property but enforces it with a 5000ms wall-clock timeout, so it fails
whenever module resolution is slow rather than when Lit is actually
eagerly evaluated.
- This is simply the first commit on the branch to touch packages/*, so it is
the first to make `nx affected` run that suite. Ninety earlier commits
touched only the demo app and never triggered it.
What WAS verified on the merged tree, by hand, before committing:
pnpm lint clean
pnpm exec tsc --noEmit 0 errors
pnpm test:unit 210 files / 2414 tests passing
pnpm build exit 0
npx nx test @copilotkit/runtime 138 files passing
That last one only passes because of a second pre-existing breakage fixed
along the way: packages/runtime's better-sqlite3 binary was compiled against
NODE_MODULE_VERSION 137 (Node 24) while .nvmrc pins Node 22 (127), so every
SqliteAgentRunner test threw on load. `pnpm rebuild -r better-sqlite3` fixed
it. That fix is environmental and is not part of this commit.
Two follow-ups worth someone's time, neither blocking:
1. The vue SSR test should assert the property (module not evaluated) rather
than time the import.
2. Nothing in the repo pins the Node version for native rebuilds, so a
contributor who once ran a task under Node 24 silently poisons
better-sqlite3 for every later Node 22 run.
|
||
|
|
fb2aedb0e0 |
docs(reskinnable-demo): de-narrate the reskin skill and its guard comments
Second pass of the history sweep. The first cleaned CLAUDE.md and README.md; this finishes the reskin skill and the in-code comments that still recounted who hit a defect, when it was found, and how long it survived. Every rule, gate, command and checklist item is kept. What went is the narration around them — "it named only the first four skins for two releases", "caught by `eslint --print-config`, by hand, once", "drifted out of true three review rounds running", "it shipped that way once", "one CR pass found sixteen of them live", "measured in logistics", "each raised after the fact". Where a cut would have left a rule reading as arbitrary, the mechanism is restated in one present-tense clause instead: a hand-copied list rots silently and nothing fails when it is stale; flat-config `rules` are REPLACED, not merged, so a block silently drops every selector it does not restate; a schema leak is routinely line-wrapped, so a source-text guard never matches. Two stale cross-references fixed while in there: failure-modes.md quoted a CLAUDE.md sentence that the first pass removed, and claimed the roster-docs test header lists "two" known instances outside its doc set (it lists one). Skill impact, per the standing rule in CLAUDE.md: this change IS the skill, and it is prose-only — no contract field, link builder, lint rule, gate, beat mechanism, skin identity or file path changed, so no template or verification step needed a matching edit. The two doc properties `skin-roster-docs.test.ts` depends on were preserved deliberately: templates.md keeps "the six shipped skins" ahead of its brace glob, and SKILL.md keeps its "Six are registered —" id list, since both are what arm the brace-glob and valid-id-list rules. Gates: `pnpm lint`, `pnpm exec tsc --noEmit`, `pnpm test:unit` (197 files / 2227 tests) and `pnpm build` all green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6740613fb8 |
docs(reskinnable-demo): de-narrate in-code comments that recounted build history
Same cutting rule, applied only where a comment narrated what a past slot or
agent did rather than explaining the code: "a later slot owns that file", "as of
the beat-parity work", "two parallel agents each hand-edited this paragraph",
"this very paragraph did it once", "learned the hard way in the banking skin".
Every WHY stays; several are restated as present-tense properties (do not
reintroduce a client ticker, do not add a second seed of AV1423).
Four of these were also FACTUALLY STALE and are now correct:
- airline/attach-hotel-confirmation.ts claimed no pill carries
HOTEL_CONFIRMATION_MESSAGE; suggestions.ts has carried it since beat 3d landed.
- keel/attach-bulletin.ts said the same of BULLETIN_MESSAGE.
- keel/tools-replay-safety.test.ts said keel was not yet in the
statusKeyedTerminalRender glob; it is.
- airline/data/{store,trip-types,types}.ts described use-data.ts / useAirlineData
as "still live and still driving the trip, loyalty and disruption pages"; the
hook is deleted and the ledger is the only substrate.
skin-roster-docs.test.ts: comments only. No fixture entry, exemption or rule was
touched — the "legitimate phrasings" list still pins the numeral+adjective
discriminator, and the header still documents both false-positive shapes.
Reskin skill impact: checked — no rule, path or symbol the skill references
changed, so no skill edit is required beyond the prose pass in
|
||
|
|
085c92e6cc |
docs(reskinnable-demo): cut historical prose from the reskin skill
Same rule as the previous commit, applied to SKILL.md, demo-beats.md, failure-modes.md and templates.md: keep the rule and the mechanism that makes it a rule, drop who hit it, when, how it was found and how long it survived. Largest removals: the "there is no longer a partial skin to warn you off" retrospective closing demo-beats.md, the CR-pass provenance header on failure-modes.md, "this paragraph has now been wrong twice" under the pill count, the three-copies-of-the-staging-chain incident report, and the count-of-selectors paragraph that recorded its own rot. Past-tense incident illustrations were restated in the present tense rather than deleted, so every worked example still names its file. Two stale claims fixed while passing through: templates.md § tools.tsx said only banking, people and commerce key renders off `result` (every skin does), and SKILL.md described `--nw-nav-inset-*` as recently retired rather than simply absent. Reskin skill impact: this IS the skill; the app docs move in the commit before this one and the two are consistent. |