Skip to content

A2A: propagate request and message metadata through the HTTP→bus bridge #281

@rockfordlhotka

Description

@rockfordlhotka

Context

Second-consumer feedback from Foragent (see Foragent project spec §8.3 — expected framework push area is A2A server-side primitives).

The gap

The A2A v1-preview protocol carries structured input on two paths the RockBot bridge doesn't propagate today:

  1. Request-level metadata — `SendMessageRequest.Metadata` (`Dictionary<string, JsonElement>`). `RockBotBridgeHandler` reads one key (`"skill"`) and drops the rest (`src/RockBot.A2A.Gateway/RockBotBridgeHandler.cs:44-46`).
  2. Message-level metadata — `Message.Metadata` (`Dictionary<string, JsonElement>`). The bridge builds a fresh `RockBot.A2A.AgentMessage` from only the `UserText` field (`src/RockBot.A2A.Gateway/RockBotBridgeHandler.cs:124-134`), and `RockBot.A2A.AgentMessage` itself has no `Metadata` property to carry the values.

Net effect: any structured input a caller attaches to an A2A request — URLs, identifiers, per-task configuration — is lost before the handler sees it. Handlers today only receive what fits into a single text blob.

Concrete Foragent case

Foragent's `extract-structured-data` capability wants two distinct inputs per call: (a) the URL to navigate to, (b) a natural-language description of what to extract. The clean contract is "URL in metadata, description in text." Because metadata doesn't round-trip, Foragent is shipping a shim that packs both values into a single JSON blob in the text part — `{"url":"...","description":"..."}` — and parsing them capability-side. The shim is intentionally isolated behind a `CapabilityInput` helper so it can be swapped for real metadata access once this is resolved.

Expect the same tension to surface for every future Foragent capability that takes more than one input (form fields, per-task credential refs, selector hints).

Ask

  1. Add metadata to the RockBot A2A model.

    • `RockBot.A2A.AgentMessage.Metadata` — `IReadOnlyDictionary<string, string>?`
    • `RockBot.A2A.AgentTaskRequest.Metadata` — `IReadOnlyDictionary<string, string>?`
      (String values are sufficient for the common case; JSON elements are richer but bring serialization baggage. Either shape works for Foragent.)
  2. Pass metadata through the bridge.

    • Populate `AgentTaskRequest.Metadata` from `context.Metadata` (minus the `"skill"` key already consumed for routing).
    • Populate `AgentMessage.Metadata` from the incoming `Message.Metadata`.
    • On the way back, map handler-returned `AgentMessage.Metadata` onto outbound `Message.Metadata` in the bridge's reply path.
  3. Consider propagating non-text parts at the same time. `AgentMessagePart` already supports `Kind = "data"` with a `Data` / `MimeType` pair; the bridge should copy those through too so callers can attach structured content without encoding it as text.

Workaround in Foragent (for reference)

`src/Foragent.Capabilities/CapabilityInput.cs` — single helper that parses `{"url":"...","description":"..."}` out of the text part. When this issue ships, the helper switches to reading `request.Message.Metadata["url"]` and the capability contract stays stable.

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions