Add schemas, fixtures, conformance suite, reference impls, and docs - #1
Conversation
…docs Turn the spec-only repo into a machine-checkable, implementable, and governed protocol profile, addressing the "What this repository still needs" list. - schemas/v0.1: JSON Schema 2020-12 for every profile payload - fixtures: valid/invalid payloads per schema + event sequences - conformance: pytest schema + behavioral suite, wired into CI - examples: Python Live client + Reasoner server over WebSocket JSON-RPC - docs: compatibility, security, errors, escalation, operations - governance: GOVERNANCE.md, DEVELOPING.md, .github templates, CHANGELOG - hygiene: rewrite CONTRIBUTING.md/SECURITY.md for a spec repo; reconcile README
…d dumps Pretty-prints every JSON-RPC payload sent and received when -v/--verbose is passed, to aid manual review of the wire-level message flow. Default output is unchanged.
…agent Updates the extension URI path segment across schemas ($id/$ref), fixtures, spec, docs, and reference implementations. The extension is now identified by https://schemas.salesforce.com/a2a/ext/realtime-agent/v0.1. Conformance suite and reference client both pass unchanged.
| pytest conformance/ | ||
| ``` | ||
|
|
||
| Run the reference implementations end to end (once present): |
There was a problem hiding this comment.
To run examples, we need to run pip install -r examples/requirements.txt as pre-requisite.
There was a problem hiding this comment.
Good catch, added it to the prerequisites. I also cleaned up the "(once present)" wording while I was there.
| "description": "The DataPart 'data' object the Live layer sends on barge-in, alongside tasks/cancel for the in-flight task (spec §8). The task then transitions to CANCELED and the next utterance begins a new task under the same context (roll-forward model).", | ||
| "type": "object", | ||
| "required": [ | ||
| "played_text", |
There was a problem hiding this comment.
As we are working with amazon for A2A, they can't send us any of these fields. So I am thinking we can make all of these fields optional. Without these reasoner has to make best guess ( for eg it can consider all text as unplayed and update the history, use last turn_id for interrupted_turn_id ). Is that acceptable?
There was a problem hiding this comment.
Yeah, that makes sense to me. I made all the fields optional and added the fallbacks to section 8. With no played_text, the Reasoner treats everything as unplayed. With no interrupted_task_id, it uses the latest task, and with no request guid it just correlates at the task level. I also added an empty {} fixture so the Amazon case is covered.
| "played_text", | ||
| "planned_text", | ||
| "unspoken_text", | ||
| "interrupted_turn_id", |
There was a problem hiding this comment.
With full-duplex mode in GPT Live, we’re moving away from the concept of a turn-based conversation. So, I think we should rename this to interrupted_task_id.
There was a problem hiding this comment.
Renamed. I changed the same field in the escalation outcome payload too so they stay consistent. Should we do the same for turn_id in conversationHistoryUpdate and rta/turnId? I left those alone for now since it's a bigger change, but happy to do it in a follow-up.
| "title": "Spoken output artifact", | ||
| "description": "An A2A artifact carrying user-facing spoken output, streamed via TaskArtifactUpdateEvent (spec §6.2). Also the shape used by the say_exactly and convey directives on the artifact channel (spec §7); those additionally carry directive metadata (see directive-metadata.schema.json) with renderMode verbatim/paraphrase. Each chunk SHOULD contain both a normalized TextPart (for TTS) and a transcript TextPart (for history/UI); an untagged text part is interpreted as transcript.", | ||
| "type": "object", | ||
| "required": ["parts"], |
There was a problem hiding this comment.
can we add artifactId as required to make sure we always have unique id to track?
There was a problem hiding this comment.
Done. It's required now, and the spec says it has to be unique within the task.
| "title": "Agent Card extension entry", | ||
| "description": "One entry in the Agent Card's capabilities.extensions[] array advertising the Agentforce Live profile (spec §4). For v0.1 'required' MUST be false.", | ||
| "type": "object", | ||
| "required": ["uri", "required"], |
There was a problem hiding this comment.
can we mark params as required field to make sure directiveType extensions are always available on agent card?
There was a problem hiding this comment.
Done. Made params required and added a line in section 4.1 about it.
There was a problem hiding this comment.
We don't have example that triggers confirm_entities, can we add?
There was a problem hiding this comment.
Added one. If you say something like "I'd like to pay my bill", the server sends back a confirm_entities, and the client confirms and checks that the turn completes.
| print(f" [{'PASS' if ok else 'FAIL'}] {name}") | ||
|
|
||
|
|
||
| async def run(verbose: bool = False) -> int: |
There was a problem hiding this comment.
This example code doesn't pass content_id in request. can we update it?
There was a problem hiding this comment.
you mean context_id, right? Yes, will add it.
There was a problem hiding this comment.
I'm assuming you meant contextId, let me know if not! The server now returns it, and the client sends it back on every message after the first turn. I also added a check for it.
|
|
||
|
|
||
| # §6.2 — reaching a terminal A2A state ends the turn | ||
| @pytest.mark.parametrize("state", ["COMPLETED", "CANCELED", "FAILED"]) |
There was a problem hiding this comment.
can we add test for REJECTED terminal state as well?
| | Profile version | `v0.1` (draft) | Extension URI `https://schemas.salesforce.com/a2a/ext/realtime-agent/v0.1` | | ||
| | Core A2A constructs used | `Message`, `Task`, `TaskArtifactUpdateEvent`, `TaskStatusUpdateEvent`, task states, `contextId`/`taskId`, metadata, `TextPart`, `DataPart` | No new RPC methods or task states are defined (spec §1). | | ||
| | A2A methods used | `message/stream`, `tasks/cancel` | `tasks/resubscribe` is reserved for a future version (spec §11). | | ||
| | Task states relied on | `WORKING`, `INPUT_REQUIRED`, `COMPLETED`, `CANCELED`, `FAILED` | Terminal state — not an SDK `final` flag — is the end-of-turn signal (spec §6.2). | |
There was a problem hiding this comment.
can we add REJECTED state here?
| §5), not by transport/channel order. Consumers MUST reorder by | ||
| `afl/sequenceId` rather than assume in-order delivery across artifact and | ||
| status event channels. | ||
| * Reaching a terminal A2A state (`COMPLETED`, `FAILED`, `CANCELED`) is the |
There was a problem hiding this comment.
can we add REJECTED state here?
There was a problem hiding this comment.
Added. I updated the same list in operations.md too.
The profile is vendor-agnostic. Rename "Agentforce Live A2A Profile Extension" to "Realtime Agent A2A Profile Extension" across docs, schema descriptions, and example docstrings, and rename the afl/ metadata-key shorthand (and the AFL Python constant) to rta/ / RTA to match the realtime-agent extension URI. No wire-format change: on-the-wire keys are already full extension URIs.
Schemas and spec: - interruption: every field is now optional (some Live layers, e.g. A2A integrations that cannot observe playback, can't supply them); spec §8 documents the Reasoner fallbacks. Rename interrupted_turn_id to interrupted_task_id (also in the escalation-outcome payload), since full-duplex modes move away from turn-based conversation. - Require artifactId on spoken artifacts, params on the Agent Card extension entry, and cancellation on ask_for / confirm_entities (§7.1). Each gets an invalid fixture that isolates the violation. Examples: - Add a confirm_entities handoff + resume scenario, with ask_for_data / confirm_entities_data builders. - The Reasoner returns contextId on every event and the client reuses it on every later message (spec §3). - live_client now self-checks 11 scenarios/assertions. Docs and tests: - Add REJECTED to the terminal-state lists and the conformance test. - DEVELOPING.md lists examples/requirements.txt as a prerequisite.
Summary
Turns the spec-only repository into a machine-checkable, implementable, and governed protocol profile, addressing the "What this repository still needs" list in the README.
Spec sections affected
None (no normative changes to
spec.md). This PR adds supporting artifacts around the existing Draft v0.1 spec.What's added
schemas/v0.1/— JSON Schema 2020-12 for every profile payload (Agent Card extension,clientCapabilities, directive metadata, spoken artifacts, each directive, interruption, conversation-history-update) + sharedcommondefs and a versioning README.fixtures/— valid and invalid payloads per schema (directory name = schema name) plus happy-path and barge-in event sequences.conformance/— pytest suite with a schema-validation layer and a behavioral layer (negotiation, capability intersection,sequenceIdordering, task lifecycle, barge-in), each test mapped to a spec section. Wired into CI via.github/workflows/conformance.yml.examples/— Python Live client + Reasoner server over WebSocket JSON-RPC, self-checking four scenarios.docs/— compatibility matrix, security profile, error/retry policy, escalation design, operational profile.GOVERNANCE.md,DEVELOPING.md,.github/issue + PR templates,.gitignore,CHANGELOG.md; rewroteCONTRIBUTING.md/SECURITY.mdfor a spec repo and reconciledREADME.md.Validation
pytest conformance/→ 36 passed.reasoner_server+live_client) → 8/8 checks passed.Outstanding (follow-ups, not blocking)
CODEOWNERSstill has*with no owner assigned — needs a real@org/team.docs/compatibility.md(left as stated draft intent).spec.mdstatus left as Draft v0.1;GOVERNANCE.mddocuments promotion criteria.docs/escalation.mdproposes anescalationOutcomeevent type kept as flagged future work (not yet in the schema/spec enum).