A glass prism splitting a beam of white light into a rainbow
Image: Encyclopædia Britannica, Inc. ·CC BY 4.0

Silver Protocol

AgJSON — the open, typed transport for normalized agent-framework I/O, unopinionated about storage and rendering.

In plain terms: write your client once, then plug in any agent framework it has a normalizer for.

@silverprotocol/core on npmAgJSON spec version

Every agent framework — Claude Agent SDK, OpenAI Agents SDK, Google ADK, LangGraph, Vercel AI SDK — streams its own shape of events. If you're building a client, a UI, or a tool that needs to work across more than one of them, you end up writing (and maintaining) a bespoke adapter per framework.

AgJSON is the wire format that ends that. It normalizes any framework's native event stream into one typed, versioned, forward-compatible shape — so a client written against AgJSON works with every framework a normalizer exists for, today and after the next SDK release.

Claude Agent SDK
OpenAI Agents SDK
Google ADK
Vercel AI SDK
+ more
Silver Protocol
AgJSON
Your client

Two SDKs. One event vocabulary.

Here's the same “call the echo tool” interaction as each framework actually emits it — a single fat message from Claude, a stream of deltas from OpenAI. Different shapes, different field names. Watch them land on the same AgJSON events.

Claude Agent SDK — native
// Claude Agent SDK — one message carries the whole tool call
{
  "type": "assistant",
  "message": {
    "role": "assistant",
    "content": [
      { "type": "tool_use", "id": "toolu_echo",
        "name": "echo", "input": { "text": "hi" } }
    ]
  }
}
OpenAI Agents SDK — native
// OpenAI Agents SDK — the same call arrives as streamed deltas
{ "type": "response.output_item.added",
  "item": { "type": "function_call", "name": "echo",
            "call_id": "call_Ewi…", "arguments": "" } }
{ "type": "response.function_call_arguments.delta", "delta": "{\"" }
{ "type": "response.function_call_arguments.delta", "delta": "text" }
{ "type": "response.function_call_arguments.delta", "delta": "\":\"hi\"}" }
AgJSON — identical from either SDK
// …both normalize to the exact same AgJSON events
{ "type": "tool.start",          "name": "echo", "toolCallId": "…" }
{ "type": "tool.args.delta",     "delta": "{\"text\":\"hi\"}" }
{ "type": "tool.args.assembled", "input": { "text": "hi" } }
{ "type": "tool.done",           "outcome": "ok",
  "content": [{ "type": "text", "text": "echo: hi" }] }

Write your client against these events once — every framework with a normalizer speaks them. That's the whole idea.

Typed, not any

Every event and block is a discriminated union member — no Record<string, unknown> escape hatches for shaped data.

Streaming-first

push(native): AgEvent[] / flush(): AgEvent[]— a stateful, per-invoke normalizer that folds a live stream into a persisted AgReduceResult.

Respects the UI layer

AgJSON carries content and interaction anchors (Layer-A); rendering is left to MCP Apps and A2UI — no reinvented component schema.

Forward-compatible by rule

Unknown event types and unknown fields are always safe to ignore, so a v1.0 client keeps working as the spec grows.

FAQ

What is AgJSON?

AgJSON is an open, typed wire format, unopinionated about storage and rendering, that normalizes the event streams of different agent frameworks — Claude Agent SDK, OpenAI Agents SDK, Google ADK, and others — into one consistent shape, so a client written once works across all of them.

How is AgJSON different from MCP?

MCP (Model Context Protocol) connects a model to tools and data sources. AgJSON is a different layer: it normalizes the agent framework's own event stream — turns, messages, tool calls, reasoning, HITL asks — for a client to consume. AgJSON's resource blocks and ui.* events carry MCP Apps surfaces opaquely, so the two specs compose rather than compete.

How is it different from A2UI or other agent-UI protocols?

AgJSON defines zero component schema and zero render layer, deliberately. A2UI and MCP Apps own UI rendering; AgJSON just carries their payloads as opaque Layer-A content so a client can route them to the right renderer.

How is AgJSON different from AG-UI?

