0.9.0

AgJSON draft.6: three fold stalls fixed, and hosts declare the memory they keep

A minor release that ships spec revision 1.0.0-draft.6 (AGJSON_VERSION). 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. toPersistable() now also drops memory a host hasn’t declared it keeps, so read the change below if you persist folds.

Fixes: streams that parked the fold

  • Claude Agent SDK, parallel tool calls with partial messages on. 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, message.end now follows the round’s tool.done. Five existing goldens reorder only, and in one the message’s streamed usage now lands on message.end instead of a raw carry (a recovery, not a meaning change).
  • Google ADK, a paused turn followed by more output from the same invocation. 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 hostCompletion, 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 flush() as turn.abort (stream-truncated), or as turn.error for an error the host feeds.
    • Known limit, for hosts without hostCompletion: 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 hostCompletion to fold it clean today; the bare-host fix, the paused close deferred to the invocation’s end, lands in the next minor.
  • Google ADK Live, a tool call followed by the model’s reply. On the Live path, for a model whose name doesn’t contain -flash-live, ADK yields the call generation’s turnComplete 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 turnComplete 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 turnComplete, with or without the marker.

Changed: toPersistable()

  • toPersistable(result) now also omits memory records whose scope isn’t thread, unless you declare that scope in { memoryScopes }. A bare call keeps thread memory only. This corrects the 0.8.0 note, which said the projection removed displayRequired “and nothing else changed”: from 0.9.0 it also drops agent, user and skill memory you haven’t declared. A host that keeps those scopes passes them, for example toPersistable(result, { memoryScopes: ["user"] }).

Spec: AgJSON 1.0.0-draft.6

  • Memory scopes. A non-thread memory scope (agent, user, skill) 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 AgCapabilities.memoryScopes (§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 durable flag reads as the producer’s statement about its own store.
  • URLs (§13.4). 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 kind: "auth" 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.
  • 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).

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

@silverprotocol/core

  • AgCapabilities.memoryScopes, an optional list of the non-thread scopes the host persists. Absent declares none.
  • toPersistable(result, { memoryScopes }) keeps thread memory and each declared scope (see Changed).
  • toPersistableWithReport(result, { memoryScopes }) returns { result, omitted }: 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.

Wire version

1.0.0-draft.6. 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. @silverprotocol/richtext is unchanged apart from its version.