Skip to content

feat(crewai): START/CONTENT/END triples as the default wire shape (PNI-136, part 2) - #2266

Open
ranst91 wants to merge 1 commit into
mainfrom
claude/pni-136-protocol-surface
Open

feat(crewai): START/CONTENT/END triples as the default wire shape (PNI-136, part 2)#2266
ranst91 wants to merge 1 commit into
mainfrom
claude/pni-136-protocol-surface

Conversation

@ranst91

@ranst91 ranst91 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Part 2 of PNI-136: the wire-shape decision. Rebuilt fresh on current main (part 1's RAW passthrough + get_capabilities, plus PNI-130 MCP, PNI-134 checkpointing, and PNI-131 multi-agent attribution are all merged and integrated with), not the original tangled branch.

The decision

START/CONTENT/END triples become the default, with emission_shape="chunks" (or AGUI_CREWAI_EMISSION_SHAPE=chunks) as a compatibility opt-out.

The ticket's original premise — that CrewAI is the only integration emitting chunks — is wrong (a2a, aws-strands, langchain, mastra, vercel-ai-sdk, agent-spec emit them on some paths too), so the bare parity argument is withdrawn. What the decision actually rests on:

  1. CrewAI's chunk stream is broken for its own progressive-state demos. A copilotkit_emit_state / copilotkit_predict_state call landing between two tool-call argument deltas makes the client-side chunk transform close the call and throw on the next delta. Triples keep the call open server-side, because the server owns the message-boundary state and the client does not.
  2. Triples are the canonical form. apply/default.ts throws on an untransformed chunk, so any raw-SSE reader (the Python SDK, conformance tooling, custom clients) cannot consume CrewAI's stream without a transform stage.
  3. The server already owns message boundaries, so emitting END server-side is strictly more information than making each client re-derive it.

One shared shaper, so shape never depends on transport

The message/tool-call open-close lifecycle lives in a single EmissionShaper. The StreamFrame translator delegates to it; the legacy bus-listener driver runs its wire events through reshape(). A test drives the same logical stream through both paths and asserts identical output — the transport-parity gap that plagued the earlier attempt is closed by construction. STEP lifecycle stays owned by PNI-131's attribution BoundaryTracker; the shaper flushes open message/tool sequences before every step/snapshot/terminal boundary, so a paused flow closes message → step → RUN_FINISHED in order.

Every defect the earlier five review rounds surfaced is fixed here from the start and carries a regression test: parallel tool calls kept separate (index-keyed in sdk, id-routed in the shaper), a late delta for a closed call dropped rather than reopened (a second START duplicates it client-side), interleaved progressive state closing text but not tools, parent_message_id set so calls attach to the streamed message, and open sequences flushed before RUN_ERROR.

Testing

300 passing / 2 skipped, including PNI-131's attribution suite. get_capabilities() reports the resolved wire shape. No ticket references in comments.

Under chunks the shaper is an identity passthrough, so that mode is byte-for-byte the previous behaviour.

Ticket: PNI-136

@ranst91
ranst91 requested a review from a team as a code owner July 29, 2026 09:56
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Python Preview Packages

Version 0.0.0.dev1785406816 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.dev1785406816' --index testpypi

# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1785406816' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1785406816' --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.dev1785406816' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1785406816' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1785406816' --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.dev1785406816

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


Commit: 91e301e

@pkg-pr-new

pkg-pr-new Bot commented Jul 29, 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@2266

@ag-ui/a2ui-middleware

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

@ag-ui/event-throttle-middleware

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

@ag-ui/mcp-apps-middleware

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

@ag-ui/mcp-middleware

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

@ag-ui/a2a

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

@ag-ui/adk

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

@ag-ui/ag2

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

@ag-ui/agno

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

@ag-ui/aws-strands

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

@ag-ui/claude-agent-sdk

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

@ag-ui/claude-managed-agents

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

@ag-ui/crewai

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

@ag-ui/langchain

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

@ag-ui/langgraph

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

@ag-ui/llamaindex

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

@ag-ui/mastra

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

@ag-ui/pydantic-ai

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

@ag-ui/vercel-ai-sdk

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

@ag-ui/watsonx

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

@ag-ui/a2ui-toolkit

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

create-ag-ui-app

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

@ag-ui/client

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

@ag-ui/core

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

@ag-ui/encoder

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

@ag-ui/proto

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

commit: 3633cd3

@ranst91
ranst91 marked this pull request as draft July 29, 2026 14:45
@ranst91 ranst91 changed the title feat(crewai): triples wire shape, opt-in RAW passthrough, get_capabilities (PNI-136) feat(crewai): START/CONTENT/END triples as the default wire shape (PNI-136, part 2) Jul 29, 2026
@ranst91
ranst91 force-pushed the claude/pni-136-protocol-surface branch 2 times, most recently from 164c69a to 0de1bb5 Compare July 30, 2026 09:54
…I-136, part 2)

Text and tool-call output now streams as canonical START/CONTENT/END triples;
emission_shape="chunks" (or AGUI_CREWAI_EMISSION_SHAPE=chunks) keeps the previous
CHUNK form for consumers pinned to it.

Chunks were never universally applicable: apply/default.ts throws on an
untransformed chunk, so any raw-SSE reader could not consume CrewAI's stream, and
CrewAI's own progressive-state demos break the client-side chunk transform when a
state update interleaves a tool call. Triples are the form the server can produce
authoritatively because it owns the message-boundary state.

Both transports route through one EmissionShaper, so shape and payload never depend
on the installed crewai version. The shaper owns the message and tool-call lifecycle;
the attribution BoundaryTracker owns steps. A shared state machine tracks the open
message, the parallel tool calls (each routed by id), and closes every open sequence
before a step, snapshot, or terminal boundary. A late delta for a closed tool call is
dropped rather than reopened (a second START would duplicate the call client-side);
an interleaved progressive STATE_SNAPSHOT/CUSTOM closes an open text message but
leaves tool calls open, because litellm stamps the tool id on the first delta only.

sdk streams tool calls keyed by delta index with accumulated identity and a parent
message id, so parallel calls and split-identity providers survive and the live
TOOL_CALL_ARGS match the final ModelResponse. parent_message_id is preserved on both
transports and both shapes. get_capabilities reports the resolved wire shape.

Built fresh on current main and integrated with the merged RAW passthrough,
get_capabilities, MCP, checkpointing, and multi-agent attribution.
@ranst91
ranst91 force-pushed the claude/pni-136-protocol-surface branch from 0de1bb5 to 3633cd3 Compare July 30, 2026 10:20
@ranst91
ranst91 marked this pull request as ready for review July 30, 2026 10:20
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.

1 participant