Skip to content

Proposal: Collection Attachments #362

Description

@kycutler

Summary

Add a MessageCollectionAttachment that represents multiple heterogeneous attachments as one logical, durable attachment.

This addresses cases where clients present one attachment but the model needs several pieces of context:

  • Browser element: structured text plus an inline screenshot.
  • Directory: directory resource plus eagerly attached images.
  • Future compound resources such as document bundles.

Today these pieces must be flattened into Message.attachments. After replay, clients cannot reliably reconstruct the original logical grouping and render every piece independently.

Proposed shape

export const enum MessageAttachmentKind {
	Simple = 'simple',
	EmbeddedResource = 'embeddedResource',
	Resource = 'resource',
	Annotations = 'annotations',
	Chat = 'chat',
	Collection = 'collection',
}

export type MessageCollectionItem =
	| SimpleMessageAttachment
	| MessageEmbeddedResourceAttachment
	| MessageResourceAttachment
	| MessageAnnotationsAttachment
	| MessageChatAttachment;

/**
 * Multiple attachments presented by the originating client as one logical item.
 *
 * The collection has no model representation of its own. Hosts flatten its
 * attachments when constructing model context, while preserving the collection
 * in durable message state for replay.
 */
export interface MessageCollectionAttachment extends MessageAttachmentBase {
	type: MessageAttachmentKind.Collection;

	/**
	 * Ordered attachments composing this logical item.
	 *
	 * Collections cannot contain other collections.
	 * This array MUST contain at least one attachment.
	 */
	attachments: MessageCollectionItem[];
}

export type MessageAttachment =
	| SimpleMessageAttachment
	| MessageEmbeddedResourceAttachment
	| MessageResourceAttachment
	| MessageAnnotationsAttachment
	| MessageChatAttachment
	| MessageCollectionAttachment;

Semantics

Durable state and presentation

  • The collection is stored in Message.attachments as a single item.
  • Clients SHOULD render it as one logical attachment using the collection’s label, displayKind, and _meta.
  • Clients MAY expose its constituent attachments in expanded or detailed UI.
  • Clients MUST NOT render its members as independent top-level attachments.

Model context

The collection itself has no model representation.

When accepting a message, hosts MUST process each member in order exactly as if it appeared directly in Message.attachments. This is a one-level flattening operation; collection attachments cannot nest.

This keeps AHP focused on durable presentation state while leaving provider-specific model translation to the host.

Ordering and ranges

  • Member order is significant and MUST be preserved.
  • Producers SHOULD place the message-text range on the collection.
  • A member’s range, when present, continues to describe that member.

Metadata

Collection and member metadata are independent:

  • Collection _meta describes the logical item.
  • Member _meta describes that particular model-facing attachment.
  • Hosts MUST preserve both without merging them.

Examples

Browser element

{
	"type": "collection",
	"label": "button#submit",
	"displayKind": "element",
	"attachments": [
		{
			"type": "simple",
			"label": "button#submit structure",
			"modelRepresentation": "Element: button#submit\n\n..."
		},
		{
			"type": "embeddedResource",
			"label": "button#submit screenshot",
			"displayKind": "image",
			"contentType": "image/jpeg",
			"data": "..."
		}
	]
}

Directory with resolved images

{
	"type": "collection",
	"label": "design-assets",
	"displayKind": "directory",
	"attachments": [
		{
			"type": "resource",
			"label": "design-assets",
			"displayKind": "directory",
			"uri": "file:///workspace/design-assets"
		},
		{
			"type": "resource",
			"label": "hero.png",
			"displayKind": "image",
			"uri": "file:///workspace/design-assets/hero.png"
		}
	]
}

Why collections are not recursive

A restricted member union makes cycles and unbounded nesting impossible by construction.

This follows the existing ContainerCustomizationBase / ChildCustomization pattern, where container customization kinds are excluded from the child union. It also avoids requiring runtime recursion rules like those needed for chat attachments.

Compatibility

An unknown collection variant cannot be ignored coherently because doing so also discards all contained model context. Therefore this should not be treated like an optional rendering-only variant.

Recommended rollout:

  1. Introduce it in a new negotiated protocol version.
  2. Hosts speaking an older version flatten collections into ordinary top-level attachments.
  3. Hosts speaking the new version preserve collections in durable state.
  4. If rollout across mixed-version clients requires finer gating, add an attachmentCollections client capability and flatten for clients that do not advertise it.

This preserves today’s degraded behavior—independent attachments—for older clients rather than losing the attachments entirely.

Alternatives considered

Correlation through _meta

Attach a shared UUID to otherwise independent attachments.

This works as an implementation-specific bridge, but clients must understand private metadata and grouping remains best-effort. Once grouping is required for interoperable replay, AHP doctrine recommends graduating it into typed state.

Group fields on flat attachments

Add a group identifier and repeated group metadata to each attachment.

This gives excellent old-client degradation, but loses atomicity, duplicates metadata, and requires clients to reconcile incomplete or inconsistent groups.

Add binary content to SimpleMessageAttachment

This covers text-plus-image but not general one-to-many cases such as directories or document bundles.

Implementation impact

Expected changes in microsoft/agent-host-protocol:

  • Add the kind, interfaces, and union membership in types/channels-chat/state.ts.
  • Document collection acceptance and replay semantics in docs/specification/chat-channel.md.
  • Add a changelog fragment under docs/.changes/.
  • Regenerate schemas and TypeScript, Rust, Kotlin, Swift, and Go clients.
  • Add schema/generator coverage.
  • No reducer changes should be required because existing message-bearing actions already preserve Message.attachments.

Relevant precedent

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions