The concepts/three-types-of-gen-ui page and the canonical
learn/generative-ui/index page both use "Open-Ended" (with the
suffix) as the third gen-UI category alongside Controlled and
Declarative. The sidebar subsection had been shipped as just "Open"
in the JTBD reorg, leaving readers with three different namings
across the site ("Open" in nav, "Open-Ended" in concept page,
"Open-Ended" / "Fully Generated" in the learn page).
Align the sidebar to the canonical name. The other two subsections
("Controlled" and "Declarative") were already consistent.
Two changes to index.mdx that align it with the new JTBD sidebar
instead of competing with it:
- Drop the lower "Explore by feature" 6-card section. Every entry
there (Chat UI, Headless UI, Generative UI, Backend & Runtime,
Programmatic Control, API Reference) was a flatter version of a
sidebar section the JTBD reorg introduced. The overview pitching
the same content twice made the page heavier without adding routing
value.
- Swap one of the 4 top cards (was: Chat UI → /prebuilt-components)
for Concepts → /concepts/architecture so the conceptual entry
point is reachable from the docs front door, not just the sidebar
subgroup.
The overview is now: 4 top cards (Quickstart, API Reference, Concepts,
Generative UI), the LandingCodeShowcase, and the IntegrationGrid.
Tighter and clearly a doc-router rather than a product-pitch redux.
The merge anchor was hardcoded to "app control" with fallbacks to
"threads" and "backend". After the JTBD reorg renamed "App Control"
→ "Give Your App Agent Powers" and "Backend" → "Agents & Backends",
none of the candidates matched and every framework-scoped sidebar fell
back to append-at-end, relegating the framework section ("Built-in
Agent", "LangGraph (Python)", etc.) to the bottom of the nav.
Restore the original placement by adding the new section names to the
anchor candidate list: "give your app agent powers" (the renamed App
Control) and "agents & backends" (the renamed Backend) take priority,
with the old names kept as fallbacks for resilience. The match is
case-insensitive and stops at the first hit. Verified the framework
section now lands between Give Your App Agent Powers and Agents &
Backends for /built-in-agent/* and /langgraph-python/*.
The redirect entries are self-describing — source, destination, and
permanent flag say everything the runtime needs. The block comments
were rationale that belongs in commit messages, not in a config file
that future readers scan for shape.
The previous commit renamed the file but didn't add a redirect, so
any open browser tab or external link to /concepts/oss-vs-cloud now
404s — surfacing as a "sidebar disappeared" experience because the
404 page doesn't render the docs shell. Add the 301 redirect alongside
the migrate-to-* and frontend-actions ones.
"Cloud" implied a single deployment shape (managed-only). "Enterprise"
is the accurate name for the layer — same Intelligence Platform runs
hosted at Copilot Cloud or self-hosted via the copilot-intelligence
Helm chart. The page already used "Enterprise" terminology in the
body; this aligns the slug, page title, and meta entry to match.
File renamed via git mv (history preserved). search-index.json paths
update on the next build (gitignored, regenerated).
Replaces the TODO placeholders left by the JTBD restructure with
written explanation-layer content sourced from existing repo material
(learn/architecture, learn/generative-ui, learn/intelligence-platform,
ag-ui/concepts/architecture, premium/overview) and from two CopilotKit
blog posts ("AG-UI Is Redefining the Agent–User Interaction Layer"
and "The Three Types of Generative UI: Static, Declarative and Fully
Generated").
- concepts/architecture.mdx: the three-layer stack (frontend, runtime,
agent), AG-UI as the protocol bridge between runtime and agent, the
request flow at a glance. Reuses the existing architecture-diagram
PNG via ImageZoom and links out to the deeper learn/* coverage.
- concepts/three-types-of-gen-ui.mdx: a freedom-vs-control framing of
Controlled / Declarative / Open-Ended generative UI with one concrete
example per type and explicit "when to pick" guidance. Notes the
alias terminology (Static, Fully Generated) so readers can match the
blog and the learn/ pages. Cross-links to the implementation pages
for each type.
- concepts/oss-vs-cloud.mdx: clear OSS-vs-Enterprise feature split with
a capability-by-capability table, hosted-vs-self-hosted explanation,
and a decision rubric ("if you want CopilotKit to *be* your
conversation persistence layer, that's where Enterprise begins").
All three keep the explanation-layer scope (Diátaxis): they orient
and explain, then point the reader at the canonical detailed pages
for implementation specifics. None of them duplicate how-to content.
The concepts/what-is-copilotkit.mdx placeholder duplicated the
overview rendered at /index.mdx — same intent ("orient the reader"),
same target audience. Keep the index page as the single what-is
entry point and remove the concept-page slot.
The current meta convention pairs a `---X---` section header with a
`...x` spread that resolves to a sub-meta whose `title` is also "X".
buildNavTree emitted both a section node and a group node with the
same title, and the renderers showed each one — so users saw "BUILD
GENERATIVE UI" (section, uppercase tracking-widest) followed by
"Build Generative UI" (group, regular case) for the same content.
The doubling was already on the live site for one section
("Generative UI") and didn't get pushback there, but the JTBD reorg
makes it more visible: every spread-backed section ends up doubled
(Migrate, Other, etc.).
Fix in two halves, kept narrow:
- buildNavTree: when the previously-pushed node is a section header
with the same title that the spread's group would carry, set the
group's title to empty string. The group still wraps its children
for indentation/nesting; only the redundant inner label is dropped.
- All three nav renderers (renderNavItem in docs-page-view.tsx,
OverviewNavItem in app/[[...slug]]/page.tsx, RenderNav in
app/[framework]/[[...slug]]/page.tsx) gain a `{node.title && ...}`
guard so an empty string doesn't render an empty <div>.
Sections without a matching spread (Get Started → index/quickstart/
coding-agents listed inline) and spreads without a preceding section
header (legacy framework-tree work, override merges) are unaffected.
A spread whose sub-meta title differs from the section header (e.g.
section "Observe & Operate" + ...troubleshooting whose title is
"Troubleshooting") still renders both labels, which is correct —
they're meaningfully different and the inner one acts as a
sub-grouping label.
Restructures the top-level sidebar from feature-primitive sections
(Basics / App Control / Backend / Generative UI / Platform / Premium
Features / Troubleshooting) into 9 jobs-to-be-done sections that match
how readers describe what they want to build. Decisions captured in
the IA Restructure plan.
Section structure:
1. Get Started — index, quickstart, coding-agents, + concepts subtree
(placeholders for what-is-copilotkit, architecture,
three-types-of-gen-ui, oss-vs-cloud)
2. Build Chat UIs — prebuilt-components, custom-look-and-feel,
multimodal-attachments
3. Build Generative UI — nested Controlled / Declarative / Open
subgroups (tool-based, tool-rendering, state-rendering, reasoning,
your-components / a2ui + dynamic-schema + fixed-schema / mcp-apps)
4. Give Your App Agent Powers — frontend-tools, shared-state,
multi-agent/subagents, programmatic-control
5. Agents & Backends — built-in-agent (new), copilot-runtime,
custom-agent, ag-ui, runtime-server-adapter
6. Observe & Operate — inspector, vs-code-extension, +
troubleshooting subgroup (event-inspector, hook-explorer,
debug-mode, observability-connectors, common-issues,
error-debugging)
7. Enterprise — premium/overview, premium/observability,
premium/self-hosting, threads (merged from former Threads section
per product call: threads + persistence are part of Enterprise)
8. Migrate — v2, 1.10.X, 1.8.2 (moved out of Troubleshooting and
renamed to drop the redundant 'migrate-to-' prefix)
9. Other — contributing, telemetry (unchanged)
Reference stays as a separate root-true tree, not surfaced through the
top-level meta. Its v1/v2 treatment is a follow-up decision.
Page moves and new files:
- Migration guides moved from troubleshooting/migrate-to-{v2,1.10.X,
1.8.2}.mdx to migrate/{v2,1.10.X,1.8.2}.mdx with 301 redirects from
the old URLs in next.config.ts. Migrate gets its own first-class
section with a lucide/RefreshCw icon — separate from
Troubleshooting per IA-analysis pillar ("I'm upgrading deliberately"
≠ "something broke").
- 4 concept placeholders + index under content/docs/concepts/. Each
has frontmatter (title, description, icon, doc_type: explanation)
and a TODO body referencing where the content will come from
(existing repo material + blog posts). Slot exists; content
backfills later.
- New built-in-agent.mdx at root — 1-page summary that places BIA in
Agents & Backends as a peer to copilot-runtime / custom-agent / etc,
with a link out to the full BIA-scoped docs at /built-in-agent.
Other meta moves:
- programmatic-control: Basics → Give Your App Agent Powers
- inspector: Basics → Observe & Operate
- vs-code-extension: Platform → Observe & Operate
- multimodal-attachments: Platform → Build Chat UIs
- runtime-server-adapter: Platform → Agents & Backends
- The 3 migration pages: Troubleshooting → Migrate (with file moves)
Removed dead code: the `...unselected` line in top-level meta.json
was a no-op (unselected/meta.json has `root: true` so the spread
produced nothing). Stripped during the rewrite.
Multiple new visible labels are intentional. The renderer renders
sections (uppercase tracking-widest) and groups (medium-weight regular
case) at the same level when a section spreads a sub-meta with a
matching title — same pattern that's been live for 'Generative UI'
through every prior PR. The Build Generative UI nesting (Controlled /
Declarative / Open) is a deliberate three-types-of-gen-UI subgrouping
inside the section, surfaced via subsection headers in
generative-ui/meta.json's pages array.
## Summary
- Fix probe run history always returning empty — the invoker writes PB
rows with bare `cfg.id` (e.g. `e2e-deep`) but the HTTP handler queried
with the scheduler entry ID including the `probe:` prefix (e.g.
`probe:e2e-deep`), so they never matched
## Test plan
- [x] 1320 tests pass
- [x] Typecheck clean
- [ ] CI green
- [ ] After deploy, click probe:e2e-deep row on dashboard and confirm
"Recent Runs" populates
The invoker writes PB rows with bare cfg.id (e.g. 'e2e-deep') but
the HTTP handler passed the scheduler entry id with 'probe:' prefix
(e.g. 'probe:e2e-deep') to writer.recent(). The IDs never matched,
so run history always returned empty.
Completes the declarative-gen-ui cell for mastra (5/5 regions). The
runtime-inject-tool snippet teaches the TS-runtime injectA2UITool flag —
mastra's actual route bundles generateA2uiTool into weatherAgent so it
doesn't follow this pattern, but the docs page is teaching the runtime
feature itself, so a docs-only sibling file matches the established
pattern (see chat-component.snippet.tsx in agentic-chat).
Adds the @region[name] / @endregion[name] markers the shell-docs <Snippet>
mechanism needs to resolve mastra-scoped code blocks. Brings 10 cells from
"missing snippet" warnings to clean rendered code in the docs.
Two patterns:
1. In-place markers (12 of the 14 file edits) — wrap existing demo source
where it already reads as a clean teaching example. Comment-only,
stripped from rendered output, zero runtime change.
2. Sibling <region>.snippet.tsx files (2 new files) — for cases where
the production demo carries QA hooks (frontend tools, render tools,
agent context) that aren't relevant to the docs page being taught.
Lives in the demo folder so the bundler picks it up; not imported,
not rendered. Used for agentic-chat/chat-component (mastra Chat is
QA-laden) and tool-rendering/render-flight-tool + catchall-renderer
(mastra demo only renders weather, not flights or catchall).
Cells advanced to ready: prebuilt-popup, prebuilt-sidebar, frontend-tools,
chat-customization-css, agentic-chat-reasoning, chat-slots, agentic-chat,
tool-rendering, headless-complete, plus partial coverage on declarative-gen-ui
(provider-a2ui-prop only; runtime-inject-tool is missing on every framework
and needs a cross-cutting authoring round on langgraph-python first).
Out of scope: a2ui-fixed-schema and the runtime-inject-tool region on
declarative-gen-ui (cross-cutting — reference framework lacks them too);
shared-state-*, subagents, gen-ui-*, mcp-apps, open-gen-ui*, interrupt-headless
(stub or no-folder on mastra — engineering work).
Documents the D5 e2e-deep Chromium memory envelope (4 services x 2
features = 8 concurrent contexts, ~2.4GB peak) and the two tuning
knobs operators should adjust if OOM occurs.
Semaphore unit tests verify the counting semaphore gates concurrent
access correctly at limit=1, limit=2, and limit>=N. Feature parallelism
test confirms FEATURE_CONCURRENCY=2 produces peak context count of 2
when 4 features run. Feature-type filtering tests cover the driver-level
ctx.featureTypes filter (filtered-by-trigger note, all-filtered short-
circuit) and the HTTP handler validation (empty array, empty strings,
non-array, combined slugs+featureTypes, rate-limit non-consumption on
400). Graceful degradation test verifies correct aggregate results
regardless of concurrency level.
Exports Semaphore class and FEATURE_CONCURRENCY constant from
e2e-deep.ts for direct unit testing.
When ctx.featureTypes is set from the trigger layer, partition runnable
features into kept and filtered sets before the execution loop begins.
Filtered features are recorded as skipped with a "filtered-by-trigger"
note so dashboards can distinguish trigger-scoped runs from no-script
skips. Logging emits the filter and excluded count when active.
Accept optional featureTypes in the trigger request body filter. Validates
that featureTypes is a non-empty array of non-empty strings when present,
returning 400 with a descriptive error otherwise. Forwards the validated
featureTypes into TriggerOptions alongside existing slug filtering.
Replace the sequential for-loop over runnable features with a
semaphore-gated Promise.allSettled pool capped at
FEATURE_CONCURRENCY=2. Each feature already creates its own
independent browser context, so there is no shared mutable state
between concurrent runs. Results are collected from settled promises
to preserve input ordering. Setting FEATURE_CONCURRENCY=1 degrades
to identical sequential behaviour.
Add optional featureTypes?: string[] to ProbeContext, TriggerOptions,
and InvokerTriggerOptions. The invoker extracts the field from trigger
opts and forwards it into the ProbeContext passed to each driver, so
drivers like e2e-deep can restrict their run scope to specific feature
types without changes to the scheduler or HTTP layer.
## Summary
- Removes `packages/vscode-extension/` (176 files) — migrated to
[CopilotKit/vscode-extension](https://github.com/CopilotKit/vscode-extension)
- Removes 3 extension-specific GitHub workflows (CI, publish,
changelog-sync)
- The standalone repo has full git history, working CI, verified OIDC
publishing, and branch protection
## Why
The extension is a leaf node in the monorepo dependency graph (nothing
depends on it) and has its own independent release cycle. Extracting it
simplifies the monorepo and gives the extension its own CI/CD without Nx
overhead.
## Test plan
- [x] Standalone repo builds, types clean, 184 tests pass
- [x] Dry-run publish validates OIDC auth chain (Marketplace + Open VSX)
- [x] CI green on standalone repo main branch
- [x] Landing page live at vscode.copilotkit.dev
The extension is a leaf node in the monorepo dependency graph (nothing
depends on it) and has its own independent release cycle. The standalone
repo at CopilotKit/vscode-extension has full git history, working CI,
verified OIDC publishing, and branch protection.
<!--
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?
(Describe the changes introduced in this PR)
## Related PRs and Issues
- (Direct link to related PR or issue, if relevant)
## Checklist
- [ ] 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
## Summary
- Add missing `@json-render/core` and `@json-render/react` deps to
google-adk `package.json` (Docker build was failing on byoc-json-render
demo)
- Update catalog test expectations to match actual generated counts
after merge
- Regenerate google-adk starter to match template output (drift check
was failing)
## Why
PR #4318 (google-adk parity) introduced merge conflicts during rebase
that left catalog counts stale, and the byoc-json-render demo was
missing its `@json-render/*` dependencies. The starter had manual CR
fixes that diverged from the generator template.
## Test plan
- [ ] Showcase: Validate passes (catalog counts)
- [ ] Showcase: Verify Starters Match Template passes (drift check)
- [ ] Showcase: Build & Deploy passes (Docker build with @json-render
deps)
## Release monorepo v1.56.4
**Scope:** `monorepo` | **Bump:** `patch`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `monorepo` packages to `1.56.4`
and generated AI-enhanced release notes.
2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
must pass before merging. This is the review gate.
3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.
4. **When this PR is merged**, the `release / publish` workflow
automatically:
- Builds all packages
- Publishes the `monorepo` packages to npm at version `1.56.4`
- Creates git tag `monorepo/v1.56.4`
- Creates a GitHub Release with the final release notes
### Before merging
- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)
---
> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
1. Add missing @json-render/core and @json-render/react deps to
google-adk package.json (Docker build failed on byoc-json-render
demo imports)
2. Update catalog test expectations to match actual generated counts
(wired 482, crewai-crews 28, LGP 37 wired + 2 unshipped)
3. Regenerate google-adk starter from template to eliminate drift
detected by the starter consistency check
## Summary
Brings the **Google ADK** column on
https://dashboard.showcase.copilotkit.ai/#coverage from 8 wired cells
(parity_tier `minimal`) up to **35 wired cells** (parity_tier `partial`)
— using `langgraph-python` (38/39) as the reference. All ports use real
Google ADK + Gemini 2.5 Flash agents wired through `ag-ui-adk`; nothing
is stubbed.
### Backend
- `src/agent_server.py` refactored from a single shared `LlmAgent`
mounted at `/` to one `ADKAgent` per demo at `/<agent_name>`, driven by
a new `src/agents/registry.py` source-of-truth (with an `AgentSpec`
dataclass for `predict_state` / `emit_messages_snapshot` kwargs).
- 18 new per-demo agent modules covering reasoning (Gemini thinking
mode), tool-rendering (4 variants), HITL (in-chat + in-app),
shared-state (read-write + per-token streaming via
`PredictStateMapping`), multi-agent supervisor, A2UI dynamic +
fixed-schema, BYOC, open-gen-ui (basic + advanced), agent-config,
readonly-context, multimodal, beautiful-chat, plus the existing
recipe-themed shared-state-read.
### Frontend
- `src/app/api/copilotkit/route.ts` now maps each agent name to its
matching backend path.
- 6 dedicated runtime routes for demos that need bespoke runtime config:
- `copilotkit-auth` — V2 `createCopilotRuntimeHandler` with an
`onRequest` 401 gate
- `copilotkit-declarative-gen-ui` and `copilotkit-a2ui-fixed-schema` —
`a2ui.injectA2UITool: false` because the agents own their own A2UI tools
- `copilotkit-byoc-hashbrown`, `copilotkit-byoc-json-render`,
`copilotkit-ogui` (shared by open-gen-ui + advanced)
- 26 new / replaced demo pages with their helper components.
### Manifest
`showcase/packages/google-adk/manifest.yaml` declares 35 features (was
8). `pnpm generate-registry` validates clean.
### Skipped (intentional)
- `gen-ui-interrupt`, `interrupt-headless` — depend on LangGraph's
`interrupt()` graph primitive; ADK has no direct equivalent. The
high-level `useHumanInTheLoop` case is covered via `hitl-in-chat`.
- `mcp-apps` — `ag_ui_adk` does support `MCPToolset`, but a working
MCP-driven UI demo is a separate chunk; left for follow-up.
## Test plan
- [x] `pnpm generate-registry` validates manifest end-to-end (Catalog:
reference = langgraph-python with 37 wired features; google-adk now at
35/39 wired)
- [x] `npx tsc --noEmit` in `showcase/packages/google-adk` — no new
errors (pre-existing `byoc-json-render/registry.tsx` and
`shared-state-write/page.tsx` errors untouched)
- [x] Python syntax: `ast.parse` over every file in `src/agents/` — all
parse cleanly
- [ ] Local smoke test: `pnpm dev` in `showcase/packages/google-adk`,
hit each new `/demos/*` route, verify L2/L3 health
- [ ] Railway deploy: confirm health probes go green and dashboard
column lights up
## Caveats worth knowing before deploy
1. **Per-token streaming** in `shared-state-streaming` uses
`PredictStateMapping(stream_tool_call=True)`. Per `ag_ui_adk` docs, true
per-token argument streaming requires Gemini 3+ via Vertex AI; on Gemini
2.5 Flash this should still work but at chunk-level granularity.
2. **Subagents** uses direct `genai.Client.models.generate_content`
calls inside each delegation tool, not ADK's native `sub_agents` /
`AgentTool`. Functionally equivalent for the demo; a follow-up could
refactor to ADK-native multi-agent.
3. **A2UI fixed-schema** has an inline `_build_flight_operations` helper
instead of importing `copilotkit.a2ui`. The `copilotkit` Python SDK
isn't in `requirements.txt` for this package — adding it later and
refactoring is straightforward.
4. **`/agents/state` collision**: `ag_ui_adk.add_adk_fastapi_endpoint`
hardcodes `POST /agents/state` regardless of the `path` arg. With N
agents mounted, only the first registered agent's state endpoint is
reachable. Most demos don't use it; `headless-complete` could.
5. **Beautiful-chat** is intentionally minimal vs. the langgraph-python
sibling's richer canvas/layout split.
Rewrite starter route.ts to use HttpAgent (matching packages pattern),
fix default port to 8123, add full 37-agent list including sample_agent,
add missing @ag-ui/client dependency, register sample_agent in registry.
Update open-gen-ui agent instructions to emit HTML directly instead
of fenced blocks (the runtime's generateSandboxedUi tool handles
rendering). Tighten sandbox evaluateExpression regex to reject **
exponentiation. Fix ternary operator precedence in 3 agent files
where string concatenation bound tighter than the conditional.
Remove stack trace leaks from error responses, replace @ts-ignore
with @ts-expect-error, hoist CopilotRuntime to module scope, and
add [[...slug]] catch-all for the V2 auth endpoint so sub-route
requests don't 404.
Round 5 review surfaced a fragility in the empty-write guard. The
`if not new_text` short-circuit added in Round 4 correctly avoids
clobbering the LlmAgent static `instruction=` field when there's
nothing to inject — but if a prior callback DID embed a block in the
existing Content and the user has since cleared state, the early
return leaves the stale block in place because we never write the
stripped result back.
In practice this is unreachable: `_INSTRUCTION` is non-empty for all
three agents, so `original_text` after stripping always retains at
least the static instruction. But the explicit `stripped_prior_block`
flag makes the invariant honest if anyone ever wires up an LlmAgent
with an empty `instruction=` and removes the implicit dependency on
the static instruction being non-empty.
eslint-plugin-unicorn no-useless-fallback-in-spread — spreading
undefined into an object literal is a no-op, so '?? {}' is redundant.
Functionally identical, silences the lint warning.
Two related issues surfaced in Round 4 review of the ADK showcase:
1. The before-model callbacks in agent_config_agent,
readonly_state_agent_context_agent, and shared_state_read_write_agent
guarded the empty-write branch with `if not new_text and original is
None`. That missed the case where `original` is a non-None Content
with empty text — possible after stripping a prior block that was
the entire Content. In that branch the callback wrote
Content(text=""), clobbering the LlmAgent's static `instruction=`.
Tightened the guard to `if not new_text: return None`.
2. The shared-state-read-write seed effect spread the observed agent
state but unconditionally assigned `notes: []`, wiping any
backend-persisted notes when the user landed without persisted
preferences. Falls back to `agentState?.notes ?? []` instead.
Round 3 confirmation found bugs Round 2's fixes left behind in the same
files. All fixes target subject-scoped + load-bearing concerns.
bucket (a) findings:
A1 (strip-prior-block content loss): all three before-model-callback
agents (\`agent_config_agent\`, \`readonly_state_agent_context_agent\`,
\`shared_state_read_write_agent\`) sliced
\`original_text[end_idx + len(END_MARKER):]\` to remove the prior
injected block. That dropped content from index 0 to sig_idx as well —
fine for the steady-state pattern where our block is at position 0,
but if any other middleware ever prepends content to system_instruction
between turns, that prefix would be silently lost. Splice instead:
\`original_text[:sig_idx] + original_text[end_idx + len(END_MARKER):]\`.
A2 (empty-block + empty-original still rewrites system_instruction):
when both \`block\` is None and \`original\` is None, the callbacks were
still writing \`Content(role="system", parts=[Part(text="")])\`,
overwriting whatever the LlmAgent's \`instruction=\` field would have
contributed. Added an early return for that case so the static
instruction passes through.
A3 (sub-agent error message exposes raw exception text):
\`_invoke_sub_agent\` was raising
\`_SubAgentError(f"sub-agent call failed: {exc.__class__.__name__}: {exc}")\`.
\`{exc}\` for genai HTTP errors can include URLs, request IDs, partial
credentials, and quota details. The error string lands in
\`state["delegations"][i].result\` and ships to the public Railway
deploy. Sanitized to surface only \`exc.__class__.__name__\` to clients;
\`logger.exception\` keeps the full trace + str(exc) server-side.
A4 (agent.setState seed/handler effects clobber unrelated state keys):
\`agent.setState({ config: INITIAL_CONFIG })\` and the matching prefs
seed/handlers replaced the whole state object rather than merging. If
the runtime adds any other keys (e.g. \`copilotkit\` for the read-only
context middleware, future framework additions), they were wiped on
every config write. Now spread the observed state on every write.
A5 (\`_build_prefs_block\` missing the empty-recognized-keys guard): a
truthy prefs dict containing only unrecognized keys (schema drift /
forward-extension UI prop) would still emit a block consisting only of
\`[SIG, "Tailor every response to these preferences. ADDR_USER_BY_NAME"]\`,
i.e. directives without directives. Added the same
\`if len(lines) == 1: return None\` guard already present in
\`agent_config_agent._format_config\` and
\`readonly_state_agent_context_agent._format_context\`.
Bucket (b) defensive items applied:
- subagents \`_invoke_sub_agent\` parts extraction: dropped the redundant
\`if content is not None\` ternary; \`getattr(None, "parts", None)\`
already returns the default.
Starter regenerated.
Call-site enumeration:
- splice path inside \`_inject_config\`/\`_inject_context\`/
\`_inject_preferences\`: signature unchanged; only callers are the
matching LlmAgent before_model_callback bindings.
- \`_SubAgentError\` constructor message format changed (still str
argument): consumers stringify it via str(exc); behaviour unchanged
apart from the redacted exception body.
- \`agent.setState\` payloads in agent-config + shared-state-read-write
pages: consumed only by the matching runtime route per page; the
spread shape is a superset of the prior shape, so backend callbacks
see the same fields plus any pre-existing keys (which they already
read defensively).
- \`_build_prefs_block\` early-return: only consumer is
\`_inject_preferences\` in the same module; new return path is None,
same as the existing \`not prefs\` early return.
Round 2 (confirmation) surfaced bugs in code my Round 1 fixes touched.
bucket (a) findings:
- subagents_agent._update_delegation: dropped the synthetic
\`sub_agent: "unknown"\` fallback I added in Round 1. The frontend's
\`Delegation.sub_agent\` union is
\`research_agent | writing_agent | critique_agent\`; an "unknown" value
fell through SUB_AGENT_BADGE_CLASS / SUB_AGENT_LABEL maps and rendered
as \`undefined\` text + className. Now we log loudly and skip — no row
beats a malformed row.
- subagents_agent._invoke_sub_agent: cleaned up the
\`getattr(content, "parts", None) or [] if content is not None else []\`
ternary. \`getattr(None, ...)\` already returns the default, so the
outer conditional was dead.
- agent_config_agent._format_config: added \`if len(lines) == 1: return None\`
guard before appending CONFIG_END_MARKER. A truthy config dict with no
recognized keys (schema drift / future-extension forwardedProps) was
emitting a meaningless [SIGNATURE, END_MARKER] block telling the LLM
to "Honour every directive above" with zero directives. Mirrors the
same guard in readonly_state_agent_context_agent._format_context.
- delegation-log.tsx: added STATUS_BADGE_CLASS + STATUS_LABEL maps and
a status-derived border-color so running/completed/failed entries are
visually distinguishable. Without this, my Round 1 backend fix that
actually emits all three states had no UI signal — every entry was
the same muted gray. Now: amber spinner + "running…" while the
sub-agent works, emerald on completion, red on failure.
- hitl-in-app/page.tsx: added an unmount cleanup that rejects every
still-pending approval (with reason "user navigated away before
responding") so the agent's tool-call promises never orphan when
the user leaves the page mid-approval. Also added \`key={head.id}\`
on ApprovalDialog so any future local state in the dialog component
is per-request rather than leaking from one approval to the next as
the head rotates.
- tool-rendering-custom-catchall: aligned the renderer's status type
with what the runtime + page actually emit. The runtime's
DefaultRenderProps status enum is
\`"inProgress" | "executing" | "complete"\` and the page collapses both
pre-completion variants to "executing"; there is no "incomplete"
state today. The renderer's \`"incomplete"\` branch was unreachable
dead code from a copy-paste of an older type. Narrowed the prop type
to \`"executing" | "complete"\` and dropped the dead branch.
- manifest.yaml highlight arrays: 7 demos with dedicated runtime
routes (a2ui-fixed-schema, declarative-gen-ui, byoc-json-render,
byoc-hashbrown, auth, open-gen-ui, open-gen-ui-advanced) were
pointing at the unified \`src/app/api/copilotkit/route.ts\` instead
of their own dedicated route file. Documentation drift the showcase
tooling uses to render source listings — pointed each at its real
route. Also added \`src/app/demos/auth/demo-token.ts\` to the auth
highlight list so the bundled demo-token note is visible alongside
the page.
bucket (b) cheap items applied:
- AgentSpec.predict_state: \`Iterable\` → \`Sequence\`. A one-shot
generator passed by mistake would empty after the first request;
Sequence forces a re-iterable container. Module-level lists already
satisfy this contract.
Starter regenerated to keep drift-check green.
Call-site enumeration:
- \`STATUS_BADGE_CLASS\`, \`STATUS_LABEL\`, \`ENTRY_BORDER_BY_STATUS\`
(new): private to \`delegation-log.tsx\`.
- \`pendingQueueRef\` (new): private to hitl-in-app/page.tsx unmount cleanup.
- \`_update_delegation\` signature unchanged; only the missing-entry
branch behaviour changed (no longer appends).
- \`AgentSpec.predict_state\` widened constraint
(Iterable→Sequence): only consumed in agent_server.py's
ADKAgent construction; passing a list still satisfies Sequence.
- CustomCatchallRendererProps.status narrowed
(executing|complete|incomplete → executing|complete): only
consumer is the page in the same demo folder; it now passes a
matching union.