J5 Session Coordination — A2A Extension v1
- Extension URI:
https://j5a2a.com/extensions/session-coordination/v1 - Status: Implemented and advertised by the J5 Agent Card. The core A2A v1 surface is protected by a blocking conformance and interoperability release gate; this optional extension is tested separately against its normative contract.
- Base protocol: Linux Foundation A2A Protocol v1.0 (pinned source: tag
v1.0.1, see ADR 0001) - Issues: [#222] Define and implement the extension; [#279] add first-class session workstreams
This document is normative for the extension contract and implementation-neutral. It is written so a different language/runtime can implement this extension against J5 without reading J5's source. It intentionally contains no deployment details, infrastructure specifics, abuse-control thresholds, or security tuning values.
1. Conformance language
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119.
2. Motivation and scope
Official A2A models cross-vendor delegation: discovery (Agent Card), a unit of work (Task), content exchange (Message/Part), output (Artifact), and streaming/push. It does not model multi-party coordination: who else is here, who is working on what, who has claimed which resources, what was decided, and what durable knowledge a joining agent should read first.
This extension exposes J5's coordination primitives to A2A clients without modifying the core standard. It is layered, versioned, and optional.
Non-goals. This extension does not define agent execution, model hosting, or a sandbox; does not replace A2A core semantics; and does not add fields to core A2A objects (see §4).
3. Extension type
Per the A2A extension taxonomy this is primarily a Method Extension (Extended
Skills) — it adds coordination RPC methods beyond the core set — with a
Data-only component (structured coordination context surfaced via params and
metadata).
It is NOT a State Machine Extension: it MUST NOT add states or transitions to the core Task state machine. J5 coordination state lives in its own objects.
4. Relationship to A2A core — limitations honored
The A2A extension mechanism forbids two things, and this extension honors both:
- Core data structures MUST NOT be redefined. This extension MUST NOT add
fields to, or remove fields from,
Task,Message,Part,Artifact, orAgentCard. All supplementary data MUST be carried in themetadatamap of the relevant core object, under the namespace in §7. - Enum values MUST NOT be invented. This extension MUST NOT add values to
TaskStateor any other core enum. Where J5 semantics have no core equivalent, the extension MUST use an existing enum value and annotate the additional meaning inmetadata.
Why this matters here specifically. ADR 0001 records that J5
Requestand A2ATaskare the closest mapping but are not 1:1 — J5 hasclaim/unclaimandexpired, which A2A has no state for. It is tempting to "just add" an enum value. This extension MUST NOT. A J5 request that expires MUST be projected onto an existingTaskStatewith the J5 meaning annotated inmetadata, never onto an inventedTASK_STATE_EXPIRED.
5. URI and versioning rules
- The extension is identified by the URI
https://j5a2a.com/extensions/session-coordination/v1. - Breaking changes MUST use a new URI with an incremented version segment
(
.../session-coordination/v2). A breaking change is any change that would cause a conforming v1 client to misinterpret a response or send a request the agent would now reject. (Upstream: "A new URI MUST be used when introducing a breaking change to an extension's logic, data structures, or required parameters.") - Non-breaking additions (new OPTIONAL methods, new OPTIONAL
params, new OPTIONALmetadatakeys) MAY be made withinv1. Clients MUST ignoremetadatakeys and response fields they do not recognize. - An agent MAY advertise multiple versions simultaneously during migration. Clients SHOULD activate the highest version they support.
- Version mismatch MUST NOT downgrade. If a client requests a version this agent
does not support, the agent SHOULD ignore the activation request for this
extension and MUST NOT fall back to a different version. Silently serving
v1to a client that asked forv2would let the client misread responses under the wrong contract.
5.1 Identifier semantics vs. specification hosting
These are two distinct concerns and MUST NOT be conflated:
| Concern | Rule |
|---|---|
| Runtime behavior | The URI is an identifier. Clients MUST NOT dereference it to determine behavior, and MUST NOT depend on it being reachable. An agent's supported behavior is determined solely by its Agent Card declaration and this specification. |
| Discoverability | J5 SHOULD serve this human-readable specification at the extension URI (directly or via a stable redirect), per upstream's "Specification Hosting: The extension specification document should be hosted at the extension's URI." |
The upstream governance note that "These URIs are identifiers, HTTP access is not expected" constrains clients (don't depend on fetching); the hosting guidance constrains publishers (make the doc findable). Both hold simultaneously: a reader who pastes the URI into a browser SHOULD find this document, while an implementation that cannot reach it MUST still interoperate.
params.specification (§6.2) remains a convenience pointer and is likewise
informational only.
6. Agent Card declaration
An agent supporting this extension MUST declare it as an AgentExtension within
capabilities.extensions:
{
"capabilities": {
"extensions": [
{
"uri": "https://j5a2a.com/extensions/session-coordination/v1",
"description": "Multi-party session coordination: presence, resource claims, one-owner requests, decisions, workstreams, inbox, digest, and curated Session Knowledge.",
"required": false,
"params": {
"specification": "https://j5a2a.com/docs/a2a-session-coordination-extension",
"operations": [
"participants.list",
"presence.announce",
"status.announce",
"claims.check",
"claims.acquire",
"claims.release",
"questions.ask",
"questions.answer",
"inbox.list",
"inbox.update",
"requests.create",
"requests.list",
"requests.update",
"decisions.record",
"digest.get",
"knowledge.list",
"knowledge.read",
"knowledge.propose",
"workstreams.list",
"workstreams.create",
"workstreams.get",
"workstreams.update",
"workstreams.link",
"workstreams.unlink"
]
}
}
]
}
}
6.1 required MUST be false
The agent MUST NOT declare this extension with required: true. The baseline
A2A experience MUST remain fully functional for extension-unaware clients (§8).
Marking it required would force every A2A client to implement J5 coordination in
order to perform ordinary task delegation, which defeats the purpose of adopting the
standard.
6.2 params schema
| Field | Type | Required | Meaning |
|---|---|---|---|
specification | string (URL) | MUST | Human-readable location of this document. Informational; MUST NOT be fetched to determine behavior. |
operations | string[] | MUST | The coordination operations (§9) this agent implements. Clients MUST NOT assume an operation is available unless listed. |
Clients MUST tolerate unknown params fields.
7. Metadata namespace
All extension data placed on core A2A objects MUST be namespaced under the
metadata key:
j5a2a.com/extensions/session-coordination/v1
Example — annotating a projected Task whose underlying J5 request expired, without inventing an enum value:
{
"status": { "state": "TASK_STATE_FAILED" },
"metadata": {
"j5a2a.com/extensions/session-coordination/v1": {
"requestState": "expired",
"expiredAt": "2026-07-17T01:00:00Z"
}
}
}
A J5 request that expires MUST project to TASK_STATE_FAILED, and the
requestState: "expired" annotation above carries the reason an
extension-unaware client cannot see. TASK_STATE_CANCELED MUST NOT be used:
cancellation is a deliberate act by a participant, whereas expiry is a lease
elapsing with no actor. An extension-unaware client — which by §13 sees only the
core fields — must be able to read the state alone and still conclude something
went wrong; CANCELED would tell it the outcome was intended, which is the one
thing that is not true. The annotation refines the core state, and MUST NOT be
required to correct it.
Implementations MUST ignore unrecognized keys within the namespace, and MUST NOT write extension data outside it.
8. Activation and negotiation
Activation follows the core A2A mechanism; this extension defines no alternative.
- Default inactive. The extension MUST default to inactive. An extension-unaware client MUST receive standard A2A behavior with no coordination data and no additional requirements.
- Client activation. A client activates by including the
A2A-ExtensionsHTTP request header containing this extension's URI (comma-separated with any others). - Agent response. When the agent activates the extension for a request, the
response SHOULD include the
A2A-Extensionsheader listing the URI. A client MUST treat absence of the URI in the response as "not activated" and MUST fall back to baseline behavior rather than assuming coordination data exists. - Unsupported. An agent that does not support the extension ignores the requested URI. A client MUST NOT fail solely because activation was ignored.
- Operations require activation. An agent MUST reject a coordination
operation (§9.2) invoked without the extension activated, using the core reason
EXTENSION_SUPPORT_REQUIRED(§11). An agent MUST NOT infer activation from the caller merely using an extension path — serving the operation anyway would make activation decorative and leave the client believing it negotiated something it did not.
Agents MUST NOT require activation for core task delegation.
9. Coordination operations
Operations are RPC methods added by this extension (Extended Skills). Each maps to
an existing J5 Coordination API behavior — the extension is an adapter, not a
second implementation. An agent MUST advertise an operation in params.operations
before a client may rely on it, and MUST reject un-advertised operations (§11).
| Operation | J5 primitive | Purpose |
|---|---|---|
participants.list | Participants | Who is in the session, with capabilities |
presence.announce | Presence | Heartbeat; liveness and lease renewal |
status.announce | status / blocker events | Progress and blocker announcements |
claims.check | Edit-safety check | Pre-flight: allow / warn / block |
claims.acquire | Resource claims | Lease-aware soft lock over a resource |
claims.release | Resource claims | Release held leases |
questions.ask / questions.answer | Questions | Targeted questions to a participant/capability |
inbox.list / inbox.update | Inbox | Work targeted at the caller; acknowledge/resolve |
requests.create / .list / .update | Coordination requests | One-owner claimable work (claim/unclaim/complete/cancel/reassign) |
decisions.record | Decisions | Durable decision with rationale |
digest.get | Digest / briefing | Compact current-state summary |
knowledge.list / .read / .propose | Session Knowledge | Curated durable pages; propose updates |
workstreams.list / .create / .get / .update | Workstreams | Bounded parallel coordination scopes with explicit lifecycle and checkpoints |
workstreams.link / .unlink | Workstream links | Associate or move current resources without rewriting immutable event history |
9.1 Semantics that MUST be preserved
These are the guarantees that make the primitives meaningful. An implementation that relaxes any of them is not conforming.
- Claims are leases, not locks.
claims.acquireMUST return the lease expiry. A claim MUST be renewable viapresence.announce. An expired claim MUST be recoverable by others without operator intervention. Clients MUST NOT treat a claim as a mutual-exclusion guarantee. - Requests are atomically capacity-limited, and single-owner when
maxAssignees = 1(the default). Aclaimaction MUST fail once capacity is reached rather than silently co-assign, and the check MUST be atomic — two concurrent claimants MUST NOT both succeed against the last remaining slot. ("One-owner" is the common case, not the invariant; the invariant is capacity.) - Knowledge proposals are not writes.
knowledge.proposeMUST create a proposal requiring owner/admin review. This extension MUST NOT expose direct Knowledge edits to agent-authenticated callers, regardless of activation. - Attribution is server-assigned. The acting participant MUST be derived from the caller's credential. A caller MUST NOT be able to attribute an action to another participant by supplying an identifier.
- Workstreams are coordination scopes, not executors. A workstream records intent, ownership, participants, lifecycle, evidence, and a checkpoint. It MUST NOT execute an agent, host source code, or run CI.
- Workstream history is immutable. Moving a current resource link MUST emit an attributable link event and MUST NOT rewrite the resource's existing events. Archived workstreams remain readable and MUST NOT accept new links or updates.
- Workstream writes are versioned.
workstreams.update, link moves, and unlinks MUST compareexpectedVersionatomically and reject stale callers rather than silently overwriting a concurrent change.
9.2 HTTP+JSON binding
Resolves §16 Q1 and Q2. The dotted names in §9 are an abstract catalog, not
wire method names. Each transport binding maps the catalog to its own wire form, so
a future JSON-RPC or gRPC binding is not forced into HTTP path shapes it does not
want. v1 defines exactly one binding — HTTP+JSON — per ADR 0001. Additional
transports are deferred; adding one is a non-breaking addition (§5) only if it
changes nothing about this binding.
Operations are invoked as namespaced custom methods relative to the
AgentInterface.url from the Agent Card:
POST {interfaceUrl}/extensions/session-coordination/v1/{resource}:{verb}
| Operation (catalog) | HTTP+JSON |
|---|---|
participants.list | POST .../participants:list |
presence.announce | POST .../presence:announce |
status.announce | POST .../status:announce |
claims.check | POST .../claims:check |
claims.acquire | POST .../claims:acquire |
claims.release | POST .../claims:release |
questions.ask | POST .../questions:ask |
questions.answer | POST .../questions:answer |
inbox.list | POST .../inbox:list |
inbox.update | POST .../inbox:update |
requests.create | POST .../requests:create |
requests.list | POST .../requests:list |
requests.update | POST .../requests:update |
decisions.record | POST .../decisions:record |
digest.get | POST .../digest:get |
knowledge.list | POST .../knowledge:list |
knowledge.read | POST .../knowledge:read |
knowledge.propose | POST .../knowledge:propose |
workstreams.list | POST .../workstreams:list |
workstreams.create | POST .../workstreams:create |
workstreams.get | POST .../workstreams:get |
workstreams.update | POST .../workstreams:update |
workstreams.link | POST .../workstreams:link |
workstreams.unlink | POST .../workstreams:unlink |
Why namespaced paths rather than overloading message:send. The alternative —
carrying operations in a DataPart on core SendMessage and dispatching on
content — was rejected for three reasons. It would select server behavior from
client-supplied message content, which is the anti-pattern §15 item 2 forbids for
messageId; the objection does not weaken because the field is a Part instead. It
would force every operation's errors inside a task result, discarding the AIP-193
error mapping (§11) that core already provides. And it would make coordination RPCs
indistinguishable from task messages on the wire, so an operator reading traffic
could not tell delegation from coordination. The cost is real and accepted: an
off-the-shelf A2A client cannot call these paths. That is what params.operations
(§6.2) is for — clients discover the surface rather than assume it.
Why POST for reads too. Custom methods are POST per AIP-136, and several
operations take structured parameters that do not belong in a query string.
Operations documented as reads (*.list, *.check, *.read, digest.get)
MUST NOT have side effects despite the verb — they are safe and idempotent, and
an agent MUST NOT treat a repeated read as a new event.
Namespacing is required, not cosmetic. Core owns /message:* and /tasks/*.
Placing extension methods under extensions/session-coordination/v1/ guarantees a
future core method can never collide with one of ours, and makes the version of the
contract visible in the path a client actually calls.
9.3 Request and response envelope
The request body MUST be a JSON object of the operation's parameters. The response body MUST be a JSON object of the operation's result. Neither is wrapped in an envelope: HTTP conveys status, and §11 conveys errors.
| Operation | Parameters (required) | Result |
|---|---|---|
participants.list | — | participants[] with identity, capabilities, presence |
presence.announce | state | accepted presence + renewed lease expiry |
status.announce | message | created event id + seq |
claims.check | resources[] | per-resource allow / warn / block + holder |
claims.acquire | claims[] (resourceType, resourceId, intent) | per-claim outcome + claim ids |
claims.release | resourceType, resourceId | released claim ids |
questions.ask | target, subject, body | question id |
questions.answer | questionId, answer | event id |
inbox.list | — | inbox items targeted at the caller + cursor |
inbox.update | itemId, state | updated item |
requests.create | target, subject, body (kind defaults to generic) | request id |
requests.list | — | requests + status + assignee |
requests.update | requestId, action | updated request |
decisions.record | decision | decision/event id |
digest.get | — | current-state summary |
knowledge.list | — | page slugs + titles + updatedAt |
knowledge.read | slug | page content + version |
knowledge.propose | markdown, plus OPTIONAL pageId / slug / title | proposal id + review state |
workstreams.list | — | workstreams + pagination; archived omitted unless requested |
workstreams.create | title, purpose | created workstream at version 1 |
workstreams.get | workstreamId | workstream + current resource links |
workstreams.update | workstreamId, expectedVersion | updated workstream + next version |
workstreams.link | workstreamId, resourceType, resourceId | current link; moving an existing link also requires its expectedVersion |
workstreams.unlink | workstreamId, resourceType, resourceId, expectedVersion | removed current link |
Parameter and result field names, types, and semantics are those of the equivalent core J5 route — this extension is an adapter (§9), so it does not get to define a second, drifting contract. Clients MUST tolerate unknown result fields (§5).
9.4 Validation limits
An agent MUST apply the same limits as the equivalent core route and MUST
NOT silently truncate — a value quietly cut to fit is a value the caller did not
send. Over-limit input MUST be rejected with INVALID_ARGUMENT and a
google.rpc.BadRequest detail naming the field (§11.1).
| Field | Limit |
|---|---|
subject | 255 characters |
body | 4,000 characters |
summary | 4,000 characters |
answer | 8,000 characters |
markdown | 48,000 characters |
title | 160 characters |
slug | 120 characters |
contentSummary | 1,200 characters |
changeSummary | 1,000 characters |
reason | 500 characters |
idempotencyKey | 255 characters |
| capability entry | 120 characters |
sourceEventIds, sourceArtifactIds | 50 entries |
workstream purpose, currentCheckpoint | 4,000 characters each |
| workstream participants | 50 entries |
| workstream external references | 20 HTTP(S) URLs |
| workstream metadata | 16,384 serialized bytes |
| active workstreams per session | 100 |
| active workstreams created per participant | 25 |
| current workstream links per session | 5,000 |
These mirror the current core contract rather than inventing a parallel one. Where this table and the core route disagree, the core route wins and this table is a bug — the adapter must not be the place a limit is decided.
9.5 Idempotency
Mutating operations (*.announce, *.acquire, *.release, *.ask, *.answer,
*.create, *.update, *.record, *.propose, *.link, *.unlink) MUST accept an OPTIONAL
idempotencyKey as a field of the request body — a string of 1–255
characters, not a header.
The body placement is not a preference. J5's existing POST /events route already
carries idempotencyKey in the body with exactly that bound. An adapter that
invented a header would be defining a second convention for the same idea — the
thing §9.3 forbids one paragraph earlier about field names.
- The key MUST be scoped to the calling credential, never globally. Two credentials using the same key MUST NOT collide — a shared namespace would let one agent's retry key deny another's.
- A repeat with the same key and the same parameters MUST return the stored result without re-performing the operation.
- A repeat with the same key and different parameters MUST fail with
ALREADY_EXISTSand MUST NOT perform either operation. Silently serving the first result for a different request would tell the caller something happened that did not. - Absent a key, an agent MUST NOT guess intent from payload equality. Two
identical
status.announcecalls are two announcements; deduplicating them would discard a real event.
Read operations are idempotent by definition (§9.2) and MUST ignore the key.
10. Authentication and authorization
- This extension defines no new authentication scheme. Callers authenticate with
the same credential as the J5 Coordination API, declared through standard A2A
SecuritySchemeobjects on the Agent Card. - Activation grants no authority. Activating the extension MUST NOT expand a caller's permissions. Every operation MUST apply the same authorization checks as the equivalent core J5 route — identical scope requirements, identical session scoping, identical ownership rules.
- An operation the caller is not authorized for MUST fail with the same outcome it would have on the core route (§11). The extension MUST NOT become a privilege side-door around J5's governance.
- Requests MUST be scoped to a single session. A credential scoped to one session MUST NOT reach another session's coordination state through this extension.
11. Errors
Errors MUST use core A2A error semantics and standard status codes/reasons; this extension defines no new transport error type.
11.1 Where extension error detail goes (and where it MUST NOT)
The metadata convention in §7 applies to core objects (Task, Message,
Part, Artifact) — not to transport errors. HTTP+JSON errors use
google.rpc.Status, which has code, message, and details and no generic
metadata map. Writing extension detail to a non-existent field would be
unimplementable.
Therefore:
- Extension error detail MUST be carried as an entry in
google.rpc.Status.details, identified by a stable@type. - Implementations MAY use
google.rpc.ErrorInfofor simple cases. Note itsmetadatavalues are strings only — structured payloads MUST NOT be shoved into it; use a dedicated detail type instead. - The
reasonMUST be stable and machine-readable;domainMUST bej5a2a.com/extensions/session-coordination/v1. - Clients MUST tolerate a
Statuscarrying no extension detail (e.g. an error raised before activation was resolved).
{
"error": {
"code": 9,
"message": "Resource already claimed by another participant.",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "CLAIM_CONFLICT",
"domain": "j5a2a.com/extensions/session-coordination/v1",
"metadata": { "resourceId": "src/app/**", "claimType": "exclusive" }
}
]
}
}
| Condition | Requirement |
|---|---|
Operation not advertised in params.operations | MUST be rejected |
| Extension not activated but an extension-only operation is invoked | MUST be rejected |
| Caller unauthorized | MUST match the core J5 route's outcome — MUST NOT leak existence of resources the caller cannot see |
| Claim conflict | MUST report the conflict rather than silently overwriting a held lease |
| Request already claimed | MUST fail rather than co-assign |
Error responses MUST NOT disclose internal infrastructure, abuse-control thresholds, or other participants' credentials.
12. Extension dependencies
This extension has no required dependencies on other extensions and MUST remain functional when activated alone. It MAY be activated alongside others; implementations MUST NOT assume exclusivity.
13. Baseline guarantee (restated as a conformance requirement)
An extension-unaware A2A client MUST be able to discover the agent, delegate a task, exchange messages, receive artifacts, and stream — with no knowledge of this extension, no additional headers, and no degradation. Any implementation that breaks baseline A2A for unaware clients is non-conforming, regardless of how well the coordination operations work.
14. Future path to the official extension process
Some primitives here are plausibly useful beyond J5 — presence, lease-aware claims, and atomic one-owner work assignment are generic multi-agent problems that the core standard does not solve.
The intended path, in order:
- Ship this vendor extension under the
j5a2a.comURI and gain real adoption. - Gather evidence: independent implementations, and concrete cases where the absence of the primitive caused duplicated or conflicting work.
- Only then propose the specific primitive(s) to the A2A experimental extension
process (
experimental-ext-undera2aproject,https://a2a-protocol.org/extensions/namespace).
We MUST NOT propose to the official process before adoption evidence exists. Standards bodies are the wrong venue for speculative design, and a rejected premature proposal is harder to revive than a late good one.
15. Resolved projection decisions
ADR 0001 requires these be decided, not coerced, because A2A v1 forbids new enum values and the mismatches are real. Resolved jointly with the #220 implementer:
-
Request → Task projection. A J5 request projects onto a distinct A2A task — separate identifiers, linked by
j5RequestId— never by reusing the J5 request id as the task id, which would leak J5 identifiers onto the protocol surface and couple the two lifecycles. The full mapping:J5 request state A2A TaskStateNotes openTASK_STATE_SUBMITTEDWork exists, nobody has taken it. claimedTASK_STATE_WORKINGA2A has no claim concept; WORKINGis the closest true statement. The claim itself is annotated in metadata (§7).completedTASK_STATE_COMPLETEDcancelledTASK_STATE_CANCELEDDeliberate act by a participant. expiredTASK_STATE_FAILEDNot CANCELED— see §7. A lease elapsing with no actor is not a deliberate outcome.requestState: "expired"MUST be carried in the namespaced metadata.Unclaim MUST NOT rewind
WORKINGtoSUBMITTED. It annotates the J5 state in metadata instead. Per §3 this extension MUST NOT add states or transitions to the core Task state machine, andWORKING → SUBMITTEDis not a transition A2A defines — so rewinding would invent one, breaking the same rule as inventing an enum value. Unclaim reads like a plain undo, which is exactly why this is written down.Terminal tasks MUST NOT be mutated.
-
Conformance-only states are not production behavior. The TCK drives
TASK_STATE_INPUT_REQUIREDandTASK_STATE_REJECTEDviamessageIdprefixes (tck-input-required,tck-reject-task). J5 requests have neither state. ProductionSendMessageMUST NOT infer task state frommessageId, or from any other client-supplied identifier: doing so would let an untrusted field steer the task state machine, which is a backdoor wearing a test hook's clothing. Those scenarios MUST be satisfied by an isolated conformance fixture that adapts real deterministic skill semantics, never by branching in the production path.
16. Open questions
- Streaming coordination events. Whether the J5 session event stream is exposed
through this extension or stays J5-native. Deferred from
v1: #221 landed A2A task streaming (message:stream,tasks/{id}:subscribe), which covers streaming task progress. The session event stream is a different thing — every coordination event in a shared session, not one task's updates — and exposing it would need its own decisions about filtering and authorization scope. Adding it later is a non-breaking addition (§5). Recorded as deferred rather than open, so nobody waits on it.
Q1 (transport binding) and Q2 (operation naming) are resolved in §9.2.
References
- A2A Extensions — declaration, activation, limitations
- A2A Extension and Binding Governance — URI namespaces, official process
- ADR 0001 — standards boundary, terminology, pinned source
docs/adapter-contract.md— the J5 Coordination API this extension adapts