<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Silver Protocol updates</title>
    <link>https://silverprotocol.io/updates/</link>
    <description>Release notes for the @silverprotocol packages: every cohort, what changed, and the verification behind it.</description>
    <language>en</language>
    <atom:link href="https://silverprotocol.io/updates/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>0.12.3 — Security patch: a `__proto__` member in received data no longer picks an object&apos;s prototype</title>
      <link>https://silverprotocol.io/updates/0-12-3/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-12-3/</guid>
      <pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.12.3 — Security patch: a `__proto__` member in received data no longer picks an object&apos;s prototype.</description>
      <content:encoded><![CDATA[<p>A security patch. All six packages move to 0.12.3 together. The spec revision stays <code>1.0.0-draft.8</code>. Details are in the advisory, <a href="https://github.com/silverprotocol/typescript-sdk/security/advisories/GHSA-4pcf-49g3-h325">GHSA-4pcf-49g3-h325</a>: who is affected, workarounds, and what isn’t covered.</p>
<h3 id="what-changed">What changed</h3>
<ul>
<li><strong><code>@silverprotocol/core</code> requires zod 4.4.0 or later.</strong> With an older zod, <code>AgEvent.parse()</code> could give a received <code>ext.*</code> event a prototype taken from its own <code>__proto__</code> member. Three side effects:
<ul>
<li>An application that pins an older zod for itself gets a second copy of zod for core.</li>
<li>An application that combines core’s exported schemas with its own zod should move its own zod to 4.4.0 or later.</li>
<li><code>overrides</code> or <code>resolutions</code> that force zod below 4.4.0 defeat the requirement. Remove them, or decode with <code>ingestAgEvent()</code>.</li>
</ul>
</li>
<li><strong>Claude Agent SDK:</strong> a model name in the native <code>modelUsage</code> no longer sets the prototype of <code>usage.byModel</code> or of <code>modelUsage</code> in <code>ext.anthropic.result-meta</code>, and a <code>supersedes</code> list is copied like the other carries.</li>
<li><strong>Vercel AI SDK:</strong> an emitted value never holds a member named <code>__proto__</code>, at any depth, and a step’s stop details in <code>message.metadata</code> no longer take their prototype from one; the members beside it are carried as before.</li>
<li><strong>Google ADK:</strong> a member named <code>__proto__</code> in a native is dropped at every depth, and the unparsed-native carry no longer takes its prototype from one.</li>
<li><strong>OpenAI Agents SDK:</strong> a host error’s usage reaches <code>turn.error</code> copied, without an own <code>__proto__</code> key; a well-formed usage is unchanged. A host usage that isn’t a valid usage once that key is dropped is no longer put on <code>turn.error</code>; it rides <code>ext.openai.unparsed</code> instead.</li>
</ul>
<h3 id="what-to-do">What to do</h3>
<ol>
<li>Upgrade every <code>@silverprotocol/*</code> package to 0.12.3, including where another package pins one (check your lockfile).</li>
<li>If your application decodes received events with <code>AgEvent.parse()</code> or <code>ingestAgEvent()</code>, upgrade core now. Versions of core before 0.6.4 are being deprecated on npm.</li>
<li>Review the advisory for what to check in application code that copies values taken from events.</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>0.12.2 — Security patch: Google ADK stops emitting a remote agent&apos;s repeat of the forwarded request</title>
      <link>https://silverprotocol.io/updates/0-12-2/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-12-2/</guid>
      <pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.12.2 — Security patch: Google ADK stops emitting a remote agent&apos;s repeat of the forwarded request.</description>
      <content:encoded><![CDATA[<p>A security patch. All six packages move to 0.12.2 together, and only <code>@silverprotocol/google-adk</code> changes. The spec revision stays <code>1.0.0-draft.8</code>. Details are in the advisory, <a href="https://github.com/silverprotocol/typescript-sdk/security/advisories/GHSA-7mgv-vf77-ch53">GHSA-7mgv-vf77-ch53</a>. This is separate from the <a href="/updates/0-12-1/">0.12.1 patch</a>, which doesn’t stop the repeat described here.</p>
<h3 id="what-changed">What changed</h3>
<ul>
<li><strong>A remote agent’s repeat of the forwarded message is no longer mapped.</strong> Some remote A2A agents repeat the forwarded message back in their first reply. ADK-Python’s A2A executor on a2a-sdk 0.3.x sends it back as a new task’s <code>submitted</code> status, in the <code>"user"</code> role, and the relay yields it as the remote agent’s own output: model text on <code>@google/adk</code>, reasoning on ADK-Python’s relay. Before 0.12.2 the normalizer mapped that output like any other content, so the forwarded conversation reached the emitted events. It can hold the user’s text, other agents’ replies, tool-call arguments and tool results, and, on <code>@google/adk</code> before 2.1.0, an <code>adk_request_credential</code> call’s OAuth client secret.</li>
<li><strong>0.12.2 recognizes the repeat</strong> in a relayed event whose recorded reply is a <code>submitted</code> status with its message in the <code>"user"</code> role, and maps the event as if the repeated message’s parts were absent. Every other part of the event, including a task’s artifact parts, is mapped as before. In their place it emits one <code>ext.google.relay-echo-omitted</code> event inside the turn the relayed event belongs to. That event carries only the number of parts omitted, and none of their content.</li>
<li>A user-role message relayed in any other state, such as a remote workflow’s tool result while it’s working, is mapped as before.</li>
</ul>
<h3 id="who-is-affected">Who is affected</h3>
<p>An application on <code>@silverprotocol/google-adk</code> 0.3.0 to 0.12.1 that runs an agent tree containing a <code>RemoteA2AAgent</code>, or feeds the normalizer ADK events serialized from ADK-Python’s relay, when the remote agent repeats the forwarded message. An application that doesn’t relay to a remote A2A agent, or whose remote agents don’t repeat it, isn’t affected.</p>
<h3 id="what-to-do">What to do</h3>
<ol>
<li>Upgrade every <code>@silverprotocol/*</code> package to 0.12.2, including where another package pins one (check your lockfile), and rebuild any image that bundles the normalizer.</li>
<li>Rotate any secret that could have ridden in the repeat, as the advisory lists.</li>
<li>Purge those relay turns’ stored output, as the advisory describes.</li>
</ol>
<p>Until you can upgrade, the advisory describes a workaround for each relay. What 0.12.2 doesn’t cover is listed there too.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.12.1 — Security patch: Google ADK stops carrying an A2A relay&apos;s forwarded request</title>
      <link>https://silverprotocol.io/updates/0-12-1/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-12-1/</guid>
      <pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.12.1 — Security patch: Google ADK stops carrying an A2A relay&apos;s forwarded request.</description>
      <content:encoded><![CDATA[<p>A security patch. All six packages move to 0.12.1 together, and only <code>@silverprotocol/google-adk</code> changes. The spec revision stays <code>1.0.0-draft.8</code>. Details are in the advisory, <a href="https://github.com/silverprotocol/typescript-sdk/security/advisories/GHSA-685v-3m98-6f7p">GHSA-685v-3m98-6f7p</a>.</p>
<h3 id="what-changed">What changed</h3>
<ul>
<li><strong>Google ADK no longer carries an A2A relay’s bookkeeping.</strong> When a run relays a conversation through <code>@google/adk</code>’s <code>RemoteA2AAgent</code>, ADK records its exchange with the remote agent in the <code>customMetadata</code> of the events it relays, under <code>a2a:request</code>, <code>a2a:response</code>, <code>a2a:task_id</code> and <code>a2a:context_id</code>. The forwarded request can hold the host’s A2A push-notification token and credentials, request metadata, local ids and the conversation it forwards. Before 0.12.1, the normalizer copied <code>customMetadata</code> unchanged into <code>provider-raw</code> blocks, and a relayed event it couldn’t map reached an <code>ext.google.unparsed</code> event with its native copy intact.</li>
<li><strong>0.12.1 drops those four entries</strong> from each event’s <code>customMetadata</code>, and from the native copy in an <code>ext.google.unparsed</code> event under either spelling of the field (<code>customMetadata</code> or <code>custom_metadata</code>). Every other entry still rides, and no <code>customMetadata</code> member is emitted when none remains. The package’s README documents this exception to draft.8’s carry of unmapped ADK event fields.</li>
<li>No recorded stream carries these entries, so none changes.</li>
</ul>
<h3 id="who-is-affected">Who is affected</h3>
<p>An application on <code>@silverprotocol/google-adk</code> 0.3.0 to 0.12.0 (the versions before 0.12.1 that declare <code>@google/adk</code> as a peer) that either runs an agent tree containing a <code>RemoteA2AAgent</code>, or feeds the normalizer ADK events serialized from ADK-Python’s relay. An application that doesn’t relay to a remote A2A agent isn’t affected.</p>
<h3 id="what-to-do">What to do</h3>
<ol>
<li>Upgrade every <code>@silverprotocol/*</code> package to 0.12.1, including where another package pins one (check your lockfile), and rebuild any image that bundles the normalizer.</li>
<li>If AgJSON events from an affected version were forwarded or persisted while your application relayed through a <code>RemoteA2AAgent</code>, rotate the credentials the advisory lists, starting with the A2A push-notification token and credentials.</li>
<li>Purge stored <code>provider-raw</code> blocks, and <code>ext.google.unparsed</code> events whose native carries <code>a2a:*</code> entries in <code>customMetadata</code> or <code>custom_metadata</code>.</li>
</ol>
<h3 id="not-covered">Not covered</h3>
<p>These are unchanged in every version, 0.12.1 included:</p>
<ul>
<li>other <code>customMetadata</code> entries, which 0.12.1 still carries;</li>
<li>copies of ADK’s native events that your application reads, stores or forwards itself;</li>
<li>copies ADK keeps or forwards itself;</li>
<li>a remote agent that repeats the forwarded message in its reply, which <code>@google/adk</code> yields as the relay’s own output. Upgrading doesn’t stop this. The advisory describes a <code>RemoteA2AAgent</code> <code>afterRequestCallbacks</code> entry that removes the repeated message from a response in the <code>submitted</code> state, and what to rotate and purge for it. ADK’s own copies and the repeated message have been reported to Google. For the repeated message in the events this package emits, 0.12.2 addresses it; see <a href="/updates/0-12-2/">its release note</a>.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>0.12.0 — Node 22.12 or later, and which APIs the 1.x promise covers</title>
      <link>https://silverprotocol.io/updates/0-12-0/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-12-0/</guid>
      <pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.12.0 — Node 22.12 or later, and which APIs the 1.x promise covers.</description>
      <content:encoded><![CDATA[<p>A minor release of the SDK on the unchanged spec revision <strong><code>1.0.0-draft.8</code></strong>. Every package now declares Node.js 22.12 or later, the types that restate an upstream SDK’s native shapes are marked <code>@beta</code>, and a Google ADK error close now carries the turn’s usage. It also publishes draft.8’s first erratum, which changes no set, meaning, wire, fold or golden.</p>
<h3 id="upgrading-from-011x">Upgrading from 0.11.x</h3>
<ul>
<li><strong>Node.js 22.12 or later.</strong> Every package now declares <code>"engines": {"node": "&gt;=22.12.0"}</code>. The packages are ES modules and use no Node API; the floor is 22.12 because that’s the first 22.x whose <code>require()</code> loads ES modules from CommonJS. CI now runs on Node 22 and 24. On an older Node, npm warns (<code>EBADENGINE</code>); nothing is removed.</li>
<li><strong>The 1.x promise, and what <code>@beta</code> means.</strong> A symbol marked <code>@beta</code> in the type declarations is outside the 1.x promise: it may change in a minor release. This release marks as <code>@beta</code> the types that restate an upstream SDK’s native shapes: the OpenAI Agents SDK package’s 31 native-mirror types, and the Google ADK package’s <code>AdkEvent</code>, <code>AdkContent</code> and <code>AdkPart</code>. These follow the package’s peer range. Core’s producer helpers were already <code>@beta</code>. Each package’s own API (its <code>createXNormalizer()</code>, the options it takes and its helpers) carries the 1.x promise. It’s a marking, not a removal: nothing stops compiling. The rule is stated in core’s README, under “API stability”.</li>
</ul>
<h3 id="google-adk">Google ADK</h3>
<ul>
<li><strong>An error close carries the turn’s usage.</strong> When ADK ends a turn with an error in the stream (<code>errorCode</code> and <code>errorMessage</code>), the turn’s <code>turn.error</code> now carries the turn’s usage, as a host-reported error close already did. No recorded stream reaches this path, so none changes. This is a producer fix: the in-band close now carries the usage the spec defines for <code>turn.error</code>, and no field’s meaning changes.</li>
<li><strong>An empty <code>artifactDelta</code> no longer rides a <code>provider-raw</code> block.</strong> ADK gives every event an empty <code>artifactDelta</code>, and the normalizer carried it each time as a <code>provider-raw</code> block in the message content. It now skips <code>artifactDelta</code> only when it’s an empty object. A non-empty one still rides exactly as before, alone or beside another unmapped action. Recorded ADK streams change by design: those blocks leave, a block that also carried another action keeps it without the empty key, and <code>seq</code> renumbers. The fold keeps every message, and every turn record is unchanged.</li>
</ul>
<h3 id="openai-agents-sdk">OpenAI Agents SDK</h3>
<ul>
<li><strong>A handoff at the start of a resumed run gets a real parent turn.</strong> A resumed run can stream a handoff before any turn of the invoke has opened. The normalizer now opens the invoke’s own turn (<code>turn_resume_&lt;callId&gt;</code>) at the handoff’s <code>handoff_requested</code>, instead of at <code>handoff_occurred</code>, and makes it the parent of the handoff’s nested turn, so the nested turn carries the host’s thread. Before, the nested turn’s <code>parentTurnId</code> was a placeholder that named no turn. In a stored fold, the nested turn’s <code>parentTurnId</code> changes, and the resumed turn now comes before it in <code>turns[]</code>. Messages and artifacts are unchanged, and no recorded stream has this shape. This is a defect fix: the nested turn now gets the parent the spec already prescribes (§1.1: a nested turn’s <code>parentTurnId</code> points at the enclosing turn), and no field’s meaning changes.</li>
</ul>
<h3 id="silverprotocolcore"><code>@silverprotocol/core</code></h3>
<ul>
<li>The MCP App display-mode request lists its modes in <code>AgDisplayMode</code>’s order (<code>inline</code>, <code>pip</code>, <code>fullscreen</code>). The values are the same, so validation is unchanged; the type unions print in the new order.</li>
<li>The README gains the “API stability” section described above.</li>
</ul>
<h3 id="spec-an-erratum-to-draft8">Spec: an erratum to draft.8</h3>
<p>The first erratum to draft.8, applied in place under §15’s errata policy, with no version change. §12’s closed-set paragraph now notes, in an informative parenthetical, that the reference SDK checks its schema against the table: its spec-drift check reads the table and compares its sets. §3’s <code>mcp-app</code> <code>mode</code> union is listed in <code>AgDisplayMode</code>’s order. Neither edit changes a set, a meaning, the wire, a fold or a golden.</p>
<h3 id="wire-version">Wire version</h3>
<p><code>1.0.0-draft.8</code>, unchanged. Replaying every recorded stream through the 0.11.0 and 0.12.0 packages, the Claude Agent SDK (30), OpenAI Agents SDK (15) and Vercel AI SDK (8) streams are byte-identical, in their events and in the fold. Of the 116 Google ADK runs (the recorded streams, the same streams with and without the host-completion event, and the pause fixtures in both modes), 6 are identical, and 110 differ only by the removed empty <code>artifactDelta</code> blocks, the empty key taken off the blocks that also carry another action, and the <code>seq</code> numbering that follows. Every fold matches once the same changes are applied.</p>
<p>The v0.12.0 release is tagged at typescript-sdk commit ef24ebd4, the commit npm’s provenance for all six 0.12.0 packages attests.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.11.0 — AgJSON draft.8: the closing draft freezes the vocabulary for major 1</title>
      <link>https://silverprotocol.io/updates/0-11-0/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-11-0/</guid>
      <pubDate>Sun, 27 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.11.0 — AgJSON draft.8: the closing draft freezes the vocabulary for major 1.</description>
      <content:encoded><![CDATA[<p>A minor release that ships spec revision <strong><code>1.0.0-draft.8</code></strong> (<code>AGJSON_VERSION</code>), the closing draft before the release candidate. It fixes the vocabulary for major 1: §12 now lists every closed set and its class, a new block type reaches only a client that declares support for it, and after 1.0 a change to what a field or fold means is a major. On the wire, 0.11.0 adds optional fields only, and every recorded stream serializes byte-identically to 0.10.0.</p>
<h3 id="what-the-reference-producers-emit-for-now">What the reference producers emit for now</h3>
<p>Until every deployed consumer runs a draft.4 or later reader, the reference producers emit no event type, closed-set value or block type beyond draft.3’s vocabulary except <code>ext.&lt;vendor&gt;.*</code>. A reader from before draft.4 drops an event type it doesn’t know and parks on the gap, while a draft.4 or later reader stubs it in place and keeps its <code>seq</code> slot (§12).</p>
<h3 id="upgrading-from-010x">Upgrading from 0.10.x</h3>
<ul>
<li><strong><code>AgReasoningConfig.effort</code> is an open string.</strong> <code>"minimal"</code>, <code>"low"</code>, <code>"medium"</code> and <code>"high"</code> stay documented, and any other string now passes. A receiver on an earlier core still rejects an undocumented value as <code>unknown-value</code>, so upgrade the receiving side before you send one. A non-string <code>effort</code> is still <code>malformed</code>, and <code>mode</code> stays a closed set.</li>
<li><strong>A memory record keeps the write’s <code>_meta</code>.</strong> <code>AgMemoryRecord</code> gains an optional <code>_meta</code>, copied from <code>memory.write._meta</code>. A stream whose <code>memory.write</code> carried <code>_meta</code> re-folds with it, where 0.10.0 dropped it. A reader that stores a strict parse’s output drops the field; the reference reader keeps unknown fields (§0.2).</li>
</ul>
<h3 id="spec-agjson-100-draft8">Spec: AgJSON 1.0.0-draft.8</h3>
<ul>
<li><strong>Closed sets for major 1.</strong> §12’s table is the normative list of closed sets, each with its class. Within major 1, a set classed FROZEN gains no value; new vocabulary for it lands only as an optional open-string companion where the table names one, as <code>turn.done.finishReasonRaw</code> does for the finish reason. <code>AgBlock</code> has 16 kinds.</li>
<li><strong>New block types.</strong> A later minor may define a new <code>AgBlock</code> type together with an additive <code>AgClientCapabilities</code> field that declares support for it. A producer must not emit that block type to a client whose declared capabilities omit it.</li>
<li><strong>Version acceptance.</strong> A consumer rejects a different major and should accept any same-major version, including any prerelease tag of that major (<code>-draft.*</code>, <code>-rc.*</code>). The per-client version gate and its <code>1.0.0-draft.3</code> default are withdrawn. No producer implemented them, and a receiver already rejects an absent <code>version</code> as <code>malformed</code>, so nothing changes in practice.</li>
<li><strong>After 1.0,</strong> a change to what an existing field or fold means is a major.</li>
<li><strong><code>ext.&lt;vendor&gt;.&lt;key&gt;</code>:</strong> §12 states the grammar the schema enforces. The vendor is one dot-free token that the emitter owns, and the key may carry dots. The reversed-domain recommendation is retired.</li>
<li><strong>Built records.</strong> A record the fold builds carries only the members its type declares (§5). The reference reducer already does.</li>
<li><strong>Fold changes, both additive record fields (§12):</strong> <code>AgMemoryRecord._meta</code> (above), and <code>prompt.blocked.reasonRaw</code> carried onto <code>AgTurnRecord.promptBlocked</code> (below). 0.10.0 dropped both.</li>
<li><strong><code>prompt.blocked.reasonRaw</code>,</strong> an optional open-string companion that carries a native block reason the mapping reports as <code>"other"</code> (§4, §10 item 23).</li>
<li><strong>Recalled memory has no AgJSON event.</strong> The Claude Agent SDK’s <code>memory_recall</code> frame rides <code>ext.anthropic.frame</code>, byte-preserved, and a host-executed memory-tool read reports on that call’s <code>tool.done</code> (§13.11). This draft neither adds nor reserves a <code>memory.read</code> event.</li>
<li><strong>Housekeeping, text only:</strong>
<ul>
<li>§10 now reads as a conformance definition: each framework-specific item states whom it binds.</li>
<li>§11 is resolved.</li>
<li>§13.11 no longer offers ADK <code>user:</code>/<code>app:</code> keys as a <code>memory.write</code> example; they ride <code>state.delta</code> for all of 1.x (§8.0 item 30).</li>
<li>The draft notes move to a new §15, Revision history, with an errata policy. An erratum corrects the text of a published revision, never what a field, event or fold means. It’s applied in place and doesn’t move the version.</li>
</ul>
</li>
<li>No golden changes. draft.7 envelopes remain accepted (same major, §12).</li>
</ul>
<h3 id="silverprotocolcore"><code>@silverprotocol/core</code></h3>
<ul>
<li><strong><code>prompt.blocked.reasonRaw</code>,</strong> folded onto <code>AgTurnRecord.promptBlocked</code>, as <code>turn.done.finishReasonRaw</code> is carried. An event without it lands a record without it.</li>
<li><strong><code>AgMemoryRecord._meta</code>.</strong> A <code>value</code> write replaces the record whole, <code>_meta</code> included, so a write without one lands a record without one. A <code>patch</code> replaces <code>_meta</code> only when the write carries one, as it does <code>reason</code> and <code>durable</code>. A <code>messages.snapshot</code>’s memory elements declare the same field, and <code>toPersistable()</code> keeps it.</li>
<li><strong><code>AgReasoningConfig.effort</code></strong> is typed <code>string</code>, so <code>checkAgInput()</code> no longer reports an undocumented effort as <code>unknown-value</code>.</li>
<li><code>AGJSON_VERSION</code> is <code>1.0.0-draft.8</code>.</li>
</ul>
<h3 id="google-adk">Google ADK</h3>
<ul>
<li><strong>An unmapped prompt block reason rides <code>prompt.blocked.reasonRaw</code>.</strong> <code>SAFETY</code>, <code>BLOCKLIST</code> and <code>PROHIBITED_CONTENT</code> map to their reasons and carry no <code>reasonRaw</code>. Any other <code>promptFeedback.blockReason</code> maps to <code>"other"</code> and now rides <code>reasonRaw</code> verbatim. On <code>@google/adk</code> 2.1.0, no ADK-built event carries <code>promptFeedback</code>, because ADK turns a prompt block into an error code and message. So this serves events built elsewhere.</li>
</ul>
<h3 id="wire-version">Wire version</h3>
<p><code>1.0.0-draft.8</code>, with optional fields only. Replaying the 77 recorded streams through the 0.10.0 and 0.11.0 packages, output is byte-identical for every one: Claude Agent SDK 30 of 30, Google ADK 24 of 24 (with and without the host-completion event), OpenAI Agents SDK 15 of 15 and Vercel AI SDK 8 of 8. The new optional fields appear in no recorded stream. The other framework packages change only their READMEs’ wire-version line, and <code>@silverprotocol/richtext</code> is unchanged apart from its version.</p>
<p>The v0.11.0 release is tagged at typescript-sdk commit d24faaaf, the commit npm’s provenance for all six 0.11.0 packages attests.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.10.0 — AgJSON draft.7: an ADK pause closes at the invocation&apos;s end, on every host</title>
      <link>https://silverprotocol.io/updates/0-10-0/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-10-0/</guid>
      <pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.10.0 — AgJSON draft.7: an ADK pause closes at the invocation&apos;s end, on every host.</description>
      <content:encoded><![CDATA[<p>A minor release that ships spec revision <strong><code>1.0.0-draft.7</code></strong> (<code>AGJSON_VERSION</code>). It’s a fix: a Google ADK pause that more of the same invocation follows no longer parks the fold, whether or not the host sends the completion event. This lands the bare-host fix the 0.9.0 note said would come in the next minor. No field’s meaning changes.</p>
<h3 id="google-adk">Google ADK</h3>
<ul>
<li><strong>A paused turn closes at the invocation’s end, on every host.</strong> A credential or input request doesn’t have to end an ADK invocation. More of that invocation can follow the pause: the model runs again after a usage-only report (including through ADK’s SSE streaming path), an after-agent callback adds content, or a SequentialAgent root moves to its next sub-agent. That output belongs to the paused turn. The paused close is now the turn’s last event: from <code>push()</code> on the host-completion event when you use <code>hostCompletion</code>, otherwise from <code>flush()</code>. Before, a host without <code>hostCompletion</code> got the paused close at the pause, the later output landed after it, and the fold parked. The 0.9.0 note listed this as a known limit.</li>
<li><strong>What a host sees differs from 0.9.0 in three ways:</strong>
<ul>
<li>Without <code>hostCompletion</code>, the paused <code>turn.done</code> now arrives from <code>flush()</code>, after the invocation’s other events, instead of at the pause. Only the terminal moves: each <code>hitl.ask</code> arrives where it did. A host may act on a live <code>hitl.ask</code> before the terminal; the paused record in <code>turn.done</code> stays the durable declaration.</li>
<li>The paused turn’s <code>usage</code> covers the invocation’s later rounds.</li>
<li>A turn that holds an ask when the stream ends, with no pause signal seen, still closes as <code>turn.abort</code> (<code>stream-truncated</code>), never as paused.</li>
</ul>
</li>
<li><strong>Correction to the 0.9.0 note.</strong> It said ADK ends the invocation at a tool confirmation. That holds for the pausing agent’s own run, but a confirmation in a SequentialAgent sub-agent that isn’t the last one is still followed by the next sub-agent’s output in the same invocation. So the rule above covers every pause kind, confirmations included.</li>
<li><strong>Still host-completion-only:</strong> two shapes of a completed, not paused, run. Without <code>hostCompletion</code>, a SequentialAgent root outside ADK’s Workflow plane, or an after-agent callback that appends content after a success close, lands after the in-band terminal and parks the reducer.</li>
<li>Every recorded stream serializes byte-identically. Only the engine fixtures whose invocation outlives the pause change: they now fold clean without the completion event.</li>
<li><strong>Known limit, unchanged:</strong> under ADK’s SSE streaming mode, ADK’s closing usage report repeats the call’s usage, so a turn’s summed <code>usage</code> can count that call twice. Because a paused turn now sums the invocation’s later rounds on every host, this can show on a paused turn too.</li>
</ul>
<h3 id="spec-agjson-100-draft7">Spec: AgJSON 1.0.0-draft.7</h3>
<ul>
<li><strong>The ADK paused close moves to the invocation’s end</strong> (§8.0 item 26): from <code>push()</code> on the host-completion event, otherwise from <code>flush()</code>. INV-FLUSH (2) now releases a turn whose pause was signalled as <code>paused</code> at flush, and a turn with no pause signal still closes as a truncation. INV-TURN, §8.0 “Ids across invokes”, item 12 and §10 items 25 and 40 read accordingly. §7 says a host may act on a live <code>hitl.ask</code> before the turn’s terminal.</li>
<li>Every recorded cassette serializes byte-identically. Six engine fixtures that parked on hosts without the completion event now fold, and a paused turn’s <code>usage</code> covers the invocation’s later rounds.</li>
<li>This is a defect fix: a fold that parked now closes, and no field’s meaning changes.</li>
</ul>
<p>draft.6 envelopes remain accepted (same major, §12).</p>
<h3 id="silverprotocolcore"><code>@silverprotocol/core</code></h3>
<ul>
<li><code>AGJSON_VERSION</code> is <code>1.0.0-draft.7</code>.</li>
<li>The producer API is marked <code>@beta</code> in the type declarations. That covers <code>StreamAssembler</code> and its field types, <code>AssemblerCheckpoint</code>, <code>withAtomicPush</code> with its options and error constants, and the JSON-safety helpers. The wire vocabulary and the consumer API stay unmarked, including <code>Normalizer</code> and <code>ToolOutcome</code>. Nothing changes at runtime.</li>
</ul>
<h3 id="wire-version">Wire version</h3>
<p><code>1.0.0-draft.7</code>. Replaying the 77 recorded streams through the 0.9.0 and 0.10.0 packages, output is byte-identical for every one: Claude Agent SDK 30 of 30, Google ADK 24 of 24 (with and without the host-completion event), OpenAI Agents SDK 15 of 15 and Vercel AI SDK 8 of 8. No recorded stream has a pause its invocation outlives. The other framework packages change only their READMEs’ wire-version line, and <code>@silverprotocol/richtext</code> is unchanged apart from its version.</p>
<p>npm’s provenance for the 0.10.0 packages names typescript-sdk commit 4fd5a02a, one commit after the v0.10.0 tag, which changes only a repository check script outside every package; the package contents are identical.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.9.0 — AgJSON draft.6: three fold stalls fixed, and hosts declare the memory they keep</title>
      <link>https://silverprotocol.io/updates/0-9-0/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-9-0/</guid>
      <pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.9.0 — AgJSON draft.6: three fold stalls fixed, and hosts declare the memory they keep.</description>
      <content:encoded><![CDATA[<p>A minor release that ships spec revision <strong><code>1.0.0-draft.6</code></strong> (<code>AGJSON_VERSION</code>). draft.6 is the last draft planned to change what a field or a fold means before 1.0.0. This release fixes three streams that stopped a conformant fold: parallel tool calls with partial messages on in the Claude Agent SDK, and two Google ADK paths. <code>toPersistable()</code> now also drops memory a host hasn’t declared it keeps, so read the change below if you persist folds.</p>
<h3 id="fixes-streams-that-parked-the-fold">Fixes: streams that parked the fold</h3>
<ul>
<li><strong>Claude Agent SDK, parallel tool calls with partial messages on.</strong> The Claude normalizer no longer seals a streamed assistant message when a tool result arrives mid-stream, so parallel tool calls with partial messages on no longer start a call twice (a fold park since core 0.7.0). On streamed tool rounds, <code>message.end</code> now follows the round’s <code>tool.done</code>. Five existing goldens reorder only, and in one the message’s streamed usage now lands on <code>message.end</code> instead of a raw carry (a recovery, not a meaning change).</li>
<li><strong>Google ADK, a paused turn followed by more output from the same invocation.</strong> ADK ends the invocation at a tool confirmation, but not at a credential request or an input request, so more of the same invocation can follow the pause. For example, the model runs again after a stream that ends with a usage-only chunk, an after-agent callback runs, or a SequentialAgent root moves to its next sub-agent. The normalizer closed the turn paused at the pause, so that later output landed after the terminal and the fold parked. With <code>hostCompletion</code>, the paused close now waits for the host-completion event (SPEC §8.0 item 26). A paused run that ends without that event closes at <code>flush()</code> as <code>turn.abort</code> (<code>stream-truncated</code>), or as <code>turn.error</code> for an error the host feeds.
<ul>
<li><strong>Known limit, for hosts without <code>hostCompletion</code>:</strong> a paused ADK turn whose invocation keeps emitting (a credential or input request on a backend whose stream ends with a usage-only chunk, an after-agent callback, or a SequentialAgent root) still parks on a host that doesn’t feed the completion marker. Opt in to <code>hostCompletion</code> to fold it clean today; the bare-host fix, the paused close deferred to the invocation’s end, lands in the next minor.</li>
</ul>
</li>
<li><strong>Google ADK Live, a tool call followed by the model’s reply.</strong> On the Live path, for a model whose name doesn’t contain <code>-flash-live</code>, ADK yields the call generation’s <code>turnComplete</code> between the function response and the model’s reply. The normalizer closed the turn there, so without the host-completion marker the reply landed on the closed turn and the fold parked (a park since core 0.7.0), losing the reply and its usage. A turn now waits for the model’s reply after each function response; a <code>turnComplete</code> with no finish reason or error code in that window doesn’t close it, and an error still closes it at once. A live tool-call capture now folds as one turn, closed on the reply’s <code>turnComplete</code>, with or without the marker.</li>
</ul>
<h3 id="changed-topersistable">Changed: <code>toPersistable()</code></h3>
<ul>
<li><strong><code>toPersistable(result)</code> now also omits memory records whose scope isn’t <code>thread</code>,</strong> unless you declare that scope in <code>{ memoryScopes }</code>. A bare call keeps <code>thread</code> memory only. This corrects the 0.8.0 note, which said the projection removed <code>displayRequired</code> “and nothing else changed”: from 0.9.0 it also drops <code>agent</code>, <code>user</code> and <code>skill</code> memory you haven’t declared. A host that keeps those scopes passes them, for example <code>toPersistable(result, { memoryScopes: ["user"] })</code>.</li>
</ul>
<h3 id="spec-agjson-100-draft6">Spec: AgJSON 1.0.0-draft.6</h3>
<ul>
<li><strong>Memory scopes.</strong> A non-thread memory scope (<code>agent</code>, <code>user</code>, <code>skill</code>) records a write to the producer’s own cross-thread store, never a claim about the host (§2, §4, §5, §13.11). A host’s storage projection may omit the non-thread scopes it hasn’t declared it persists (§5.0, §8.0 host obligation 7). The declaration rides the additive <code>AgCapabilities.memoryScopes</code> (§3), and the reference projection takes it (§10 item 51). A stored fold written before draft.6 keeps every scope its host kept, and its <code>durable</code> flag reads as the producer’s statement about its own store.</li>
<li><strong>URLs (§13.4).</strong> A consumer’s scheme validation now covers any URL it navigates to or renders as a hyperlink, whatever carried it. The former list of carriers becomes examples, and a <code>kind: "auth"</code> ask’s authorization URL is covered on either carrier. A fetch made to render or resolve a wire URL follows the host care that the section sets out.</li>
<li>The draft.6 text itself changes no producer’s bytes and no golden. The release as a whole does move goldens: the Claude fix above reorders five and recovers usage in one, and the release adds four recorded streams (three Claude parallel-call captures and a Gemini Live tool call).</li>
</ul>
<p>draft.5 envelopes remain accepted (same major, §12).</p>
<h3 id="silverprotocolcore"><code>@silverprotocol/core</code></h3>
<ul>
<li><strong><code>AgCapabilities.memoryScopes</code></strong>, an optional list of the non-thread scopes the host persists. Absent declares none.</li>
<li><strong><code>toPersistable(result, { memoryScopes })</code></strong> keeps <code>thread</code> memory and each declared scope (see Changed).</li>
<li><strong><code>toPersistableWithReport(result, { memoryScopes })</code></strong> returns <code>{ result, omitted }</code>: the same projection, plus each omitted record by scope and key in the fold’s order, for a host that reports what it didn’t store.</li>
</ul>
<h3 id="wire-version">Wire version</h3>
<p><code>1.0.0-draft.6</code>. Replaying the 77 recorded streams through the 0.8.0 and 0.9.0 packages, output is byte-identical for every OpenAI Agents SDK stream (15 of 15), every Vercel AI SDK stream (8 of 8) and every Google ADK stream (24 of 24). The recorded ADK streams feed the host-completion marker, and on them this release’s ADK changes move nothing. 22 of 30 Claude streams are byte-identical. The eight that change are the six goldens above and two new parallel-call captures with partial messages on. <code>@silverprotocol/richtext</code> is unchanged apart from its version.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.8.0 — AgJSON draft.5: closed turns stay closed, and input tokens include the cache</title>
      <link>https://silverprotocol.io/updates/0-8-0/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-8-0/</guid>
      <pubDate>Sat, 26 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.8.0 — AgJSON draft.5: closed turns stay closed, and input tokens include the cache.</description>
      <content:encoded><![CDATA[<p>A minor release that ships spec revision <strong><code>1.0.0-draft.5</code></strong> (<code>AGJSON_VERSION</code>). It’s a minor because the fold and the meaning of a field change. A turn’s closure now follows its turn record, so a <code>messages.snapshot</code> no longer reopens a closed turn, and a second terminal for a closed turn stops the fold and asks for a resync. A record event for a turn whose thread the reducer doesn’t know yet is held, instead of landing on a placeholder record. A Claude <code>inputTokens</code> now counts cache reads and writes. The release also adds <code>toPersistable()</code> for hosts that store folds, a <code>threadId</code> option on the OpenAI Agents SDK, Google ADK and Vercel AI SDK packages, MCP resource links as first-class blocks, and turns per generation for Google ADK Live sessions. Read the upgrading notes first.</p>
<h3 id="upgrading-from-07x">Upgrading from 0.7.x</h3>
<ul>
<li><strong>Claude <code>inputTokens</code> is cache-inclusive.</strong> In draft.5, <code>inputTokens</code> counts every input token the provider processed, cache reads and writes included, with <code>cacheReadTokens</code> and <code>cacheWriteTokens</code> as its breakdown, never an addition. Anthropic reports input without the cache, so a Claude <code>inputTokens</code> (on the turn, on each message and in each <code>byModel</code> entry) now includes its cache reads and writes, and rises on cached calls. One with no cache counters is unchanged. In the echo capture, the turn’s <code>inputTokens</code> goes from 4 to 4,056: 4 uncached tokens, plus 1,966 cache reads and 2,086 cache writes. A Claude turn terminal’s counters cover that turn, so its usage now reads <code>cumulative: false</code>; 0.7.x marked it <code>true</code>. Its <code>costUsd</code> is the SDK’s running estimate over the whole <code>query()</code> call, so it carries the new <code>costScope: "query"</code>. Without partial messages, an assistant message’s <code>outputTokens</code> is a placeholder, so <code>message.end.usage</code> now omits it. Usage from the OpenAI Agents SDK, the Vercel AI SDK and Google ADK’s <code>generateContent</code> route is unchanged; Gemini Live changes (see Google ADK).
<ul>
<li><strong>Repairing stored Claude rows.</strong> A stored Claude turn record is from before 0.8.0 exactly when its top-level <code>usage</code> carries a <code>costUsd</code> key (a <code>0</code> included) and no <code>costScope</code>; a record without <code>usage</code> has nothing to repair. To repair one, add <code>cacheReadTokens + cacheWriteTokens</code> to its <code>inputTokens</code> and, for each <code>byModel</code> entry, that entry’s own <code>cacheReadTokens + cacheWriteTokens</code> to the entry’s <code>inputTokens</code>. Read its top-level <code>cumulative</code> as <code>false</code> and its <code>costUsd</code> as carrying <code>costScope: "query"</code>, and leave each entry’s <code>cumulative</code> as stored. The record’s token counters and cost then read as a 0.8.0 record’s. A host that rewrites the row stores the repaired counters and <code>costScope: "query"</code> together, which ends the marker, so the row is never repaired twice. A stored Claude message record has no exact marker: <code>inputTokens &lt; cacheReadTokens + cacheWriteTokens</code> identifies some older rows, not all.</li>
</ul>
</li>
<li><strong>A snapshot no longer reopens a closed turn.</strong> A turn is closed while the fold holds a turn record for it that carries an <code>outcome</code>. A <code>messages.snapshot</code> changes closure only through the turn records it leaves: one that carries a turn with an <code>outcome</code> keeps that turn closed, and one that omits <code>turns</code> keeps every closure. After such a snapshot, a <code>message.start</code>, a <code>tool.done</code> adopting a message, or a terminal for that turn parks the reducer, where 0.7.x folded it. To reopen a turn, carry its record without an <code>outcome</code> (a snapshot with <code>turns: []</code> drops every record, and so every closure). No framework package’s output changes.
<ul>
<li><strong>This reverses part of what draft.4 and 0.7.0 said.</strong> The draft.4 spec note and <a href="https://github.com/silverprotocol/AgJSON/issues/1">AgJSON#1</a> (section 4) said a turn stays closed “until a <code>messages.snapshot</code>”, and the 0.7.0 note advised: when <code>needsResync</code> is set, resume from a snapshot. A snapshot still clears a message’s seal, since it replaces the messages. It no longer clears a turn’s closure. Resuming from a snapshot of the fold still recovers from a gap or a re-delivery. It doesn’t license new content for a turn the snapshot records as closed: a finished invoke re-sent from <code>seq</code> 0 after a snapshot of its own fold parks, and the fold stays the snapshot’s.</li>
</ul>
</li>
<li><strong>A second terminal for a closed turn parks.</strong> A <code>turn.done</code>, <code>turn.error</code> or <code>turn.abort</code> for a closed turn now sets <code>needsResync</code> and leaves the fold unchanged, whatever its outcome and whether or not it repeats the recorded one. In 0.7.x each terminal overwrote the record, so an error followed by a success read as a success. The one exception is a host’s paused refresh: a re-sent <code>turn.start</code>, then a <code>turn.done</code> with a <code>paused</code> outcome and refreshed asks, for a turn closed as paused. It replaces the outcome and asks, keeps the recorded usage, and the turn stays closed. The refresh still works when a <code>messages.snapshot</code> falls between the <code>turn.start</code> and the <code>turn.done</code>. The one-terminal rule binds a host’s own appended events as well as a normalizer’s. Every committed golden folds as before.</li>
<li><strong>Record events for a turn whose thread isn’t known are held.</strong> A <code>source</code>, <code>handoff</code>, <code>prompt.blocked</code>, <code>guardrail.result</code>, <code>agent.capabilities</code> or <code>display.required</code> event lands on a record carrying the turn’s thread. The thread comes from a <code>turn.start</code>, <code>subagent.start</code>, <code>message.start</code> or <code>messages.snapshot</code> of that turn. Until the reducer knows it, the landing is held and <code>result()</code> omits the record.
<ul>
<li><strong>Before (0.7.x):</strong> a record event for a turn not yet opened created a stub record whose <code>threadId</code> was the <code>turnId</code>, or an <code>unknown-turn</code> record.</li>
<li><strong>After (0.8.0):</strong> no turn record carries a <code>threadId</code> that no event carried. A later opener sets a held record’s thread, and a <code>subagent.start</code> also sets its <code>parentTurnId</code>.</li>
<li><strong>In the fold:</strong> a terminal or record event with no <code>turnId</code> resolves to the sole open turn, never a closed one. With no open turn, or several, its owner is unresolvable and the reducer parks. The reference reducer holds up to 64 turns and 1,024 landings; past either, it parks.</li>
<li><code>Reducer.ensureTurn()</code>, which minted those stubs, is removed.</li>
</ul>
</li>
<li><strong>Ids change: Google ADK.</strong> Turn and message ids now carry a per-invoke stem, random by default, or the <code>invokeId</code> option: <code>turn_&lt;invocationId&gt;</code> becomes <code>turn_adk_&lt;16 hex&gt;_&lt;invocationId&gt;</code>, and message ids follow. <code>invokeId: "adk"</code> doesn’t reproduce 0.7.x’s ids. The host-error sentinel’s <code>invocationId</code> no longer names the turn. This keeps ids unique when an invoke reuses an <code>invocationId</code>, as a resumed ADK-Python run does; in 0.7.x two such invokes folded into one <code>Reducer</code> parked it.</li>
<li><strong>Claude host-only fields moved. These are breaking if you read them:</strong>
<ul>
<li>The CLI wrapper keys <code>context_usage</code>, <code>usage_report</code>, <code>user_message_uuid</code>, <code>user_message_uuids</code>, <code>resume_reason</code>, <code>aborted</code> and <code>supersedes</code> move from <code>providerMetadata</code> to host-only <code>_meta</code>, on the first block’s start event, or on a <code>message.metadata</code> event when no block anchors them. On a frame whose first block is a tool call, they now always arrive on <code>message.metadata</code>. This retracts the <code>providerMetadata</code> channel described for <code>aborted</code> in 0.3.7, for <code>context_usage</code> in 0.4.4, and for the <code>user_message_uuid</code> family, <code>resume_reason</code> and <code>usage_report</code> in 0.6.1–0.6.3.</li>
<li>A tool result’s <code>resourceLinks</code> moves from <code>tool.done.providerMetadata</code> (0.5.4) to <code>tool.done._meta["anthropic/resourceLinks"]</code>, the SDK’s list, verbatim. The result’s <code>content</code> stays the rendered text.</li>
<li>A permission denial’s context (<code>decisionReasonType</code>, <code>decisionReasonCode</code>, <code>decisionReason</code>, <code>agentId</code>) now rides one record, <code>tool.done._meta["anthropic/permissionDenied"]</code>, on the denial and on the closing tool result alike. That includes <code>decisionReasonCode</code>, added in 0.6.3.</li>
<li>A notice’s <code>level</code>, <code>preventContinuation</code> and <code>toolUseId</code> ride the notice text block’s <code>_meta</code>.</li>
<li>Host records carry no durability promise (SPEC §2.1). A host may drop <code>_meta</code> when it persists a conversation, so a record read from the live stream can be absent after a reload.</li>
</ul>
</li>
<li><strong>Pass <code>threadId</code> if you persist.</strong> A host that persists, routes or folds across invokes by <code>threadId</code> must give the normalizer the thread it assigned the invoke to (SPEC §8.0 host obligation 6). Without the option, each package stamps a facet-local placeholder that is not a thread identity: <code>"openai"</code>, <code>"google"</code> or <code>"vercel"</code>, or the Claude session id. Output without the option is unchanged.</li>
</ul>
<h3 id="spec-agjson-100-draft5">Spec: AgJSON 1.0.0-draft.5</h3>
<ul>
<li><strong>Turn closure follows the fold’s turn records</strong> across a <code>messages.snapshot</code> (§5.0 INV-MSG, §5, §10 item 27).</li>
<li><strong>A second terminal for a closed turn</strong> is a <code>reduce()</code> error and a snapshot resync, except the paused refresh (§5.0 INV-MSG and INV-TURN, §8.0 host obligation 5, §10 item 27).</li>
<li><strong>Record events on unopened turns</strong> land on the turn’s known thread, never a placeholder, and a normalizer opens a turn before any event of it, record events included (§5.0 INV-OWNER and INV-TURN, §10 items 48 and 49). Erratum to draft.4: its note said a terminal for a turn seen only in a snapshot’s messages folds onto a record carrying that message’s <code>threadId</code>; the 0.7.x reducer did so only when no record event had reached that turn first.</li>
<li><strong>The input side of <code>AgUsage</code></strong> (§4; §8.0 items 4, 19, 24 and 29; §10 items 8, 21 and 50): <code>inputTokens</code> is cache-inclusive, the revision draft.3 deferred; <code>cumulative</code> binds the object it sits on; the optional <code>costScope</code> names a cost whose scope differs from its object’s counters. Over the Gemini Live API, <code>thoughtsTokenCount</code> always folds into <code>outputTokens</code>, <code>totalTokens</code> is <code>inputTokens + outputTokens + (toolUseInputTokens ?? 0)</code>, and a differing provider total rides the new optional <code>totalTokensRaw</code>.</li>
<li><strong><code>display.required</code> is recorded for the live render, not for replay</strong> (§5, §5.0 INV-FOLD, §13.3, §10 item 51). A host’s persistence and re-display of a turn’s grounding records follow the grounding provider’s terms, and the render duty is conditioned on them. A host’s storage projection may omit <code>turns[].displayRequired</code>, which <code>toPersistable()</code> does in one call. Nothing changes on the wire or in the fold.</li>
<li><strong>MCP resource links in tool results</strong> (§8.0 item 31, §10 item 44): a normalizer emits an MCP <code>resource_link</code> part as one <code>resource-link</code> block, never a <code>provider-raw</code> block. The block gains MCP’s optional <code>name</code>, <code>title</code>, <code>description</code> and <code>size</code>. A member that doesn’t fit the block rides one <code>provider-raw</code> right after it, and a part without a string <code>uri</code> stays one <code>provider-raw</code>. §13.4’s scheme validation now covers <code>resource-link</code> uris and links a consumer takes from a tool result. A <code>resource-link</code> uri is carried byte for byte, so a signed or credential-bearing link is the host’s to handle: under §13.4 for its scheme, and with its own care when it fetches or persists it.</li>
<li><strong>Host records are side metadata</strong> (§2.1, §8.0 items 19, 21 and 29, §10 item 45): values that never round-trip to the provider ride <code>_meta</code>, not <code>providerMetadata</code>.</li>
<li><strong>The OpenAI handoff round release</strong> (§8.0 item 14, §10 item 46): when a handoff resolves a round, a call of that round with no result and no run-item stops being pending at <code>handoff_occurred</code>, and the round’s deferred close is released.</li>
<li><strong>A cross-invoke tool result</strong> lands in the later invoke’s own turn, as its own <code>role: "tool"</code> message (§5.0 INV-XINV), and a Claude deferred tool closes the turn paused with one approval ask (§8.0 item 32; §10 item 47).</li>
<li><strong>Remedy-shaped vendor data</strong> is advisory and never executed automatically, and a provider credit token is never emitted (§13.10, §10 item 52). A fix the framework waits on is a <code>hitl.ask</code>; a fix it only reports rides, less what §13.10 forbids, in a carrier that folds (§8.0 item 33, §10 item 53). §13.6 names <code>messageMetadata</code>. No carry moved: the homes are the ones the 0.7.0 and 0.7.1 notes describe.</li>
<li><strong><code>retriable</code> on the error outcome</strong> (§10 item 54): <code>AgOutcome</code>’s error variant gains an optional <code>retriable</code>, copied from <code>turn.error</code> as the producer set it.</li>
<li><strong>Whose value <code>threadId</code> is</strong> (§1.2; §8.0 Partition root and host obligation 6; §10 item 55): the host’s. No schema change.</li>
<li>Editorial: a key-replace <code>state.delta</code> patch never removes a member; <code>handoff</code>‘s <code>transfer</code> and <code>escalate</code> are defined from the producers’ semantics; <code>turn.done.messageMetadata</code> replaces the named message’s bag whole; §13.4 and §13.6 gain host-care sentences.</li>
</ul>
<p>draft.4 envelopes remain accepted (same major, §12).</p>
<h3 id="silverprotocolcore"><code>@silverprotocol/core</code></h3>
<ul>
<li><strong><code>toPersistable(result)</code></strong> returns a deep copy of a fold with <code>displayRequired</code> removed from every turn record, and nothing else changed. Persist its result unless the grounding provider’s terms permit storing <code>displayRequired</code>.</li>
<li><strong><code>outcome.retriable</code>.</strong> The error outcome records <code>turn.error</code>’s <code>retriable</code> when the producer set it; an absent one stays absent. The non-terminal <code>error</code> event’s <code>retriable</code> never folds. Two committed Claude golden folds, an API authentication failure and a deferred tool no longer available, now carry <code>retriable: false</code>. A reader that validates a stored <code>AgTurnRecord</code> with an older core’s schema drops the field.</li>
<li><strong><code>AgUsage</code></strong> gains the optional <code>costScope</code> and <code>totalTokensRaw</code>. A core from 0.6.5 on passes them through as unknown fields; a core up to 0.6.4 drops them at ingest.</li>
<li><strong>The <code>resource-link</code> block</strong> gains the optional <code>name</code>, <code>title</code>, <code>description</code> and <code>size</code> (an integer), and its schema is exported as <code>AgResourceLinkBlock</code>, so a normalizer can check each native member against it.</li>
<li><strong><code>StreamAssembler</code></strong> never reopens a closed turn, so a late merging <code>turn.start</code> or a second <code>subagentStart</code> for a closed id no longer makes <code>flush()</code> emit a second terminal. <code>openTurn()</code> on a turn already seen now keeps a trigger passed to it. No shipped framework package reaches either path.</li>
<li><code>Reducer.ensureTurn()</code> is removed (see Upgrading).</li>
</ul>
<h3 id="claude-agent-sdk">Claude Agent SDK</h3>
<ul>
<li><strong>Usage accounting</strong> follows draft.5 (see Upgrading).</li>
<li><strong>A deferred tool pauses the turn.</strong> When a PreToolUse hook answers <code>defer</code>, the result closes the turn from <code>push()</code> with one <code>hitl.ask</code> (<code>kind: "approval"</code>, <code>askId</code> <code>approval_&lt;call id&gt;</code>) and <code>turn.done</code> with a <code>paused</code> outcome naming it. The resumed invoke opens its own turn, and the call’s result lands there as a <code>role: "tool"</code> message. In 0.7.x the turn closed as a success with <code>finishReason: "unknown"</code>, <code>finishReasonRaw: "tool_deferred"</code> and no ask. The <code>ext.anthropic.result-meta</code> carry is unchanged.</li>
<li><strong>Host-only fields moved to <code>_meta</code></strong> (see Upgrading).</li>
<li><strong><code>turn.start.trigger</code>.</strong> A top-level turn’s <code>turn.start</code> carries <code>trigger: {kind: "user", ref}</code> from the CLI’s <code>user_message_uuid</code>, taken from the frame that opens the turn. <code>kind: "user"</code> means a user-role send the host submitted with that uuid, not human authorship. It’s never set from an error result, a nested frame, a notice or a later frame of the turn.</li>
<li>Hardens the <code>tool_result_meta</code> carry: a provider credit token at any depth is removed before the entry is emitted, as for the other CLI wrapper carries.</li>
<li>The README documents the existing <code>threadId</code> option as the thread root.</li>
</ul>
<h3 id="openai-agents-sdk">OpenAI Agents SDK</h3>
<ul>
<li><strong>A parallel handoff no longer leaves its source turn open.</strong> When the model requests several transfers in one response, <code>@openai/agents</code> from 0.8.1 runs the first and streams nothing for the others. At <code>handoff_occurred</code>, each call of the source round that has no result and that no run-item named now stops being pending. It’s carried as <code>ext.openai.dropped-call</code> inside the source turn and gets no <code>tool.done</code>. Once nothing of the round is pending, the round’s deferred close is released in the same <code>push()</code>: <code>message.end</code>, then the deferred <code>turn.done</code>, with its outcome, finish reason and usage. In 0.7.x the source turn ended with <code>turn.abort</code> (<code>stream-truncated</code>) at flush, its usage on the round’s <code>message.end</code>. A result that arrives later for a released call rides the same <code>ext.openai.dropped-call</code> key after the turn’s terminal, never as a <code>tool.done</code>. This replaces the parallel-handoff limit described in the 0.7.1 and 0.7.2 notes for <code>@openai/agents</code> 0.8.1 and later.
<ul>
<li><strong>Still deferred:</strong> when a filter removes <code>handoff_occurred</code> or tool results; an approval pending beside the transfer on <code>@openai/agents</code> 0.8.x, which closes paused at flush; and pending program, hosted-shell or tool-search calls.</li>
</ul>
</li>
<li><code>createOpenaiNormalizer()</code> takes an optional <code>{ threadId }</code>, stamped on every turn and message, nested handoff turns included. A handoff that streams before any turn of the invoke opened gets the parent id <code>turn_&lt;stem&gt;_handoff_parent</code>.</li>
</ul>
<h3 id="vercel-ai-sdk">Vercel AI SDK</h3>
<ul>
<li><strong>An MCP tool error folds as an error.</strong> <code>@ai-sdk/mcp</code> returns an MCP result with <code>isError: true</code> as ordinary tool output; it now folds as <code>outcome: "error"</code> with <code>isError: true</code>, where 0.7.x folded it as a success. The facet reads MCP members only from a result <code>@ai-sdk/mcp</code> stamped, so an ordinary tool that returns an MCP-shaped object is unaffected.</li>
<li><strong>An MCP Apps view locator rides <code>tool.done._meta</code>.</strong> An MCP result’s <code>_meta.ui</code> is now carried unchanged on <code>tool.done._meta</code>, so it survives a later snapshot.</li>
<li><strong>A result for a call made in a previous call</strong> (a tool result, tool error or denied approval that <code>streamText</code> delivers before its first step) now names its own message, <code>&lt;toolCallId&gt;:result</code>, in this invoke’s turn, and folds as a <code>role: "tool"</code> message. In 0.7.x it had no <code>messageId</code> and parked the fold.</li>
<li><strong>Anthropic stop details.</strong> A refusal’s <code>providerMetadata.anthropic.stopDetails</code> on the finish step now rides the step’s <code>message.metadata</code>, verbatim, with any provider credit token removed at any depth. In 0.7.x it was dropped.</li>
<li><code>createVercelNormalizer()</code> takes an optional <code>{ threadId }</code>, stamped on every entity. <code>ext.vercel.*</code> never follows it.</li>
</ul>
<h3 id="google-adk">Google ADK</h3>
<ul>
<li><strong>Ids change</strong> to a per-invoke stem (see Upgrading).</li>
<li><strong>Live sessions get a turn per generation.</strong> On a barge-in, ADK sends <code>interrupted: true</code> first, then the interrupted generation’s usage and <code>turnComplete</code>, then the reply. The interrupted generation now closes as <code>turn.abort</code> (<code>interrupted</code>) on its own <code>turnComplete</code>, with its usage on its <code>message.end</code>, and the reply opens the next turn, <code>turn_&lt;invokeId&gt;_&lt;invocationId&gt;_g1</code>. A live barge-in closes the interrupted generation and folds clean, as two live captures show, one of them with two barge-ins. This closes the known limit in the 0.7.1 note, where a real barge-in parked the reducer.
<ul>
<li><strong>Still limited:</strong> a generation that follows a completed reply with no barge-in lands in the closed turn, and parks, unless you opt into <code>hostCompletion</code>, which defers that close. When text is buffered at the interrupt, ADK yields only the text aggregate, without the flag, so the turn closes at <code>flush()</code> as <code>stream-truncated</code>.</li>
<li><strong>A transcription reads once.</strong> ADK’s <code>finished</code> transcription is no longer re-emitted when it repeats the chunks already streamed.</li>
</ul>
</li>
<li><strong>Gemini Live usage.</strong> Over the Live API, <code>outputTokens</code> now counts the response and the thoughts; 0.7.x counted the thoughts alone. <code>totalTokens</code> is derived as <code>inputTokens + outputTokens + (toolUseInputTokens ?? 0)</code>, the total Gemini’s <code>generateContent</code> surface reports for the same counts, not a billing figure. Google’s own total rides <code>totalTokensRaw</code> where it differs, and neither is emitted when Google reports no total. Each report’s per-modality breakdown rides <code>message.metadata</code> as <code>usageDetails</code>, one verbatim entry per report. Live usage stored before 0.8.0 is unreliable. Usage on the <code>generateContent</code> route is unchanged.</li>
<li><strong>MCP resource links.</strong> An MCP <code>resource_link</code> part of a tool result becomes one <code>resource-link</code> block, with a <code>provider-raw</code> right after it only for members that don’t fit. Image, audio and embedded-resource parts stay <code>provider-raw</code>.</li>
<li><code>createAdkNormalizer()</code> takes an optional <code>{ threadId }</code>, stamped on every turn and message it opens, the host-error turn included.</li>
</ul>
<h3 id="wire-version">Wire version</h3>
<p><code>1.0.0-draft.5</code>. Replaying the 73 recorded streams through the 0.7.2 and 0.8.0 packages, output is byte-identical for 14 of 15 OpenAI Agents SDK streams and 6 of 8 Vercel AI SDK streams. Every Claude stream changes (usage accounting), and so does every Google ADK stream (ids). <code>@silverprotocol/richtext</code> is unchanged apart from its version.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.7.2 — An ignored handoff closes its nested turn at its result (@openai/agents 0.8.0 and earlier)</title>
      <link>https://silverprotocol.io/updates/0-7-2/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-7-2/</guid>
      <pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.7.2 — An ignored handoff closes its nested turn at its result (@openai/agents 0.8.0 and earlier).</description>
      <content:encoded><![CDATA[<p>A patch release. It changes the event stream only for hosts on <code>@openai/agents</code> 0.8.0 or earlier; streams from 0.8.1 on, including the default 0.18.0, are unchanged. The wire version stays <code>1.0.0-draft.4</code>.</p>
<h3 id="openai-agents-sdk">OpenAI Agents SDK</h3>
<ul>
<li><strong>An ignored handoff closes its nested turn at its result.</strong> When the model requests several handoffs in one response, the SDK runs only the first. On <code>@openai/agents</code> 0.8.0 and earlier it streams a result for each ignored call (“Multiple handoffs detected, ignoring this one.”). The ignored handoff’s nested turn, opened at its <code>handoff_requested</code>, now closes at that result, with <code>turn.abort</code> and then <code>subagent.done</code>, before the call’s <code>tool.done</code>. In 0.7.1 it stayed open until flush aborted it. This <code>turn.abort</code> carries no <code>reason</code>.</li>
<li><strong>With several handoffs open, each is matched to its own call,</strong> and the <code>handoff</code> event belongs to the source turn. Before, it could attach to the first handoff’s nested turn, which had already closed. A stream with a single handoff is unchanged.</li>
<li>From 0.8.1 the SDK doesn’t stream the ignored calls at all, so the parallel-handoff limit described in 0.7.1 still applies there: the source turn ends with <code>turn.abort</code> (<code>stream-truncated</code>) at flush.</li>
</ul>
<h3 id="spec">Spec</h3>
<ul>
<li><code>1.0.0-draft.4</code> gains a clarification, with no version change: a state patch or snapshot that is itself a credential object is omitted whole and emitted as <code>{}</code> (§8.0 item 28(b)). The reference Google ADK package already behaved this way, so nothing changes on the wire.</li>
</ul>
<p>The compatibility tables on each npm page are unchanged.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.7.1 — Claude subagent and tool-result carries</title>
      <link>https://silverprotocol.io/updates/0-7-1/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-7-1/</guid>
      <pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.7.1 — Claude subagent and tool-result carries.</description>
      <content:encoded><![CDATA[<p>A patch release. Nothing changes meaning, and the wire version stays <code>1.0.0-draft.4</code>. The Claude Agent SDK now carries a subagent’s run report, the turn a background subagent woke, the CLI’s per-tool-result stamp, and the tool input exactly as the API produced it. In Google ADK Live, a barge-in’s turn now closes in order, though a real barge-in still parks the reducer (see below).</p>
<h3 id="claude-agent-sdk">Claude Agent SDK</h3>
<ul>
<li><strong>A subagent’s run report reaches you.</strong> When an Agent (or legacy Task) call finishes, the subagent’s run report now rides that call’s <code>tool.done</code>, verbatim, in host-only <code>_meta["anthropic/agentOutput"]</code>. The report covers status, agent id and type, the model it resolved to, totals, usage and tool statistics; for a background launch, it’s the launch acknowledgement instead. <code>content</code> and <code>prompt</code> are left out, since the event already carries them. The report’s usage is the subagent’s own, so it stays out of <code>AgUsage</code>, whose parent total already includes it. 0.7.0 didn’t carry the report.</li>
<li><strong>A turn woken by a background subagent says so.</strong> A result’s <code>origin</code>, for example <code>{kind: "task-notification"}</code> on the turn a background subagent’s completion woke, rides <code>ext.anthropic.result-meta.origin</code>. 0.7.0 dropped it.</li>
<li><strong>The CLI’s stamp on a tool result rides its <code>tool.done</code>.</strong> A <code>tool_result_meta</code> entry (for example, for a call a hook blocked or the user cancelled) now rides <code>tool.done._meta["anthropic/toolResultMeta"]</code>, verbatim, beside any MCP <code>_meta</code>. 0.7.0 used it to decide the outcome but didn’t carry it.</li>
<li><strong>The API’s own tool input.</strong> When the CLI rewrote a tool call’s input, the input exactly as the API produced it rides <code>tool.args.assembled.providerMetadata.wireInput</code> and folds onto the tool-call block. That’s the input a replayed history must send back. It appears only when it differs from the event’s input.</li>
<li>Hardens how the harness writes its facts on a tool result: the <code>anthropic/</code> keys on a <code>tool.done</code>’s <code>_meta</code> are reserved for them, so a tool’s own keys under that prefix aren’t carried. Every other <code>_meta</code> key the tool returned is kept, verbatim.</li>
</ul>
<h3 id="google-adk">Google ADK</h3>
<ul>
<li><strong>A Live barge-in’s turn closes in order.</strong> Only hosts on ADK’s <code>runLive</code> path see this change, since <code>runAsync</code> never sets <code>interrupted</code>. In 0.7.0 a barge-in (<code>interrupted: true</code>) emitted <code>turn.abort</code> in the middle of its event, so that event’s own content, ADK’s full-text aggregate and the message’s <code>message.end</code> all arrived after the terminal. Now the message closes first, and <code>turn.abort</code> with <code>reason: "interrupted"</code> follows the event’s own content. The turn has one terminal, and the event’s own content precedes it. Ids are unchanged.</li>
<li><strong>Known limit: a real Live barge-in still parks the reducer,</strong> as it did in 0.7.0. On the live wire, ADK sends the interrupt flag first, and then that generation’s own usage and completion events and the model’s reply, all in the same invoke. Those land after the turn’s terminal, so the reducer parks and the reply isn’t in the fold. A clean fold of a barge-in needs each generation to get its own turn.</li>
</ul>
<h3 id="silverprotocolcore"><code>@silverprotocol/core</code></h3>
<ul>
<li><code>StreamAssembler.toolArgsAssembled()</code> takes an optional <code>providerMetadata</code>, for normalizer authors. Without it, the event is unchanged from 0.7.0.</li>
</ul>
<h3 id="known-limit-unchanged">Known limit, unchanged</h3>
<ul>
<li>
<p><strong>Parallel handoffs (OpenAI Agents SDK).</strong> When the model requests several handoffs in one response, the SDK runs only the first, and what happens to the ignored calls depends on the <code>@openai/agents</code> version.</p>
<ul>
<li>From 0.8.1, nothing about them reaches the stream. The SDK drops them from the run’s history; only in a server-managed conversation (<code>conversationId</code> or <code>previousResponseId</code>) does it send the model a synthetic result for each. With no <code>tool.done</code> for them, the source turn ends with <code>turn.abort</code> (<code>stream-truncated</code>) at flush.</li>
<li>Up to 0.8.0, the SDK streams a result for each ignored call, which lands as that call’s <code>tool.done</code>, and the source turn closes normally. The ignored handoff’s nested turn is closed at flush with <code>turn.abort</code> (<code>stream-truncated</code>).</li>
</ul>
<p>The limit was disclosed in 0.7.0 and has now been measured on a live response at <code>@openai/agents</code> 0.18.0; the behaviour is unchanged in 0.7.1. The 0.7.0 note said the ignored calls’ results go to the model only, which holds only in a server-managed conversation from 0.8.1. This corrects it.</p>
</li>
</ul>
<p>The compatibility tables on each npm page are unchanged.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.7.0 — AgJSON draft.4: forward-compatible ingest and stricter folds</title>
      <link>https://silverprotocol.io/updates/0-7-0/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-7-0/</guid>
      <pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.7.0 — AgJSON draft.4: forward-compatible ingest and stricter folds.</description>
      <content:encoded><![CDATA[<p>A minor release that ships spec revision <strong><code>1.0.0-draft.4</code></strong> (<code>AGJSON_VERSION</code>). An event a 0.7.0 reader doesn’t know now keeps its place in the sequence instead of stalling the fold. The reference <code>Reducer</code> now stops and asks for a resync on delivery faults it used to fold silently. The framework packages close turns, tool calls and handoffs more honestly. The changes to the fold rules, with before-and-after examples, are tracked in <a href="https://github.com/silverprotocol/AgJSON/issues/1">AgJSON#1</a>. Read the upgrading notes first: several ids change.</p>
<h3 id="upgrading-from-06x">Upgrading from 0.6.x</h3>
<ul>
<li><strong>Upgrade the framework packages no later than <code>@silverprotocol/core</code>.</strong> If you fold several invokes into one <code>Reducer</code>, a 0.7.0 core stops and asks for a resync on a <code>message.start</code> for a turn that has already closed, and a closed turn stays closed across invokes. The framework packages up to 0.6.7 reuse turn ids from one invoke to the next, so a 0.7.0 core folding their output parks on the second invoke. Upgrade them together with core, or before it.</li>
<li><strong>Honour <code>needsResync</code>.</strong> The reference reducer now parks on a re-delivered event, and on a block id or a final <code>tool.done</code> repeated within one invoke; 0.6.7 folded these silently. It also extends the closure rule. 0.6.7 already resynced on a new block or a delta into a sealed message, and on a new block into a closed turn. 0.7.0 adds block-finalizing events (<code>text.end</code>, <code>reasoning.end</code>, <code>reasoning.opaque</code>, <code>tool.args.assembled</code>), a delta into a still-open message of a closed turn and a <code>message.start</code> for a closed turn, and it keeps a turn closed across the invokes of one fold. When <code>needsResync</code> is set, resume from a snapshot.</li>
<li><strong>Ids change.</strong> If you store or compare them:
<ul>
<li><strong>Claude:</strong> every turn id changes. A turn is now <code>turn_&lt;first assistant message id&gt;</code> (or the id of the notice or result frame that opens it), and each subagent run gets its own id. In 0.6.7 every turn in an invoke was <code>turn_&lt;session_id&gt;</code>, so a multi-turn invoke folded into a single turn record.</li>
<li><strong>OpenAI Agents SDK and Vercel AI SDK:</strong> turn and message ids they mint now carry a per-invoke stem, random by default, or the new <code>invokeId</code> option. <code>invokeId: "vercel"</code> reproduces 0.6.7’s Vercel ids exactly. <code>invokeId: "openai"</code> reproduces OpenAI’s turn ids, but handoff turns become <code>turn_openai_handoff_&lt;n&gt;</code>.</li>
<li><strong>Google ADK:</strong> streaming block ids count per invoke, so a second turn continues at <code>text:1</code> instead of reopening <code>text:0</code>. The id-less fallback turn is <code>turn_adk_&lt;random&gt;</code> instead of <code>turn_adk</code>.</li>
<li><strong>Google ADK asks:</strong> approval and authentication asks are now keyed by the reserved call ADK creates for the pause. Their <code>askId</code> is <code>approval_&lt;reservedId&gt;</code> / <code>auth_&lt;reservedId&gt;</code>, <code>toolCallId</code> is the reserved id, and the original call id moves to <code>metadata.originalFunctionCallId</code>. A host that answers asks by <code>askId</code> must use the new ids.</li>
</ul>
</li>
<li><strong>Handle <code>paused</code>.</strong> An OpenAI Agents SDK approval pause now closes its turn as <code>paused</code>, naming its ask. In 0.6.7 it folded as a success.</li>
<li><strong>ADK tool failures now fold as failures.</strong> An ADK tool result carrying an <code>error</code> member now folds as <code>outcome: "error"</code> with <code>isError: true</code>, and a declined approval as <code>"denied"</code>; in 0.6.7 both folded as <code>"ok"</code>. A tool of yours that returns <code>{error: …}</code> as ordinary data now reads as a failure.</li>
<li><strong>Two things moved. These are breaking if you read them:</strong>
<ul>
<li><strong>OpenAI misalignment.</strong> Since 0.6.1, <code>@silverprotocol/openai-agents</code> has emitted OpenAI’s <code>error.misalignment</code> as a turn-scoped <code>ext.openai.misalignment</code> event. From 0.7.0 it arrives as an adapter notice, just before the <code>turn.error</code> it explains. The notice is one text block carrying <code>detailed_explanation</code> (or <code>error.message</code> when there is none), and the whole misalignment object is on that block’s <code>_meta["openai/misalignment"]</code>. The notice is part of the transcript, so it’s persisted with it. <code>ext.openai.misalignment</code> is no longer emitted.</li>
<li><strong>Claude CLI wrapper fields.</strong> <code>narration_block_indexes</code>, <code>api_error</code>, <code>api_error_params</code> and <code>api_error_code</code> move from <code>providerMetadata</code> to host-only <code>_meta</code>, on the first block’s start event (folded onto that block), or on <code>message.metadata</code> when the first block isn’t text or thinking. <code>estimated_tokens</code> moves to <code>reasoning.delta</code>’s <code>_meta</code> and is now live-only. <code>providerMetadata</code> is kept for values that must round-trip to the provider.</li>
</ul>
</li>
</ul>
<h3 id="spec-agjson-100-draft4">Spec: AgJSON 1.0.0-draft.4</h3>
<ul>
<li><strong>Forward-compatible ingest.</strong> A well-formed event (an object with a string <code>type</code> and a numeric <code>seq</code>) that this version can’t validate (an unknown event type, an unknown value, an unknown block type) is no longer dropped. <code>ingestAgEvent(s)</code> returns it in place as <code>ext.agjson.ignored {seq, ignoredType, raw}</code>. That event never folds but keeps its <code>seq</code> slot, so the reducer doesn’t see a false gap. <code>raw</code> is live-only; don’t persist it. Input that isn’t a well-formed event is still dropped, and is now reported through the optional <code>onReject</code> callback. Ingest never throws.</li>
<li><strong>Closed value sets are frozen for 1.x.</strong> New values go to open companions instead. The first is <strong><code>turn.done.finishReasonRaw</code></strong>: the framework’s native finish value, set when the mapped <code>finishReason</code> loses it. It’s recorded on the turn record. All four framework packages set it; Google ADK also keeps <code>message.metadata.rawFinishReason</code> for one more release.</li>
<li><strong><code>phase</code> on text and reasoning blocks.</strong> A new optional open string; <code>"interim"</code> marks narration between tool calls. It’s set on <code>*.start</code> and may be replaced on <code>*.end</code>. The OpenAI Agents SDK sets it for commentary, the Vercel AI SDK for an OpenAI commentary text part, and the Claude Agent SDK for narration blocks the CLI marks (which the current CLI doesn’t yet do).</li>
<li><strong>Stored records and inputs follow the forward-compatibility rule too.</strong> <code>readStoredAgMessage</code>, <code>readStoredAgMessages</code> and <code>readStoredAgMemoryRecords</code> read what you persisted. An element they can’t read, such as a content block of a type this version doesn’t define, is left out and reported with its position and its stored value, and a stored record that isn’t a message at all is reported whole; everything else reads normally, unknown fields included. Don’t persist the view they return. <code>checkAgInput</code> checks an input before you act on any of it. A value outside a closed set, or any other schema failure, rejects the whole input with a typed result (<code>malformed</code>, <code>unknown-value</code> or <code>major-mismatch</code>); an unknown object field never does. It checks <code>protocol</code>, then <code>version</code>, then the rest: an input whose <code>protocol</code> is <code>"agjson"</code> but whose major version differs is <code>major-mismatch</code>, whatever else it carries. Otherwise a malformed value in any part it checks makes the result <code>malformed</code>, even if the input also carries an unknown value. Members that only an unknown <code>kind</code> would select, and the fields of a block of an unknown type, aren’t checked.</li>
<li><strong>New: <code>uiResources.viewMessageTurns</code>.</strong> An optional client capability. A client sets it on <code>AgClientCapabilities</code> to say it delivers a message sent from a view (an MCP Apps <code>ui/message</code> or an OpenAI Apps <code>sendFollowUpMessage</code>) as the user message of a later <code>kind: "start"</code> input: at once when no turn is in progress, otherwise after that turn ends. Such a client never cancels a turn in progress to deliver it, and it answers the view with an error, never a success, for a message it doesn’t deliver (for example, one the user declines). The flag applies only to the input that carries it, so an agent host shouldn’t rely on this delivery for an input without it. Cores before 0.7.0 ignore it. Discussion: <a href="https://github.com/silverprotocol/AgJSON/issues/2">AgJSON#2</a>.</li>
<li><strong>Erratum to draft.2:</strong> “additive” there meant an absent field or role is byte-identical to draft.1. Under draft.4’s rules a new <code>AgRole</code> value is not additive for consumers, and a consumer from before draft.2 can’t fold <code>notice</code> messages.</li>
</ul>
<h3 id="fold-changes-silverprotocolcore">Fold changes (<code>@silverprotocol/core</code>)</h3>
<p>These change what <code>reduce()</code> builds from a stream. They are tracked, with their spec text, in <a href="https://github.com/silverprotocol/AgJSON/issues/1">AgJSON#1</a>, along with the ignored-event slot above and the nested-turn terminals below.</p>
<ul>
<li><strong>Tool results kept open are snapshots.</strong> A later <code>tool.done</code> for a result kept open with <code>more: true</code> replaces the result’s payload as a unit: <code>content</code>, <code>outcome</code>, <code>isError</code>, <code>structuredContent</code>, <code>uiData</code>, <code>sideData</code>, <code>errorText</code>, <code>errorCode</code> and <code>pendingInput</code>. A payload field the later event omits is cleared, including when the final <code>tool.done</code> reports an error. <code>_meta</code>, <code>toolMetadata</code> and <code>dynamic</code> are kept unless re-sent, <code>providerMetadata</code> merges by key, and an explicit <code>uiData: null</code> is stored as a value. A producer must carry an MCP Apps result’s <code>_meta.ui</code> unchanged on <code>tool.done._meta</code>, so a view keyed on <code>_meta.ui.resourceUri</code> survives. The Vercel AI SDK is the one framework package that keeps results open: a generator tool that yields and then throws now folds to the error alone.</li>
<li><strong><code>state.delta</code> object patches replace each top-level key whole</strong>, the way ADK applies a state delta: <code>{cfg:{a:1,b:2}}</code> then <code>{cfg:{a:5}}</code> now folds to <code>{cfg:{a:5}}</code>, not <code>{cfg:{a:5,b:2}}</code>. <code>null</code> is stored as a value, a scalar patch does nothing, and JSON Patch arrays are unchanged. On core 0.6.x the one-level merge stays; from 0.7.0, anything the old merge left behind in a key clears the next time that key is written.</li>
<li><strong>A re-delivered event never folds twice,</strong> and the other delivery faults listed under Upgrading now park the fold instead of corrupting it.</li>
<li><strong>A terminal (<code>turn.done</code>, <code>turn.error</code> or <code>turn.abort</code>) for a turn the reducer never saw opened</strong> folds onto no turn record and doesn’t park. In 0.6.7 such a terminal minted a turn record of its own. A turn counts as seen opened once a <code>turn.start</code> or <code>subagent.start</code> opens it, or once a folded <code>messages.snapshot</code> carries it; a snapshot that carries <code>turns</code> replaces that set. A terminal for a turn that was seen opened but has no record gets one only when the latest snapshot carrying that turn gives it a <code>threadId</code>.</li>
</ul>
<h3 id="silverprotocolcore-new-apis-and-fixes"><code>@silverprotocol/core</code>: new APIs and fixes</h3>
<ul>
<li><strong><code>withAtomicPush(createInner, opts?)</code></strong> wraps a normalizer so each <code>push()</code> is atomic. If handling one native event throws, that event’s partial output is discarded (no <code>seq</code> is used) and one <code>error {message: "normalizer error", code}</code> takes its place. The OpenAI, Claude and Google packages now use it. <code>StreamAssembler.checkpoint()</code> / <code>rollback()</code> give the same guarantee to a normalizer that can’t journal; the Vercel package uses them.</li>
<li><strong><code>toJsonValueSafe(v)</code></strong> turns any live value into JSON without throwing: cycles become <code>"[Circular]"</code>, a value past the depth cap becomes <code>"[MaxDepth]"</code>, and an <code>Error</code> becomes <code>{name, message}</code> plus its own enumerable fields. <code>toJsonValueSafeWithIssues(v)</code> also reports what it replaced, and <code>isJsonValue(v)</code> is a type guard.</li>
<li><strong><code>Reducer.push()</code> keeps its own copy.</strong> It now folds a copy of each event, so a host that reuses or changes an event object after pushing it no longer changes the fold. <code>result()</code> already returned a copy, so reading was never affected.</li>
<li><strong><code>validateHitlAnswer</code></strong> now rejects an answer whose <code>status</code> isn’t defined (<code>unknown-status</code>) before dispatch. 0.6.7 could read it as a grant.</li>
<li><code>reduce()</code> no longer leaves an explicit <code>providerMetadata: undefined</code> key on a block.</li>
</ul>
<h3 id="claude-agent-sdk">Claude Agent SDK</h3>
<ul>
<li><strong>One turn id per turn,</strong> unique across invokes (see Upgrading). A multi-turn invoke now folds into one turn record per turn, keeping each turn’s outcome and usage. New option: <code>invokeId</code>.</li>
<li><strong>Subagent runs close on their own.</strong> A run opens once and closes once with its own <code>turn.done</code>, <code>turn.error</code> or <code>turn.abort</code> (with no <code>usage</code>) right before <code>subagent.done</code>, as draft.4 requires. It closes on the spawning task’s result, on a <code>task_notification</code>, or at flush. A background sub-run stays open until its <code>task_notification</code> arrives; the parent’s result no longer closes it. A nested API error no longer closes the parent turn. In 0.6.7 every nested message opened and closed its own bracket.</li>
<li><strong>Every turn opens with <code>turn.start</code>,</strong> including one that starts with a result (a startup failure, a local command, a result-only API error) and a resumed invoke that starts with a tool result.</li>
<li><strong>A denied tool call closes once, as <code>denied</code>.</strong> A harness-denied call used to close as <code>error</code> and then get a second <code>denied</code> close from the result’s denials. An error-subtype result now emits its denials too.</li>
<li><strong>More CLI facts reach you.</strong> <code>diagnostics</code>, <code>error_details</code>, <code>advisor_model</code> and <code>attribution_agent</code> now ride host-only <code>_meta</code> beside the fields that moved there (see Upgrading). A non-null <code>stop_details</code>, for example a refusal’s category and its fallbacks, folds onto its message through <code>turn.done.messageMetadata</code>. Wrapper fields on a first block that isn’t text (a compaction, a content block, an MCP tool result) now ride <code>message.metadata</code>; 0.6.7 dropped them. Unknown top-level frame types (for example <code>command_lifecycle</code>) and the CLI’s synthetic user frames ride <code>ext.anthropic.frame</code>. <code>deferred_tool_use</code> rides <code>ext.anthropic.result-meta.deferredToolUse</code>, and an error close adds <code>apiErrorStatus</code> and <code>stopReason</code> there.</li>
<li><strong>A result-only error close says why.</strong> Its <code>code</code> is the API error code, else the CLI’s <code>terminal_reason</code>, else <code>"api_error"</code>, and its <code>message</code> is the result text, else the code. 0.6.7 could close with an empty message.</li>
<li><strong><code>finishReasonRaw</code></strong> carries the native <code>stop_reason</code> when <code>finishReason</code> falls back to <code>"unknown"</code>.</li>
<li><strong>A stream that ends early closes cleanly.</strong> When a stream is cut short, <code>flush()</code> closes the blocks still open without inventing content: no tool input assembled from partial JSON, no reasoning signature and no compaction block.</li>
<li><strong><code>push()</code> never throws.</strong> Each frame is read as plain JSON first. A frame that still can’t be handled is rolled back and replaced by one core <code>error</code> event, and emitted values never share objects with the frame you pushed.</li>
<li>Hardens how verbatim vendor carries (<code>ext.anthropic.*</code> frames, provider-raw blocks and CLI wrapper fields) are copied.</li>
</ul>
<h3 id="openai-agents-sdk">OpenAI Agents SDK</h3>
<ul>
<li><strong>An approval pause stays paused.</strong> When a stream ends while the facet is still holding a round open, it releases that round, in order and before the live response closes. While a call still waits for approval, the round comes out as <code>paused</code>, naming its approval ask (<code>askId</code> <code>approval_&lt;callId&gt;</code>, <code>kind: "approval"</code>). When the round’s own terminal isn’t a success, it comes out as that terminal. Otherwise its usage goes on <code>message.end</code>, followed by <code>turn.abort</code> (<code>stream-truncated</code>). An Agents SDK approval pause, which used to fold as a success, now folds as <code>paused</code> with its ask.</li>
<li><strong>Handoffs close cleanly.</strong> A handoff now closes both its transfer call (the <code>transfer_to_&lt;agent&gt;</code> call gets its <code>tool.done</code> from the SDK’s <code>handoff_occurred</code>) and its nested turn (<code>turn.done</code> with success and <code>finishReason: "unknown"</code>, then <code>subagent.done</code>), and the source agent’s turn closes as a success at the handoff, before the target agent’s rounds. In 0.6.7 the transfer call never got a result, the nested turn had no outcome, and the source agent’s turn stayed open until the end of the stream, closing after the target agent’s rounds. One limit: when the model requests several handoffs in one response, the SDK sends the results of the ignored calls only to the model, so the source turn still ends with <code>turn.abort</code> (<code>stream-truncated</code>) at flush.</li>
<li><strong>An approval resume opens its own turn.</strong> A resumed run’s leading tool results now open <code>turn.start</code>, each as its own tool message, followed by the resumed response in the same turn. In 0.6.7 they arrived with no turn open, and the fold parked.</li>
<li><strong>Commentary is marked <code>phase: "interim"</code></strong> on its text block. <code>providerMetadata.phase</code> still carries OpenAI’s value, and a null or empty phase is no longer emitted.</li>
<li><strong><code>finishReasonRaw</code></strong> carries the native finish reason when the mapping falls back to <code>"other"</code> or <code>"unknown"</code>.</li>
<li><strong><code>push()</code> never throws.</strong> The normalizer is wrapped in <code>withAtomicPush</code>, and each native event is read as plain JSON once. In 0.6.7, pushing live Agents SDK objects threw for six of nine live shapes. <code>ext.openai.unparsed</code> now carries a copy, not your object.</li>
<li><code>createOpenaiNormalizer()</code> takes an optional <code>{ invokeId }</code>.</li>
</ul>
<h3 id="vercel-ai-sdk">Vercel AI SDK</h3>
<ul>
<li><strong>Ids are unique across invokes,</strong> with a per-invoke stem, or <code>invokeId</code> (see Upgrading). Step ids still repeat; they’re live-only and never folded.</li>
<li><strong><code>finishReasonRaw</code></strong> carries the finish part’s <code>rawFinishReason</code> when the mapping falls back to <code>"other"</code> or <code>"unknown"</code>.</li>
<li><strong>An OpenAI commentary text part</strong> opens with <code>phase: "interim"</code>.</li>
<li><strong>A cycle or <code>BigInt</code> in a live value degrades only its own node</strong> (<code>"[Circular]"</code>, or the number’s decimal string); 0.6.7 turned the whole value into <code>"[object Object]"</code>.</li>
<li><strong>Each stream part is a transaction.</strong> A part that throws midway is rolled back (no <code>seq</code> is used) and replaced by one core <code>error</code> event.</li>
<li><code>createVercelNormalizer()</code> takes an optional <code>{ invokeId }</code>.</li>
</ul>
<h3 id="google-adk">Google ADK</h3>
<ul>
<li><strong>Tool failures fold as failures</strong> (see Upgrading), in this order: an MCP result with <code>isError</code> is an error; ADK’s pause placeholder stays <code>"ok"</code>; a declined approval is <code>"denied"</code>; any other result with a truthy <code>error</code> member is an error, with <code>errorText</code> and <code>errorCode</code>; everything else is <code>"ok"</code>.</li>
<li><strong>Pauses:</strong> each pause is keyed by its reserved call: an approval request becomes an approval ask, an authentication request an auth ask, and an input request a text or form ask. A Workflow function node’s authentication request now closes its turn as paused; in 0.6.7 it aborted at flush.</li>
<li><strong>Opt-in host completion.</strong> Create the normalizer with <code>{ hostCompletion: true }</code> and push <code>{ type: "__host_complete__" }</code> after <code>runAsync()</code> returns normally, and a completed Workflow closes its turn as a success from <code>push()</code>. Without the option, a completed Workflow still aborts at flush, as before. Turn it on only if you push the sentinel: with the option on and no sentinel, even a plain agent’s turn aborts at flush.</li>
<li><strong>Host-error sentinel.</strong> Push <code>{ type: "__host_error__", code, message }</code> (or the caught <code>Error</code>) and every open turn closes as <code>turn.error</code>, innermost first. With no turn open, a new turn is opened to carry the error. In 0.6.7 a caught throw could only ride <code>ext.google.unparsed</code>, and the turn aborted at flush.</li>
<li><strong><code>finishReasonRaw</code></strong> is set on the lossy mappings, and <code>message.metadata.rawFinishReason</code> / <code>rawErrorCode</code> stay for one more release.</li>
<li><strong>Block ids</strong> count per invoke, and the fallback turn id is unique (see Upgrading).</li>
<li><strong><code>push()</code></strong> now runs inside <code>withAtomicPush</code>, with the same guarantees as 0.6.7’s guard. A native event with nothing serializable now rides <code>ext.google.unparsed</code> with <code>native: null</code>; 0.6.7 sent <code>{reason: "not-serializable"}</code>.</li>
<li><strong>Security advisory <a href="https://github.com/silverprotocol/typescript-sdk/security/advisories/GHSA-w78f-8q9f-jwpg">GHSA-w78f-8q9f-jwpg</a>.</strong> Versions before 0.7.0 could carry credential material from ADK events into the events they emit, and 0.7.0 completes the fix, including how ADK shared state is carried. If you ran an affected version, the advisory lists which versions are affected and what to do.</li>
</ul>
<h3 id="wire-version">Wire version</h3>
<p><code>AGJSON_VERSION</code> is <code>1.0.0-draft.4</code>. draft.3 envelopes remain accepted (same major). The compatibility tables on each npm page are unchanged.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.6.7 — Google ADK: push() no longer throws on live or malformed events</title>
      <link>https://silverprotocol.io/updates/0-6-7/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-6-7/</guid>
      <pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.6.7 — Google ADK: push() no longer throws on live or malformed events.</description>
      <content:encoded><![CDATA[<p>A patch to <code>@silverprotocol/google-adk</code>. The wire vocabulary is untouched (<code>AGJSON_VERSION</code> stays <code>1.0.0-draft.3</code>), no peer versions moved, and <code>@silverprotocol/core</code>, <code>@silverprotocol/claude-agent-sdk</code>, <code>@silverprotocol/openai-agents</code>, <code>@silverprotocol/vercel-ai</code> and <code>@silverprotocol/richtext</code> change only their version number.</p>
<h3 id="google-adk">Google ADK</h3>
<ul>
<li><code>push()</code> no longer throws on live or malformed ADK events (SPEC.md §8.0). A host that pushes the <code>Event</code> objects ADK yields, as the README shows, can hand the normalizer members that are not JSON: <code>undefined</code>, a <code>Date</code>, a <code>BigInt</code>, a cycle. Each event is now read once as plain JSON before anything maps it: a <code>Date</code> becomes its ISO string, a <code>BigInt</code> its decimal string, a repeated ancestor <code>"[Circular]"</code>, and members JSON would drop are dropped.</li>
<li>An event that still cannot be mapped (for example, one whose parts are not a list) is dropped whole and reported once as a non-fatal core <code>error</code> event (<code>message: "normalizer error"</code>, <code>code</code>: the error’s constructor name, no payload). Apart from that report, the stream is the same as if the event had never arrived. If <code>flush()</code> itself fails, it is reported the same way and still ends the open message and turn. An event with nothing serializable at all is reported once as <code>ext.google.unparsed</code>, with a reason in place of its content.</li>
<li>A live <code>Error</code> inside an event now rides as <code>{name, message}</code> plus its own enumerable fields (without its stack). An app tool that returns an <code>Error</code> as its value now folds that message into <code>tool.done</code>; before this release, pushing that live event threw.</li>
<li>Hardens how ADK workflow node data is carried.</li>
</ul>
<p>The compatibility tables on each npm page are unchanged.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.6.6 — OpenAI reasoning blocks, ADK workflow pauses and reducer conformance</title>
      <link>https://silverprotocol.io/updates/0-6-6/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-6-6/</guid>
      <pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.6.6 — OpenAI reasoning blocks, ADK workflow pauses and reducer conformance.</description>
      <content:encoded><![CDATA[<p>A patch to <code>@silverprotocol/core</code>, <code>@silverprotocol/openai-agents</code> and <code>@silverprotocol/google-adk</code>. The wire vocabulary is untouched (<code>AGJSON_VERSION</code> stays <code>1.0.0-draft.3</code>), no peer versions moved, and <code>@silverprotocol/claude-agent-sdk</code>, <code>@silverprotocol/vercel-ai</code> and <code>@silverprotocol/richtext</code> change only their version number.</p>
<h3 id="core-two-reducer-conformance-fixes">Core: two reducer conformance fixes</h3>
<ul>
<li>A late delta for a message the <code>Reducer</code> has already sealed no longer attaches to it. A text, reasoning, opaque-reasoning or tool-argument delta whose block belongs to a sealed message now sets <code>needsResync</code>, as block-creating events already did (SPEC.md INV-MSG). A delta for a block the reducer has never seen behaves as before. If your stream delivers deltas after <code>message.end</code>, you will now see a resync instead of a message that grew after it closed.</li>
<li>A final <code>tool.done</code> that follows a preliminary one (<code>more: true</code>) now keeps <code>toolMetadata</code> and <code>dynamic</code>. Until now only the first delivery carried them into the folded result.</li>
</ul>
<h3 id="openai-agents-sdk-reasoning-as-a-first-class-block">OpenAI Agents SDK: reasoning as a first-class block</h3>
<ul>
<li>Reasoning now folds as a first-class <code>reasoning</code> block. The Agents SDK reports a reasoning item only after the response completes, so until now it reached AgJSON only as <code>ext.openai.late-reasoning</code>. The block now opens where the item appears on the wire, ahead of the function call it precedes, and is filled from the completed response: OpenAI re-encrypts a reasoning item at each stage, and only the final copy is valid to send back when you replay the conversation.</li>
<li><code>message.start.model</code> is filled from <code>response.created</code>, so the folded message records the model that answered.</li>
<li>A built-in tool call that OpenAI runs itself now carries <code>providerExecuted: true</code> on <code>tool.start</code>, so a client that relays tool calls knows not to run it again.</li>
<li>An assistant message’s <code>phase</code> (for example, commentary versus the final answer) now also rides <code>text.start</code> in <code>providerMetadata</code>, so it is known before the first delta. <code>text.end</code> carries it as before.</li>
</ul>
<h3 id="google-adk">Google ADK</h3>
<ul>
<li>Streaming block ids no longer repeat within a turn. Each id is now a per-turn count for its kind, not the part’s position in its event, so two thinking events in one invoke no longer both open <code>reasoning:0</code>. The first block of each kind keeps its old id.</li>
<li>A workflow’s pause closes its turn as paused. A nested model node’s final answer no longer closes the whole invocation turn, so a later request for input lands on an open turn instead of making the <code>Reducer</code> resync.</li>
<li><code>hitl.ask</code> for an authentication pause fills the spec’s <code>authConfig</code> field: the scheme, and the authorization URL, token URL and scopes where ADK provides them.</li>
<li><code>displayName</code> on inline data and file data is carried: as the <code>filename</code> of a file block, in <code>providerMetadata</code> on image and audio blocks, and alongside a resource link.</li>
<li>Events that carry JSON <code>null</code> where the genai types say a field is optional (for example, session payloads serialized from Python) no longer throw or produce output the schema rejects.</li>
<li>Hardens how authentication objects from ADK are carried.</li>
</ul>
<p>The compatibility tables on each npm page are unchanged.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.6.5 — Unknown fields now survive ingest at every depth</title>
      <link>https://silverprotocol.io/updates/0-6-5/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-6-5/</guid>
      <pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.6.5 — Unknown fields now survive ingest at every depth.</description>
      <content:encoded><![CDATA[<p>A one-change patch to <code>@silverprotocol/core</code>. The wire vocabulary is untouched (<code>AGJSON_VERSION</code> stays <code>1.0.0-draft.3</code>), no peer versions moved, and the other five packages change only their version number.</p>
<h3 id="unknown-fields-now-pass-through-ingest-at-every-depth">Unknown fields now pass through ingest at every depth</h3>
<p>SPEC.md §0.2 asks a consumer to pass unknown fields through untouched, so a stream from a newer producer survives an older reader. Through 0.6.4, <code>ingestAgEvent</code> / <code>ingestAgEvents</code> honoured that only at the top level of an event: an unknown key one level down — a new counter inside <code>turn.done.usage</code>, a new property on a block sent by <code>content.block</code> — was silently removed during validation. From 0.6.5 it is kept, at every depth. Where the <code>Reducer</code> folds a sub-object whole (<code>usage</code> on turns and messages, a block delivered by <code>content.block</code>), the unknown key reaches its result too.</p>
<p>Ordinary events keep every field and value they had in 0.6.4. One difference is visible: nested objects now keep the producer’s key order, where 0.6.4 re-ordered them to the schema’s. You will see it in <code>usage</code> (for example, <code>totalTokens</code> now sits where the producer put it). Deep-equality checks are unaffected, but anything that compares serialized JSON, such as a snapshot, a hash or a byte baseline, can change once.</p>
<p>One thing to check if you persist or strictly validate the reduced result: records built from a newer producer’s stream can now carry keys your schema does not know. That is the point of the change, but a closed schema on your side will see them.</p>
<p>The compatibility tables on each npm page are unchanged.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.6.4 — An ingest fix in core; upgrading is recommended</title>
      <link>https://silverprotocol.io/updates/0-6-4/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-6-4/</guid>
      <pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.6.4 — An ingest fix in core; upgrading is recommended.</description>
      <content:encoded><![CDATA[<p>A patch cohort, headlined by an ingest fix in <code>@silverprotocol/core</code>: upgrading is recommended. It also ships four facet changes that landed since 0.6.3. The wire vocabulary is untouched (<code>AGJSON_VERSION</code> stays <code>1.0.0-draft.3</code>), and no peer versions moved.</p>
<h3 id="fixed-an-ingest-issue-in-silverprotocolcore">Fixed: an ingest issue in <code>@silverprotocol/core</code></h3>
<p>0.6.4 fixes an issue in <code>ingestAgEvent</code> / <code>ingestAgEvents</code>. Upgrading is recommended for every consumer that calls either function. Ordinary events are unaffected: their output is byte-identical to 0.6.3’s.</p>
<h3 id="claude-a-carry-and-a-fix">Claude: a carry and a fix</h3>
<ul>
<li><strong>Refusal fallback, whole.</strong> When Claude Code falls back to another model after a refusal, the facet used to read only the retracted message ids (→ <code>message.remove</code>) and drop the rest of the frame. The whole frame now also rides <code>ext.anthropic.frame{kind: "model_refusal_fallback", frame}</code>, after the removes, so its trigger, direction, original and fallback model, refusal category and explanation reach you. They reach you on the event stream only; the folded transcript does not show them. The retraction path is unchanged.</li>
<li><strong>Replayed user frames emit nothing.</strong> A user frame the Agent SDK marks <code>isReplay: true</code> (prompt acknowledgements, queued-prompt merges, history re-sends) is now a no-op. Nothing the facet emitted before is lost: replays carry content it never mapped. Before, a replay acknowledgement arriving mid-stream could split the message being streamed in two. A replayed tool result would also have re-emitted its <code>tool.done</code>, though no current Agent SDK version sends one.</li>
</ul>
<h3 id="openai-agents-sdk-mcp-tool-result-_meta">OpenAI Agents SDK: MCP tool-result <code>_meta</code></h3>
<p>When your <code>customDataExtractor</code> returns the MCP result’s <code>_meta</code> (as <code>ctx.resultMeta</code>), <code>@silverprotocol/openai-agents</code> now puts it on the tool result’s <code>_meta</code>, verbatim. When that <code>_meta</code> includes <code>ui</code> (MCP Apps), the structured content is also placed on <code>uiData</code>, the same routing the Claude facet already applies. <code>structuredContent</code> itself is unchanged. With no <code>_meta</code>, output is byte-identical to 0.6.3. The package README gains an “MCP tool results” section on what your extractor should return.</p>
<h3 id="vercel-ai-sdk-openais-phase-reaches-you">Vercel AI SDK: OpenAI’s <code>phase</code> reaches you</h3>
<p>With <code>@ai-sdk/openai</code>, text parts from GPT-5.5 and later carry <code>providerMetadata.openai.phase</code>: <code>"commentary"</code> for text between tool calls, <code>"final_answer"</code> for the answer. The facet used to drop it. <code>text.start</code> and <code>text.end</code> now carry the part’s <code>providerMetadata</code> verbatim, so the phase is known before the first delta. OpenAI asks that <code>phase</code> be sent back on follow-up requests, and a UI that ignores it merges commentary into the answer. Text parts without metadata are byte-identical to 0.6.3.</p>
<p>The compatibility tables on each npm page are unchanged.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.6.3 — A Claude API error now closes the turn as an error</title>
      <link>https://silverprotocol.io/updates/0-6-3/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-6-3/</guid>
      <pubDate>Wed, 23 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.6.3 — A Claude API error now closes the turn as an error.</description>
      <content:encoded><![CDATA[<p>A peer-and-model cohort whose headline is, again, a wrong mapping of ours. When a Claude turn ends on an API error, <code>@silverprotocol/claude-agent-sdk</code> now closes it exactly once, as <code>turn.error</code>, with its usage. Four new models are live-verified, <code>@google/adk</code> moves to 2.1.0, and several upstream signals get a lossless home. The wire vocabulary is untouched — <code>AGJSON_VERSION</code> stays <code>1.0.0-draft.3</code>.</p>
<h3 id="fixed-a-claude-api-error-folded-as-a-successful-turn">Fixed: a Claude API error folded as a successful turn</h3>
<p>When a Claude turn ends on an API error, the Agent SDK ends it with two frames (after any <code>api_retry</code> notices): an assistant message carrying <code>error</code> (for example <code>authentication_failed</code>), then a result with <code>subtype: "success"</code> and <code>is_error: true</code>. Through 0.6.2 the facet closed the turn on the first frame and then closed it <em>again</em> on the second — <code>turn.error</code> followed by <code>turn.done{outcome: success}</code> on the same <code>turnId</code> — which breaks the rule that every turn closes exactly once. The reducer kept the last close, so the folded turn read as a success whose <code>result</code> was the error text.</p>
<p>We confirmed the sequence on the live wire with a deliberately invalid API key, and folded the same frames through both versions:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>0.6.2   turn.error, turn.done{success}   →  folded: success, result "Failed to authenticate. API Error: 401 API key is invalid."</span></span>
<span class="line"><span>0.6.3   turn.error{authentication_failed, retriable: false}   →  folded: error</span></span></code></pre>
<p>From 0.6.3 the assistant error frame no longer closes the turn; the result frame emits the single <code>turn.error</code>, and it now carries the result’s usage, so a long agentic turn that fails on its last round keeps the cost of the rounds before it. The same change removes a second double close: an assistant error followed by an error-subtype result used to emit <code>turn.error</code> twice. If a stream ends before its result frame, the turn still closes as <code>turn.error</code> at flush.</p>
<p>The same release also changes a second, rarer shape. A result that reports <code>is_error: true</code> with <strong>no</strong> assistant error frame before it was folded by 0.6.2 as a plain success; it now closes as <code>turn.error</code> too, with code <code>api_error_code</code> (or <code>"api_error"</code> when absent), retriable only for HTTP status 429 or 5xx.</p>
<p>Apart from these and the startup-failure change below, turns that do not end on an API error are byte-identical to 0.6.2.</p>
<p>Two consequences to check. The error close for these turns now arrives at the result frame, after <code>ext.anthropic.result-meta</code>, rather than at the assistant frame. And if you persist AgJSON streams: a stored 0.6.2-or-earlier stream in which one <code>turnId</code> has a <code>turn.error</code> followed by a <code>turn.done</code> is the two-frame case, its folded success is wrong, and the <code>turn.error</code> before it carries the real code. The rarer result-only case cannot be recovered from the AgJSON alone — it is a lone <code>turn.done{success}</code> whose <code>result</code> is the API error’s text.</p>
<h3 id="vercel-ai-sdk-denied-tool-calls-reach-their-home-and-one-upstream-shape-changed">Vercel AI SDK: denied tool calls reach their home, and one upstream shape changed</h3>
<ul>
<li><code>ai</code> 7.0.102 emits <code>tool-output-denied</code> inside the step when a tool approval is denied automatically. The facet used to pass it through as a raw frame; it now becomes <code>tool.done{outcome: "denied"}</code>, the event the spec defines for it.</li>
<li><code>ai</code> 7.0.108 changed what happens when a model violates <code>toolChoice</code> by calling a different tool: that tool is no longer executed, and the run ends as <code>turn.error</code>, now carrying its usage. The facet follows the new behaviour.</li>
</ul>
<p>Neither path is reachable unless you use tool approvals or a forced <code>toolChoice</code>.</p>
<h3 id="new-models-live-verified">New models, live-verified</h3>
<ul>
<li><strong>Claude Opus 5.5</strong> (<code>claude-opus-5-5</code>, GA 2026-09-22) needs <code>@anthropic-ai/claude-agent-sdk</code> 0.3.280 or later. Older versions still send the id to the API, but price it, set its default effort and report its model as Opus 5. <strong>Claude Opus 5</strong> (<code>claude-opus-5</code>), GA since July, had no seed until now; it is also the model Claude Code falls back to after certain refusals, so its frames can appear in sessions that never asked for it.</li>
<li><strong>GPT-6 Sol</strong> and <strong>GPT-6 Luna</strong> (GA 2026-09-22) on both the OpenAI Agents SDK and the Vercel AI SDK. The Agents SDK has no model-specific settings for either, so both run at the API’s default reasoning effort, medium.</li>
</ul>
<h3 id="thinking-on-claude-set-the-display-mode-if-you-want-summaries">Thinking on Claude: set the display mode if you want summaries</h3>
<p>With <code>thinking.display</code> unset, Claude Code (2.1.272 and 2.1.280 alike, on the first-party API) asks the API for between-tool narration only. No thinking summary reaches the stream: at most an empty <code>reasoning.*</code> block that carries only its signature, whichever model you use. That is upstream behaviour, not a facet change, but it explains an empty reasoning panel. To stream summaries, pass <code>thinking: { type: "adaptive", display: "summarized" }</code> to the Agent SDK. The corpus now carries a seed that does exactly that.</p>
<h3 id="signals-that-no-longer-fall-on-the-floor">Signals that no longer fall on the floor</h3>
<p>Additive — absent, every stream is byte-identical to 0.6.2 — except that a present <code>startupFailureReason</code> now also changes <code>retriable</code>, as described:</p>
<ul>
<li><strong>Claude:</strong> <code>api_error</code>, <code>api_error_params</code> (whose <code>remedy</code> names the fix a host should offer) and <code>api_error_code</code> on the API-error frame; <code>usage_report</code> on <code>/usage</code> results; <code>startupFailureReason</code> in <code>ext.anthropic.result-meta</code>, which also marks the turn non-retriable unless the reason is one upstream calls retriable, so an auto-retry loop no longer spins on a failure that cannot clear; and <code>decisionReasonCode</code> on permission denials.</li>
<li><strong>Google ADK:</strong> <code>Part.speechMetadata</code> (new in <code>@google/genai</code> 2.24.0). Parts missing a field the genai types mark optional — <code>inlineData</code> without <code>mimeType</code>, code without <code>code</code>, a file without <code>fileUri</code> — now pass through verbatim instead of throwing or producing an invalid block.</li>
</ul>
<h3 id="googleadk-210"><code>@google/adk</code> 2.1.0</h3>
<p>The event and response types we normalize are unchanged, but the package’s dependencies grew. It adds <code>dockerode</code> as an optional dependency, which brings in <code>ssh2</code> and <code>cpu-features</code>, both of which run install scripts: with pnpm’s strict build approval you will need to allow or deny them explicitly. Its <code>@types/dockerode</code> dependency also adds a second <code>@types/node</code> (18.x) to your type program, which matters only if you compile with <code>skipLibCheck: false</code>.</p>
<p>One ADK change we have not mapped yet: when a model calls a tool name that is not registered, ADK 2.1.0 now answers with a <code>{error: "Function … is not found in the toolsDict."}</code> function response instead of throwing, and the facet reports that tool call as <code>outcome: "ok"</code>.</p>
<h3 id="known-gap-openai-reasoning-items">Known gap: OpenAI reasoning items</h3>
<p>GPT-6 Sol is the first model in our OpenAI Agents SDK corpus that actually reasons on a simple prompt (the Agents SDK pins GPT-5.6 Sol to effort none and GPT-6 Astra to low, but runs Sol at the API default, medium), and it shows a limitation of <code>@silverprotocol/openai-agents</code>: reasoning items reach you as <code>ext.openai.late-reasoning { itemId, encryptedContent }</code> rather than as first-class <code>reasoning.*</code> blocks, because the Agents SDK announces them only after the response has closed. The encrypted content is the correct one to send back for stateless replay — OpenAI re-encrypts at each stage, and the facet carries the final blob.</p>
<h3 id="peers-and-security">Peers and security</h3>
<ul>
<li><code>@anthropic-ai/claude-agent-sdk</code> 0.3.280, <code>@google/adk</code> 2.1.0 with <code>@google/genai</code> 2.24.0, <code>ai</code> 7.0.111 with <code>@ai-sdk/openai</code> 4.0.72, and the openai client 7.22.0 under an unchanged <code>@openai/agents</code> 0.18.0.</li>
<li>The <code>@opentelemetry/core</code> override is retired: <code>@google/adk</code> 2.1.0 moved to exporters that already require a patched version. The <code>adm-zip</code> 0.6.1 floor stays, because <code>@google/adk</code> 2.1.0 still asks for 0.5.x; 0.6.1 also fixes a third advisory published since 0.6.2. Neither package is a runtime dependency of any <code>@silverprotocol</code> package.</li>
</ul>
<p>The compatibility tables on each npm page carry the dated evidence.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.6.2 — A turn that failed no longer reports success</title>
      <link>https://silverprotocol.io/updates/0-6-2/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-6-2/</guid>
      <pubDate>Tue, 15 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.6.2 — A turn that failed no longer reports success.</description>
      <content:encoded><![CDATA[<p>The second cohort from the weekly maintenance sweep, and the first one whose headline is a bug we shipped rather than a peer that moved. One wrong mapping is corrected, four upstream signals get a lossless home, and the <code>@openai/agents</code> peer ceiling opens so installing the latest no longer warns. The wire vocabulary is untouched — <code>AGJSON_VERSION</code> stays <code>1.0.0-draft.3</code>.</p>
<h3 id="fixed-a-failed-openai-response-could-close-as-a-successful-turn">Fixed: a failed OpenAI response could close as a successful turn</h3>
<p><code>@silverprotocol/openai-agents</code> declared the <code>response.completed</code> event’s status as <code>"completed" | "incomplete"</code>. The wire is wider than that: openai-node types <code>ResponseStatus</code> as <code>completed</code>, <code>failed</code>, <code>in_progress</code>, <code>cancelled</code>, <code>queued</code> or <code>incomplete</code>, and a <code>response.completed</code> event carries a whole <code>Response</code> object, status included. A response that reported <code>status: "failed"</code> or <code>"cancelled"</code> therefore fell past every error branch and closed the turn as <code>turn.done</code> with <code>outcome: {type: "success"}</code>.</p>
<p>From 0.6.2 an unsuccessful terminal status closes as <code>turn.error</code>, carrying the status as the error code. The branch sits last, so refusal, <code>content_filter</code> and <code>response.incomplete</code> keep their exact prior precedence and output; a <code>response.completed</code> with an absent or <code>"completed"</code> status is byte-identical to 0.6.1. One refinement beyond the literal fix: if a producer sends the self-contradictory combination of <code>status: "incomplete"</code> <em>and</em> an <code>incomplete_details.reason</code>, the sharper reason wins, so <code>code</code> reads the same as it would have from a correctly-labelled <code>response.incomplete</code>.</p>
<p>This is worth an explicit check if you persist turn outcomes. <code>@openai/agents</code> 0.17.1 began rejecting unsuccessful terminal states upstream, but it forwards the raw event to normalizers <em>before</em> it throws — so on 0.6.1 and earlier the success close was already emitted by the time the run aborted. Rows written by an older version cannot be repaired from the AgJSON alone; the status only survives in the provider-raw payload.</p>
<h3 id="four-upstream-signals-that-no-longer-fall-on-the-floor">Four upstream signals that no longer fall on the floor</h3>
<ul>
<li><strong><code>narration_block_indexes</code></strong> (Claude Agent SDK 0.3.272) names which of a frame’s content blocks are user-facing <em>narration</em> rather than private reasoning — the progress updates Anthropic’s <code>thinking.display: "updates"</code> mode returns between tool calls. Without it a client cannot tell a thinking block meant to be shown from one usually hidden, so it rides the block’s <code>providerMetadata</code> verbatim. It appears in no SDK type declaration; only a live capture surfaced it.</li>
<li><strong><code>resume_reason</code></strong> (0.3.268) says why a turn is the automatic re-run of one a worker restart interrupted. On such a re-run <code>user_message_uuid</code> names the <em>interrupted</em> turn’s prompt, so without this a consumer cannot distinguish the re-run’s first reply from the original attempt’s. It joins <code>user_message_uuid</code> and <code>user_message_uuids</code> as the third leg of one turn-binding family, on the same channels under the same once-per-message rule.</li>
<li><strong><code>resultIndex</code></strong> (from <code>result_index</code>, 0.3.268) is the delivery sequence of a result within a run. A result whose write fails still consumes its number, so a gap in the sequence is how you detect a result that was lost in transit. It rides <code>ext.anthropic.result-meta</code> beside <code>queuedTurnCount</code>.</li>
<li><strong><code>localCommand</code></strong> (from <code>local_command</code>, 0.3.268) marks a turn that ran a slash command without entering the model loop. Its presence is the signal — it is the only thing separating that from an empty turn. Note the value is coarse by design upstream: every user, project or third-party command reports <code>custom</code>, and every MCP command reports <code>mcp</code>.</li>
</ul>
<p><code>@openai/agents</code> 0.18.0’s new <code>toolSearchAgentName</code> is carried on the tool lifecycle alongside the existing <code>executionStatus</code>. All five are additive: absent, every stream is byte-identical to 0.6.1.</p>
<h3 id="errored-vercel-turns-now-report-the-tokens-they-burned">Errored Vercel turns now report the tokens they burned</h3>
<p><code>ai</code> 7.0.94 made <code>streamText</code> enforce <code>toolChoice</code>, closing a violating run as <code>finish{finishReason: "error"}</code> with <code>totalUsage</code> populated. The facet’s error close discarded that usage even though <code>AgTurnError</code> has a <code>usage</code> slot the OpenAI facet already fills. It now forwards it through the same mapper the success close uses. Absent usage stays absent.</p>
<h3 id="openaiagents-0180-is-in-range"><code>@openai/agents</code> 0.18.0 is in range</h3>
<p>The declared peer range moves from <code>&gt;=0.2.0 &lt;0.18</code> to <code>&gt;=0.2.0 &lt;0.19</code>, so installing the current <code>@openai/agents</code> beside this facet no longer produces an npm peer warning. The typed streaming surface is unchanged across the whole 0.17.0 → 0.18.0 span: identical event and result declarations, identical stream-event and protocol-item inventories. 0.18.0’s headline changes — Docker file APIs moving inside the container, sandbox file-I/O protection, image-generation actions — are all off the path this facet normalizes.</p>
<h3 id="peer-sweep-and-security">Peer sweep and security</h3>
<ul>
<li><code>@anthropic-ai/claude-agent-sdk</code> 0.3.261 → 0.3.272 (nine published versions). The <code>SDKAssistantMessageError</code> union widened to include <code>verification_required</code> and <code>cloud_credential_error</code>; both are non-retriable, which is what the facet already reported, now recorded deliberately rather than by omission. All six Claude seeds were re-captured.</li>
<li><code>ai</code> 7.0.100 with <code>@ai-sdk/openai</code> 4.0.66 and <code>@ai-sdk/mcp</code> 2.0.49; <code>@google/genai</code> 2.22.0 with <code>@google/adk</code> unchanged at 2.0.0. Both Vercel and both OpenAI seeds re-captured.</li>
<li><strong>adm-zip 0.6.1.</strong> A medium-severity advisory (CVE-2026-76845) covers every published version through 0.6.0: archive extraction followed symlinks in the destination, allowing writes outside it. 0.6.1 adds the guard and fixes six further issues. It reaches this workspace only as a transitive of <code>@google/adk</code>’s skills loader and is not a runtime dependency of any published <code>@silverprotocol</code> package, so no consumer was exposed through us — but the floor is now pinned so it cannot regress.</li>
</ul>
<h3 id="also">Also</h3>
<p>The site’s spec badge had been serving <code>1.0.0-draft.1</code> since 0.5.0 — two revisions stale — because the shields URL escapes the hyphen and no search for the plain string ever matched it. The version is now generated from the SDK’s <code>AGJSON_VERSION</code> and checked by the same gate that guards the compatibility tables, as is the machine-readable peer list in <code>llms.txt</code>.</p>
<p>The compatibility tables on each npm page carry the dated evidence.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.6.1 — Gemini 3.8 Flash and GPT-6 Astra on the wire, plus the weekly peer sweep</title>
      <link>https://silverprotocol.io/updates/0-6-1/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-6-1/</guid>
      <pubDate>Sat, 05 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.6.1 — Gemini 3.8 Flash and GPT-6 Astra on the wire, plus the weekly peer sweep.</description>
      <content:encoded><![CDATA[<p>The first cohort produced by the weekly maintenance sweep. Two new frontier models landed in the corpus within three days of their releases, every companion library moved a patch or a minor (the two agent-framework peers themselves, <code>@openai/agents</code> 0.17.0 and <code>@google/adk</code> 2.0.0, stayed put), and one new error shape gets a lossless home. The wire vocabulary is untouched — <code>AGJSON_VERSION</code> stays <code>1.0.0-draft.3</code>.</p>
<h3 id="gemini-38-flash--verified-and-now-the-adk-capture-default">Gemini 3.8 Flash — verified, and now the ADK capture default</h3>
<p><code>gemini-3.8-flash</code> went GA on 2026-09-02 with the same thinking knob and the same price as 3.7. The corpus carries a live trio for it (echo, MCP Apps card, thinking with thought summaries and signatures), captured through the unchanged <code>@silverprotocol/google-adk</code> facet on the unchanged <code>@google/adk</code> 2.0.0 peer, with <code>@google/genai</code> 2.21.0, whose only change is the new model id. The census found nothing new on the wire, and the token identity from draft.3 holds on every event. The e2e capture default for ADK moves to 3.8; consumers do not need to change anything.</p>
<h3 id="gpt-6-astra--captured-on-both-openai-paths">GPT-6 Astra — captured on both OpenAI paths</h3>
<p><code>gpt-6-astra</code> (2026-09-03) reasons on every request, rejects <code>temperature</code>/<code>top_p</code>, and offers no dated snapshot. It is now a standing seed on both <code>@silverprotocol/openai-agents</code> (via <code>@openai/agents</code> 0.17.0 with the openai client at 7.10.0) and <code>@silverprotocol/vercel-ai</code> (ai 7.0.93). Neither facet needed a code change, and neither capture path sends the rejected parameters: the Vercel provider strips <code>temperature</code>/<code>top_p</code> for reasoning models, while the Agents SDK forwards whatever you set, so do not set them for Astra. What the captures showed:</p>
<ul>
<li>The Vercel path streams a reasoning item with no text, normalized to <code>reasoning.start</code>/<code>reasoning.end</code>, and 13 reasoning tokens (the raw wire also echoes <code>reasoningContext: "all_turns"</code>); the Agents SDK path chose not to reason for the echo.</li>
<li>OpenAI stamps a new response-level attribute, <code>access_programs.cyber</code>, on every Astra response. It is program-tier metadata, not turn content, and is treated like <code>service_tier</code>.</li>
<li>The capture default for the OpenAI family stays <code>gpt-5.6-sol</code> for now (Astra lists at $10/$50 per MTok against Sol’s $4/$20 and cannot stop reasoning; the choice is with the founder).</li>
</ul>
<h3 id="one-new-lossless-carry-misalignment-errors">One new lossless carry: misalignment errors</h3>
<p>openai-node 7.10.0 adds <code>error.misalignment</code> (<code>error_type</code>, <code>detailed_explanation</code>, and a <code>steer.message</code> continuation instruction) beside the <code>misalignment_policy_violation</code> code on <code>response.failed</code>. The openai-agents facet now emits it verbatim as a turn-scoped <code>ext.openai.misalignment</code> event immediately before the <code>turn.error</code> it annotates — a safety classification a host should be able to show. Absent, nothing changes. The documented auto-stop only fires on persisted-reasoning, WebSocket, or compaction requests, so this is synthetic-tested.</p>
<h3 id="peer-sweep">Peer sweep</h3>
<ul>
<li><code>@anthropic-ai/claude-agent-sdk</code> 0.3.261: <code>user_message_uuids</code> (every client uuid a merged or queued turn answered) now rides the same three channels as <code>user_message_uuid</code>; a few remote-session latency fields are recognised as router-plane. All six Claude seeds were refreshed at 0.3.261 with a clean census.</li>
<li><code>ai</code> 7.0.93 trio: typed surface byte-identical; <code>usage.raw</code> now carries the provider’s complete usage object.</li>
<li>Toolchain: Vitest 5 (note for contributors: runs from <code>packages/e2e</code> must pass <code>--config ../../vitest.config.ts</code>; <code>pnpm e2e:capture</code> does), astro 7.3.1 on the site.</li>
</ul>
<p>The compatibility tables on each npm page carry the dated evidence.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.6.0 — AgJSON draft.3 — `outputTokens` now means the same thing on every framework</title>
      <link>https://silverprotocol.io/updates/0-6-0/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-6-0/</guid>
      <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.6.0 — AgJSON draft.3 — `outputTokens` now means the same thing on every framework.</description>
      <content:encoded><![CDATA[<p>A one-rule spec revision, shipped as a minor because it changes a number consumers may have pinned. The wire shape is untouched; one field’s meaning is now defined.</p>
<h3 id="breaking-for-gemini--adk-consumers-usageoutputtokens-includes-reasoning">Breaking for Gemini / ADK consumers: <code>usage.outputTokens</code> includes reasoning</h3>
<p>Spec <code>1.0.0-draft.3</code> defines <code>AgUsage.outputTokens</code> as <strong>every token the provider generated, including reasoning</strong>, with <code>reasoningTokens</code> a breakdown of it. That is the convention Anthropic and OpenAI already document (“billed as output tokens”), the one the OpenTelemetry GenAI conventions prescribe, and the one the Vercel AI SDK, LangChain, Pydantic AI and LiteLLM all normalize Gemini to. Until now <code>@silverprotocol/google-adk</code> copied Gemini’s <code>candidatesTokenCount</code>, which <em>excludes</em> thoughts — so <code>outputTokens</code> meant “billable output” on three frameworks and “visible text” on the fourth, and no consumer could tell which.</p>
<p>From 0.6.0 the adk facet folds <code>thoughtsTokenCount</code> into <code>outputTokens</code>, guarded by Gemini’s own total so an endpoint that already reports candidates inclusively is never double-added. On a thinking turn <code>turn.done.usage.outputTokens</code> rises by exactly <code>reasoningTokens</code>:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>echo-gemini35     31 → 156   (394 + 156 == 550)</span></span>
<span class="line"><span>thinking-gemini37 29 → 152</span></span>
<span class="line"><span>tool-error        35 → 212</span></span></code></pre>
<p><code>inputTokens</code>, <code>reasoningTokens</code> and <code>totalTokens</code> are unchanged, and <code>inputTokens + outputTokens + (toolUseInputTokens ?? 0) == totalTokens</code> now holds on Gemini as it already did on OpenAI and Vercel. Claude, OpenAI and Vercel usage is byte-identical. Two correct ways to read the fields, everywhere:</p>
<ul>
<li>All generated tokens, what you are billed for at the output rate: <code>outputTokens</code>.</li>
<li>Visible text only: <code>outputTokens − reasoningTokens</code> (approximate on Anthropic, whose <code>thinking_tokens</code> is a re-tokenization estimate).</li>
</ul>
<p><strong>Repairing persisted rows</strong> produced by earlier versions: a Gemini row is pre-0.6.0 exactly when <code>totalTokens − inputTokens − outputTokens − (toolUseInputTokens ?? 0) == reasoningTokens &gt; 0</code>; add <code>reasoningTokens</code> to <code>outputTokens</code> for those rows. Claude, OpenAI and Vercel rows need no change.</p>
<p><strong>Correction to the 0.5.4 claude facet documentation:</strong> its source comments stated that the adk facet already followed the subset convention. It did not; 0.6.0 makes the statement true. (The 0.5.4 release note itself spoke only about Claude and was accurate.)</p>
<p><strong>Known gap:</strong> <code>@google/adk</code>’s Interactions-API route (<code>useInteractionsApi</code>) synthesizes usage without thoughts upstream, so on that route <code>reasoningTokens</code> is absent and <code>outputTokens</code> stays exclusive. An upstream issue is being filed against google/adk-js.</p>
<h3 id="wire-version">Wire version</h3>
<p><code>AGJSON_VERSION</code> is now <code>1.0.0-draft.3</code>. Draft.2 inputs remain accepted (same major). Unlike draft.2, draft.3 is not byte-identical for Gemini/ADK producers, as described above; no field shapes changed, and the spec is explicit that the input side (<code>inputTokens</code> versus cache counters) is out of scope for this revision. A conformance identity joins §10 and the replay suite now asserts it on every golden that reports a total.</p>
<p>The compatibility tables on each npm page carry the dated evidence.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.5.4 — Claude Fable 5.1 on the wire, Google ADK 2.x supported, and a dependency sweep</title>
      <link>https://silverprotocol.io/updates/0-5-4/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-5-4/</guid>
      <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.5.4 — Claude Fable 5.1 on the wire, Google ADK 2.x supported, and a dependency sweep.</description>
      <content:encoded><![CDATA[<p>A maintenance cohort: every upstream agent framework we normalize moved, one of them by a major, and Anthropic shipped a new top model. The wire vocabulary is untouched — <code>AGJSON_VERSION</code> stays <code>1.0.0-draft.2</code> — but several fields that used to be zero or absent now carry real values, and two new signals ride the provider-raw channels.</p>
<h3 id="claude-fable-51-captured--thinking-you-can-count-but-not-read">Claude Fable 5.1 captured — thinking you can count but not read</h3>
<p><code>claude-fable-5-1</code> went GA on 2026-09-01 and the corpus now carries a live seed trio for it (echo, streamed partials, two ordered tool calls), captured through <code>@anthropic-ai/claude-agent-sdk</code> 0.3.258 — the first SDK build whose Claude Code knows the model id. Fable thinks on every turn but, by default, does not show its reasoning text. Three things change for a consumer as a result:</p>
<ul>
<li><code>usage.reasoningTokens</code> is now populated for Claude, per turn and per model (<code>usage.byModel[model].reasoningTokens</code>). It is a <strong>subset</strong> of <code>outputTokens</code> — do not add the two. This closes a gap where the facet had been discarding the Agent SDK’s thinking-token telemetry as a constant zero.</li>
<li><code>reasoning.delta</code> may arrive with an empty <code>delta</code> and <code>providerMetadata.estimated_tokens</code> (a number, or <code>null</code> before the first estimate). That is the whole payload when reasoning text is withheld; render it as progress. <code>@silverprotocol/core</code>’s <code>StreamAssembler.reasoningDelta</code> gained the optional <code>providerMetadata</code> slot to make this possible (additive).</li>
<li><code>ext.anthropic.frame</code> now carries <code>kind: "thinking_tokens"</code> frames — Claude Code’s running estimate while the model thinks, the only live signal a host gets before the first visible token.</li>
</ul>
<p>The result-frame extension also grows: <code>ext.anthropic.result-meta</code> gains per-model <code>costBasis</code> (<code>list</code> / <code>managed</code> / <code>unknown</code> — which price table <code>costUsd</code> was computed from, now that Claude Code applies managed rates and a US-only-inference multiplier), <code>userMessageUuid</code>, <code>queuedTurnCount</code>, and the CLI’s <code>subagentStats</code> tally. MCP tools that return <code>resource_link</code> blocks land those on <code>tool.done</code> <code>providerMetadata.resourceLinks</code>.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#6A737D">// A Fable 5.1 reasoning stream with display omitted:</span></span>
<span class="line"><span style="color:#E1E4E8">{ </span><span style="color:#B392F0">type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"reasoning.start"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">id</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"msg_…:reasoning:0"</span><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">{ </span><span style="color:#B392F0">type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"reasoning.delta"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">id</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"…"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">delta</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">""</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">providerMetadata</span><span style="color:#E1E4E8">: { </span><span style="color:#B392F0">estimated_tokens</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">50</span><span style="color:#E1E4E8"> } }</span></span>
<span class="line"><span style="color:#E1E4E8">{ </span><span style="color:#B392F0">type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"reasoning.end"</span><span style="color:#E1E4E8">,   </span><span style="color:#B392F0">id</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"…"</span><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#E1E4E8">{ </span><span style="color:#B392F0">type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"reasoning.opaque"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">id</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"…"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">kind</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"signature"</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">value</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"CAQS…"</span><span style="color:#E1E4E8"> }</span></span></code></pre>
<p>The default capture model stays <code>claude-sonnet-5</code>; Fable is a named seed family, not the default.</p>
<h3 id="google-adk-2x-supported">Google ADK 2.x supported</h3>
<p><code>@google/adk</code> 2.0.0 landed outside the declared range (<a href="https://github.com/silverprotocol/typescript-sdk/issues/22">typescript-sdk#22</a>). The major rewrites the agent-graph plane (workflow engine, <code>BaseAgent extends BaseNode</code>, <code>LLMAgentWrapper</code> removed) and leaves the event wire additive-only: four optional workflow-plane fields on <code>Event</code> and an object-valued <code>actions.agentState</code>, all carried in the event-level provider-raw ledger and never present on a plain <code>LlmAgent</code> run. <code>@google/genai</code> 2.20.0 adds one <code>Part</code> field (<code>mediaProcessing</code>), carried beside <code>mediaResolution</code>. The peer range widens to <code>&gt;=1.0.0 &lt;3</code>; the gemini-3.7-flash trio was re-captured live at 2.0.0 with a clean census.</p>
<p>One thing to know if you use <code>MCPToolset</code>: ADK 2.0 made <code>@modelcontextprotocol/sdk</code> an <strong>optional peer</strong> loaded lazily at connect time. Install it yourself alongside <code>@google/adk</code>.</p>
<h3 id="vercel-ai-sdk-7090--errors-carry-their-code">Vercel AI SDK 7.0.90 — errors carry their code</h3>
<p>The <code>ai</code> 7.0.90 trio (<code>@ai-sdk/openai</code> 4.0.56, <code>@ai-sdk/mcp</code> 2.0.43) leaves the typed stream surface byte-identical. Since 7.0.80 the SDK wraps provider mid-stream errors in a <code>StreamProviderError</code>; the facet now populates <code>error.code</code> and <code>error.retriable</code> (and the same fields on <code>turn.error</code>) from it. <code>retriable</code> may be inferred by the SDK from the HTTP status rather than asserted by the provider.</p>
<h3 id="dependency-sweep">Dependency sweep</h3>
<p>Development-only Dependabot alerts across both lockfiles were cleared: ADK 2.0’s optional peers dropped the <code>mikro-orm</code> / <code>sqlite3</code> / <code>tar</code> subtree, the test runner moved to Vitest 4, and three transitive pins that upstream still holds below the patched release are overridden (<code>adm-zip</code>, <code>@opentelemetry/core</code>, <code>uuid</code>). None of these were reachable from the published packages, which depend only on <code>zod</code>. The workspace also moved to pnpm 11.25.0, which restores README metadata on <code>pnpm publish</code> — from this cohort on, the npm package pages render their READMEs.</p>
<p>The compatibility tables on each npm page carry the dated evidence.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.5.3 — Tables in the rich-text AST — plus OpenAI Agents 0.17 support</title>
      <link>https://silverprotocol.io/updates/0-5-3/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-5-3/</guid>
      <pubDate>Fri, 28 Aug 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.5.3 — Tables in the rich-text AST — plus OpenAI Agents 0.17 support.</description>
      <content:encoded><![CDATA[<p>Two consumer-driven changes in one cohort, both filed from the first downstream host to hit them. The wire vocabulary is untouched — <code>AGJSON_VERSION</code> stays <code>1.0.0-draft.2</code>.</p>
<h3 id="silverprotocolrichtext-learns-tables"><code>@silverprotocol/richtext</code> learns tables</h3>
<p>Agents fall back to markdown pipe tables whenever a result is tabular, and until now the rich-text AST deliberately parsed them as literal text — the honest fail-safe, but a chat bubble full of <code>| # | Task |</code> is not what anyone wanted (<a href="https://github.com/silverprotocol/typescript-sdk/issues/23">typescript-sdk#23</a>, from a docs capture pass on a downstream chat kit). The block vocabulary gains a <code>table</code> node covering the GFM pipe-table subset: a header row, a delimiter row that supplies per-column alignment, and body rows of inline content.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#B392F0">parseRichText</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"| # | Task |</span><span style="color:#79B8FF">\n</span><span style="color:#9ECBFF">|:-:|------|</span><span style="color:#79B8FF">\n</span><span style="color:#9ECBFF">| 1 | **Ship** it |"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"><span style="color:#6A737D">// [{ type: "table", align: ["center", undefined], header: { cells: […] }, rows: [{ cells: […] }] }]</span></span></code></pre>
<p>The header fixes the column count and every row has exactly that many cells — short rows are padded with empty cells, long rows drop the excess, which is GFM’s rule and what every renderer the model was trained against already does. Outer pipes are optional, <code>\|</code> is the cell-pipe escape (it works inside code spans), and cells carry inline content only, through the same parser and the same safety policy: raw HTML stays literal text, unsafe link targets get no <code>href</code>. The streaming contract holds: the delimiter row has to complete before the table forms, so a renderer sees a paragraph flip once into a table and never flap back; body rows then grow in place.</p>
<p>This is an additive union member. A host that switches exhaustively on <code>block.type</code> should add a <code>table</code> arm in the same change that bumps the package — TypeScript will not force the issue when the switch returns <code>ReactNode</code>, and a missing arm renders a table as nothing.</p>
<h3 id="openai-agents-017-supported">OpenAI Agents 0.17 supported</h3>
<p><code>@openai/agents</code> 0.17.0 landed outside the declared peer range (<a href="https://github.com/silverprotocol/typescript-sdk/issues/24">typescript-sdk#24</a>). All seven agents-core wire files the facet consumes are byte-identical to 0.16.0, and the provider package’s only change is websocket replay-safety bookkeeping — a wire no-op. The range widens to <code>&gt;=0.2.0 &lt;0.18</code>.</p>
<p>The live capture that accompanies the widening earned its keep: the census caught a wrapper-level field the type inventory cannot see. Since agents-core 0.15.0, <code>RunToolCallOutputItem.executionStatus</code> is <code>"executed"</code> when the runner actually invoked a function tool and absent when the result was synthesized instead — an input-guardrail rejection, a cancellation, a refused approval. The facet now carries it verbatim on <code>tool.done</code> <code>providerMetadata</code>, beside the existing <code>caller</code> carry, so a consumer can tell a tool’s own result from a runner substitute.</p>
<p>The compatibility tables on each npm page carry the dated evidence.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.5.2 — Caller-owned threadId for the Claude facet</title>
      <link>https://silverprotocol.io/updates/0-5-2/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-5-2/</guid>
      <pubDate>Tue, 25 Aug 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.5.2 — Caller-owned threadId for the Claude facet.</description>
      <content:encoded><![CDATA[<p>A single-fix cohort, shipped for the first downstream consumer that hit it in production forensics. The wire vocabulary is untouched — <code>AGJSON_VERSION</code> stays <code>1.0.0-draft.2</code>.</p>
<h3 id="createclaudenormalizer-threadid-"><code>createClaudeNormalizer({ threadId })</code></h3>
<p>The Claude Agent SDK wire has no thread concept — only <code>session_id</code> — so the claude facet has always relabeled that id as the AgJSON <code>threadId</code>, a placeholder in the same spirit as the openai/vercel facets’ fixed labels. That placeholder is fine for self-contained streams, but it leaks: a consumer that persists events verbatim under its own thread identity ends up with mid-stream rows keyed by the SDK’s session id while its session records carry the real thread id (guuey#415, where the mismatch confounded an identity investigation).</p>
<p>The factory now accepts an options object with <code>threadId</code>: the runtime that owns the real partition root stamps it at construction, and every emitted entity — messages, synthesized <code>turn.start</code>s, notice rows, denial records — carries one id everywhere. Per SPEC §3, <code>threadId</code> is the partition root a key-value persistence layer writes by; the layer that owns that identity is the caller, not the wire.</p>
<p>Absent the option, the legacy <code>session_id</code> relabeling stands unchanged — recorded cassettes and existing consumers are byte-identical. Synthesized turn ids continue to embed the session id as opaque identifiers; that is deliberate and not part of this change.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.5.1 — Gemini 3.7 Flash validated — first real Gemini thinking capture</title>
      <link>https://silverprotocol.io/updates/0-5-1/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-5-1/</guid>
      <pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.5.1 — Gemini 3.7 Flash validated — first real Gemini thinking capture.</description>
      <content:encoded><![CDATA[<p>Google shipped <a href="https://blog.google/innovation-and-ai/models-and-research/gemini-models/introducing-gemini-3-7-flash/">Gemini 3.7 Flash</a> on August 13; this cohort makes the google-adk facet’s support attested rather than assumed, six days later. The wire format is untouched — <code>AGJSON_VERSION</code> stays <code>1.0.0-draft.2</code>; everything here is adapter behavior and capture evidence.</p>
<h3 id="corpus-the-gemini-37-flash-validation-trio">Corpus: the gemini-3.7-flash validation trio</h3>
<p>Three new live cassettes, captured at <code>@google/adk</code> 1.6.0 and enrolled as CI replay seeds in the same change: <code>echo-gemini37</code> (tool loop), <code>app-spec-gemini37</code> (re-attesting the MCP-Apps <code>structuredContent</code>/<code>_meta.ui</code> carry that only a live capture caught last generation), and <code>thinking-gemini37</code> — the corpus’s <strong>first real Gemini <code>thought: true</code> capture</strong>. Until now the adapter’s reasoning path was attested only by synthetic frames: 3.7 returns no thought summaries by default, so the harness grew a per-scenario <code>thinkingLevel</code> knob (low/medium/high, 3.7’s set) that maps to <code>thinkingConfig { includeThoughts, thinkingLevel }</code> on the ADK agent. The capture yields real thought parts with signatures, normalized to <code>reasoning.start/delta/end</code> plus the signature’s <code>reasoning.opaque</code> carry. Census came back clean — no unknown 3.7 wire fields; the one new triage is the <code>thought</code> boolean itself, a structurally-consumed flag under the same rule as <code>partial</code>.</p>
<h3 id="finish-reasons-the-genai-216-tool-call-pair-mapped-and-lossless">Finish reasons: the genai 2.16 tool-call pair, mapped and lossless</h3>
<p>The SDK generation 3.7 launched with declares two finish reasons the adapter didn’t map: <code>UNEXPECTED_TOOL_CALL</code> now lands on its exact AgJSON home (<code>unexpected_tool_call</code>, in the superset since draft.1), and <code>TOO_MANY_TOOL_CALLS</code> maps to <code>other</code> pending a spec-process decision on first-class standing. More importantly, lossy mappings no longer discard the wire string: any finish reason that maps inexactly (the unknown arm included) is carried in <code>message.metadata</code> before the message seals — <code>rawFinishReason</code>, the same channel and key the vercel-ai facet already uses, or <code>rawErrorCode</code> when the wire field was actually a block reason. Exact mappings emit nothing, so existing streams are byte-identical.</p>
<h3 id="defaults-and-evidence">Defaults and evidence</h3>
<p><code>DEFAULT_MODEL</code> for adk captures bumps to <code>gemini-3.7-flash</code> per the corpus-verified policy; the google-adk evidence log and the generated compatibility tables record the day-6 validation. No dependency changes were needed — <code>@google/adk</code> 1.6.0 with <code>@google/genai</code> 2.17.x already covers the model.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.5.0 — Grant modes + the notice role — AgJSON 1.0.0-draft.2</title>
      <link>https://silverprotocol.io/updates/0-5-0/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-5-0/</guid>
      <pubDate>Sat, 15 Aug 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.5.0 — Grant modes + the notice role — AgJSON 1.0.0-draft.2.</description>
      <content:encoded><![CDATA[<p>The wire vocabulary grew for the first time since the HITL merge — two additive constructs, designed in the open on <a href="https://github.com/silverprotocol/typescript-sdk/issues/16">typescript-sdk#16</a> with guuey as the first consumer, and signed off against their production surfaces before this cut. Everything is additive: streams that don’t use the new vocabulary are byte-identical to draft.1.</p>
<h3 id="grant-modes--scope-qualified-consent-7">Grant modes — scope-qualified consent (§7)</h3>
<p>Real permission systems answer “yes” with a scope: <em>always</em>, <em>this chat only</em>, <em>just once</em>. <code>hitl.ask</code> (and its persisted <code>AgPausedAsk</code> record) can now declare <code>grantModes</code> — the accept variants the asker offers — and the answer echoes exactly one as <code>AgHitlAnswer.grantModeId</code>. The design is strictly asker-declared: ids are opaque, AgJSON hard-codes nobody’s mode set, and declining stays the universal affordance outside the declaration (a renderer can always show refusal without consulting it). Anchored on the Claude Agent SDK’s <code>PermissionUpdate.destination</code> scopes and the OpenAI Agents <code>alwaysApprove</code>/<code>alwaysReject</code> options; frameworks with binary confirmation (ADK) simply don’t declare, which is the valid absent case.</p>
<p>Enforcement ships with the shape: <code>grantModeId</code> on a non-resolved answer rejects at parse, and the new <code>validateHitlAnswer(ask, answer)</code> checks the cross-object contract (required-iff-declared, echo-must-be-declared, plus the pre-existing <code>requestState</code> byte-echo rule) against the fold’s own paused-ask record. Clients advertise support via the <code>hitl.grantModes</code> capability flag.</p>
<h3 id="the-notice-role--a-first-class-home-for-transcript-annotations-3">The <code>notice</code> role — a first-class home for transcript annotations (§3)</h3>
<p><code>AgRole</code> gains <code>"notice"</code>: a persisted, non-conversational transcript row for host banners, adapter annotations, and framework notices — with <code>noticeSource: host | adapter | framework</code> naming the layer that injected it. A notice is never replayed to the model, never merges into assistant content, and never renders as agent-authored. The admission test is deliberately narrow (persisted + user-facing + non-conversational); ephemeral status chatter stays in vendor carries.</p>
<p>The Claude normalizer ships the first producer: <code>SDKInformationalMessage</code> frames (transcript notices, halt explanations) are promoted from their vendor-extension carry to full notice messages — and now carry <code>tool_use_id</code>, which the old route dropped.</p>
<h3 id="wire-version">Wire version</h3>
<p><code>AGJSON_VERSION</code> is now <code>1.0.0-draft.2</code>. Draft.1 inputs remain accepted (same major); consumers pinning 0.4.x should bump to parse streams that use the new vocabulary.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.4.4 — Claude /context report carried — plus OpenAI Agents 0.16 support</title>
      <link>https://silverprotocol.io/updates/0-4-4/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-4-4/</guid>
      <pubDate>Sat, 15 Aug 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.4.4 — Claude /context report carried — plus OpenAI Agents 0.16 support.</description>
      <content:encoded><![CDATA[<p>Claude Agent SDK 0.3.230 started attaching a structured twin of the <code>/context</code> report — model, token totals, over-limit detail, and per-category/MCP-tool/memory-file/agent breakdowns — to the synthetic assistant message that delivers the markdown table. The Claude normalizer now carries that <code>context_usage</code> object verbatim as <code>providerMetadata</code> on the message’s first block (or a <code>message.metadata</code> event when the frame has no blocks), joining the existing wrapper-sibling carries. Clients can render a context-usage card straight from the fold instead of parsing markdown.</p>
<h3 id="openai-agents-016-supported">OpenAI Agents 0.16 supported</h3>
<p><code>@openai/agents</code> 0.16.0 landed four days after 0.15.0, again outside our declared peer range. The study came back as clean as it gets: both consumed wire files are byte-identical to 0.15.0 (run-item event names, protocol items, and the full literal inventory all zero-delta) — the minor is internal to agent/handoff/runner plumbing. The range widens to <code>&gt;=0.2.0 &lt;0.17</code> and the npm peer warning disappears.</p>
<h3 id="verification-refresh">Verification refresh</h3>
<ul>
<li><strong>Claude Agent SDK 0.3.233</strong> — <code>SDKMessage</code> union unchanged (39 members); the only stream-wire addition is the <code>context_usage</code> sibling carried above.</li>
<li><strong>genai 2.17.1</strong> (under ADK 1.6.0) — the <code>Part</code> surface is byte-identical.</li>
<li><strong>Vercel AI 7.0.66</strong> with the aligned <code>@ai-sdk/mcp</code> 2.0.32 / <code>@ai-sdk/openai</code> 4.0.42 trio — provider-utils pin unchanged at 5.0.27, both patches crossed the nightly’s family-forced leg silently.</li>
</ul>
<p>The compatibility tables on each npm page carry the dated evidence.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.4.3 — OpenAI Agents 0.15 supported — peer range widened</title>
      <link>https://silverprotocol.io/updates/0-4-3/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-4-3/</guid>
      <pubDate>Thu, 13 Aug 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.4.3 — OpenAI Agents 0.15 supported — peer range widened.</description>
      <content:encoded><![CDATA[<p><code>@openai/agents</code> published 0.15.0 outside our declared peer range, which meant npm warned anyone installing latest alongside <code>@silverprotocol/openai-agents</code>. The study came back clean — every consumed wire surface is unchanged (run-item event names and protocol items both zero-delta) — so this release simply widens the range to <code>&gt;=0.2.0 &lt;0.16</code> and the warning disappears.</p>
<h3 id="verification-refresh">Verification refresh</h3>
<p>The whole peer matrix moved and re-verified as wire no-ops:</p>
<ul>
<li><strong>Claude Agent SDK 0.3.229</strong> — <code>SDKMessage</code> union unchanged across three patches.</li>
<li><strong>genai 2.17.0</strong> (under ADK 1.6.0) — the <code>Part</code> surface is zero-delta; the minor is elsewhere.</li>
<li><strong>Vercel AI 7.0.64</strong> with the aligned <code>@ai-sdk/mcp</code> 2.0.31 / <code>@ai-sdk/openai</code> 4.0.41 trio — six upstream patches absorbed with zero compatibility incidents, courtesy of the nightly’s lockstep-aware verification.</li>
</ul>
<p>No behavior changes; the compatibility tables on each npm page carry the dated evidence.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.4.2 — OpenAI compaction joins the shared vocabulary</title>
      <link>https://silverprotocol.io/updates/0-4-2/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-4-2/</guid>
      <pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.4.2 — OpenAI compaction joins the shared vocabulary.</description>
      <content:encoded><![CDATA[<p>The OpenAI Agents facet now maps <code>compaction_item_created</code> run items (new in <code>@openai/agents</code> 0.14.3) onto AgJSON’s first-class <code>compaction</code> content block — the <strong>same shape the Claude facet already emits</strong> for its compaction markers. Two frameworks’ context-compaction, one folded vocabulary: a consumer that renders or replays compaction handles both without knowing which framework produced the stream.</p>
<ul>
<li>The replay-load-bearing <code>encrypted_content</code> blob rides the block’s ciphertext opaque (<code>provider: "openai"</code>), mirroring the Claude mapping exactly.</li>
<li>Previously these items fell through to the lossless <code>ext.openai.unparsed</code> channel — nothing was dropped, but nothing converged either.</li>
</ul>
<h3 id="verification-refresh">Verification refresh</h3>
<p>Every facet’s compatibility table on npm now carries the week’s evidence: Claude Agent SDK <strong>0.3.226</strong> (five patches, wire-no-op — <code>SDKMessage</code> union verified unchanged), OpenAI Agents <strong>0.14.3</strong>, Google ADK <strong>1.6.0</strong> + genai <strong>2.16.0</strong> (full d.ts inventory diff: <code>Part</code>/<code>Event</code>/<code>LlmResponse</code> all zero-delta), and the Vercel AI <strong>7.0.58</strong> provider-utils-aligned trio.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.4.1 — Folded consumers get their card bootstraps</title>
      <link>https://silverprotocol.io/updates/0-4-1/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-4-1/</guid>
      <pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.4.1 — Folded consumers get their card bootstraps.</description>
      <content:encoded><![CDATA[<p>A one-day-turnaround fix for a real consumer gap: the normative Reducer’s <code>tool.done</code> handling now carries <code>_meta</code> — the spec §0.4 host side-channel — onto tool-result blocks, in both the CREATE and MERGE arms.</p>
<p><code>_meta</code>-only payloads are exactly the generative-UI bootstraps (canonical MCP-Apps <code>_meta.ui</code>, A2UI render descriptors). Before this release the facet stamped them onto <code>tool.done</code> events and the fold silently discarded them, so a folded consumer couldn’t mount cards from <code>AgReduceResult</code> without a repair wrapper. Now:</p>
<ul>
<li><strong>CREATE</strong> spreads <code>_meta</code> onto the new tool-result block, mirroring the <code>text.start</code>/<code>reasoning.start</code> precedent.</li>
<li><strong>MERGE</strong> assigns it guarded, so a preliminary (<code>more: true</code>) result’s bootstrap <strong>survives</strong> a final replace that omits its own — and a final carrying its own overwrites.</li>
</ul>
<p>If you carried a sidecar to re-attach <code>_meta</code> after folding, it collapses to <code>new Reducer()</code>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.4.0 — @silverprotocol/richtext — a sixth package</title>
      <link>https://silverprotocol.io/updates/0-4-0/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-4-0/</guid>
      <pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.4.0 — @silverprotocol/richtext — a sixth package.</description>
      <content:encoded><![CDATA[<p>The lockstep grows to six packages with <strong><code>@silverprotocol/richtext</code></strong>: the headless rich-text block model for AgJSON <code>text</code> content.</p>
<p>Agents emit markdown in chat (<code>**bold**</code>, lists, headings); every host needs the same answer to “what does this text <em>mean</em> to a renderer”. This package owns that seam and nothing more:</p>
<ul>
<li><strong>Typed AST</strong> for the conversational subset — paragraphs (explicit <code>break</code> nodes; chat prose stays line-broken), headings, fenced code, flat lists, strong/em/inline code/links, backslash escapes.</li>
<li><strong>The safety policy lives here, once.</strong> No HTML node type exists — markup in model output can only ever be literal text. Link <code>href</code> populates only for <code>http</code>/<code>https</code>/<code>mailto</code>; everything else parses as a link with <code>href: undefined</code>.</li>
<li><strong>Streaming-tolerant by design.</strong> Every prefix parses; <code>**bol</code> yields <code>{type: "strong", closed: false}</code> that completes in place; completed constructs never reshape on re-parse. Built for token deltas (see 0.3.11).</li>
<li>Zero dependencies, zero components — you map the AST onto your own renderers.</li>
</ul>
<p>The 0.x minor signals the feature moment: token streaming (0.3.11) plus the new package. Rendering stays host business; the AST is the contract.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.3.11 — Token streaming for the Claude facet</title>
      <link>https://silverprotocol.io/updates/0-3-11/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-3-11/</guid>
      <pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.3.11 — Token streaming for the Claude facet.</description>
      <content:encoded><![CDATA[<p>The headline: <strong><code>@silverprotocol/claude-agent-sdk</code> now streams</strong>. Run the Claude Agent SDK with <code>includePartialMessages: true</code> and the facet emits token-granular <code>text.delta</code> / <code>reasoning.delta</code> / <code>tool.args.delta</code> events as <code>stream_event</code> partials arrive — instead of one burst at turn end.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#F97583">const</span><span style="color:#79B8FF"> response</span><span style="color:#F97583"> =</span><span style="color:#B392F0"> query</span><span style="color:#E1E4E8">({ prompt, options: { includePartialMessages: </span><span style="color:#79B8FF">true</span><span style="color:#E1E4E8"> } });</span></span></code></pre>
<p>No other change needed:</p>
<ul>
<li>The complete assistant message the SDK still emits after partials is <strong>deduplicated automatically</strong> — reducer state after partials + suppressed-complete is provably identical to the complete-only fold (pinned by test).</li>
<li>Streams without partials produce <strong>byte-identical</strong> output to previous releases.</li>
<li>Time-to-first-token rides <code>message.metadata</code> as <code>ttft_ms</code>.</li>
<li>Census-caught bonus: the SDK’s <code>system/status</code> frames (<code>"requesting"</code>, …) are now carried via <code>ext.anthropic.frame</code> — wire-level input for agent-status UI.</li>
</ul>
<p>Verified live, not just synthetically: a new corpus seed (<code>partials-sonnet5</code>) captured the real interleaving — complete frames landing mid-stream, tool results binding before <code>message_stop</code> — and the mapping handles all of it.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.3.10 — ADK 1.5.0 carries and the provider-utils repair</title>
      <link>https://silverprotocol.io/updates/0-3-10/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-3-10/</guid>
      <pubDate>Tue, 04 Aug 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.3.10 — ADK 1.5.0 carries and the provider-utils repair.</description>
      <content:encoded><![CDATA[<p>A peer catch-up cohort with substance on two facets:</p>
<ul>
<li><strong>Google ADK 1.5.0 + genai 2.15.0</strong>: the new <code>Part.audioTranscription</code> field is carried whole via provider-raw (nothing silently dropped when native-audio models transcribe), and <code>CompactedEvent</code> gains a projection + event-level provider-raw ledger for session-replay tolerance.</li>
<li><strong>Vercel AI 7.0.51</strong>: empty text-deltas carrying a <code>providerMetadata</code> bag now emit <code>text.delta</code> with the first-class metadata slot — the Reducer merges it onto the sealed block. Scoped to empty deltas so existing output stays byte-identical.</li>
<li><strong>The <code>ai</code>/<code>@ai-sdk/mcp</code> lockstep repair</strong>: both pin <code>@ai-sdk/provider-utils</code> exactly; mismatched pairs install duplicate copies whose unique-symbol-branded <code>Schema</code> breaks typecheck. This cohort aligned the pairing and documented the ladder — the compatibility tables now record which combinations are verified.</li>
<li>Claude Agent SDK <strong>0.3.221</strong> and OpenAI Agents <strong>0.14.2</strong> verified as wire no-ops.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>0.3.9 — Sealed message ids never re-open</title>
      <link>https://silverprotocol.io/updates/0-3-9/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-3-9/</guid>
      <pubDate>Wed, 29 Jul 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.3.9 — Sealed message ids never re-open.</description>
      <content:encoded><![CDATA[<p>A consumer-reported production defect, fixed at the root: the Claude Agent SDK splits one API message across <strong>multiple</strong> <code>assistant</code> frames (a thinking block arrives as its own frame, the tool_use block as a second frame with the same <code>message.id</code>). The facet’s per-frame open/seal re-opened an id the consumer had already sealed — and <code>reduce()</code> correctly refuses a sealed attach target, parking the fold and discarding the rest of the turn.</p>
<p>Now the message lifecycle is keyed on the SDK’s own <code>message.id</code> with a <strong>deferred seal</strong>: one <code>message.start</code>/<code>message.end</code> pair per id, block indices continuing across frames, the close riding the next fold-binding frame. A defensive path handles the pathological orderings (<code>:cont:</code> derived carriers — sealed ids are never re-opened, ever).</p>
<p>And so this class of bug can’t return: <strong>corpus-wide INV-MSG fold gates</strong> now run in CI — every cassette, every facet, folded through the normative Reducer, asserting no re-opens and no parking.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.3.8 — Peer carries with three live census catches</title>
      <link>https://silverprotocol.io/updates/0-3-8/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-3-8/</guid>
      <pubDate>Wed, 29 Jul 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.3.8 — Peer carries with three live census catches.</description>
      <content:encoded><![CDATA[<p>Peer adoption across three facets — Claude Agent SDK <strong>0.3.220</strong>, OpenAI Agents <strong>0.14.0</strong>, Vercel AI <strong>7.0.41</strong> — with the census earning its keep on live wire three times:</p>
<ul>
<li><strong>Claude result-frame enrichment</strong>: <code>fast_mode_disabled_reason</code> and per-model <code>canonicalModel</code>/<code>provider</code> identity (billing rate-table selection) now ride a structured <code>ext.anthropic.result-meta</code> event before the turn close.</li>
<li><strong>OpenAI programmatic tool calling</strong>: the 0.14.0 <code>program</code>/<code>program_output</code> arms map to the builtin-tool lifecycle, and the live capture revealed final-round <code>message_output_created</code> arriving <em>after</em> <code>response_done</code> — the phase annotation now rides a dedicated <code>ext.openai.late-phase</code> event on that path.</li>
<li>Registry/transforms grew accordingly, including the first production use of <code>{*}</code> dynamic-key wildcards for per-model usage paths.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>0.3.7 — Claude wrapper carries: interruption and resumption signals</title>
      <link>https://silverprotocol.io/updates/0-3-7/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-3-7/</guid>
      <pubDate>Sat, 25 Jul 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.3.7 — Claude wrapper carries: interruption and resumption signals.</description>
      <content:encoded><![CDATA[<p>Two disclosed gaps from the 0.3.217 SDK audit, closed:</p>
<ul>
<li><strong><code>resumed_from_incomplete_thinking</code></strong> — replay-load-bearing per the SDK’s own docs: this turn continued a truncated prior turn inside its trailing signed thinking block. A replayed history must carry the flag back; it now rides the first-block <code>providerMetadata</code> carrier.</li>
<li><strong><code>aborted</code></strong> — the interrupt-truncation signal (no stop_reason ever arrives; content may end mid-word). Without it a truncated frame folds indistinguishably from a complete one. Block-less aborted frames fall back to <code>message.metadata</code>.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>0.3.6 — MCP-Apps carries and the corpus consumer contract</title>
      <link>https://silverprotocol.io/updates/0-3-6/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-3-6/</guid>
      <pubDate>Sat, 25 Jul 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.3.6 — MCP-Apps carries and the corpus consumer contract.</description>
      <content:encoded><![CDATA[<ul>
<li><strong>ADK MCP-Apps sibling carry</strong> (census-caught): <code>functionResponse.response</code>’s <code>structuredContent</code> and <code>_meta.ui.resourceUri</code> were silently dropped by the content-array branch — now carried onto <code>tool.done</code>’s first-class §2.1 channels, converging with the OpenAI facet.</li>
<li><strong>Vercel encrypted-reasoning carry</strong> (census-caught during the facet’s first full wire triage): <code>providerMetadata.&lt;provider&gt;.reasoningEncryptedContent</code> — the Claude-signature/ADK-thoughtSignature analog — now rides <code>reasoning.opaque {kind: "encrypted"}</code>.</li>
<li><strong><code>FIXTURES.md</code></strong>: the cassette corpus gained its consumer contract — non-normative status, the stable/incidental assertion tiers, read-only + refresh-via-ritual, and the pinned-SHA consumption pattern downstream projects use today.</li>
<li>Two new standing corpus seeds: an ADK MCP-Apps capture and a two-beat <code>render_card</code>/<code>update_card</code> re-render sequence.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>0.3.5 — ADK 1.4.0 interactionId + refreshed evidence</title>
      <link>https://silverprotocol.io/updates/0-3-5/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-3-5/</guid>
      <pubDate>Wed, 22 Jul 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.3.5 — ADK 1.4.0 interactionId + refreshed evidence.</description>
      <content:encoded><![CDATA[<p>Google ADK 1.4.0’s new <code>interactionId</code> correlation field is carried, and every facet’s compatibility evidence was refreshed against then-current peers (Claude 0.3.217 live-captured on the standing echo seed). Groundwork release for the census/corpus discipline the later cohorts run on.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.3.4 — Compatibility evidence on every npm page</title>
      <link>https://silverprotocol.io/updates/0-3-4/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-3-4/</guid>
      <pubDate>Wed, 22 Jul 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.3.4 — Compatibility evidence on every npm page.</description>
      <content:encoded><![CDATA[<p>Docs-only cohort: each package’s README gained its generated, CI-enforced <strong>upstream-compatibility table</strong> — verified peer versions with dated evidence entries, rendered from the append-only <code>sdk-surface.json</code> logs. The vercel-ai corpus entries gained provenance sidecars. What you see on npm is what CI verified.</p>
]]></content:encoded>
    </item>
    <item>
      <title>0.1.0 – 0.3.3 — Genesis: from first publish to the official ADK</title>
      <link>https://silverprotocol.io/updates/0-1-0-to-0-3-3/</link>
      <guid isPermaLink="true">https://silverprotocol.io/updates/0-1-0-to-0-3-3/</guid>
      <pubDate>Mon, 13 Jul 2026 00:00:00 GMT</pubDate>
      <dc:creator>Silver Protocol</dc:creator>
      <description>What changed in @silverprotocol 0.1.0 – 0.3.3 — Genesis: from first publish to the official ADK.</description>
      <content:encoded><![CDATA[<p>The founding arc, compressed:</p>
<ul>
<li><strong>0.1.0</strong> (2026-07-06): first publish — <code>@silverprotocol/core</code> (the typed <code>AgEvent</code> schema, <code>Normalizer</code> interface, and normative <code>reduce()</code>) plus the Claude Agent SDK, OpenAI Agents, and Google ADK facets.</li>
<li><strong>0.2.x</strong> (2026-07-07): packaging polish — self-contained READMEs on npm, install docs.</li>
<li><strong>0.3.0</strong> (2026-07-07): the Google ADK facet retargeted from the community <code>@iqai/adk</code> to the <strong>official <code>@google/adk</code></strong> — shipped as a minor, with the hand-typed projection discipline that still guards that facet today.</li>
<li><strong>0.3.3</strong> (2026-07-13): first release shipped through the public mirror’s OIDC trusted-publishing pipeline — provenance attestations on every package since.</li>
</ul>
]]></content:encoded>
    </item>
  </channel>
</rss>
