diff --git a/agent-client-protocol-schema/src/v2/client.rs b/agent-client-protocol-schema/src/v2/client.rs index 4f72157c9..e81c8ea27 100644 --- a/agent-client-protocol-schema/src/v2/client.rs +++ b/agent-client-protocol-schema/src/v2/client.rs @@ -299,9 +299,14 @@ pub struct SessionMessage { #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))] #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")] pub content: MaybeUndefined>, - /// Omitted leaves metadata unchanged; `null` clears it. + /// The _meta property is reserved by ACP to allow clients and agents to attach additional + /// metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` + /// clears it. An object patches the stored metadata by top-level key: keys it omits are + /// unchanged, a key set to `null` is removed, and any other value replaces that key's + /// stored value without merging nested objects. Beyond this patch rule, implementations + /// MUST NOT make assumptions about values at these keys. /// - /// Implementations MUST NOT make assumptions about values in `_meta`. + /// See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/extensibility#metadata-patches) #[serde_as(deserialize_as = "DefaultOnError>")] #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))] #[serde( @@ -350,7 +355,10 @@ impl SessionMessage { self } - /// Sets, clears, or leaves unchanged message metadata. + /// Patches, clears, or leaves unchanged message metadata. + /// + /// A concrete object patches stored metadata by top-level key; see + /// [`apply_meta_update`](super::apply_meta_update). #[must_use] pub fn meta(mut self, meta: impl IntoMaybeUndefined) -> Self { self.meta = meta.into_maybe_undefined(); @@ -726,7 +734,8 @@ pub enum CompactionStatus { /// /// `summary`, `error`, and `_meta` have patch semantics: omission leaves the /// stored value unchanged, `null` clears it, and a concrete value replaces it. -/// `summary: []` also clears the summary. +/// `summary: []` also clears the summary. A concrete `_meta` object patches the +/// stored metadata by top-level key instead of replacing it. #[serde_as] #[skip_serializing_none] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] @@ -748,7 +757,14 @@ pub struct CompactionUpdate { #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))] #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")] pub error: MaybeUndefined, - /// Extensible metadata patch for this compaction. + /// The _meta property is reserved by ACP to allow clients and agents to attach additional + /// metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` + /// clears it. An object patches the stored metadata by top-level key: keys it omits are + /// unchanged, a key set to `null` is removed, and any other value replaces that key's + /// stored value without merging nested objects. Beyond this patch rule, implementations + /// MUST NOT make assumptions about values at these keys. + /// + /// See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/extensibility#metadata-patches) #[serde_as(deserialize_as = "DefaultOnError>")] #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))] #[serde( @@ -786,7 +802,10 @@ impl CompactionUpdate { self } - /// Sets, clears, or omits the metadata patch. + /// Patches, clears, or leaves unchanged compaction metadata. + /// + /// A concrete object patches stored metadata by top-level key; see + /// [`apply_meta_update`](super::apply_meta_update). #[must_use] pub fn meta(mut self, meta: impl IntoMaybeUndefined) -> Self { self.meta = meta.into_maybe_undefined(); @@ -852,9 +871,10 @@ impl CompactionSummaryChunk { /// /// Only [`SubagentUpdate::session_id`] is required. Other fields have /// patch semantics: omitted fields leave the stored value unchanged, `null` -/// clears or unsets the value, and concrete values replace it. For `state`, -/// `null` removes the current state report and leaves activity unconfirmed; -/// it does not report idle or cancellation. A child whose capabilities are +/// clears or unsets the value, and concrete values replace it. A concrete +/// `_meta` object instead patches the stored metadata by top-level key. For +/// `state`, `null` removes the current state report and leaves activity +/// unconfirmed; it does not report idle or cancellation. A child whose capabilities are /// unset permits no Client-initiated session mutations. /// /// The title and description provide the parent's display metadata for the @@ -910,10 +930,13 @@ pub struct SubagentUpdate { #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")] pub state: MaybeUndefined, /// The _meta property is reserved by ACP to allow clients and agents to attach additional - /// metadata to their interactions. Omitted means no metadata update; `null` is an - /// explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. + /// metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` + /// clears it. An object patches the stored metadata by top-level key: keys it omits are + /// unchanged, a key set to `null` is removed, and any other value replaces that key's + /// stored value without merging nested objects. Beyond this patch rule, implementations + /// MUST NOT make assumptions about values at these keys. /// - /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + /// See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/extensibility#metadata-patches) #[serde_as(deserialize_as = "DefaultOnError>")] #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))] #[serde( @@ -970,7 +993,10 @@ impl SubagentUpdate { self } - /// Sets, clears, or leaves unchanged subagent metadata. + /// Patches, clears, or leaves unchanged subagent metadata. + /// + /// A concrete object patches stored metadata by top-level key; see + /// [`apply_meta_update`](super::apply_meta_update). #[must_use] pub fn meta(mut self, meta: impl IntoMaybeUndefined) -> Self { self.meta = meta.into_maybe_undefined(); @@ -1278,7 +1304,8 @@ impl ConfigOptionUpdate { /// This allows clients to display dynamic session names and track session state changes. /// /// Omitted fields leave the existing session info unchanged. `null` clears the -/// corresponding value. +/// corresponding value. A concrete `_meta` object patches the stored metadata by +/// top-level key. #[serde_as] #[skip_serializing_none] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] @@ -1297,10 +1324,13 @@ pub struct SessionInfoUpdate { #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")] pub updated_at: MaybeUndefined, /// The _meta property is reserved by ACP to allow clients and agents to attach additional - /// metadata to their interactions. Omitted means no metadata update; `null` is an - /// explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. + /// metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` + /// clears it. An object patches the stored metadata by top-level key: keys it omits are + /// unchanged, a key set to `null` is removed, and any other value replaces that key's + /// stored value without merging nested objects. Beyond this patch rule, implementations + /// MUST NOT make assumptions about values at these keys. /// - /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + /// See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/extensibility#metadata-patches) #[serde_as(deserialize_as = "DefaultOnError>")] #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))] #[serde( @@ -1332,11 +1362,10 @@ impl SessionInfoUpdate { self } - /// The _meta property is reserved by ACP to allow clients and agents to attach additional - /// metadata to their interactions. Omitted means no metadata update; `null` is an - /// explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. + /// Patches, clears, or leaves unchanged session metadata. /// - /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + /// A concrete object patches stored metadata by top-level key; see + /// [`apply_meta_update`](super::apply_meta_update). #[must_use] pub fn meta(mut self, meta: impl IntoMaybeUndefined) -> Self { self.meta = meta.into_maybe_undefined(); @@ -1933,10 +1962,13 @@ pub struct UserMessage { #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")] pub content: MaybeUndefined>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional - /// metadata to their interactions. Implementations MUST NOT make assumptions about values at - /// these keys. Omitted means no metadata update; `null` is an explicit clear signal. + /// metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` + /// clears it. An object patches the stored metadata by top-level key: keys it omits are + /// unchanged, a key set to `null` is removed, and any other value replaces that key's + /// stored value without merging nested objects. Beyond this patch rule, implementations + /// MUST NOT make assumptions about values at these keys. /// - /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + /// See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/extensibility#metadata-patches) #[serde_as(deserialize_as = "DefaultOnError>")] #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))] #[serde( @@ -1965,11 +1997,10 @@ impl UserMessage { self } - /// The _meta property is reserved by ACP to allow clients and agents to attach additional - /// metadata to their interactions. Implementations MUST NOT make assumptions about values at - /// these keys. + /// Patches, clears, or leaves unchanged message metadata. /// - /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + /// A concrete object patches stored metadata by top-level key; see + /// [`apply_meta_update`](super::apply_meta_update). #[must_use] pub fn meta(mut self, meta: impl IntoMaybeUndefined) -> Self { self.meta = meta.into_maybe_undefined(); @@ -2005,10 +2036,13 @@ pub struct AgentMessage { #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")] pub content: MaybeUndefined>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional - /// metadata to their interactions. Implementations MUST NOT make assumptions about values at - /// these keys. Omitted means no metadata update; `null` is an explicit clear signal. + /// metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` + /// clears it. An object patches the stored metadata by top-level key: keys it omits are + /// unchanged, a key set to `null` is removed, and any other value replaces that key's + /// stored value without merging nested objects. Beyond this patch rule, implementations + /// MUST NOT make assumptions about values at these keys. /// - /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + /// See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/extensibility#metadata-patches) #[serde_as(deserialize_as = "DefaultOnError>")] #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))] #[serde( @@ -2037,11 +2071,10 @@ impl AgentMessage { self } - /// The _meta property is reserved by ACP to allow clients and agents to attach additional - /// metadata to their interactions. Implementations MUST NOT make assumptions about values at - /// these keys. + /// Patches, clears, or leaves unchanged message metadata. /// - /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + /// A concrete object patches stored metadata by top-level key; see + /// [`apply_meta_update`](super::apply_meta_update). #[must_use] pub fn meta(mut self, meta: impl IntoMaybeUndefined) -> Self { self.meta = meta.into_maybe_undefined(); @@ -2077,10 +2110,13 @@ pub struct AgentThought { #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")] pub content: MaybeUndefined>, /// The _meta property is reserved by ACP to allow clients and agents to attach additional - /// metadata to their interactions. Implementations MUST NOT make assumptions about values at - /// these keys. Omitted means no metadata update; `null` is an explicit clear signal. + /// metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` + /// clears it. An object patches the stored metadata by top-level key: keys it omits are + /// unchanged, a key set to `null` is removed, and any other value replaces that key's + /// stored value without merging nested objects. Beyond this patch rule, implementations + /// MUST NOT make assumptions about values at these keys. /// - /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + /// See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/extensibility#metadata-patches) #[serde_as(deserialize_as = "DefaultOnError>")] #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))] #[serde( @@ -2109,11 +2145,10 @@ impl AgentThought { self } - /// The _meta property is reserved by ACP to allow clients and agents to attach additional - /// metadata to their interactions. Implementations MUST NOT make assumptions about values at - /// these keys. + /// Patches, clears, or leaves unchanged thought metadata. /// - /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + /// A concrete object patches stored metadata by top-level key; see + /// [`apply_meta_update`](super::apply_meta_update). #[must_use] pub fn meta(mut self, meta: impl IntoMaybeUndefined) -> Self { self.meta = meta.into_maybe_undefined(); diff --git a/agent-client-protocol-schema/src/v2/ext.rs b/agent-client-protocol-schema/src/v2/ext.rs index 0cc4e9b5e..f5d585a75 100644 --- a/agent-client-protocol-schema/src/v2/ext.rs +++ b/agent-client-protocol-schema/src/v2/ext.rs @@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; use std::sync::Arc; +use crate::MaybeUndefined; + /// Value attached to a given ACP type on the `_meta` field. /// /// The _meta property is reserved by ACP to allow clients and agents to attach @@ -13,6 +15,44 @@ use std::sync::Arc; /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) pub type Meta = serde_json::Map; +/// Applies the top-level `_meta` patch of an upsert update to stored metadata. +/// +/// An omitted `update` leaves `stored` unchanged, and `null` clears it. An +/// object patches `stored` one top-level key at a time: keys it omits keep +/// their stored values, a key set to `null` is removed, and any other value +/// replaces that key's stored value whole. Nested objects are not merged, and +/// a `null` nested inside a value is kept as data. Removing every key leaves an +/// empty object, which means the same as no metadata. +/// +/// Use this for the top-level `_meta` of upsert updates such as +/// [`ToolCallUpdate`](super::ToolCallUpdate), +/// [`TerminalUpdate`](super::TerminalUpdate), message updates, +/// [`SessionInfoUpdate`](super::SessionInfoUpdate), and +/// [`CompactionUpdate`](super::CompactionUpdate). Other `_meta` fields are not +/// patches. +/// +/// See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/extensibility#metadata-patches) +pub fn apply_meta_update(stored: &mut MaybeUndefined, update: MaybeUndefined) { + match update { + MaybeUndefined::Undefined => {} + MaybeUndefined::Null => *stored = MaybeUndefined::Null, + MaybeUndefined::Value(patch) => { + let mut meta = match std::mem::take(stored) { + MaybeUndefined::Value(meta) => meta, + MaybeUndefined::Undefined | MaybeUndefined::Null => Meta::new(), + }; + for (key, value) in patch { + if value.is_null() { + meta.remove(&key); + } else { + meta.insert(key, value); + } + } + *stored = MaybeUndefined::Value(meta); + } + } +} + /// Allows for sending an arbitrary request that is not part of the ACP spec. /// Extension methods provide a way to add custom functionality while maintaining /// protocol compatibility. @@ -95,3 +135,76 @@ impl ExtNotification { } } } + +#[cfg(test)] +mod tests { + use serde_json::{from_value, json}; + + use super::*; + + fn meta(value: serde_json::Value) -> MaybeUndefined { + from_value(value).unwrap() + } + + #[test] + fn meta_update_patches_top_level_keys() { + let mut stored = meta(json!({ + "zed.dev/kept": 1, + "zed.dev/removed": true, + "acme.dev/nested": {"a": 1, "b": 2}, + "acme.dev/list": [1, 2] + })); + + apply_meta_update( + &mut stored, + meta(json!({ + "zed.dev/removed": null, + "zed.dev/absent": null, + "acme.dev/nested": {"b": 3, "c": null}, + "acme.dev/list": [3], + "acme.dev/added": [] + })), + ); + + // Values replace the stored key whole instead of merging or appending, + // and nested nulls are data rather than removals. + assert_eq!( + stored, + meta(json!({ + "zed.dev/kept": 1, + "acme.dev/nested": {"b": 3, "c": null}, + "acme.dev/list": [3], + "acme.dev/added": [] + })) + ); + } + + #[test] + fn meta_update_omitted_empty_and_null() { + let original = meta(json!({"zed.dev/kept": 1})); + let mut stored = original.clone(); + + apply_meta_update(&mut stored, MaybeUndefined::Undefined); + assert_eq!(stored, original); + + // An empty patch changes no keys. + apply_meta_update(&mut stored, meta(json!({}))); + assert_eq!(stored, original); + + apply_meta_update(&mut stored, MaybeUndefined::Null); + assert_eq!(stored, MaybeUndefined::Null); + } + + #[test] + fn meta_update_starts_from_empty_after_clear_or_on_first_update() { + for initial in [MaybeUndefined::Undefined, MaybeUndefined::Null] { + let mut stored = initial; + apply_meta_update( + &mut stored, + meta(json!({"zed.dev/added": 1, "zed.dev/removed": null})), + ); + // A `null` key value is never stored. + assert_eq!(stored, meta(json!({"zed.dev/added": 1}))); + } + } +} diff --git a/agent-client-protocol-schema/src/v2/terminal.rs b/agent-client-protocol-schema/src/v2/terminal.rs index d378a5ca0..d626409c1 100644 --- a/agent-client-protocol-schema/src/v2/terminal.rs +++ b/agent-client-protocol-schema/src/v2/terminal.rs @@ -6,7 +6,7 @@ use derive_more::{Display, From}; use serde::{Deserialize, Serialize}; use serde_with::{DefaultOnError, serde_as, skip_serializing_none}; -use super::{AbsolutePath, Meta}; +use super::{AbsolutePath, Meta, apply_meta_update}; use crate::{IntoMaybeUndefined, IntoOption, MaybeUndefined}; /// Unique identifier for an agent-owned terminal within a session. @@ -188,8 +188,9 @@ impl TerminalExitStatus { /// /// Only [`TerminalUpdate::terminal_id`] is required. Other fields have patch /// semantics: omitted fields leave the stored value unchanged, `null` clears -/// it, and concrete values replace it. When the terminal ID is new, omitted -/// fields start unknown. +/// it, and concrete values replace it. A concrete `_meta` object instead +/// patches the stored metadata by top-level key. When the terminal ID is new, +/// omitted fields start unknown. #[serde_as] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -219,10 +220,13 @@ pub struct TerminalUpdate { #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")] pub exit_status: MaybeUndefined, /// The _meta property is reserved by ACP to allow clients and agents to attach additional - /// metadata to their interactions. Omitted means no metadata update; `null` is an - /// explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. + /// metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` + /// clears it. An object patches the stored metadata by top-level key: keys it omits are + /// unchanged, a key set to `null` is removed, and any other value replaces that key's + /// stored value without merging nested objects. Beyond this patch rule, implementations + /// MUST NOT make assumptions about values at these keys. /// - /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + /// See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/extensibility#metadata-patches) #[serde_as(deserialize_as = "DefaultOnError>")] #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))] #[serde( @@ -275,7 +279,10 @@ impl TerminalUpdate { self } - /// Sets, clears, or leaves unchanged terminal metadata. + /// Patches, clears, or leaves unchanged terminal metadata. + /// + /// A concrete object patches stored metadata by top-level key; see + /// [`apply_meta_update`]. #[must_use] pub fn meta(mut self, meta: impl IntoMaybeUndefined) -> Self { self.meta = meta.into_maybe_undefined(); @@ -285,7 +292,9 @@ impl TerminalUpdate { /// Applies a later terminal patch to this stored terminal state. /// /// Fields set to `null` remain `null` so callers can distinguish explicit - /// clearing from an update that did not mention the field. + /// clearing from an update that did not mention the field. A concrete + /// `_meta` object patches the stored metadata by top-level key, as described + /// by [`apply_meta_update`]. pub fn apply_update(&mut self, update: TerminalUpdate) { debug_assert_eq!(self.terminal_id, update.terminal_id); if !update.command.is_undefined() { @@ -300,9 +309,7 @@ impl TerminalUpdate { if !update.exit_status.is_undefined() { self.exit_status = update.exit_status; } - if !update.meta.is_undefined() { - self.meta = update.meta; - } + apply_meta_update(&mut self.meta, update.meta); } } @@ -478,7 +485,6 @@ mod tests { ("command", json!("")), ("output", json!({"data": ""})), ("exitStatus", json!({})), - ("_meta", json!({})), ] { let original = expected[field].clone(); // Nested objects are replacements too: an empty output snapshot @@ -495,6 +501,21 @@ mod tests { assert_eq!(to_value(&stored).unwrap(), expected, "omitting {field}"); } } + + // Top-level `_meta` patches individual keys instead of replacing the object. + stored.apply_update( + from_value(json!({ + "terminalId": "term_1", + "_meta": {"source": null, "opaque": {"other": 2}, "extra": true} + })) + .unwrap(), + ); + expected["_meta"] = json!({"opaque": {"other": 2}, "extra": true}); + assert_eq!(to_value(&stored).unwrap(), expected, "patching _meta keys"); + + stored.apply_update(from_value(json!({"terminalId": "term_1", "_meta": null})).unwrap()); + expected["_meta"] = json!(null); + assert_eq!(to_value(&stored).unwrap(), expected, "clearing _meta"); } #[test] diff --git a/agent-client-protocol-schema/src/v2/tool_call.rs b/agent-client-protocol-schema/src/v2/tool_call.rs index 61e477085..c3080ea68 100644 --- a/agent-client-protocol-schema/src/v2/tool_call.rs +++ b/agent-client-protocol-schema/src/v2/tool_call.rs @@ -12,7 +12,7 @@ use schemars::Schema; use serde::{Deserialize, Serialize}; use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none}; -use super::{AbsolutePath, ContentBlock, MediaType, Meta, Terminal}; +use super::{AbsolutePath, ContentBlock, MediaType, Meta, Terminal, apply_meta_update}; use crate::{IntoMaybeUndefined, IntoOption, MaybeUndefined}; /// Represents an upsert for a tool call that the language model has requested. @@ -24,8 +24,9 @@ use crate::{IntoMaybeUndefined, IntoOption, MaybeUndefined}; /// omitted fields leave the existing tool call value unchanged, `null` clears or /// unsets the value, and concrete values replace the previous value. For /// collection fields, concrete arrays replace the previous collection, and both -/// `null` and `[]` clear the collection. When a client receives a tool call ID it -/// has not seen before, omitted fields use client defaults. +/// `null` and `[]` clear the collection. A concrete `_meta` object instead +/// patches the stored metadata by top-level key. When a client receives a tool +/// call ID it has not seen before, omitted fields use client defaults. /// /// See protocol docs: [Tool Calls](https://agentclientprotocol.com/protocol/tool-calls) #[serde_as] @@ -85,10 +86,13 @@ pub struct ToolCallUpdate { #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")] pub raw_output: MaybeUndefined, /// The _meta property is reserved by ACP to allow clients and agents to attach additional - /// metadata to their interactions. Omitted means no metadata update; `null` is an - /// explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. + /// metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` + /// clears it. An object patches the stored metadata by top-level key: keys it omits are + /// unchanged, a key set to `null` is removed, and any other value replaces that key's + /// stored value without merging nested objects. Beyond this patch rule, implementations + /// MUST NOT make assumptions about values at these keys. /// - /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + /// See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/extensibility#metadata-patches) #[serde_as(deserialize_as = "DefaultOnError>")] #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))] #[serde( @@ -175,11 +179,10 @@ impl ToolCallUpdate { self } - /// The _meta property is reserved by ACP to allow clients and agents to attach additional - /// metadata to their interactions. Omitted means no metadata update; `null` is an - /// explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. + /// Patches, clears, or leaves unchanged tool-call metadata. /// - /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + /// A concrete object patches stored metadata by top-level key; see + /// [`apply_meta_update`]. #[must_use] pub fn meta(mut self, meta: impl IntoMaybeUndefined) -> Self { self.meta = meta.into_maybe_undefined(); @@ -189,7 +192,8 @@ impl ToolCallUpdate { /// Applies a later tool-call patch to this stored tool-call state. /// /// Fields set to `null` are preserved as `null` so callers can decide how to - /// render an explicitly cleared value. + /// render an explicitly cleared value. A concrete `_meta` object patches the + /// stored metadata by top-level key, as described by [`apply_meta_update`]. pub fn apply_update(&mut self, update: ToolCallUpdate) { debug_assert_eq!(self.tool_call_id, update.tool_call_id); if !update.name.is_undefined() { @@ -216,9 +220,7 @@ impl ToolCallUpdate { if !update.raw_output.is_undefined() { self.raw_output = update.raw_output; } - if !update.meta.is_undefined() { - self.meta = update.meta; - } + apply_meta_update(&mut self.meta, update.meta); } } @@ -1119,6 +1121,35 @@ mod tests { assert_eq!(stored.meta, MaybeUndefined::Null); } + #[test] + fn tool_call_update_patches_meta_by_top_level_key() { + let mut stored: ToolCallUpdate = from_value(json!({ + "toolCallId": "tc_1", + "_meta": {"zed.dev/a": 1, "acme.dev/b": {"x": 1}} + })) + .unwrap(); + + // A writer that only knows its own key must not erase other keys. + stored.apply_update( + from_value(json!({"toolCallId": "tc_1", "_meta": {"proxy.dev/c": true}})).unwrap(), + ); + stored.apply_update( + from_value(json!({ + "toolCallId": "tc_1", + "_meta": {"zed.dev/a": null, "acme.dev/b": {"y": 2}} + })) + .unwrap(), + ); + + assert_eq!( + to_value(&stored).unwrap(), + json!({ + "toolCallId": "tc_1", + "_meta": {"acme.dev/b": {"y": 2}, "proxy.dev/c": true} + }) + ); + } + #[test] fn tool_call_wire_patches_preserve_omitted_fields_and_replace_values() { let initial = json!({ @@ -1157,7 +1188,6 @@ mod tests { ("locations", json!([])), ("rawInput", json!({})), ("rawOutput", json!({})), - ("_meta", json!({})), ] { let original = expected[field].clone(); // Empty values replace rather than merge; null remains distinct. diff --git a/docs/docs.json b/docs/docs.json index 6ebe6190f..f80410620 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -218,7 +218,8 @@ "rfds/v2/required-session-methods", "rfds/v2/session-resume-replay", "rfds/v2/diff-file-states", - "rfds/v2/terminal-output" + "rfds/v2/terminal-output", + "rfds/v2/meta-patches" ] } ] diff --git a/docs/protocol/v2/draft/extensibility.mdx b/docs/protocol/v2/draft/extensibility.mdx index 7223dda45..e1a872fb6 100644 --- a/docs/protocol/v2/draft/extensibility.mdx +++ b/docs/protocol/v2/draft/extensibility.mdx @@ -94,6 +94,55 @@ Clients may propagate fields to the agent for correlation purposes, such as `req Implementations **MUST NOT** add any custom fields at the root of a type that's part of the specification. All possible names are reserved for future protocol versions. +### Metadata Patches + +Upsert updates carry a top-level `_meta` that the Client stores with the entity they create or update: `tool_call_update` (including the `toolCall` of a permission request's `tool_call` subject), `terminal_update`, `user_message`, `agent_message`, `agent_thought`, `session_info_update`, `compaction_update`, and the draft-only `subagent_update` and `session_message`. Clients apply this `_meta` as a patch, one top-level key at a time, using the same rule as the update's other fields: + +- Omit `_meta` to leave the stored metadata unchanged. +- Set `_meta` to `null` to clear all stored metadata. +- Send an object to patch the stored metadata: + - Keys the object omits keep their stored values, so `_meta: {}` changes nothing. + - A key set to `null` is removed. Removing a key that is not stored does nothing. + - Any other value replaces that key's stored value whole. Clients **MUST NOT** merge nested objects, and a `null` nested inside the value is ordinary data. + +For example, if a tool call's stored `_meta` is: + +```json +{ + "zed.dev/progress": 0.5, + "acme.dev/review": { "state": "pending" } +} +``` + +and a later update sends: + +```json +{ + "sessionUpdate": "tool_call_update", + "toolCallId": "call_001", + "_meta": { + "zed.dev/progress": null, + "acme.dev/review": { "reviewer": "sam" }, + "proxy.dev/route": "edge" + } +} +``` + +the stored `_meta` becomes: + +```json +{ + "acme.dev/review": { "reviewer": "sam" }, + "proxy.dev/route": "edge" +} +``` + +A key set to `null` never holds stored data, including on the first update for an entity, so a key cannot store a literal JSON `null`; an extension that needs one can wrap it, for example `{ "value": null }`. Stored metadata with no keys is the same as no metadata. Because each top-level key is replaced whole, the implementation that owns a key, such as an extension using a namespaced key, can set, replace, or remove it without knowing the other keys. This does not resolve conflicts between writers: updates to the same key apply in receive order, and `_meta: null` clears every key. + +Replay and resynchronization apply these patches exactly as live delivery does and never reset metadata implicitly. An update that replaces another field, such as a message update with `content`, leaves the `_meta` keys it does not mention. To send the current metadata of an entity the Client may already hold, the Agent removes stale keys: it either sets each one to `null` alongside the current keys, or sends `_meta: null` and then the current object in a later update. The second form is not atomic, even within a JSON-RPC batch, so the Client may briefly hold no metadata. Send `_meta: null`, not `{}`, when the current metadata is empty. Intermediaries that combine updates **MUST** keep removals and clears. + +Other `_meta` fields are not patches, including the `_meta` of `plan_update`. Metadata on requests, responses, notification params, chunks, content items, snapshots, and other nested objects describes only the object that carries it, and a key set to `null` there is ordinary data. Do not copy such metadata into a patch unchanged, for example from a `session/list` entry into a `session_info_update`: its `null` key values would become removals. Put data about a single notification, rather than the entity, in the `session/update` notification's `params._meta`. + ## Unknown Object Fields Receivers **MUST** ignore unrecognized properties of protocol-defined objects, including properties nested within capability objects. An otherwise valid message **MUST NOT** be rejected solely because it contains unrecognized properties. Ignoring a property means not assigning protocol behavior to it. It does not imply support for the feature it describes. diff --git a/docs/protocol/v2/draft/prompt-lifecycle.mdx b/docs/protocol/v2/draft/prompt-lifecycle.mdx index 9ab15ffc4..014a1b84c 100644 --- a/docs/protocol/v2/draft/prompt-lifecycle.mdx +++ b/docs/protocol/v2/draft/prompt-lifecycle.mdx @@ -97,7 +97,9 @@ For example, an incoming message in the child can begin with a streamed chunk: ``` Chunks append normal message content for the same IDs. A `session_message` -upsert may replace the whole `content` array or update message metadata. +upsert may replace the whole `content` array or patch message metadata by +top-level key, as described in +[Metadata Patches](/protocol/v2/draft/extensibility#metadata-patches). Omission leaves either field unchanged; `null` clears it. `content: []` also clears accumulated content. Chunk `_meta` is scoped to that chunk and does not patch message metadata. @@ -411,7 +413,9 @@ Clients apply message updates and chunks in the order they are received for each - A message update with `content` replaces all content currently stored for that message, including content accumulated from earlier chunks. - A message update with `content: []` or `content: null` clears the message content. - A chunk appends its `content` to whatever content is current for that message, whether that content came from an earlier message update or earlier chunks. -- A chunk's `_meta`, when present, is chunk-scoped. +- A message update's top-level `_meta` patches the message's stored metadata by + top-level key, even when the same update replaces `content`, as described in [Metadata Patches](/protocol/v2/draft/extensibility#metadata-patches). A chunk's `_meta`, when present, is + chunk-scoped. For example, if an Agent sends `agent_message` with `content: [A]`, then sends `agent_message_chunk` with `B`, the rendered message content is `[A, B]`. If it later sends another `agent_message` with `content: [C]`, the rendered content becomes `[C]`; the earlier full content and chunks are replaced. Subsequent chunks append to `[C]`. @@ -651,7 +655,9 @@ The following `compaction_update` fields are optional, nullable patches: For each patch field, omission leaves the stored value unchanged, `null` clears it, and a concrete value replaces it. On a first-seen ID, omission and `null` -both start with no value. `summary: []` also clears the summary. +both start with no value. `summary: []` also clears the summary. A `_meta` +object patches the stored metadata by top-level key instead of replacing it; +see [Metadata Patches](/protocol/v2/draft/extensibility#metadata-patches). Clients apply updates and chunks in receive order for each ID. Chunks append to the current summary; a concrete `summary` replaces all previously diff --git a/docs/protocol/v2/draft/schema.mdx b/docs/protocol/v2/draft/schema.mdx index 4dade568b..b79f2e847 100644 --- a/docs/protocol/v2/draft/schema.mdx +++ b/docs/protocol/v2/draft/schema.mdx @@ -2358,10 +2358,13 @@ content. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Implementations MUST NOT make assumptions about values at -these keys. Omitted means no metadata update; `null` is an explicit clear signal. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) ContentBlock[] | null} > @@ -2393,10 +2396,13 @@ content. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Implementations MUST NOT make assumptions about values at -these keys. Omitted means no metadata update; `null` is an explicit clear signal. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) ContentBlock[] | null} > @@ -3048,43 +3054,34 @@ timeline position. Later updates with the same ID patch that entity in place. `summary`, `error`, and `_meta` have patch semantics: omission leaves the stored value unchanged, `null` clears it, and a concrete value replaces it. -`summary: []` also clears the summary. +`summary: []` also clears the summary. A concrete `_meta` object patches the +stored metadata by top-level key instead of replacing it. **Type:** Object **Properties:** - - Extensible metadata patch for this compaction. + + The _meta property is reserved by ACP to allow clients and agents to attach additional +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. + +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) + -CompactionId} - required -> +CompactionId} required> The Agent-owned ID of this compaction, unique within the session. - + Human-readable error details for the compaction. -CompactionStatus} - required -> +CompactionStatus} required> Current lifecycle status. - - - ContentBlock[] - - | null - - } -> +ContentBlock[] | null} > Complete replacement user-displayable summary content for the compaction. @@ -8342,7 +8339,8 @@ Agents send this notification to update session information like title or custom This allows clients to display dynamic session names and track session state changes. Omitted fields leave the existing session info unchanged. `null` clears the -corresponding value. +corresponding value. A concrete `_meta` object patches the stored metadata by +top-level key. **Type:** Object @@ -8350,10 +8348,13 @@ corresponding value. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Omitted means no metadata update; `null` is an -explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) @@ -8385,9 +8386,14 @@ An upsert for an inter-session message. **Properties:** - Omitted leaves metadata unchanged; `null` clears it. + The _meta property is reserved by ACP to allow clients and agents to attach additional +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -Implementations MUST NOT make assumptions about values in `_meta`. +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) ContentBlock[] | null} > @@ -8572,10 +8578,13 @@ fields in the new update patch the previous fields for that message. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Implementations MUST NOT make assumptions about values at -these keys. Omitted means no metadata update; `null` is an explicit clear signal. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) ContentBlock[] | null} > @@ -8632,10 +8641,13 @@ fields in the new update patch the previous fields for that message. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Implementations MUST NOT make assumptions about values at -these keys. Omitted means no metadata update; `null` is an explicit clear signal. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) ContentBlock[] | null} > @@ -8692,10 +8704,13 @@ fields in the new update patch the previous fields for that message. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Implementations MUST NOT make assumptions about values at -these keys. Omitted means no metadata update; `null` is an explicit clear signal. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) ContentBlock[] | null} > @@ -9007,10 +9022,13 @@ A tool call has been created or updated. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Omitted means no metadata update; `null` is an -explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) ToolCallContent[] | null} > @@ -9062,10 +9080,13 @@ An agent-owned terminal has been created or updated. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Omitted means no metadata update; `null` is an -explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) @@ -9224,10 +9245,13 @@ Session metadata has been updated (title, timestamps, custom metadata) The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Omitted means no metadata update; `null` is an -explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) @@ -9322,40 +9346,30 @@ A context compaction has been created or updated. - - Extensible metadata patch for this compaction. + + The _meta property is reserved by ACP to allow clients and agents to attach additional +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. + +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) + -CompactionId} - required -> +CompactionId} required> The Agent-owned ID of this compaction, unique within the session. - + Human-readable error details for the compaction. The discriminator value. Must be `"compaction_update"`. -CompactionStatus} - required -> +CompactionStatus} required> Current lifecycle status. - - - ContentBlock[] - - | null - - } -> +ContentBlock[] | null} > Complete replacement user-displayable summary content for the compaction. @@ -9403,10 +9417,13 @@ that ownership association's metadata. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Omitted means no metadata update; `null` is an -explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) SubagentSessionCapabilities | null} > @@ -9459,9 +9476,14 @@ received from another session. - Omitted leaves metadata unchanged; `null` clears it. + The _meta property is reserved by ACP to allow clients and agents to attach additional +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -Implementations MUST NOT make assumptions about values in `_meta`. +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) ContentBlock[] | null} > @@ -10017,9 +10039,10 @@ is required. Only `SubagentUpdate::session_id` is required. Other fields have patch semantics: omitted fields leave the stored value unchanged, `null` -clears or unsets the value, and concrete values replace it. For `state`, -`null` removes the current state report and leaves activity unconfirmed; -it does not report idle or cancellation. A child whose capabilities are +clears or unsets the value, and concrete values replace it. A concrete +`_meta` object instead patches the stored metadata by top-level key. For +`state`, `null` removes the current state report and leaves activity +unconfirmed; it does not report idle or cancellation. A child whose capabilities are unset permits no Client-initiated session mutations. The title and description provide the parent's display metadata for the @@ -10038,10 +10061,13 @@ change this association. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Omitted means no metadata update; `null` is an -explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) SubagentSessionCapabilities | null} > @@ -10310,8 +10336,9 @@ An upsert for the stored state of an agent-owned terminal. Only `TerminalUpdate::terminal_id` is required. Other fields have patch semantics: omitted fields leave the stored value unchanged, `null` clears -it, and concrete values replace it. When the terminal ID is new, omitted -fields start unknown. +it, and concrete values replace it. A concrete `_meta` object instead +patches the stored metadata by top-level key. When the terminal ID is new, +omitted fields start unknown. **Type:** Object @@ -10319,10 +10346,13 @@ fields start unknown. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Omitted means no metadata update; `null` is an -explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) @@ -10723,8 +10753,9 @@ Only `ToolCallUpdate::tool_call_id` is required. Other fields have patch semanti omitted fields leave the existing tool call value unchanged, `null` clears or unsets the value, and concrete values replace the previous value. For collection fields, concrete arrays replace the previous collection, and both -`null` and `[]` clear the collection. When a client receives a tool call ID it -has not seen before, omitted fields use client defaults. +`null` and `[]` clear the collection. A concrete `_meta` object instead +patches the stored metadata by top-level key. When a client receives a tool +call ID it has not seen before, omitted fields use client defaults. See protocol docs: [Tool Calls](https://agentclientprotocol.com/protocol/v2/draft/tool-calls) @@ -10734,10 +10765,13 @@ See protocol docs: [Tool Calls](https://agentclientprotocol.com/protocol/v2/draf The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Omitted means no metadata update; `null` is an -explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) ToolCallContent[] | null} > @@ -10973,10 +11007,13 @@ content. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Implementations MUST NOT make assumptions about values at -these keys. Omitted means no metadata update; `null` is an explicit clear signal. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches) ContentBlock[] | null} > diff --git a/docs/protocol/v2/draft/session-list.mdx b/docs/protocol/v2/draft/session-list.mdx index 7d0821878..4aef71f82 100644 --- a/docs/protocol/v2/draft/session-list.mdx +++ b/docs/protocol/v2/draft/session-list.mdx @@ -186,8 +186,9 @@ All fields are optional. Only include fields that have changed; omitted fields a - Agent-specific metadata update. Omit to leave unchanged; set to `null` to - clear. See [Extensibility](/protocol/v2/draft/extensibility). + Agent-specific metadata patch. Omit to leave unchanged; set to `null` to + clear. An object patches the stored metadata by top-level key. See [Metadata + Patches](/protocol/v2/draft/extensibility#metadata-patches). The `sessionId`, `cwd`, and `additionalDirectories` fields are **not** included diff --git a/docs/protocol/v2/draft/session-setup.mdx b/docs/protocol/v2/draft/session-setup.mdx index af18d8121..eef5d334f 100644 --- a/docs/protocol/v2/draft/session-setup.mdx +++ b/docs/protocol/v2/draft/session-setup.mdx @@ -291,6 +291,13 @@ arrive after a message update, they append to that update's current content. Message updates that omit `content` can update other optional fields without changing the current content. +Replay also applies top-level `_meta` as a +[metadata patch](/protocol/v2/draft/extensibility#metadata-patches) and does not reset it. +A message update that replaces `content` leaves `_meta` keys it does not +mention. When the Client may already hold metadata for an entity, the Agent +removes stale keys by setting them to `null`, or sends `_meta: null` before the +current object. + When all requested replay entries have been reported to the Client, the Agent **MUST** synchronize current state as described below, then respond to the original `session/resume` request. diff --git a/docs/protocol/v2/draft/tool-calls.mdx b/docs/protocol/v2/draft/tool-calls.mdx index e04f75bfe..da2e36b62 100644 --- a/docs/protocol/v2/draft/tool-calls.mdx +++ b/docs/protocol/v2/draft/tool-calls.mdx @@ -106,10 +106,11 @@ unchanged when omitted, explicitly clear or unset the value when `null`, and replace the previous value when concrete values are sent. For a new `toolCallId`, omitted fields use Client defaults. `content` and `locations` are replaced as whole arrays; send `[]` or `null` to clear them. For `_meta`, omit -the field to leave it unchanged, set it to `null` to clear it, or send an -object to replace the whole stored object; Clients do not merge its keys with -earlier ones. Use -`tool_call_content_chunk` when a tool produces content incrementally and the +the field to leave it unchanged or set it to `null` to clear it. An object +patches the stored metadata by top-level key: a key set to `null` is removed, +any other value replaces that key whole, and keys the object omits are +unchanged. See [Metadata Patches](/protocol/v2/draft/extensibility#metadata-patches). +Use `tool_call_content_chunk` when a tool produces content incrementally and the Client should append each item instead of replacing the whole `content` collection. Display-only terminal bytes use `terminal_output_chunk` instead. For `name` specifically, omission leaves the existing name unchanged, `null` @@ -457,7 +458,9 @@ empty. and `null` both mean the value was not provided. Conventional POSIX signal names include `SIGTERM`, `SIGKILL`, and `SIGINT`; other platforms may use platform-specific names. Terminal exit and tool call status are independent. -- The top-level `_meta` is terminal-scoped and follows the same patch semantics. +- The top-level `_meta` is terminal-scoped and follows the same patch semantics, + except that an object patches the stored metadata by top-level key. See + [Metadata Patches](/protocol/v2/draft/extensibility#metadata-patches). For example, a process terminated by a POSIX termination signal can report `"exitStatus": { "signal": "SIGTERM" }`. diff --git a/docs/protocol/v2/extensibility.mdx b/docs/protocol/v2/extensibility.mdx index 7223dda45..7d1e04aaa 100644 --- a/docs/protocol/v2/extensibility.mdx +++ b/docs/protocol/v2/extensibility.mdx @@ -94,6 +94,55 @@ Clients may propagate fields to the agent for correlation purposes, such as `req Implementations **MUST NOT** add any custom fields at the root of a type that's part of the specification. All possible names are reserved for future protocol versions. +### Metadata Patches + +Upsert updates carry a top-level `_meta` that the Client stores with the entity they create or update: `tool_call_update` (including the `toolCall` of a permission request's `tool_call` subject), `terminal_update`, `user_message`, `agent_message`, `agent_thought`, `session_info_update`, and `compaction_update`. Clients apply this `_meta` as a patch, one top-level key at a time, using the same rule as the update's other fields: + +- Omit `_meta` to leave the stored metadata unchanged. +- Set `_meta` to `null` to clear all stored metadata. +- Send an object to patch the stored metadata: + - Keys the object omits keep their stored values, so `_meta: {}` changes nothing. + - A key set to `null` is removed. Removing a key that is not stored does nothing. + - Any other value replaces that key's stored value whole. Clients **MUST NOT** merge nested objects, and a `null` nested inside the value is ordinary data. + +For example, if a tool call's stored `_meta` is: + +```json +{ + "zed.dev/progress": 0.5, + "acme.dev/review": { "state": "pending" } +} +``` + +and a later update sends: + +```json +{ + "sessionUpdate": "tool_call_update", + "toolCallId": "call_001", + "_meta": { + "zed.dev/progress": null, + "acme.dev/review": { "reviewer": "sam" }, + "proxy.dev/route": "edge" + } +} +``` + +the stored `_meta` becomes: + +```json +{ + "acme.dev/review": { "reviewer": "sam" }, + "proxy.dev/route": "edge" +} +``` + +A key set to `null` never holds stored data, including on the first update for an entity, so a key cannot store a literal JSON `null`; an extension that needs one can wrap it, for example `{ "value": null }`. Stored metadata with no keys is the same as no metadata. Because each top-level key is replaced whole, the implementation that owns a key, such as an extension using a namespaced key, can set, replace, or remove it without knowing the other keys. This does not resolve conflicts between writers: updates to the same key apply in receive order, and `_meta: null` clears every key. + +Replay and resynchronization apply these patches exactly as live delivery does and never reset metadata implicitly. An update that replaces another field, such as a message update with `content`, leaves the `_meta` keys it does not mention. To send the current metadata of an entity the Client may already hold, the Agent removes stale keys: it either sets each one to `null` alongside the current keys, or sends `_meta: null` and then the current object in a later update. The second form is not atomic, even within a JSON-RPC batch, so the Client may briefly hold no metadata. Send `_meta: null`, not `{}`, when the current metadata is empty. Intermediaries that combine updates **MUST** keep removals and clears. + +Other `_meta` fields are not patches, including the `_meta` of `plan_update`. Metadata on requests, responses, notification params, chunks, content items, snapshots, and other nested objects describes only the object that carries it, and a key set to `null` there is ordinary data. Do not copy such metadata into a patch unchanged, for example from a `session/list` entry into a `session_info_update`: its `null` key values would become removals. Put data about a single notification, rather than the entity, in the `session/update` notification's `params._meta`. + ## Unknown Object Fields Receivers **MUST** ignore unrecognized properties of protocol-defined objects, including properties nested within capability objects. An otherwise valid message **MUST NOT** be rejected solely because it contains unrecognized properties. Ignoring a property means not assigning protocol behavior to it. It does not imply support for the feature it describes. diff --git a/docs/protocol/v2/migration.mdx b/docs/protocol/v2/migration.mdx index b5cff3e17..35b983d6b 100644 --- a/docs/protocol/v2/migration.mdx +++ b/docs/protocol/v2/migration.mdx @@ -497,11 +497,13 @@ extensible. Top-level `_meta` is now stored tool-call state, too. v1 gave no rule for keeping a `tool_call_update`'s `_meta`, so Agents could use it for data about -one report. In v2, a concrete `_meta` replaces the tool call's whole stored -`_meta` object, and Clients do not merge keys. An Agent that sends different -keys on different reports loses the earlier keys at each later report. To keep -them, resend every key on each report that includes `_meta`, or omit `_meta` -from reports that don't change it. +one report. In v2, a concrete `_meta` patches the tool call's stored metadata +by top-level key: keys it omits keep their stored values, a key set to `null` +is removed, and any other value replaces that key whole (see +[Metadata Patches](/protocol/v2/extensibility#metadata-patches)). A key sent on +an earlier report therefore stays until the Agent sets it to `null` or clears +`_meta`. Put data about a single report in the `session/update` +notification's `params._meta` instead. ### Streaming tool-call content @@ -549,7 +551,7 @@ The reference contains no mutable state. The Agent reports that separately with } ``` -Only `terminalId` is required. `command`, `cwd`, `output`, `exitStatus`, and `_meta` are patch fields: omission leaves the stored value unchanged, `null` clears it, and a concrete value replaces it. On a first-seen terminal, omitted fields start unknown or empty. Every supplied `cwd` must be absolute. +Only `terminalId` is required. `command`, `cwd`, `output`, `exitStatus`, and `_meta` are patch fields: omission leaves the stored value unchanged, `null` clears it, and a concrete value replaces it, except that a `_meta` object patches stored metadata by top-level key. On a first-seen terminal, omitted fields start unknown or empty. Every supplied `cwd` must be absolute. `output` is an authoritative replacement snapshot for replay, correction, or resynchronization. Its required `data` field is RFC 4648 base64-encoded bytes and is the complete byte sequence the Agent wants the Client to retain for the terminal at that point. A concrete snapshot replaces all previously stored bytes; Clients must not merge or splice bytes from the previous snapshot into it. Optional nullable `output._meta` is snapshot-scoped; omission and `null` both mean no snapshot metadata was provided. @@ -869,7 +871,7 @@ A few schema definition names also changed without any wire-format change, which v2 makes the whole schema forward-compatible by convention: - **Open enums and tagged unions.** Every enum-like string accepts unknown values. Values beginning with `_` are reserved for implementation-specific extensions (e.g. `_mycompany_widget`). Unknown values _without_ a leading `_` are reserved for future ACP versions. Receivers **SHOULD** preserve unknown values when storing, replaying, proxying, or forwarding, and either render them generically or fall back safely. Do not mint non-underscore custom values. -- **`_meta` everywhere.** As in v1, `_meta` objects carry implementation metadata. In v2's upsert-style updates, top-level `_meta` follows the same patch semantics as other fields: omitted means unchanged, `null` clears. Metadata nested in a snapshot, exit-status object, content item, command permission subject, or chunk is scoped to that object; omission and `null` are equivalent. +- **`_meta` everywhere.** As in v1, `_meta` objects carry implementation metadata. In v2's upsert-style updates, top-level `_meta` is a patch: omitted means unchanged, `null` clears it, and an object patches stored metadata by top-level key, where a key set to `null` is removed and any other value replaces that key whole (see [Metadata Patches](/protocol/v2/extensibility#metadata-patches)). Metadata nested in a snapshot, exit-status object, content item, command permission subject, or chunk is scoped to that object; omission and `null` are equivalent. - **Custom methods** still use the `_` prefix, unchanged from v1. ## Transports @@ -885,13 +887,13 @@ A standard remote transport (streamable HTTP with SSE streams, plus WebSocket) i 3. **Auth.** Rename `authenticate` → `auth/login` for method types that define protocol-driven login. If you return one or more valid `authMethods`, implement both `auth/login` and `auth/logout`; if you omit the field or return an empty array, Clients **MUST NOT** call either method. Do not add a logout support marker: `capabilities.auth` is only for authentication-related extensions. Add `type` and `methodId` to your auth method descriptors, and send terminal method `env` as `{ "name", "value" }` entries. 4. **Prompt lifecycle.** Respond to an accepted `session/prompt` with `{ "messageId": "…" }`, carrying the agent-generated ID of the inserted user message. Emit a `user_message` acknowledgment with the same `messageId`, a `running` state update when foreground work starts, and an idle `state_update` carrying the stop reason when it ends, including `cancelled` after `session/cancel`. Report a failure after insertion as an idle `state_update` with `stopReason: "error"` and the JSON-RPC error in `error`, never as an error response to the prompt. Background updates may continue while idle. 5. **Message IDs.** Generate and attach a `messageId` to every message chunk and update. -6. **Tool calls.** Stop sending `tool_call`. Send `tool_call_update` for creation and patches. Use `tool_call_content_chunk` to stream content. Respect omit/`null`/value patch semantics. +6. **Tool calls.** Stop sending `tool_call`. Send `tool_call_update` for creation and patches. Use `tool_call_content_chunk` to stream content. Respect omit/`null`/value patch semantics, including key-level `_meta` patches. 7. **Terminal output.** Add `terminal` content references for Agent-owned display terminals. Send `terminal_update` for command, absolute cwd, snapshots, and exit state; send independently base64-encoded `terminal_output_chunk` values for live bytes. Do not expect Client control methods or capability negotiation. 8. **Diffs.** Replace `oldText`/`newText` with `changes` (+ `fileType`/`mimeType` where known) and provide `patch` in `git_patch` format whenever feasible. If a tool's input describes an edit only as a snippet, read the file somewhere the tool waits on, before it can run, or send no diff until the change is done (see [Edits known only as a snippet](#edits-known-only-as-a-snippet)). 9. **Permissions.** Put prompt copy in required `title` (+ optional `description`). Use the `tool_call` subject for tool-call state and the `command` subject for a command, required absolute cwd, and optional tool-call/terminal associations. 10. **Plans.** Send `plan_update` with `{ "type": "items", "planId": ..., "entries": [...] }`. 11. **Modes.** Drop `session/set_mode`, `modes`, and `current_mode_update`. Expose the same state as config options with `category: "mode"` and emit `config_option_update`. -12. **Replay.** Handle `replayFrom` on `session/resume`. Replay history as ordinary `session/update` notifications when asked, including terminal replacement snapshots rather than requiring every historical output chunk. Remove `session/load`. +12. **Replay.** Handle `replayFrom` on `session/resume`. Replay history as ordinary `session/update` notifications when asked, including terminal replacement snapshots rather than requiring every historical output chunk. When the Client may already hold an entity's `_meta`, remove stale keys explicitly, since an `_meta` object no longer replaces them. Remove `session/load`. 13. **Client surface.** Remove all `fs/*` and `terminal/*` calls. The new terminal content is Agent-owned display state, not a Client resource. 14. **MCP config.** Require and emit `type` discriminators. Drop SSE. Advertise `session.mcp.stdio` / `session.mcp.http` as appropriate. 15. **Slash commands.** Add the `type: "text"` discriminator to command `input` specifications. Optionally advertise initial commands in setup responses via `availableCommands`, and use complete-replacement `available_commands_update` notifications for later changes. @@ -903,7 +905,7 @@ A standard remote transport (streamable HTTP with SSE streams, plus WebSocket) i 2. **Rely on the baseline.** When `capabilities.session` is present, use list/resume/close/prompt/cancel without probing markers. Keep checking `session.delete` and other optional extras. 3. **Prompt lifecycle.** Don't treat the `session/prompt` response as turn completion. Drive UI from `state_update` (`running` / `idle` / `requires_action`), take the stop reason from the idle update, and render the user's message from the `user_message` acknowledgment (which carries its canonical `messageId`). Treat `stopReason: "error"` as a failed outcome and read its details from the idle update's `error`; an error response to `session/prompt` means the message was not inserted. Keep accepting background updates while idle. 4. **Messages.** Track messages by required `messageId`. Implement upsert semantics: whole-message updates replace content (or clear it with `null`/`[]`), chunks append. -5. **Tool calls.** Create tool calls on first-seen `toolCallId` in `tool_call_update`. Apply omit/`null`/value patches. Append `tool_call_content_chunk` items. Replace accumulated content when an update carries `content`. +5. **Tool calls.** Create tool calls on first-seen `toolCallId` in `tool_call_update`. Apply omit/`null`/value patches, and patch top-level `_meta` by key. Append `tool_call_content_chunk` items. Replace accumulated content when an update carries `content`. 6. **Terminal output.** Maintain Agent-owned terminal state by `terminalId`. Apply update patch and snapshot-replacement semantics; independently decode and append chunk bytes. Render them in an isolated terminal emulator or sanitized transcript, with no input or process controls. 7. **Diffs.** Render `patch.text` when present. Drive file trees and summaries from `changes`. Handle patch-less diffs (binary, symlink, directory). 8. **Permissions.** Render `title`/`description`. Apply `tool_call` subjects like ordinary tool-call updates; render `command` subjects from their command, required absolute cwd, and optional associations. Show a generic prompt for absent or unknown subjects. @@ -917,7 +919,7 @@ A standard remote transport (streamable HTTP with SSE streams, plus WebSocket) i ## Migration checklist: SDKs 1. **Separate the versions.** Keep v1 and v2 schemas, generated models, and test fixtures fully separate. Exposing unstable-v2 types does not advertise support; enforce each feature's documented negotiation requirements independently of `protocolVersion: 2`. -2. **Three-state fields.** Model omitted vs `null` vs concrete values distinctly wherever v2 defines patch semantics. A plain nullable/optional type erases a distinction the protocol depends on. +2. **Three-state fields.** Model omitted vs `null` vs concrete values distinctly wherever v2 defines patch semantics. A plain nullable/optional type erases a distinction the protocol depends on. Apply the top-level `_meta` of upserts as a key-level patch rather than a replacement. 3. **Strict where it counts.** Reject malformed payloads for _known_ discriminator values instead of demoting them to the unknown-variant fallback. The fallback exists only for genuinely unknown values. 4. **Preserve what you don't understand.** Round-trip `_`-prefixed extension values, unknown future variants, and `_meta` when storing, replaying, proxying, or forwarding. 5. **Fixtures.** Cover the full prompt lifecycle, resume replay, cancellation, permission flow, message replacement and clearing, tool-call content chunks, terminal snapshot replacement, split and invalid UTF-8 bytes, exit state, command subjects, structured diffs, and JSON-RPC batches. diff --git a/docs/protocol/v2/prompt-lifecycle.mdx b/docs/protocol/v2/prompt-lifecycle.mdx index ae75983c0..f74cac28a 100644 --- a/docs/protocol/v2/prompt-lifecycle.mdx +++ b/docs/protocol/v2/prompt-lifecycle.mdx @@ -363,7 +363,9 @@ Clients apply message updates and chunks in the order they are received for each - A message update with `content` replaces all content currently stored for that message, including content accumulated from earlier chunks. - A message update with `content: []` or `content: null` clears the message content. - A chunk appends its `content` to whatever content is current for that message, whether that content came from an earlier message update or earlier chunks. -- A chunk's `_meta`, when present, is chunk-scoped. +- A message update's top-level `_meta` patches the message's stored metadata by + top-level key, even when the same update replaces `content`, as described in [Metadata Patches](/protocol/v2/extensibility#metadata-patches). A chunk's `_meta`, when present, is + chunk-scoped. For example, if an Agent sends `agent_message` with `content: [A]`, then sends `agent_message_chunk` with `B`, the rendered message content is `[A, B]`. If it later sends another `agent_message` with `content: [C]`, the rendered content becomes `[C]`; the earlier full content and chunks are replaced. Subsequent chunks append to `[C]`. @@ -599,7 +601,9 @@ The following `compaction_update` fields are optional, nullable patches: For each patch field, omission leaves the stored value unchanged, `null` clears it, and a concrete value replaces it. On a first-seen ID, omission and `null` -both start with no value. `summary: []` also clears the summary. +both start with no value. `summary: []` also clears the summary. A `_meta` +object patches the stored metadata by top-level key instead of replacing it; +see [Metadata Patches](/protocol/v2/extensibility#metadata-patches). Clients apply updates and chunks in receive order for each ID. Chunks append to the current summary; a concrete `summary` replaces all previously diff --git a/docs/protocol/v2/schema.mdx b/docs/protocol/v2/schema.mdx index 9d3d171b9..b310a806a 100644 --- a/docs/protocol/v2/schema.mdx +++ b/docs/protocol/v2/schema.mdx @@ -1448,10 +1448,13 @@ content. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Implementations MUST NOT make assumptions about values at -these keys. Omitted means no metadata update; `null` is an explicit clear signal. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) ContentBlock[] | null} > @@ -1483,10 +1486,13 @@ content. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Implementations MUST NOT make assumptions about values at -these keys. Omitted means no metadata update; `null` is an explicit clear signal. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) ContentBlock[] | null} > @@ -2093,43 +2099,34 @@ timeline position. Later updates with the same ID patch that entity in place. `summary`, `error`, and `_meta` have patch semantics: omission leaves the stored value unchanged, `null` clears it, and a concrete value replaces it. -`summary: []` also clears the summary. +`summary: []` also clears the summary. A concrete `_meta` object patches the +stored metadata by top-level key instead of replacing it. **Type:** Object **Properties:** - - Extensible metadata patch for this compaction. + + The _meta property is reserved by ACP to allow clients and agents to attach additional +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. + +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) + -CompactionId} - required -> +CompactionId} required> The Agent-owned ID of this compaction, unique within the session. - + Human-readable error details for the compaction. -CompactionStatus} - required -> +CompactionStatus} required> Current lifecycle status. - - - ContentBlock[] - - | null - - } -> +ContentBlock[] | null} > Complete replacement user-displayable summary content for the compaction. @@ -5725,7 +5722,8 @@ Agents send this notification to update session information like title or custom This allows clients to display dynamic session names and track session state changes. Omitted fields leave the existing session info unchanged. `null` clears the -corresponding value. +corresponding value. A concrete `_meta` object patches the stored metadata by +top-level key. **Type:** Object @@ -5733,10 +5731,13 @@ corresponding value. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Omitted means no metadata update; `null` is an -explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) @@ -5806,10 +5807,13 @@ fields in the new update patch the previous fields for that message. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Implementations MUST NOT make assumptions about values at -these keys. Omitted means no metadata update; `null` is an explicit clear signal. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) ContentBlock[] | null} > @@ -5866,10 +5870,13 @@ fields in the new update patch the previous fields for that message. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Implementations MUST NOT make assumptions about values at -these keys. Omitted means no metadata update; `null` is an explicit clear signal. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) ContentBlock[] | null} > @@ -5926,10 +5933,13 @@ fields in the new update patch the previous fields for that message. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Implementations MUST NOT make assumptions about values at -these keys. Omitted means no metadata update; `null` is an explicit clear signal. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) ContentBlock[] | null} > @@ -6208,10 +6218,13 @@ A tool call has been created or updated. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Omitted means no metadata update; `null` is an -explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) ToolCallContent[] | null} > @@ -6263,10 +6276,13 @@ An agent-owned terminal has been created or updated. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Omitted means no metadata update; `null` is an -explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) @@ -6398,10 +6414,13 @@ Session metadata has been updated (title, timestamps, custom metadata) The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Omitted means no metadata update; `null` is an -explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) @@ -6496,40 +6515,30 @@ A context compaction has been created or updated. - - Extensible metadata patch for this compaction. + + The _meta property is reserved by ACP to allow clients and agents to attach additional +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. + +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) + -CompactionId} - required -> +CompactionId} required> The Agent-owned ID of this compaction, unique within the session. - + Human-readable error details for the compaction. The discriminator value. Must be `"compaction_update"`. -CompactionStatus} - required -> +CompactionStatus} required> Current lifecycle status. - - - ContentBlock[] - - | null - - } -> +ContentBlock[] | null} > Complete replacement user-displayable summary content for the compaction. @@ -7127,8 +7136,9 @@ An upsert for the stored state of an agent-owned terminal. Only `TerminalUpdate::terminal_id` is required. Other fields have patch semantics: omitted fields leave the stored value unchanged, `null` clears -it, and concrete values replace it. When the terminal ID is new, omitted -fields start unknown. +it, and concrete values replace it. A concrete `_meta` object instead +patches the stored metadata by top-level key. When the terminal ID is new, +omitted fields start unknown. **Type:** Object @@ -7136,10 +7146,13 @@ fields start unknown. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Omitted means no metadata update; `null` is an -explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) @@ -7500,8 +7513,9 @@ Only `ToolCallUpdate::tool_call_id` is required. Other fields have patch semanti omitted fields leave the existing tool call value unchanged, `null` clears or unsets the value, and concrete values replace the previous value. For collection fields, concrete arrays replace the previous collection, and both -`null` and `[]` clear the collection. When a client receives a tool call ID it -has not seen before, omitted fields use client defaults. +`null` and `[]` clear the collection. A concrete `_meta` object instead +patches the stored metadata by top-level key. When a client receives a tool +call ID it has not seen before, omitted fields use client defaults. See protocol docs: [Tool Calls](https://agentclientprotocol.com/protocol/v2/tool-calls) @@ -7511,10 +7525,13 @@ See protocol docs: [Tool Calls](https://agentclientprotocol.com/protocol/v2/tool The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Omitted means no metadata update; `null` is an -explicit clear signal. Implementations MUST NOT make assumptions about values at these keys. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) ToolCallContent[] | null} > @@ -7667,10 +7684,13 @@ content. The _meta property is reserved by ACP to allow clients and agents to attach additional -metadata to their interactions. Implementations MUST NOT make assumptions about values at -these keys. Omitted means no metadata update; `null` is an explicit clear signal. +metadata to their interactions. Omitted leaves the stored metadata unchanged; `null` +clears it. An object patches the stored metadata by top-level key: keys it omits are +unchanged, a key set to `null` is removed, and any other value replaces that key's +stored value without merging nested objects. Beyond this patch rule, implementations +MUST NOT make assumptions about values at these keys. -See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) +See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) ContentBlock[] | null} > diff --git a/docs/protocol/v2/session-list.mdx b/docs/protocol/v2/session-list.mdx index 23fbd9017..8c61765a7 100644 --- a/docs/protocol/v2/session-list.mdx +++ b/docs/protocol/v2/session-list.mdx @@ -186,8 +186,9 @@ All fields are optional. Only include fields that have changed; omitted fields a - Agent-specific metadata update. Omit to leave unchanged; set to `null` to - clear. See [Extensibility](/protocol/v2/extensibility). + Agent-specific metadata patch. Omit to leave unchanged; set to `null` to + clear. An object patches the stored metadata by top-level key. See [Metadata + Patches](/protocol/v2/extensibility#metadata-patches). The `sessionId`, `cwd`, and `additionalDirectories` fields are **not** included diff --git a/docs/protocol/v2/session-setup.mdx b/docs/protocol/v2/session-setup.mdx index e17463cc1..498ea32e6 100644 --- a/docs/protocol/v2/session-setup.mdx +++ b/docs/protocol/v2/session-setup.mdx @@ -287,6 +287,13 @@ arrive after a message update, they append to that update's current content. Message updates that omit `content` can update other optional fields without changing the current content. +Replay also applies top-level `_meta` as a +[metadata patch](/protocol/v2/extensibility#metadata-patches) and does not reset it. +A message update that replaces `content` leaves `_meta` keys it does not +mention. When the Client may already hold metadata for an entity, the Agent +removes stale keys by setting them to `null`, or sends `_meta: null` before the +current object. + When all requested replay entries have been reported to the Client, the Agent **MUST** synchronize current state as described below, then respond to the original `session/resume` request. diff --git a/docs/protocol/v2/tool-calls.mdx b/docs/protocol/v2/tool-calls.mdx index 1e09a9b1b..8d711dbdf 100644 --- a/docs/protocol/v2/tool-calls.mdx +++ b/docs/protocol/v2/tool-calls.mdx @@ -104,10 +104,11 @@ unchanged when omitted, explicitly clear or unset the value when `null`, and replace the previous value when concrete values are sent. For a new `toolCallId`, omitted fields use Client defaults. `content` and `locations` are replaced as whole arrays; send `[]` or `null` to clear them. For `_meta`, omit -the field to leave it unchanged, set it to `null` to clear it, or send an -object to replace the whole stored object; Clients do not merge its keys with -earlier ones. Use -`tool_call_content_chunk` when a tool produces content incrementally and the +the field to leave it unchanged or set it to `null` to clear it. An object +patches the stored metadata by top-level key: a key set to `null` is removed, +any other value replaces that key whole, and keys the object omits are +unchanged. See [Metadata Patches](/protocol/v2/extensibility#metadata-patches). +Use `tool_call_content_chunk` when a tool produces content incrementally and the Client should append each item instead of replacing the whole `content` collection. Display-only terminal bytes use `terminal_output_chunk` instead. For `name` specifically, omission leaves the existing name unchanged, `null` @@ -455,7 +456,9 @@ empty. and `null` both mean the value was not provided. Conventional POSIX signal names include `SIGTERM`, `SIGKILL`, and `SIGINT`; other platforms may use platform-specific names. Terminal exit and tool call status are independent. -- The top-level `_meta` is terminal-scoped and follows the same patch semantics. +- The top-level `_meta` is terminal-scoped and follows the same patch semantics, + except that an object patches the stored metadata by top-level key. See + [Metadata Patches](/protocol/v2/extensibility#metadata-patches). For example, a process terminated by a POSIX termination signal can report `"exitStatus": { "signal": "SIGTERM" }`. diff --git a/docs/rfds/session-compaction.mdx b/docs/rfds/session-compaction.mdx index cd04a9d1c..1eb367f08 100644 --- a/docs/rfds/session-compaction.mdx +++ b/docs/rfds/session-compaction.mdx @@ -133,7 +133,9 @@ The update has three optional, nullable patch fields: rules. For each patch field, omission leaves the stored value unchanged, `null` -clears it, and a concrete value replaces it. On a first-seen ID, omission and +clears it, and a concrete value replaces it. In v2, a concrete `_meta` object +instead patches the stored metadata by top-level key, as defined by +[v2 Metadata Patches](/rfds/v2/meta-patches). On a first-seen ID, omission and `null` both start with no value. For `summary`, both `null` and `[]` clear the current content. @@ -345,3 +347,6 @@ correlate events heuristically rather than updating one ID-addressed entity. artifacts, removed the unstable Cargo feature, expanded schema regression coverage, and clarified receive-order timeline placement and summary patch semantics independent of compaction status. +- 2026-10-13: In v2, a concrete `_meta` object patches stored metadata by key, + per [v2 Metadata Patches](/rfds/v2/meta-patches). v1 still replaces the whole + object. diff --git a/docs/rfds/subagents.mdx b/docs/rfds/subagents.mdx index 19bc9d091..fc6a6b826 100644 --- a/docs/rfds/subagents.mdx +++ b/docs/rfds/subagents.mdx @@ -134,7 +134,9 @@ Only `update.sessionId` is required: In both versions, these optional fields are nullable patches: omission means unchanged, `null` clears the field, and a concrete value replaces the whole -previous field. Clearing `title` or `description` removes the parent's display +previous field. The exception is a concrete `_meta` object in v2, which patches +the stored metadata by top-level key as defined by +[v2 Metadata Patches](/rfds/v2/meta-patches). Clearing `title` or `description` removes the parent's display override. Clearing `state` leaves current activity unconfirmed, not idle. Both `capabilities: null` and `capabilities: {}` disable Client-initiated mutations. @@ -286,8 +288,10 @@ missing participants do not prevent displaying the message. Whole-message updates use the same patch convention in both versions: omitted `content` and `_meta` leave the stored value unchanged, `null` clears -the field, and concrete values replace the whole field. `content: []` also -clears accumulated content. New messages start with empty content and no +the field, and concrete values replace the whole field. The exception is a +concrete `_meta` object in v2, which patches the stored metadata by top-level +key as defined by [v2 Metadata Patches](/rfds/v2/meta-patches). `content: []` +also clears accumulated content. New messages start with empty content and no metadata when those fields are omitted. Chunk `_meta` is not a message patch: omitted or `null` means no metadata for that chunk. @@ -1209,6 +1213,10 @@ important difference from a user-facing session. ## Revision history +- 2026-10-13: In v2, a concrete top-level `_meta` object on `subagent_update` + and `session_message` patches stored metadata by key, per + [v2 Metadata Patches](/rfds/v2/meta-patches). v1 still replaces the whole + object. - 2026-10-05: Limited the cancellation cascade to the descendant work that the cancelled work waits on, leaving other descendants, such as background children, to the Agent. Clients clean up a descendant's pending requests and diff --git a/docs/rfds/v2/message-updates.mdx b/docs/rfds/v2/message-updates.mdx index 867798fbf..346ccea50 100644 --- a/docs/rfds/v2/message-updates.mdx +++ b/docs/rfds/v2/message-updates.mdx @@ -50,6 +50,7 @@ Each whole-message update is an upsert keyed by `messageId`: - Any concrete value replaces the previous value. - `content` is replaced as a whole array, not appended. - `[]` and `null` both clear `content`. +- A `_meta` object patches the stored metadata by top-level key instead of replacing it, as defined by [Metadata Patches](./meta-patches.mdx). - For a new `messageId`, omitted fields use Client defaults. The matching chunk updates remain: @@ -165,4 +166,5 @@ The Client appends those chunks to the message for that `messageId`. A later mes ## Revision history +- 2026-10-13: A top-level `_meta` object now patches stored metadata by key, per [Metadata Patches](./meta-patches.mdx). - 2026-06-09: Initial draft. diff --git a/docs/rfds/v2/meta-patches.mdx b/docs/rfds/v2/meta-patches.mdx new file mode 100644 index 000000000..c861e09e3 --- /dev/null +++ b/docs/rfds/v2/meta-patches.mdx @@ -0,0 +1,173 @@ +--- +title: "v2 Metadata Patches" +--- + +Author(s): [@benbrandt](https://github.com/benbrandt) + +## Elevator pitch + +> What are you proposing to change? + +ACP v2 upsert updates should apply their top-level `_meta` as a patch one level down. Omitted `_meta` leaves the stored metadata unchanged and `null` clears it, as today. A `_meta` object patches the stored metadata by top-level key instead of replacing it: an omitted key is unchanged, a key set to `null` is removed, and any other value replaces that key whole. + +## Status quo + +> How do things work today and what problems does this cause? Why would we change things? + +v2 upserts such as [`tool_call_update`](./tool-call-updates.mdx), [`terminal_update`](./terminal-output.mdx), and [message updates](./message-updates.mdx) treat `_meta` like every other patch field: omission leaves it unchanged, `null` clears it, and a concrete object replaces the whole stored object. + +Unlike the other patch fields, `_meta` has more than one writer. Its keys are usually namespaced by the extension that owns them, such as `zed.dev/debugMode`, and some are shared conventions such as W3C trace context. Replacing the whole object makes every writer responsible for every other writer's keys: + +- An Agent that sends different keys on different reports loses the earlier keys at each later report. v1 gave no rule for keeping a `tool_call_update`'s `_meta`, so v1 Agents that do this lose data after migrating, and the v2 migration guide had to tell them to resend every key on every report. +- An intermediary, such as a proxy, that adds its own key to an update the Agent sent without `_meta` turns "unchanged" into "replace" and erases the Agent's stored metadata. To avoid that, it must track the stored metadata of every entity it forwards and resend the merged object. +- Changing one key requires resending all of the others. + +## What we propose to do about it + +> What are you proposing to improve the situation? + +For the top-level `_meta` of v2 upsert updates: + +- Omitted `_meta` leaves the stored metadata unchanged. +- `_meta: null` clears all stored metadata. +- A `_meta` object patches the stored metadata by top-level key: + - Keys the object omits keep their stored values, so `_meta: {}` changes nothing. + - A key set to `null` is removed. Removing a key that is not stored does nothing. + - Any other value replaces that key's stored value whole. Clients **MUST NOT** merge nested objects, and a `null` nested inside the value is ordinary data. +- A key set to `null` never holds stored data, including on the first update for an entity. +- Stored metadata with no keys is the same as no metadata. + +This is the three-state rule the update already applies to its own fields, applied one level down. It matches [JSON Merge Patch (RFC 7386)](https://www.rfc-editor.org/rfc/rfc7386) applied only to the top level of `_meta`. A JSON Merge Patch library merges nested objects recursively, so implementations cannot use one unmodified. + +The rule applies to the top-level `_meta` of: + +- `tool_call_update`, including the `toolCall` of a `session/request_permission` `tool_call` subject +- `terminal_update` +- `user_message`, `agent_message`, and `agent_thought` +- `session_info_update` +- `compaction_update` +- the draft-only `subagent_update` and `session_message` + +It does not apply to `_meta` anywhere else: requests, responses, notification params, chunks, content items, terminal output snapshots, exit status, command permission subjects, and other nested objects keep their current meaning, where the metadata describes only the object that carries it. Data about a single notification rather than the entity belongs in the `session/update` notification's `params._meta`. + +For example, if a tool call's stored `_meta` is: + +```json +{ + "zed.dev/progress": 0.5, + "acme.dev/review": { "state": "pending" } +} +``` + +and a later update sends: + +```json +{ + "sessionUpdate": "tool_call_update", + "toolCallId": "call_001", + "_meta": { + "zed.dev/progress": null, + "acme.dev/review": { "reviewer": "sam" }, + "proxy.dev/route": "edge" + } +} +``` + +the stored `_meta` becomes: + +```json +{ + "acme.dev/review": { "reviewer": "sam" }, + "proxy.dev/route": "edge" +} +``` + +### Replay and resynchronization + +An object is no longer a complete snapshot of an entity's metadata, so replay needs explicit rules: + +- Replay applies metadata patches exactly as live delivery does, in receive order for each entity, and never resets metadata implicitly. An update that replaces another field, such as a message update with `content`, leaves the `_meta` keys it does not mention. +- Replaying the original updates in order reproduces the stored metadata, provided their removals are kept. +- To send an entity's current metadata when the Client may already hold some, the Agent removes stale keys. It either sets each one to `null` alongside the current keys, or sends `_meta: null` and then the current object in a later update. The second form is not atomic, even within a JSON-RPC batch, so the Client may briefly hold no metadata. +- When the current metadata is empty, the Agent sends `_meta: null`, not `{}`. +- When the Client is known to hold no metadata for the entity, the current object alone is enough. +- Intermediaries that combine updates **MUST** keep removals and clears. Replacing a clear followed by an object with just that object is wrong when earlier metadata may exist. + +## Shiny future + +> How will things play out once this feature exists? + +Each extension owns its top-level keys. It can set, replace, or remove them without knowing which other keys exist, and each update to its own key replaces that key's value in a single step. Proxies and other intermediaries can annotate forwarded updates without tracking entity state. Agents can change one key without resending the rest, and sending different keys on different reports no longer loses data. + +The cost is that replay and resynchronization must remove stale keys explicitly, and replacing all metadata in one update requires knowing which keys to remove. + +## Implementation details and plan + +> Tell me more about your implementation. What is your detailed implementation plan? + +1. Keep the schema shape. These upserts already represent `_meta` as a three-state field, and a `_meta` object can already carry `null` values. +2. Define the rule, including replay, once in the v2 Extensibility page under [Metadata Patches](/protocol/v2/draft/extensibility#metadata-patches), and reference it from the tool call, terminal, message, session info, compaction, and session replay docs, the schema field descriptions, and the migration guide. +3. Add `apply_meta_update` to the Rust v2 schema module and use it in `ToolCallUpdate::apply_update` and `TerminalUpdate::apply_update`. +4. Leave v1 unchanged, including the v1 `session_message`, `subagent_update`, and `compaction_update` updates that replace `_meta` as a whole. + +### Conformance cases + +SDKs that store entity state should cover at least these cases: + +| Stored `_meta` | Incoming `_meta` | Result | +| ------------------------- | -------------------- | -------------------- | +| `{"a": 1, "b": 2}` | omitted | `{"a": 1, "b": 2}` | +| `{"a": 1, "b": 2}` | `null` | no metadata | +| `{"a": 1, "b": 2}` | `{}` | `{"a": 1, "b": 2}` | +| `{"a": 1, "b": 2}` | `{"a": 3}` | `{"a": 3, "b": 2}` | +| `{"a": 1, "b": 2}` | `{"a": null}` | `{"b": 2}` | +| no metadata | `{"a": null}` | no metadata | +| `{"a": {"x": 1, "y": 2}}` | `{"a": {"x": 3}}` | `{"a": {"x": 3}}` | +| `{"a": [1, 2]}` | `{"a": [3]}` | `{"a": [3]}` | +| no metadata | `{"a": {"x": null}}` | `{"a": {"x": null}}` | + +They should also cover unknown keys, a clear followed by an object, and replay over stale metadata. + +### Compatibility + +When converting v1 to v2, a v1 `_meta` object can be forwarded as is when the v1 source defines no replacement rule, as with `tool_call_update` and `session_info_update`; its keys now merge with earlier ones, and a key set to `null` becomes a removal, the closest v2 equivalent of storing `null`. Where v1 replaces `_meta` as a whole, an adapter that must preserve that behavior needs the previously forwarded keys so it can send `null` for each key that disappeared. Alternatively, it can send `_meta: null` before the new object. + +When converting v2 to v1, a v1 receiver may replace the whole stored object and may store `null` values literally. A stateful adapter can keep the merged metadata for each entity and send the complete merged object. A stateless adapter cannot represent key-level patches and must document that it is lossy for `_meta`. + +Within v2, an implementation must not copy metadata that is not a patch, such as a `session/list` entry's `_meta`, into a patch unchanged: its `null` key values would become removals. + +## Frequently asked questions + +> What questions have arisen over the course of authoring this document or during subsequent discussions? + +### Why not merge nested objects too? + +The top-level key is the unit of ownership. Below it, the extension that owns the key defines what its value means. A recursive merge would impose merge semantics on values the protocol otherwise treats as opaque, prevent an extension from replacing its own object in one update, and make `null` inside an extension's value mean removal. Stopping at the top level isolates writers from each other while leaving each value to its owner. + +### Why can't a key store a literal `null`? + +`null` is the removal signal, so storing it would require a second representation of removal. This is the same trade-off `rawInput` and `rawOutput` already make on tool calls, and the one JSON Merge Patch makes. An extension that needs a distinct empty value can use `false`, `{}`, or a value of its own. + +### How does a sender replace all metadata at once? + +It sets each key it wants to remove to `null` alongside the new keys in a single update. The original sender usually knows which keys it sent earlier. Otherwise it can send `_meta: null` followed by an update with the new object, accepting that the Client briefly has no metadata. + +### Why not keep whole replacement and ask writers to resend every key? + +That works only when a single writer controls every key and remembers them all. `_meta` is shared by extensions and intermediaries that do not know each other's keys, which is why it was the one patch field that needed a warning in the migration guide. + +### Does this resolve conflicts between writers? + +No. It keeps writers of different keys from erasing each other's data. Two writers of the same key still depend on receive order, and `_meta: null` still clears every key. + +### Why isn't `plan_update` included? + +`plan_update` replaces a plan's entries as a whole, and its `_meta` does not distinguish omission from `null`. Whether that metadata belongs to the plan or only to the notification is a separate decision. Making it a retained patch would need its own change. + +### Does this change `params._meta` on `session/update`? + +No. Notification-level metadata is not stored with an entity and is not a patch. It remains the place for data about a single notification, such as trace context. + +## Revision history + +- 2026-10-13: Initial draft. diff --git a/docs/rfds/v2/overview.mdx b/docs/rfds/v2/overview.mdx index 804420aba..8e232862a 100644 --- a/docs/rfds/v2/overview.mdx +++ b/docs/rfds/v2/overview.mdx @@ -40,6 +40,7 @@ Current RFDs with active maintainer focus that are targeting the v2 release: - [Session Resume Replay](./session-resume-replay.mdx) - [Diff File States](./diff-file-states.mdx) - [Terminal Output](./terminal-output.mdx) +- [Metadata Patches](./meta-patches.mdx) Other RFDs will progress separately and are not dependent on breaking changes (specifically the new prompt lifecycle) and can land in either or both v1 and v2. @@ -55,6 +56,7 @@ Other RFDs will progress separately and are not dependent on breaking changes (s - Replace the v1 `Diff` `oldText` / `newText` shape with [diff file states](./diff-file-states.mdx): optional Git `--patch` text (`git_patch`) plus structured file operations for add, delete, modify, move, copy, and non-text changes. - Make [permission requests](./permission-requests.mdx) carry a required prompt `title` and optional extensible `subject` tagged union. Tool-call permissions use `subject.type: "tool_call"` with the same `ToolCallUpdate` payload shape as session updates, command permissions use `subject.type: "command"` with the command, required absolute working directory, and optional display associations, and subject-less permissions rely on the common prompt fields. - Add [whole-message updates](./message-updates.mdx) for `user_message`, `agent_message`, and `agent_thought` alongside streamed chunks. Message updates are upserts keyed by `messageId`; their `content` arrays replace current message content, while chunks append to the current content. +- Apply the top-level `_meta` of upsert updates as a [metadata patch](./meta-patches.mdx): omission leaves it unchanged, `null` clears it, and an object patches stored metadata by top-level key, where a key set to `null` is removed and any other value replaces that key whole. - Require [message IDs](../message-id.mdx) on streamed message chunks. - Acknowledge [prompt insertion](./prompt.mdx) by returning the inserted user message's `messageId` from `session/prompt`, without waiting for foreground work to finish. Live user-message updates use the same ID; retention remains optional. - Report [failures after prompt insertion](./prompt.mdx#report-failures-after-insertion) by ending foreground work with an idle `state_update` whose `stopReason` is `error`, carrying an optional JSON-RPC error object. A JSON-RPC error on `session/prompt` always means the message was not inserted. @@ -112,6 +114,7 @@ With the needed breaking changes, as much as possible I am targeting having a co ## Revision history +- 2026-10-13: Added the v2 Metadata Patches RFD so a top-level `_meta` object on an upsert patches stored metadata by key instead of replacing it. - 2026-10-01: Recorded that failures after prompt insertion end foreground work with an `error` stop reason that carries an optional JSON-RPC error. - 2026-10-12: Specified that every `git_patch` path, including rename and copy headers, is absolute with no `a/` or `b/` prefix, and otherwise follows Git, including `/dev/null` and path quoting. - 2026-07-20: Clarified `git_patch` and renamed its payload from `diff` to `text`. diff --git a/docs/rfds/v2/session-resume-replay.mdx b/docs/rfds/v2/session-resume-replay.mdx index 906b189ac..99538d802 100644 --- a/docs/rfds/v2/session-resume-replay.mdx +++ b/docs/rfds/v2/session-resume-replay.mdx @@ -92,6 +92,11 @@ restore methods. 2. Add `replayFrom` to `ResumeSessionRequest` as an optional tagged union. 3. Add the initial `replayFrom` variant `{ "type": "start" }`. +Replay applies top-level `_meta` as a [metadata patch](./meta-patches.mdx), as +live delivery does, and does not reset it. When the Client may already hold +metadata for an entity, the Agent removes stale keys by setting them to `null`, +or sends `_meta: null` before the current object. + ## Frequently asked questions > What questions have arisen over the course of authoring this document or during subsequent discussions? @@ -107,4 +112,5 @@ An object leaves room for future cursor forms that need fields. The initial ## Revision history +- 2026-10-13: Replay applies top-level `_meta` as a key-level patch, per [Metadata Patches](./meta-patches.mdx). - 2026-07-02: Initial draft. diff --git a/docs/rfds/v2/terminal-output.mdx b/docs/rfds/v2/terminal-output.mdx index 4551bf0e9..b4b766df3 100644 --- a/docs/rfds/v2/terminal-output.mdx +++ b/docs/rfds/v2/terminal-output.mdx @@ -80,7 +80,7 @@ The patch fields are: - `cwd`: the command's working directory. Every supplied path **MUST** be absolute. - `output`: an authoritative replacement snapshot containing required `data`. `data` is RFC 4648 base64-encoded bytes and has JSON Schema `contentEncoding: "base64"`. A concrete snapshot is the complete byte sequence the Agent wants the Client to retain for the terminal at that point. It replaces all previously stored bytes, and Clients **MUST NOT** merge or splice bytes from the previous snapshot into it. Later chunks append to the replacement. Optional nullable `output._meta` is scoped to that snapshot; omission and `null` are equivalent and mean no snapshot metadata was provided. - `exitStatus`: a concrete object marks the terminal as exited. Its `exitCode`, `signal`, and `_meta` fields are optional and nullable; omission and `null` are equivalent and mean that value was not provided. Agents should use conventional platform signal names. POSIX examples include `SIGTERM`, `SIGKILL`, and `SIGINT`; other platforms may use platform-specific names. `exitStatus._meta` is scoped to that exit information. `exitStatus: null` clears a previously reported exit status. -- The top-level `_meta` is terminal-scoped and follows the same omitted, `null`, and concrete-object patch semantics as the other update fields. +- The top-level `_meta` is terminal-scoped. Omission and `null` follow the same patch semantics as the other update fields, and a concrete object patches the stored metadata by top-level key, as defined by [Metadata Patches](./meta-patches.mdx). The terminal's exit status is independent of its tool call's status. Agents should update both when both states change. @@ -187,4 +187,5 @@ Display-only terminal output has a small safe fallback: decode the bytes, saniti ## Revision history +- 2026-10-13: A top-level `_meta` object now patches stored metadata by key, per [Metadata Patches](./meta-patches.mdx). - 2026-07-14: Initial draft. diff --git a/docs/rfds/v2/tool-call-updates.mdx b/docs/rfds/v2/tool-call-updates.mdx index 632831a25..d3a3815d5 100644 --- a/docs/rfds/v2/tool-call-updates.mdx +++ b/docs/rfds/v2/tool-call-updates.mdx @@ -53,7 +53,7 @@ The v2 `ToolCallUpdate` payload has the following semantics: - Any concrete value replaces the previous value. - `content` and `locations` are replaced as whole arrays, not appended. - `[]` and `null` both clear a collection field. -- `_meta` preserves omitted, `null`, and object values as distinct update signals. +- `_meta` preserves omitted, `null`, and object values as distinct update signals. An object patches the stored metadata by top-level key, as defined by [Metadata Patches](./meta-patches.mdx). - For a new `toolCallId`, omitted fields use Client defaults. - Agents **SHOULD** include `title` the first time they report a `toolCallId`. @@ -118,6 +118,7 @@ Tool-call updates need both operations. Omission means "I am not changing this f ## Revision history +- 2026-10-13: A top-level `_meta` object now patches stored metadata by key, per [Metadata Patches](./meta-patches.mdx). - 2026-07-14: Distinguished tool-call content item chunks from terminal byte-stream chunks. - 2026-07-02: Updated permission requests to carry tool-call updates through an optional `subject` tagged union and to use separate `title` and `description` fields for permission-specific prompt text. - 2026-06-09: Added `tool_call_content_chunk` for streaming tool-call content. diff --git a/schema/v2/schema.json b/schema/v2/schema.json index 7042e3434..4103c569f 100644 --- a/schema/v2/schema.json +++ b/schema/v2/schema.json @@ -672,7 +672,7 @@ ] }, "ToolCallUpdate": { - "description": "Represents an upsert for a tool call that the language model has requested.\n\nTool calls are actions that the agent executes on behalf of the language model,\nsuch as reading files, executing code, or fetching data from external sources.\n\nOnly [`ToolCallUpdate::tool_call_id`] is required. Other fields have patch semantics:\nomitted fields leave the existing tool call value unchanged, `null` clears or\nunsets the value, and concrete values replace the previous value. For\ncollection fields, concrete arrays replace the previous collection, and both\n`null` and `[]` clear the collection. When a client receives a tool call ID it\nhas not seen before, omitted fields use client defaults.\n\nSee protocol docs: [Tool Calls](https://agentclientprotocol.com/protocol/v2/tool-calls)", + "description": "Represents an upsert for a tool call that the language model has requested.\n\nTool calls are actions that the agent executes on behalf of the language model,\nsuch as reading files, executing code, or fetching data from external sources.\n\nOnly [`ToolCallUpdate::tool_call_id`] is required. Other fields have patch semantics:\nomitted fields leave the existing tool call value unchanged, `null` clears or\nunsets the value, and concrete values replace the previous value. For\ncollection fields, concrete arrays replace the previous collection, and both\n`null` and `[]` clear the collection. A concrete `_meta` object instead\npatches the stored metadata by top-level key. When a client receives a tool\ncall ID it has not seen before, omitted fields use client defaults.\n\nSee protocol docs: [Tool Calls](https://agentclientprotocol.com/protocol/v2/tool-calls)", "type": "object", "properties": { "toolCallId": { @@ -744,7 +744,7 @@ "x-deserialize-default-on-error": true }, "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted means no metadata update; `null` is an\nexplicit clear signal. Implementations MUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true @@ -4976,7 +4976,7 @@ "x-deserialize-skip-invalid-items": true }, "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys. Omitted means no metadata update; `null` is an explicit clear signal.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true @@ -5006,7 +5006,7 @@ "x-deserialize-skip-invalid-items": true }, "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys. Omitted means no metadata update; `null` is an explicit clear signal.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true @@ -5036,7 +5036,7 @@ "x-deserialize-skip-invalid-items": true }, "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys. Omitted means no metadata update; `null` is an explicit clear signal.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true @@ -5435,7 +5435,7 @@ } }, "TerminalUpdate": { - "description": "An upsert for the stored state of an agent-owned terminal.\n\nOnly [`TerminalUpdate::terminal_id`] is required. Other fields have patch\nsemantics: omitted fields leave the stored value unchanged, `null` clears\nit, and concrete values replace it. When the terminal ID is new, omitted\nfields start unknown.", + "description": "An upsert for the stored state of an agent-owned terminal.\n\nOnly [`TerminalUpdate::terminal_id`] is required. Other fields have patch\nsemantics: omitted fields leave the stored value unchanged, `null` clears\nit, and concrete values replace it. A concrete `_meta` object instead\npatches the stored metadata by top-level key. When the terminal ID is new,\nomitted fields start unknown.", "type": "object", "properties": { "terminalId": { @@ -5488,7 +5488,7 @@ "x-deserialize-default-on-error": true }, "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted means no metadata update; `null` is an\nexplicit clear signal. Implementations MUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true @@ -5766,7 +5766,7 @@ "required": ["configOptions"] }, "SessionInfoUpdate": { - "description": "Update to session metadata. All fields are optional to support partial updates.\n\nAgents send this notification to update session information like title or custom metadata.\nThis allows clients to display dynamic session names and track session state changes.\n\nOmitted fields leave the existing session info unchanged. `null` clears the\ncorresponding value.", + "description": "Update to session metadata. All fields are optional to support partial updates.\n\nAgents send this notification to update session information like title or custom metadata.\nThis allows clients to display dynamic session names and track session state changes.\n\nOmitted fields leave the existing session info unchanged. `null` clears the\ncorresponding value. A concrete `_meta` object patches the stored metadata by\ntop-level key.", "type": "object", "properties": { "title": { @@ -5781,7 +5781,7 @@ "x-deserialize-default-on-error": true }, "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted means no metadata update; `null` is an\nexplicit clear signal. Implementations MUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true @@ -5939,7 +5939,7 @@ ] }, "CompactionUpdate": { - "description": "A context compaction upsert. The first notification fixes the compaction's\ntimeline position. Later updates with the same ID patch that entity in place.\n\n`summary`, `error`, and `_meta` have patch semantics: omission leaves the\nstored value unchanged, `null` clears it, and a concrete value replaces it.\n`summary: []` also clears the summary.", + "description": "A context compaction upsert. The first notification fixes the compaction's\ntimeline position. Later updates with the same ID patch that entity in place.\n\n`summary`, `error`, and `_meta` have patch semantics: omission leaves the\nstored value unchanged, `null` clears it, and a concrete value replaces it.\n`summary: []` also clears the summary. A concrete `_meta` object patches the\nstored metadata by top-level key instead of replacing it.", "type": "object", "properties": { "compactionId": { @@ -5973,7 +5973,7 @@ "x-deserialize-default-on-error": true }, "_meta": { - "description": "Extensible metadata patch for this compaction.", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true diff --git a/schema/v2/schema.unstable.json b/schema/v2/schema.unstable.json index e8f5a0696..14e900412 100644 --- a/schema/v2/schema.unstable.json +++ b/schema/v2/schema.unstable.json @@ -753,7 +753,7 @@ ] }, "ToolCallUpdate": { - "description": "Represents an upsert for a tool call that the language model has requested.\n\nTool calls are actions that the agent executes on behalf of the language model,\nsuch as reading files, executing code, or fetching data from external sources.\n\nOnly [`ToolCallUpdate::tool_call_id`] is required. Other fields have patch semantics:\nomitted fields leave the existing tool call value unchanged, `null` clears or\nunsets the value, and concrete values replace the previous value. For\ncollection fields, concrete arrays replace the previous collection, and both\n`null` and `[]` clear the collection. When a client receives a tool call ID it\nhas not seen before, omitted fields use client defaults.\n\nSee protocol docs: [Tool Calls](https://agentclientprotocol.com/protocol/v2/draft/tool-calls)", + "description": "Represents an upsert for a tool call that the language model has requested.\n\nTool calls are actions that the agent executes on behalf of the language model,\nsuch as reading files, executing code, or fetching data from external sources.\n\nOnly [`ToolCallUpdate::tool_call_id`] is required. Other fields have patch semantics:\nomitted fields leave the existing tool call value unchanged, `null` clears or\nunsets the value, and concrete values replace the previous value. For\ncollection fields, concrete arrays replace the previous collection, and both\n`null` and `[]` clear the collection. A concrete `_meta` object instead\npatches the stored metadata by top-level key. When a client receives a tool\ncall ID it has not seen before, omitted fields use client defaults.\n\nSee protocol docs: [Tool Calls](https://agentclientprotocol.com/protocol/v2/draft/tool-calls)", "type": "object", "properties": { "toolCallId": { @@ -825,7 +825,7 @@ "x-deserialize-default-on-error": true }, "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted means no metadata update; `null` is an\nexplicit clear signal. Implementations MUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true @@ -6486,7 +6486,7 @@ "x-deserialize-skip-invalid-items": true }, "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys. Omitted means no metadata update; `null` is an explicit clear signal.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true @@ -6516,7 +6516,7 @@ "x-deserialize-skip-invalid-items": true }, "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys. Omitted means no metadata update; `null` is an explicit clear signal.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true @@ -6546,7 +6546,7 @@ "x-deserialize-skip-invalid-items": true }, "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys. Omitted means no metadata update; `null` is an explicit clear signal.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true @@ -7047,7 +7047,7 @@ } }, "TerminalUpdate": { - "description": "An upsert for the stored state of an agent-owned terminal.\n\nOnly [`TerminalUpdate::terminal_id`] is required. Other fields have patch\nsemantics: omitted fields leave the stored value unchanged, `null` clears\nit, and concrete values replace it. When the terminal ID is new, omitted\nfields start unknown.", + "description": "An upsert for the stored state of an agent-owned terminal.\n\nOnly [`TerminalUpdate::terminal_id`] is required. Other fields have patch\nsemantics: omitted fields leave the stored value unchanged, `null` clears\nit, and concrete values replace it. A concrete `_meta` object instead\npatches the stored metadata by top-level key. When the terminal ID is new,\nomitted fields start unknown.", "type": "object", "properties": { "terminalId": { @@ -7100,7 +7100,7 @@ "x-deserialize-default-on-error": true }, "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted means no metadata update; `null` is an\nexplicit clear signal. Implementations MUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true @@ -7502,7 +7502,7 @@ "required": ["configOptions"] }, "SessionInfoUpdate": { - "description": "Update to session metadata. All fields are optional to support partial updates.\n\nAgents send this notification to update session information like title or custom metadata.\nThis allows clients to display dynamic session names and track session state changes.\n\nOmitted fields leave the existing session info unchanged. `null` clears the\ncorresponding value.", + "description": "Update to session metadata. All fields are optional to support partial updates.\n\nAgents send this notification to update session information like title or custom metadata.\nThis allows clients to display dynamic session names and track session state changes.\n\nOmitted fields leave the existing session info unchanged. `null` clears the\ncorresponding value. A concrete `_meta` object patches the stored metadata by\ntop-level key.", "type": "object", "properties": { "title": { @@ -7517,7 +7517,7 @@ "x-deserialize-default-on-error": true }, "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted means no metadata update; `null` is an\nexplicit clear signal. Implementations MUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true @@ -7675,7 +7675,7 @@ ] }, "CompactionUpdate": { - "description": "A context compaction upsert. The first notification fixes the compaction's\ntimeline position. Later updates with the same ID patch that entity in place.\n\n`summary`, `error`, and `_meta` have patch semantics: omission leaves the\nstored value unchanged, `null` clears it, and a concrete value replaces it.\n`summary: []` also clears the summary.", + "description": "A context compaction upsert. The first notification fixes the compaction's\ntimeline position. Later updates with the same ID patch that entity in place.\n\n`summary`, `error`, and `_meta` have patch semantics: omission leaves the\nstored value unchanged, `null` clears it, and a concrete value replaces it.\n`summary: []` also clears the summary. A concrete `_meta` object patches the\nstored metadata by top-level key instead of replacing it.", "type": "object", "properties": { "compactionId": { @@ -7709,7 +7709,7 @@ "x-deserialize-default-on-error": true }, "_meta": { - "description": "Extensible metadata patch for this compaction.", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true @@ -7783,7 +7783,7 @@ } }, "SubagentUpdate": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nNotification that the enclosing parent session created and owns a child session.\n\nLater updates modify the existing association's metadata, not its ownership.\n\nSent on the immediate parent session. The first update for an unknown\n[`SubagentUpdate::session_id`] announces the child and MUST be sent\nbefore any live request or notification bearing the child's session ID,\nincluding messages naming it as sender or recipient. Child events are\ndelivered automatically; no separate child load, resume, or subscription is needed.\nUnderstanding this update, registering child sessions, and applying their\noperation restrictions are baseline v2 requirements; no Client capability\nis required.\n\nOnly [`SubagentUpdate::session_id`] is required. Other fields have\npatch semantics: omitted fields leave the stored value unchanged, `null`\nclears or unsets the value, and concrete values replace it. For `state`,\n`null` removes the current state report and leaves activity unconfirmed;\nit does not report idle or cancellation. A child whose capabilities are\nunset permits no Client-initiated session mutations.\n\nThe title and description provide the parent's display metadata for the\nchild. Agents SHOULD mirror known child state changes in the parent's\n[`SubagentUpdate::state`] using the same [`StateUpdate`] snapshot as the\nordinary `state_update` notification on the child session. This reports\nchild state, not a separate parent-owned lifecycle. Consumers should\ntreat duplicate reports idempotently.\nCompleting or cancelling work does not end the association: the parent may\nmessage the same child again. Individual messages and their outcomes do not\nchange this association.", + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nNotification that the enclosing parent session created and owns a child session.\n\nLater updates modify the existing association's metadata, not its ownership.\n\nSent on the immediate parent session. The first update for an unknown\n[`SubagentUpdate::session_id`] announces the child and MUST be sent\nbefore any live request or notification bearing the child's session ID,\nincluding messages naming it as sender or recipient. Child events are\ndelivered automatically; no separate child load, resume, or subscription is needed.\nUnderstanding this update, registering child sessions, and applying their\noperation restrictions are baseline v2 requirements; no Client capability\nis required.\n\nOnly [`SubagentUpdate::session_id`] is required. Other fields have\npatch semantics: omitted fields leave the stored value unchanged, `null`\nclears or unsets the value, and concrete values replace it. A concrete\n`_meta` object instead patches the stored metadata by top-level key. For\n`state`, `null` removes the current state report and leaves activity\nunconfirmed; it does not report idle or cancellation. A child whose capabilities are\nunset permits no Client-initiated session mutations.\n\nThe title and description provide the parent's display metadata for the\nchild. Agents SHOULD mirror known child state changes in the parent's\n[`SubagentUpdate::state`] using the same [`StateUpdate`] snapshot as the\nordinary `state_update` notification on the child session. This reports\nchild state, not a separate parent-owned lifecycle. Consumers should\ntreat duplicate reports idempotently.\nCompleting or cancelling work does not end the association: the parent may\nmessage the same child again. Individual messages and their outcomes do not\nchange this association.", "type": "object", "properties": { "sessionId": { @@ -7829,7 +7829,7 @@ "x-deserialize-default-on-error": true }, "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted means no metadata update; `null` is an\nexplicit clear signal. Implementations MUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true @@ -7883,7 +7883,7 @@ "x-deserialize-skip-invalid-items": true }, "_meta": { - "description": "Omitted leaves metadata unchanged; `null` clears it.\n\nImplementations MUST NOT make assumptions about values in `_meta`.", + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged; `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT make assumptions about values at these keys.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/draft/extensibility#metadata-patches)", "type": ["object", "null"], "x-deserialize-default-on-error": true, "additionalProperties": true