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:

  1. Core data structures MUST NOT be redefined. This extension MUST NOT add fields to, or remove fields from, Task, Message, Part, Artifact, or AgentCard. All supplementary data MUST be carried in the metadata map of the relevant core object, under the namespace in §7.
  2. Enum values MUST NOT be invented. This extension MUST NOT add values to TaskState or any other core enum. Where J5 semantics have no core equivalent, the extension MUST use an existing enum value and annotate the additional meaning in metadata.

Why this matters here specifically. ADR 0001 records that J5 Request and A2A Task are the closest mapping but are not 1:1 — J5 has claim/unclaim and expired, 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 existing TaskState with the J5 meaning annotated in metadata, never onto an invented TASK_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 OPTIONAL metadata keys) MAY be made within v1. Clients MUST ignore metadata keys 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 v1 to a client that asked for v2 would 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:

ConcernRule
Runtime behaviorThe 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.
DiscoverabilityJ5 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

FieldTypeRequiredMeaning
specificationstring (URL)MUSTHuman-readable location of this document. Informational; MUST NOT be fetched to determine behavior.
operationsstring[]MUSTThe 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.

  1. 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.
  2. Client activation. A client activates by including the A2A-Extensions HTTP request header containing this extension's URI (comma-separated with any others).
  3. Agent response. When the agent activates the extension for a request, the response SHOULD include the A2A-Extensions header 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.
  4. Unsupported. An agent that does not support the extension ignores the requested URI. A client MUST NOT fail solely because activation was ignored.
  5. 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).

OperationJ5 primitivePurpose
participants.listParticipantsWho is in the session, with capabilities
presence.announcePresenceHeartbeat; liveness and lease renewal
status.announcestatus / blocker eventsProgress and blocker announcements
claims.checkEdit-safety checkPre-flight: allow / warn / block
claims.acquireResource claimsLease-aware soft lock over a resource
claims.releaseResource claimsRelease held leases
questions.ask / questions.answerQuestionsTargeted questions to a participant/capability
inbox.list / inbox.updateInboxWork targeted at the caller; acknowledge/resolve
requests.create / .list / .updateCoordination requestsOne-owner claimable work (claim/unclaim/complete/cancel/reassign)
decisions.recordDecisionsDurable decision with rationale
digest.getDigest / briefingCompact current-state summary
knowledge.list / .read / .proposeSession KnowledgeCurated durable pages; propose updates
workstreams.list / .create / .get / .updateWorkstreamsBounded parallel coordination scopes with explicit lifecycle and checkpoints
workstreams.link / .unlinkWorkstream linksAssociate 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.acquire MUST return the lease expiry. A claim MUST be renewable via presence.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). A claim action 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.propose MUST 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 compare expectedVersion atomically 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.listPOST .../participants:list
presence.announcePOST .../presence:announce
status.announcePOST .../status:announce
claims.checkPOST .../claims:check
claims.acquirePOST .../claims:acquire
claims.releasePOST .../claims:release
questions.askPOST .../questions:ask
questions.answerPOST .../questions:answer
inbox.listPOST .../inbox:list
inbox.updatePOST .../inbox:update
requests.createPOST .../requests:create
requests.listPOST .../requests:list
requests.updatePOST .../requests:update
decisions.recordPOST .../decisions:record
digest.getPOST .../digest:get
knowledge.listPOST .../knowledge:list
knowledge.readPOST .../knowledge:read
knowledge.proposePOST .../knowledge:propose
workstreams.listPOST .../workstreams:list
workstreams.createPOST .../workstreams:create
workstreams.getPOST .../workstreams:get
workstreams.updatePOST .../workstreams:update
workstreams.linkPOST .../workstreams:link
workstreams.unlinkPOST .../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.

OperationParameters (required)Result
participants.listparticipants[] with identity, capabilities, presence
presence.announcestateaccepted presence + renewed lease expiry
status.announcemessagecreated event id + seq
claims.checkresources[]per-resource allow / warn / block + holder
claims.acquireclaims[] (resourceType, resourceId, intent)per-claim outcome + claim ids
claims.releaseresourceType, resourceIdreleased claim ids
questions.asktarget, subject, bodyquestion id
questions.answerquestionId, answerevent id
inbox.listinbox items targeted at the caller + cursor
inbox.updateitemId, stateupdated item
requests.createtarget, subject, body (kind defaults to generic)request id
requests.listrequests + status + assignee
requests.updaterequestId, actionupdated request
decisions.recorddecisiondecision/event id
digest.getcurrent-state summary
knowledge.listpage slugs + titles + updatedAt
knowledge.readslugpage content + version
knowledge.proposemarkdown, plus OPTIONAL pageId / slug / titleproposal id + review state
workstreams.listworkstreams + pagination; archived omitted unless requested
workstreams.createtitle, purposecreated workstream at version 1
workstreams.getworkstreamIdworkstream + current resource links
workstreams.updateworkstreamId, expectedVersionupdated workstream + next version
workstreams.linkworkstreamId, resourceType, resourceIdcurrent link; moving an existing link also requires its expectedVersion
workstreams.unlinkworkstreamId, resourceType, resourceId, expectedVersionremoved 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).

FieldLimit
subject255 characters
body4,000 characters
summary4,000 characters
answer8,000 characters
markdown48,000 characters
title160 characters
slug120 characters
contentSummary1,200 characters
changeSummary1,000 characters
reason500 characters
idempotencyKey255 characters
capability entry120 characters
sourceEventIds, sourceArtifactIds50 entries
workstream purpose, currentCheckpoint4,000 characters each
workstream participants50 entries
workstream external references20 HTTP(S) URLs
workstream metadata16,384 serialized bytes
active workstreams per session100
active workstreams created per participant25
current workstream links per session5,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_EXISTS and 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.announce calls 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 SecurityScheme objects 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.ErrorInfo for simple cases. Note its metadata values are strings only — structured payloads MUST NOT be shoved into it; use a dedicated detail type instead.
  • The reason MUST be stable and machine-readable; domain MUST be j5a2a.com/extensions/session-coordination/v1.
  • Clients MUST tolerate a Status carrying 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" }
      }
    ]
  }
}
ConditionRequirement
Operation not advertised in params.operationsMUST be rejected
Extension not activated but an extension-only operation is invokedMUST be rejected
Caller unauthorizedMUST match the core J5 route's outcome — MUST NOT leak existence of resources the caller cannot see
Claim conflictMUST report the conflict rather than silently overwriting a held lease
Request already claimedMUST 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:

  1. Ship this vendor extension under the j5a2a.com URI and gain real adoption.
  2. Gather evidence: independent implementations, and concrete cases where the absence of the primitive caused duplicated or conflicting work.
  3. Only then propose the specific primitive(s) to the A2A experimental extension process (experimental-ext- under a2aproject, 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:

  1. 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 stateA2A TaskStateNotes
    openTASK_STATE_SUBMITTEDWork exists, nobody has taken it.
    claimedTASK_STATE_WORKINGA2A has no claim concept; WORKING is the closest true statement. The claim itself is annotated in metadata (§7).
    completedTASK_STATE_COMPLETED
    cancelledTASK_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 WORKING to SUBMITTED. It annotates the J5 state in metadata instead. Per §3 this extension MUST NOT add states or transitions to the core Task state machine, and WORKING → SUBMITTED is 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.

  2. Conformance-only states are not production behavior. The TCK drives TASK_STATE_INPUT_REQUIRED and TASK_STATE_REJECTED via messageId prefixes (tck-input-required, tck-reject-task). J5 requests have neither state. Production SendMessage MUST NOT infer task state from messageId, 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

  1. 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