AgentCall AG-UI extension 0.1
Public base profile and NFC/photo observations.
This public profile defines AgentCall discovery, sessions, text and voice turns, and WebSocket/WebRTC transport for independent agents, with optional NFC and photo observations. It publishes the shared base required for those physical inputs; other AgentCall extensions are outside this profile. The canonical A2A Agent Card extension URI is https://agentcall.smartycode.com/ext/agentcall-agui/0.1 . Agent implementations are outside this publication. No private repository access is needed to implement the messages below.
Version and migration
The extension path and params.schemaVersion: "0.1" identify this contract.
The observation and passport payloads also carry schemaVersion: "0.1".
The transport bindings remain agentcall-agui-ws-0.1 and
agentcall-webrtc-0.1. Additive optional fields may be introduced in 0.1;
removals, changed meanings, required fields, or tighter bounds require a new
versioned URI and a documented migration. Consumers pin the artifact
manifest's source commit and hashes. Published versioned artifacts are retained.
Clients and conformance accept the previous
https://agentcall.invalid/ext/agentcall-agui/0.1 identifier as a legacy alias.
Producers emit one identifier per card; duplicate recognized declarations are
rejected, including mixed canonical/legacy declarations. Existing private
producers may continue advertising the legacy URI. Rollback can restore the
previous client and legacy producer card without changing observation bytes.
An older client that does not recognize the public URI must report an
incompatibility; producers serving such clients retain the legacy card until
those clients upgrade. This release does not retire the alias.
Discovery
Serve a standard A2A 1.0 Agent Card at /.well-known/agent-card.json and add:
{
"uri": "https://agentcall.smartycode.com/ext/agentcall-agui/0.1",
"description": "AgentCall AG-UI extension 0.1: public base and physical observations",
"required": false,
"params": {
"schemaVersion": "0.1",
"agui": {
"url": "https://agent.example/agui",
"transport": "websocket",
"session": {"resume": false, "replay": false}
},
"voice": {"owner": "client"},
"physicalObservations": {
"accepted": ["nfc", "photo"],
"agentRequests": ["nfc", "photo"]
}
}
}
accepted and agentRequests are unique arrays of at most two modalities.
Every requested modality MUST also be accepted. Omission or an empty array
means unsupported. The declaration permits collection; it never grants
access to the camera, NFC hardware, persistent memory, or actions.
Use A2A supportedInterfaces only for actual A2A interfaces. The AG-UI
endpoint is advertised in this extension because its semantics differ from
A2A task transports. This extension does not change A2A request semantics;
an A2A-Extensions header does not negotiate the separate AG-UI connection.
AG-UI capability negotiation happens in session.start below. Official A2A
conformance belongs to the A2A TCK, not these AgentCall-specific fixtures.
Unknown required card extensions are enrollment incompatibilities.
Physical inputs are supported only on WebSocket or the exact
agentcall-webrtc-0.1 profile. Cards advertising NFC/photo on
openai-realtime-webrtc-1, http.sse, realtime.websocket, or another
unmapped profile MUST be rejected. Runtime connectors must also suppress
physical capabilities and reject sends for unsupported persisted profiles.
No implied mapping to a vendor's image API exists in this release.
WebSocket and WebRTC
For WebSocket, derive wss from the card's HTTPS URL. HTTP/WS is allowed only
under an explicit local-development policy. Bearer authentication, if the
card requires it, uses the upgrade Authorization header, never a URL. Apply
HTTPS, DNS/IP, redirect, timeout, credential, and size validation before
connecting. Cards are at most 256 KiB; each UTF-8 JSON frame is at most
128 KiB, before parsing. Reject oversized frames and binary input.
For transport: "webrtc", require voice.owner: "agent" and:
{
"profile": "agentcall-webrtc-0.1",
"audio": {"codec": "opus", "clockRateHz": 48000, "channels": 1, "direction": "sendrecv"},
"events": {"transport": "datachannel", "label": "agentcall.events", "protocol": "agentcall-agui-dc-0.1", "ordered": true}
}
This object is params.agui.webrtc. POST the SDP offer as application/sdp
to the card URL and accept an SDP answer of the same media type, bounded to
128 KiB. Send AgentCall-Session-ID and Accept: application/sdp headers;
do not follow redirects. Use one bidirectional Opus audio track and one ordered reliable data
channel with that exact label and subprotocol. Send the same JSON client
messages and receive the same server envelopes on that channel. Photos use
the bounded JSON observation, never RTP. Resume/replay are false in this
WebRTC version; ICE recovery does not create logical session resume.
Optional supportsExplicitTurns: true permits the client capability
audioTurnControl: {"mode":"explicit"}. audio.turn.start, .commit, and
.cancel carry the same integer turn ID; only one audio turn is open, and
the client stops its microphone before commit/cancel. RTP carries audio.
Session and input
The first client message MUST be session.start:
{"type":"session.start","sessionId":"proof-1","clientCapabilities":{"physicalObservations":{"modalities":["nfc","photo"]}}}
The server responds with {"sequence":1,"event":{"type":"session.established","resumed":false}}.
Each subsequent server envelope has a strictly increasing positive sequence.
The client can acknowledge a sequence with {"type":"ack","sequence":1}.
The public proof does not require resume support; on disconnect discard
session-local images, proposals, and reference memory. Resume is optional
and must not be advertised without a separately tested replay implementation.
The usable modality set is the intersection of the card's accepted modalities
and this session's client modalities. Missing client capabilities mean the
empty set. Every observation MUST fit that intersection. Check before any
agent/model use. Agent requests require membership in agentRequests too.
Capability support does not imply permission: the user must initiate each
collection while the app is in the foreground through a visible control.
{
"type":"user.observation",
"turn":1,
"observation":{
"schemaVersion":"0.1",
"observationId":"scan-1",
"capturedAt":"2026-09-07T10:00:00Z",
"intent":"identify",
"object":{
"objectRef":"urn:agentcall:object:workshop-router",
"carrier":"localTag",
"assurance":"unverifiedTag",
"payloadSha256":"0000000000000000000000000000000000000000000000000000000000000000"
}
}
}
The all-zero digest above is illustrative; a producer computes the actual
digest from the exact carrier bytes or local alias. At least object or
image is required. An object requires negotiated nfc; an image requires
negotiated photo; a combined input requires both. turn shares one
strictly increasing counter with user.turn; duplicate/replayed turns are
rejected without side effects. observationId is bounded correlation data,
not authentication. capturedAt is RFC 3339 data, not trusted device time.
intent is identify, remember, inspect, or act, and grants no authority.
Normal text input remains {"type":"user.turn","turn":2,"text":"What is this?"}.
Optional AG-UI Context entries are {description, value} strings. A client
may carry session-local physical focus as bounded reference/assurance facts
on later turns, but never repeats the photo or treats facts as instructions.
The server responds to a processed turn with run.started, followed by text.delta with text, optional stable
speech.segment with segmentId, text, stable:true, then
run.completed. Only one run is active; ordered envelopes provide correlation. Lifecycle details and bounds are in the
published event schema. failure carries bounded code/message and must
not leak credentials or echo image bytes. Invalid input must not create
memory/actions. response.cancel cancels the active run and pending
collection/proposal. session.end cancels pending work and closes the session.
Unknown additive fields and unknown event types never grant capabilities or
execute actions; clients ignore them or report an unsupported extension.
Why this is a versioned client input, not an upstream CUSTOM event
Pinned AG-UI release release/2026-07-31, commit
bb1c2afddb4880309879b9564cfb3a635a5da4eb, has TypeScript core 0.0.57 and
Python 0.1.19. Its official CUSTOM event is {type:"CUSTOM",name,value};
its separate RunAgentInput carries messages, context, state, and
forwardedProps. It defines no NFC/photo WebSocket input message.
user.observation is explicitly an AgentCall 0.1 client-to-agent input
binding. It preserves shipped clients and adds turn ordering, modality and
interaction correlation before model input. It is not an upstream event.
The agent adapter may map the validated observation into a run's bounded
context or multimodal input under its own application policy; it must not
accept arbitrary tag text as prompts or forwarded tool arguments. A future
CUSTOM/event or other transport adapter requires its own versioned mapping.
The normalized server event names in this binding are AgentCall names, too.
Pinned upstream definitions:
- https://github.com/ag-ui-protocol/ag-ui/blob/bb1c2afddb4880309879b9564cfb3a635a5da4eb/sdks/typescript/packages/core/src/events.ts
- https://github.com/ag-ui-protocol/ag-ui/blob/bb1c2afddb4880309879b9564cfb3a635a5da4eb/sdks/typescript/packages/core/src/types.ts
- https://github.com/a2aproject/A2A/blob/173695755607e884aa9acf8ce4feed90e32727a1/docs/topics/extensions.md
Agent-requested collection
After negotiation, an agent may emit interaction.requested with one opaque
interactionId, kind:"physical.nfc.scan" or "physical.photo.capture",
bounded prompt and spokenPrompt, options:[], and allowsFreeform:false.
The client presents a visible scan/capture action; it never silently opens
the camera or reader. Only one request is pending. The user's resulting
user.observation includes the exact interactionId and requested modality.
An unmatched, stale, cancelled, or wrong-modality response is rejected with
no action. A valid response consumes the request exactly once and emits
interaction.resolved with outcome:"answered". Cancellation emits
outcome:"cancelled"; session close clears the pending request. An unrelated
scan without the pending ID cannot satisfy it.
Security, photos, and exact object lookup
Treat tags, photos, passports, timestamps, and every supplied field as
untrusted. NFC tags are writable labels, not proof of identity, ownership, or
permission. signedTag is reserved for a separately verified cryptographic
carrier; do not infer it from a string. No tag-supplied code, URL, instruction,
credential, or action is executed merely because it was scanned.
Ordinary/empty hardware tags map locally to an opaque keyed alias; raw NFC
hardware IDs never leave the client. A localTag digest covers the alias,
not the hardware ID. Device-local aliases need not match on other devices;
portable NDEF references are separate. Receivers use exact objectRef
equality, never prefix, fuzzy, case-folded, or embedding matches for identity.
Never automatically fetch an HTTPS object reference. An explicitly authorized
resolver must apply its own HTTPS/SSRF/redirect/body-size/time policy.
An image is image/jpeg, strict base64, at most 61,440 decoded bytes and
1,024 pixels on either dimension. The producer strips metadata and derives
byteCount, sha256, pixelWidth, and pixelHeight from the final JPEG.
The receiver verifies byte count/hash and actual JPEG dimensions before use;
JSON Schema alone does not verify encoded-content annotations or digests.
Bound image decoding and reject malformed data, forged dimensions, and
oversized images. Images are ephemeral session input, excluded from
diagnostics and subsequent context. End/cancel/failure releases retained
buffers. Photo capture consent does not authorize storing or forwarding it.
An unknown object starts a teaching conversation. Stage a proposal tied to
the exact object, show what will be remembered, and obtain a separate explicit
confirmation before a persistent write or consequential action. A scan,
intent:"remember", an image, or generic assent without a current proposal
is insufficient. Replacement proposals need fresh confirmation; switching
objects invalidates an old proposal. Enforce agent/user storage isolation.
The optional passport is a bounded reference projection, not private history
or credentials; every action recipe requires requiresConfirmation:true.
Independent reference proof
The independent AgentCall reference agent's opt-in developer profile proves this contract without importing gateway, TGP, iOS, or protocol implementation code. Its ordinary public review profile remains text-only. The proof uses at most 32 session-local object labels, each at most 256 characters; no user content is written to disk. It is protocol behavior evidence, not physical NFC/camera acceptance or a production memory store.
The reference implementation's wire tests exercise: discover the card; start a session with
modalities; scan an unknown object; send teach object Workshop router;
verify no stored association yet; send confirm object; rescan the same
reference and receive the label. A different reference remains unknown.
cancel teaching cancels a proposal. A new scanned object invalidates it.
Requests scan an object and take a photo exercise collection messages,
correlation, wrong-modality rejection, and single consumption. Actual hardware
collection requires separate device acceptance. The public golden trace is a
synthetic protocol example; its schema/order checks alone cannot prove an
agent's memory or confirmation behavior. The independent reference-agent wire
tests provide that behavioral evidence. Implementers must test these outcomes
against their own agents, without copying the reference's storage/model design.
Public artifacts
All relative paths below resolve from https://agentcall.smartycode.com/ . The schemas are a generated public projection of the canonical contracts, with only the required dependency closure included. Schema omissions do not publish or authorize other private extensions.
schemas/agentcall-card-0.1.schema.json: public discovery ingest profile.schemas/agentcall-agui-ws-client-0.1.schema.json: client input messages.schemas/agentcall-events-0.1.schema.json: normalized server envelopes.schemas/agentcall-physical-observation-0.1.schema.json: object/photo input.schemas/agentcall-object-passport-0.1.schema.json: portable reference data.protocol/0.1/physical-conformance.json: positive/negative extension fixtures.protocol/0.1/manifest.json: release version, canonical source commit, upstream pins, exact public file inventory, and SHA-256 hashes.
Use the official A2A conformance tooling for the base Agent Card. The AgentCall fixtures additionally check URI migration, modality/profile gates, payload integrity, and the documented physical interaction sequence.