Skip to content

fix(agno): replace the obsolete AGUI shims with Agno 3.x public handlers - #2624

Merged
ranst91 merged 26 commits into
mainfrom
claude/replace-agno-agui-shims-d67eaa
Sep 4, 2026
Merged

fix(agno): replace the obsolete AGUI shims with Agno 3.x public handlers#2624
ranst91 merged 26 commits into
mainfrom
claude/replace-agno-agui-shims-d67eaa

Conversation

@ranst91

@ranst91 ranst91 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Moves the Agno Dojo examples onto Agno 3.x public AG-UI surfaces so nothing in the integration reimplements framework behavior.

  • every demo imports Agent from the public agno.agent module and mounts the stock AGUI interface; no local router, adapter, or copied handler logic remains
  • frontend and HITL demos rely on Agno's own client-tool parsing and paused-run resume: the same-name native tools (generate_task_steps, generate_haiku, change_background) were removed so the client tool is authoritative and silent, and every stateful demo carries a database
  • the shared Dojo pages for reasoning, agentic generative UI, predictive state, shared state, and tool-based generative UI are tightened to the contracts the stock stream actually produces: tool arguments are validated in the handler before anything is stored, the streaming preview still renders partial arguments and sanitizes only what reaches CSS or an image lookup, the reviewed document is what a predictive confirm commits, the reasoning page fixes the model for Agno, the agentic progress card renders an in_progress step as the active row and hides its own state-update tool cards, and the haiku tool schema carries field descriptions
  • the agentic generative UI prompt sends the complete step list on every update (Agno's update_session_state replaces top-level keys) and drives the pending, in progress, completed lifecycle; the shared state demo has a database so Agno's transient bookkeeping keys no longer leak into the state snapshot
  • the generated Dojo catalog mirrors the source pages byte for byte, checked per demo in both directions
  • example-level contract tests pin the public handler pipeline (input, media, context, state, client tools, streaming, reasoning, resume, HITL pairing, surfaced errors), guard against any agno.os.interfaces.agui internal import and any local adapter under server/, derive demo inventories from disk instead of hand-typed lists, and walk the converted JSON Schema of every frontend tool for $ref, type, and description invariants

No CopilotKit-specific extension was retained: every behavior the ticket lists is satisfied by the framework path and covered by the contract tests.

Validation

  • uv run --frozen python -m unittest discover -s tests in integrations/agno/python/examples: 161 passed on locked Agno 3.0.5
  • Ruff check and format on every changed Python file: clean
  • Scoped tsc --noEmit, Prettier, and ESLint on the five changed Dojo pages: clean
  • generated catalog entries are byte-equal to their sources; git diff --check clean
  • review loop: nine full rounds of independent unbiased reviewers at Tier 3 breadth, with a convergence audit between the last two fix cycles; a tenth round was started and stopped early, and the findings it did return are reflected here; ledger available on request

CI's e2e workflow runs the Dojo agno suite on this pull request. No live-model run was performed locally (no API keys on this machine), and no CI job currently runs the Agno example unit tests; that gap is PNI-407's subject.

Follow-ups (out of this PR's subject)

Bucket (d) items surfaced during review are tracked separately rather than fixed here. The ones that most affect the Agno examples: conversation history is single-turn because no demo sets add_history_to_context (PNI-392), a mid-stream failure on the stock router leaves a tool call unterminated before RUN_ERROR (PNI-411), Agno's partial-resume guard has no callers upstream, the predictive prompt ignores the page's write_document tool (PNI-404), and the weather tool's URL encoding, units, error handling, and mock gate.

Linear: PNI-403

The agentic generative UI progress card picked its active row as the
first pending step, so a streamed in_progress step rendered as a future
gray row while a later pending step got the spinner, the Processing
label and the animated background.

Resolve the active row in one place: the in_progress step when the
agent streams one, otherwise the first pending step so servers that
only emit pending/completed keep their current rendering. Later pending
steps render as future rows. The contract test executes the resolver
with Node and pins the render wiring in both the source page and the
generated Dojo catalog, which is regenerated here.
…rogress

Agno's update_session_state assigns each top-level key wholesale, so a
partial steps list truncated the plan to the steps sent. The prompt also
only allowed pending and completed, so the Dojo page never showed an
active row. The instructions now require the full steps list on every
call and the pending, in_progress, completed sequence per step, pinned
by a contract test.
The Dojo serves apps/dojo/src/files.json, but the agno example contracts only inspected the source files. Add byte-for-byte parity assertions between every agno:: page.tsx and Python entry and its source, plus the haiku page in the tool-based generative UI contract, so drift in the served copy fails the suite.
The stale-commit assertNotIn matched a single-line literal that Prettier always wraps at that indent, so reintroducing the stale agentState commit would have passed. Parse the onConfirm body of the write_document render, assert the reviewed args.document commit through a whitespace and trailing-comma normalizer, and reject any agentState reference in that body. The confirm_changes fragments use the same normalizer.
…lient tool

Agno appends AG-UI client tools after the agent's own tools and keeps the
first duplicate name, so the non-silent native stub won over the silent
client tool and every background change emitted a spurious "needs
external execution" assistant message. The Dojo page's useFrontendTool
registration is authoritative, as it already is for generate_task_steps
and generate_haiku.

Extend the dedup contract so the same-name guard covers every demo module
with a matching Dojo page, instead of only the HITL and haiku demos.
…kkeeping

Agno 3.0.5 only strips current_session_id, current_user_id and
current_run_id from session_state before the upsert when the agent has a
db. Without one those keys stayed in the live state dict that the AG-UI
handler snapshots in on_run_completed, so the Dojo received them next to
the recipe. shared_state was the only enable_agentic_state demo without
an InMemoryDb.

Pin it with a startup assertion that every stateful demo carries a db and
a route-level regression that posts a recipe state through the AgentOS
app and checks the final STATE_SNAPSHOT keeps recipe and drops the
bookkeeping keys.
…pause and resume

The model double used to raise inside the model call when the resumed
request lacked the correlated tool result. The stock Agno router folds
that exception into a RAW RunError payload and still ends the stream
with RUN_FINISHED over HTTP 200, so the regression could pass on a
broken route. The double now only records what it receives, and the
test asserts no RUN_ERROR or RunError payload in either response, the
paused stream ending with the write_document tool call events then
RUN_FINISHED, the resumed stream ending with RUN_FINISHED, and the
correlated tool result reaching the second model call.
…ties they claim

The pyproject check matched a dotted module path against a file that only
holds distribution names, reasoning_summary was matched as a whole-file
substring, and the page test never proved the dropdown items sat inside
the supportsReasoningModelSelection branch. Resolve the Agent(...) keywords
through the AST (following hoisted names), parse the agno extras from
[project].dependencies, slice the balanced conditional branch, and pin
reasoning_effort. Each strengthened assertion has a mutation regression.
…schema

The runtime passes tool arguments to the handler as parsed JSON without
validating them against the Zod parameters, so a malformed call could be
stored as a haiku and bypass the image allowlist and gradient guard. The
handler now runs the same strict schema as the streaming preview, skips
the state update on failure, and tells the model which fields to fix.
…AGUI guards

Resolve ast.ImportFrom.level against the importing file's package so
relative imports map to the same dotted names the absolute checks use,
and replace the exact server.local_agui/ResumeAwareAGUI adapter check
with a structural one: server/ may hold only __init__.py and api/, and
example modules may import nothing under server outside server.api.
The strict HAIKU_SCHEMA dropped every .describe() the previous schema
carried, and Zod refinements do not serialize to JSON Schema, so the
model saw gradient as a bare string with no hint that only linear,
radial or conic gradients without url(...) pass the render gate.
Restore field descriptions, add a tool description, and pin both in the
contract test through the same zod-to-json-schema conversion the runtime
uses (skipped when dojo node_modules are not installed).
…denylist

The adoption guard let three shims through: any agno.os.interfaces.agui
submodule other than media or utils, a shim under server/api/, and a
second binding of the AGUI name imported next to the stock one. Replace
the denylist with a rule that examples may import only the AGUI symbol,
require that AGUI is bound solely by the stock import, and extend the
layout guard to server/api/ so only the mounted demo modules may live
there. Add mutation regressions for each bypass form.
Tie EXPECTED_DOJO_MOUNTS to the modules under server/api and to the
names exported by server/api/__init__.py so the three inventories must
agree, and sweep every demo module for a persisted db, recording the
one deliberately stateless demo in STATELESS_DEMOS and asserting that
set is exactly the demos without a db.
Bound the client tool name search to each hook call's own balanced argument object, skipping strings, template literals and comments, and fail loud on a hook without exactly one name. Pin the client tools the four Dojo pages must register so the guard cannot pass on an empty set, route the native tool checks through _native_tool_names (Toolkit, Function, plain callable; anything else fails loud), and derive the haiku fixture's image_name enum from the page's VALID_IMAGE_NAMES instead of a hand-written value. Add regression tests for the bypass forms.
The stock Agno router folds a raised exception into a RAW RunError payload
and still ends with RUN_FINISHED over HTTP 200, so a route test that only
checks status or the terminal event passes on a failed run. Add
assert_stream_ok (status 200, no RUN_ERROR or RunError, RUN_STARTED first,
expected terminal event, balanced tool-call and text-message lifecycles),
route both TestClient assertions through it, and add negative controls
built from literal SSE fixtures that prove each guard rejects its input.
Route tests now use a unique thread id per run.
…ription and strictness invariants

Three review rounds surfaced defects that only appear after the runtime's
zodToJsonSchema(schema, {}) conversion: dropped describe() text, a shared
Zod instance emitting $ref, refine-only constraints. Add a generic invariant
walker over the converted JSON schema and apply it to generate_haiku,
write_document and change_background, reporting each break with its JSON
path. The Node dependency check now fails loud instead of skipping.
Every agno:: key must carry exactly one python entry byte-equal to its
server/api module (or none for the frontend-only v1 demo), its feature
entries must be exactly the generator-selected files under the feature
directory and byte-equal to them, and the key set must equal the api
modules plus the frontend-only demos. Negative controls prove swapped,
dropped, extra, mis-keyed and mis-typed entries each fail.
…tive UI demo

Register a null renderer for update_session_state scoped to the
agentic_generative_ui agent so the Dojo chat shows only the progress
card, and cut the Agno prompt to one state write per step transition
(about N + 2 writes instead of 1 + 2N) while keeping the complete-list
rule, the three statuses and the ten-step default.
Ruff I001 flagged two demo modules. The generated Dojo catalog is regenerated
to keep the server files byte-equal with their sources.
The strict schema gate added to the streaming preview rejected partial
argument snapshots, so the in-chat card only appeared once a tool call was
complete, and it blanked the card entirely for backends that send just the
haiku text. That page is shared by every integration.

The preview now renders as soon as there are Japanese lines and sanitizes only
the values that reach CSS or an image lookup. The strict schema still gates
what the handler stores.
@ranst91
ranst91 requested a review from a team as a code owner September 3, 2026 15:33
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Python Preview Packages

Version 0.0.0.dev1788451833 published to TestPyPI.

Warning: These packages are built from contributor code that may not yet have been vetted for correctness or security. Install at your own risk and do not use in production.

Install with uv

Add the TestPyPI index to your pyproject.toml:

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = true

Then install the packages you need:

# Core SDK
uv add 'ag-ui-protocol==0.0.0.dev1788451833' --index testpypi

# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1788451833' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1788451833' --index testpypi
# NOTE: ag-ui-agent-spec depends on pyagentspec (git-only, not on PyPI).
# You will need to install pyagentspec separately from its git repo.
uv add 'ag-ui-agent-spec==0.0.0.dev1788451833' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1788451833' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1788451833' --index testpypi

Install with pip

pip install \
  --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  ag-ui-protocol==0.0.0.dev1788451833

Use --extra-index-url https://pypi.org/simple/ so pip can resolve
transitive dependencies (pydantic, fastapi, etc.) from real PyPI.


Commit: a6089db

@pkg-pr-new

pkg-pr-new Bot commented Sep 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

@ag-ui/a2a-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a-middleware@2624

@ag-ui/a2ui-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-middleware@2624

@ag-ui/event-throttle-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/event-throttle-middleware@2624

@ag-ui/mcp-apps-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-apps-middleware@2624

@ag-ui/mcp-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-middleware@2624

@ag-ui/a2a

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a@2624

@ag-ui/adk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/adk@2624

@ag-ui/ag2

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/ag2@2624

@ag-ui/agno

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/agno@2624

@ag-ui/aws-strands

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/aws-strands@2624

@ag-ui/claude-agent-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-agent-sdk@2624

@ag-ui/claude-managed-agents

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-managed-agents@2624

@ag-ui/crewai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/crewai@2624

@ag-ui/langchain

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langchain@2624

@ag-ui/langgraph

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langgraph@2624

@ag-ui/llamaindex

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/llamaindex@2624

@ag-ui/mastra

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mastra@2624

@ag-ui/pydantic-ai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/pydantic-ai@2624

@ag-ui/vercel-ai-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/vercel-ai-sdk@2624

@ag-ui/watsonx

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/watsonx@2624

@ag-ui/a2ui-toolkit

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-toolkit@2624

create-ag-ui-app

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/create-ag-ui-app@2624

@ag-ui/client

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/client@2624

@ag-ui/core

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/core@2624

@ag-ui/encoder

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/encoder@2624

@ag-ui/proto

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/proto@2624

commit: 689f585

…ests

The shared Dojo pages are used by every integration and did not need to change
for Agno. Most of what this branch had done to them was reformatting, and the
rest pushed Agno's own behavior into shared UI, including an integrationId
check for "agno" in the reasoning page and a strict haiku schema that blanked
the card for backends sending only the poem text.

All five pages go back to their state on main, and the example tests added
here go with them; the pre-existing example tests are restored unchanged. What
remains is the subject of the ticket: the nine demos on Agno's public AGUI
interface, mirrored into the generated catalog.
@ranst91
ranst91 enabled auto-merge September 4, 2026 14:18
@ranst91
ranst91 merged commit a5f8fbc into main Sep 4, 2026
41 checks passed
@ranst91
ranst91 deleted the claude/replace-agno-agui-shims-d67eaa branch September 4, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants