1.0.0-rc.1

AgJSON 1.0.0-rc.1: the release candidate

The first release candidate for 1.0.0. All six packages move to 1.0.0-rc.1 together, and they ship spec revision 1.0.0-rc.1 (AGJSON_VERSION). From here to 1.0.0, only errata and fixes change the spec, each as a further release candidate.

Installing the release candidate

The release candidate is published under the rc tag. latest stays on 0.12.5, so a plain install doesn’t pick it up:

npm i @silverprotocol/core@rc

Install each framework package you use the same way (for example @silverprotocol/google-adk@rc), so every @silverprotocol/* package in your lockfile is on 1.0.0-rc.1. A receiver on 0.12.x accepts 1.0.0-rc.1 envelopes: it’s the same major (§12). While a release candidate is the most recent publish, a package’s page on npmjs.com shows no README; each package still carries its README.

Upgrading from 0.12.x

These change what you see without a code change on your side. Each is described in its package’s section below.

  • Four fold rules are stricter or newly defined. A repeated message.start id, or a more: true after a call’s final result, now parks the fold. A nested turn’s thread is defined, and an empty JSON Patch is a no-op (see “Fold changes”).
  • Google ADK: an in-band error no longer closes the turn at once. The error event arrives when ADK reports the error, and the turn closes once, as turn.error, at the invocation’s end, even when a later sub-agent answered. A host-reported error’s usage now adds to the turn’s usage instead of replacing it.
  • Google ADK: without the host-completion event, a later event after a final answer opens the next turn (<turn id>_g<n>), instead of landing after the closed turn’s terminal.
  • Claude Agent SDK: a refusal the CLI doesn’t retry now closes as turn.done with finishReason: "refusal", where 0.12.x closed it as turn.error.
  • OpenAI Agents SDK: a hosted MCP call’s tool.start.name is the MCP tool’s own name, and a hosted-MCP approval request stays a pending call until its answer closes it.

Fold changes

These change what reduce() builds, or when it stops and asks for a snapshot resync. They are tracked, with their spec rules, in AgJSON#5.

  • A message.start naming an id the fold already holds as a message parks (§5.0 INV-MSG). Earlier releases replaced the message. The held set spans the whole fold, so a later invoke that reuses a held id parks too, until a message.remove, or a snapshot that omits the id, releases it.
  • A tool.done with more: true for a call whose final result the invoke already landed parks (§5.0 INV-BLOCK). Earlier releases appended a second tool-result block. A result delivered across invokes is one tool.done without more: true (INV-XINV).
  • A nested turn takes its parent’s thread when that thread is known, else the fold’s root thread. Until one is known, its record is held and result() leaves it out. Earlier releases sometimes gave it the parentTurnId itself as its threadId. The reference Reducer holds up to 64 nested turns waiting for a thread, and a further one parks.
  • A turn-less terminal lands on the sole open turn even when that turn was opened only by a message.start. 0.12.x parked there. With a second turn open and no record for it, a turn-less terminal or tool.done now parks, where 0.12.x folded it onto the turn that held a record.
  • An empty JSON Patch ([]) applies as a no-op. As the first state event, or against an unseeded memory record, it no longer asks for a resync.

What you may see: a stream that re-sends a message.start or a preliminary result after the final one now sets needsResync. No recorded stream has either shape, and the recorded streams that reach the nested-turn change fold as before.

@silverprotocol/core

  • A messages.snapshot that carries turns keeps a held display.required. It used to discard it, though the spec says a reducer must never drop one. A turn the snapshot carries gets the held entries merged into its record, and a turn it doesn’t name keeps them held until its thread is known. This is a defect fix.
  • A subagent.start for a turn the fold already holds gives that record its parentTurnId when it has none.
  • The sole open turn counts every turn the fold has seen opened and not closed, not only turns that hold a record. A call whose final result parks is no longer marked as landed.
  • A message with no turnId is never attachable, even when a turn is named unknown-turn.
  • checkAgInput() rejects an A2UI surface action that asks for a response without an actionId, as malformed at the action’s actionId. The spec already required it.
  • New exports: AG_EVENT_MAX_DEPTH (1,016) and AG_EVENT_DEPTH_ALLOWANCE (16), the depth bound that 0.12.5 introduced, and copyJson, for a host that seeds a fold from its own store. All three are @beta.
  • A member named __proto__ is omitted at every depth of an event the reducer folds, whether or not the event was decoded first, and by core’s emit helpers. A JSON Patch path or from naming that token fails.
  • The 0.12.5 fixes are here too: a JSON Patch operation naming an inherited member fails, an event nested past the depth bound is kept as an ignored event in its seq slot, and no consumer-side parse throws on a deep value.

Google ADK

  • An in-band error no longer closes the turn at once. ADK keeps running after an event that carries errorCode and errorMessage: a SequentialAgent’s next sub-agent, a retried Workflow node, a callback or a further model round. The package now emits the non-terminal error event when the error arrives, and closes the invocation’s turn once, as turn.error, at the invocation’s end. That close carries the host-terminal event’s code when one was fed, otherwise the first in-band error’s, and the turn’s accumulated usage. Such a turn never closes as success, even when a later sub-agent or a retried node answered. When the turn’s pause was signalled, it closes paused with its asks, and the error closes on a new turn, <turn id>_error.
  • A host-reported error’s usage adds to the turn’s usage. A __host_error__ that carries usage used to replace the usage the stream had already reported. It’s now added, field by field. A host that passes the whole run’s total there will see it counted twice: pass the usage of unreported work only.
  • Without the host-completion event, a later event of the invocation opens the next turn. A final answer still closes its turn as success on arrival, but a later event of the same invocation (content, a usage report, an interruption, or a member carried verbatim) now opens the invocation’s next turn, <turn id>_g<n>, instead of landing after that turn’s terminal, and an interrupted: true that follows a completed Live generation closes that next turn as turn.abort (interrupted). An event that only changes shared state, and an aggregate that only repeats the closed turn’s text, stay on the closed turn, where they add nothing but a state.delta.
  • A ParallelAgent branch’s final answer ends its branch, not the invocation’s turn. Without the host-completion event, such a turn now closes at flush() as turn.abort (stream-truncated), as a completed Workflow does.
  • A model call’s closing aggregate is matched to the text that call streamed, per source, so the aggregate no longer renders its text a second time or parks the fold on interleaved streams. This is a defect fix.
  • On the Live path, a second barge-in now interrupts its generation, where it used to be dropped as a re-yield.
  • ADK’s prompt-block event records prompt.blocked. In StreamingMode.NONE, ADK turns a blocked prompt into an event whose errorCode is the block reason. The package now records one prompt.blocked for each such event, before the turn’s terminal, and ADK_PROMPT_BLOCK_REASONS (@beta) lists the values it treats. No event that ADK’s A2A relay forwards is read as a prompt block. The terminal is unchanged, so read a block from prompt.blocked.
  • A safety_blocked close names its block value as safety[].category when no safety rating does. IMAGE_PROHIBITED_CONTENT and IMAGE_RECITATION now map to safety_blocked, like the other six block values.
  • Values that were dropped now ride the event-level provider-raw carry: an errorMessage that arrives without an errorCode, and a grounding support’s members of an unexpected type. A part that holds text beside a call, a result or media now maps each member, and a part marked thought: true that carries a call, a result or media maps through its own arm, with {thought: true} carried. The message carry and the two part mappings are defect fixes.
  • A relayed remote failure that ADK’s A2A relay repeats in its closing event is one error.

Claude Agent SDK

  • A refusal the CLI doesn’t retry closes as turn.done. The CLI reports it as an API error and then a result marked is_error. The package now closes the turn as turn.done with finishReason: "refusal" and safety: [{category: "refusal", blocked: true}], and keeps the CLI’s error as its outcome. 0.12.x closed it as turn.error. The refusal’s stop_details also ride the refused message’s message.metadata, less any provider credit token.
  • A correlated task_notification’s carry follows the subagent run’s close it produced. Three recorded subagent streams reorder: the ext.anthropic.frame carry moves after the nested terminal and subagent.done.
  • The startup failure org_config_required_unavailable, new in 0.3.296, closes the turn as turn.error with retriable: true, since a retry may succeed. org_config_refused stays retriable: false.
  • claude-haiku-5-5 is now the Claude capture default. It affects how the test recordings are made, and nothing a host sees.

OpenAI Agents SDK

  • @silverprotocol/openai-agents now supports @openai/agents up to 0.20.x (peer range >=0.2.0 <0.21). 0.20 reads the run state 0.19 saves, so a run paused under 0.19 can be resumed on 0.20. The reverse doesn’t hold: 0.19 rejects a run state that 0.20 saved.
  • A change in @openai/agents 0.20 that hosts may notice. It’s the SDK’s, not the package’s. After an approval is answered, the next model call now counts toward maxTurns. So a resumed run with a tight maxTurns, such as 1, now stops with MaxTurnsExceededError where 0.19 let it finish. A host that reports that error to the package as documented, as a __host_error__ with code max_turns, sees the turn close as turn.error with that code. Raise maxTurns if a resumed approval needs a follow-up model response.
  • A run state saved by 0.19 that holds a deferred hosted MCP call needs a fresh tool search on 0.20 before that call runs.
  • A tool search’s hosted MCP discovery names (toolSearchMcpToolNames, new in 0.20) ride that search’s tool.done providerMetadata, next to toolSearchAgentName.
  • A built-in tool call stays in the round that made it. @openai/agents streams a round’s run-items after its response.completed. A round that lists a built-in call now waits for them, instead of closing without them and opening a turn that held them. A client-executed call’s round (shell, apply-patch, computer) closes only after its result lands, which is a defect fix.
  • A hosted-MCP approval request stays a pending call until its answer closes it, with one tool.done: ok when approved, denied when not. A round still waiting on its requests at flush() closes paused. A hosted MCP call and an approval request now name the MCP tool in tool.start.name, and the item’s type rides providerMetadata.type.
  • Hosted calls carry what they used to drop: their arguments, server label, results and failures; a file search’s queries and results; a code interpreter’s code and outputs; an image generation result as a file block, with its own fields; an MCP call’s error object; and an answer’s citations, which now land on its text block.
  • A refusal’s text lands as one text block in its round’s message, where it was dropped, and a refusal now closes its own round with finishReason: "refusal" instead of the round after it.
  • A hosted call whose native status is incomplete closes as an error, and a status other than completed rides tool.done providerMetadata.status.
  • The @beta type OpenAIOutputText now declares the providerData its annotations arrive in.

Performance

Measured against 0.12.5 on a quiet machine:

  • Folding costs the same, within measurement noise, over the recorded streams and the engine-built fixtures.
  • Emitting takes about 10% longer per event over the recorded streams. By framework, it’s about 7% longer for the Claude Agent SDK, 5% for the OpenAI Agents SDK, 17% for the Vercel AI SDK and 33% for Google ADK.

Spec: AgJSON 1.0.0-rc.1

rc.1 revises draft.8. The revision note in SPEC §15 lists every change. Among them:

  • The fold rules above, with a normalizer and a host rule for message ids. Within an invoke, a normalizer must not reuse a message id an earlier message.start or tool.done.messageId named. A host must not deliver a message.start reusing such an id, or one its own earlier invokes of the thread named.
  • §0.2 lets a consumer bound nesting depth and ignore, in its seq slot, a well-formed envelope past the bound.
  • §4 states how a block is signalled: from prompt.blocked, or from the finishReason of the turn.done that closes the turn, never from outcome.type.
  • Google ADK rules: the in-band error close (§8.0 item 35), the prompt-block record (item 34), the interrupted close (item 17), and the A2A relay rules (item 36). Each binds a normalizer targeting ADK.
  • §8.0 item 17 now directs an interrupted turn’s close (turn.abort, reason interrupted, from push() or flush()), where draft.8’s INV-TURN flush list named a turn.error; the reference ADK package already closed this way.
  • OpenAI built-in rounds and hosted-MCP approvals (§8.0 items 14 and 20, §10 item 59).
  • §13.7 names where a member named __proto__ is omitted (emit, decode and fold), and §5 resolves JSON Patch tokens by own members, as RFC 6901 reads them.
  • §12 defines OPEN STRING, the class its table already used for effort, and defines its direction column.
  • The A2UI reference now pins the v1.0 Candidate at a commit, and the OpenAI Apps reference cites an archived capture of its page.
  • Restatements that disagreed with the rules they restate are corrected, and the rows that left a case undefined now state it as the reference folds it.

draft.8 envelopes remain accepted (same major, §12).

Toolchain

The packages are built with TypeScript 7 and pnpm 12. They still require Node.js 22.12 or later, and the exported API is unchanged apart from the exports listed above.

Verified versions

Each framework package is verified end to end, tool calls included, against a live run at:

  • @anthropic-ai/claude-agent-sdk 0.3.296;
  • @openai/agents 0.20.0;
  • @google/adk 2.2.1, with @google/genai 2.28.0;
  • ai 7.0.137.

Wire version

1.0.0-rc.1. Replaying every recorded stream through the 0.12.5 and 1.0.0-rc.1 packages, 102 of the 105 runs give byte-identical events: the OpenAI Agents SDK (16) and Vercel AI SDK (9) streams, the Google ADK streams (24, each with and without the host-completion event), and 29 of the 32 Claude Agent SDK streams. The other three are the Claude subagent streams, which carry the same events in a new order (above). All 105 fold to the same result. Of the 619 runs of the engine-built ADK and OpenAI fixtures, 142 park on 0.12.5 and 14 on rc.1, and none parks on rc.1 that didn’t on 0.12.5.

The 1.0.0-rc.1 packages are built from typescript-sdk commit 8cf67384, the commit npm’s provenance for all six 1.0.0-rc.1 packages attests.