AG-UI is an agent–user interaction protocol: alongside a typed event stream it also prescribes shared read/write state sync, generative UI, and human-in-the-loop, and pairs with CopilotKit for a turnkey React UI. AgJSON is deliberately narrower — a normalization and transport layer. It translates each framework's native output into typed AgJSON events and gives you a normative reduce() into messages and turns, then stops: it owns nothing about state or rendering. Reach for AG-UI when you want a batteries-included agent-UI stack; reach for AgJSON when you already have a frontend — or a non-UI consumer like a logger, eval harness, or gateway — and just want clean, framework-agnostic, typed events to feed it. The two can even compose: an AG-UI-style renderer can sit on top of AgJSON's reduced graph.

Do I need a UI framework to use AgJSON?

No. AgJSON's reduce() folds the event stream into a plain object graph — messages, turns, tool calls, artifacts — that any UI framework can render directly, so you can wire it into an existing frontend in a few lines. There's no required renderer or component library: AgJSON normalizes the stream and hands you the data; the UI stays entirely yours, or a renderer built on top.

Which agent frameworks does AgJSON support today?

Normalizers ship today for Claude Agent SDK, OpenAI Agents SDK, Google ADK (official @google/adk), and the Vercel AI SDK as per-framework @silverprotocol packages — all four live-verified end-to-end, including tool calls. LangGraph and Pydantic AI informed the spec's design, and more normalizers are community-contributable. See the framework support matrix for the full picture.

Is AgJSON production-ready?

The spec is Draft (wire version 1.0.0-draft.8) — stable enough to build on, but it may still change before the v1 freeze. Feedback from real integrations is exactly what's useful right now.

Is AgJSON open source?

Yes — MIT licensed. The canonical AgJSON spec lives in the flagship AgJSON repository; the reference TypeScript SDK (@silverprotocol/core plus the per-framework normalizers) lives in the open typescript-sdk repository.

Get started

1 Install

The AgJSON core, a normalizer for your framework, and your framework's own SDK — the normalizer sits on top of it, it doesn't replace it.

npm install @silverprotocol/core @silverprotocol/claude-agent-sdk
# ...and the framework SDK you already use:
npm install @anthropic-ai/claude-agent-sdk
# (OpenAI / ADK / Vercel AI SDK: @silverprotocol/openai-agents · @silverprotocol/google-adk · @silverprotocol/vercel-ai)

2 Normalize a framework stream → AgJSON

You keep producing events with your framework's own SDK — the normalizer just wraps its stream. It's stateful per run: feed it each native event with push(), then flush() at end-of-stream to seal anything still open. You get back plain AgEvents.

// `query` is the Claude Agent SDK's own streaming call — you keep
// using your framework as-is; AgJSON just normalizes what it emits.
import { query } from "@anthropic-ai/claude-agent-sdk";
import { createClaudeNormalizer } from "@silverprotocol/claude-agent-sdk";

// threadId: the id you persist this conversation under (SPEC §8.0 host obligation 6)
const normalizer = createClaudeNormalizer({ threadId: "conv_123" });
const agEvents = [];

for await (const native of query({ prompt: "call the echo tool" })) {
  agEvents.push(...normalizer.push(native)); // one native event → 0+ AgEvents
}
agEvents.push(...normalizer.flush());        // close dangling turns/messages

// agEvents are now framework-neutral — send them over the wire,
// persist them, or hand them straight to the reducer below.

3 Consume AgJSON → messages & turns

On the client, validate incoming events and fold them into the normative object graph — the same code regardless of which framework produced the stream.

import { ingestAgEvents, Reducer } from "@silverprotocol/core";

const events = ingestAgEvents(rawWireObjects); // parse-known-else-skip

const reducer = new Reducer();
for (const ev of events) reducer.push(ev);

const { messages, turns, artifacts, memory } = reducer.result();
// render `messages`, inspect `turns` — your UI never sees a raw SDK shape.

The push()/flush() contract and the normative reduce() fold are the whole surface — see the spec for the details, or the framework matrix for every normalizer.