A2A Script Binding
An A2A Script Binding publishes a certified tSM Script as an Agent Skill in the Agent2Agent Protocol (A2A). The Script's parameter and result Forms become its typed protocol contract. An external agent discovers the Skill through an Agent Card and invokes it through the existing tSM Gateway.
The binding supports two tSM execution modes:
| tSM binding mode | Use it when | A2A result |
|---|---|---|
DIRECT | The Script produces the complete result within the invocation transaction | An A2A Message containing the validated Script result |
TASK | The Script hands work to an implementation that can outlive the invocation | A durable A2A Task; subsequent Scripts publish progress and an explicit terminal result |
A TASK binding can hand work to a Kafka consumer, an integration, a ticket workflow, another
Script, or a BPMN process. Each implementation preserves the opaque replyRef and completes the
same inbound A2A Task through @a2a.reply.
This article describes an inbound A2A request handled by tSM. To call a remote agent from a BPMN process, use an A2A Agent Task.
Use an MCP binding when an agent invokes a typed tSM tool and receives the result in that tool call. Use an A2A Script Binding when tSM participates as an agent with Agent Card discovery, Messages, durable Tasks, progress, interrupted states, Artifacts, streaming, or push notifications.
Execution Flow
Direct Mode
DIRECT executes the target Script in one transaction. The last Script value becomes the response.
A timeout or exception fails that invocation according to the binding policy.
Task Mode
The Gateway atomically creates the inbound Task and queues its initializer before the binding
Script starts. With returnImmediately: true, it returns the SUBMITTED Task as soon as that
durable creation commits. The initializer later receives #a2aTask, commits a hand-off, and
publishes WORKING or a terminal reply through @a2a.reply.
With returnImmediately: false, the same queued execution is used, while the HTTP operation waits
on the Ledger until the Task reaches an interrupted or terminal state. It never keeps the Script
transaction open while waiting.
The work can finish in another transaction or service. An authorized Script calls @a2a.reply,
which records a local outbox intent. The Gateway reply processor validates that intent and updates
the inbound Task Ledger. This flow supports both immediate A2A Task responses and callers that wait
according to their A2A request policy.
Configure the Binding
A2A Script Bindings are configured in Scripts / Bindings / A2A Invocations
(Scripts.Bindings.A2aInvocations).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
fullName | string | Yes | — | Stable Agent Skill ID, for example tsm.customer.balance.v1 |
script | string | Yes | — | Script that handles the initial A2A Message |
server | string | No | default | Gateway agent profile and Agent Card in which the Skill is published |
description | string | No | Script description | Purpose, effects, expected result, and usage boundary shown in the Agent Card |
executionMode | DIRECT | TASK | Yes | DIRECT | tSM execution mode for a direct Message or durable Task lifecycle |
replyPolicy | string | TASK only | — | Reply privilege, allowed service identities and Script codes, permitted states, and tenant scope |
idempotencyPolicy | string | Yes | server policy | Scope, key derivation, retention, and duplicate behavior for invocation and initializer retry |
continuationScript | string | No | — | Script invoked for an authorized follow-up Message on an existing Task |
cancellationScript | string | No | — | Script that forwards an accepted cancellation request to the underlying work |
invocationTimeout | duration | Yes | server policy | Maximum duration of a direct result or one asynchronous TASK initializer attempt |
taskTimeout | duration | TASK only | server policy | Maximum lifetime before an unfinished Task receives its governed timeout result |
availability | string | No | enabled | Tenant rollout and kill-switch policy |
The target Script remains the source of paramsFormCode, resultFormCode, target microservice,
transaction behavior, and execPrivilege. Incoming Messages are validated against that certified
configuration.
The Skill ID is a public integration contract. Breaking input, result, authorization, lifecycle, or side-effect changes use a new major Skill ID. A display-name edit preserves the same identity.
tSM Gateway and State Ownership
The existing tSM Gateway provides inbound Script protocol adapters:
| Protocol | Gateway responsibility | Example endpoint |
|---|---|---|
| MCP | Inbound tool discovery, tool calls, authentication, and routing to the Script's microservice | /api/v2/mcp/<server> |
| A2A | Inbound Agent Cards, Messages, Tasks, streaming, push, authentication, and routing to the Script's microservice | URL from the Agent Card, for example /api/v2/a2a/operations |
For an inbound TASK binding, the Gateway owns the authoritative Inbound A2A Task Ledger and
its reply processor. Domain microservices own their business state and local outbox events and
store only the opaque replyRef needed for correlation. A DIRECT binding completes in its Script
transaction and therefore has no Ledger entry.
An outbound A2A Agent Task has different state
ownership and does not traverse Gateway. The remote A2A server owns its Task, tsm-ai owns the
local Outbound A2A Invocation and client transport, and the embedded Process Engine owns the BPMN
activity and mapped business state.
Agent Card Discovery and tSM Governance
The A2A standard defines Agent Card discovery. An Agent Card describes the agent, its Skills, supported interfaces, protocol versions, capabilities, and security requirements. Its standard well-known location is:
https://operations-agent.tsm.example.com/.well-known/agent-card.json
The client reads the card, selects a compatible entry from supportedInterfaces, uses its exact
url and protocolBinding, sends A2A-Version: 1.0, and applies the declared security
requirements. When the interface contains tenant, every request carries the same opaque value.
One tSM Gateway can publish several agent profiles. Each public profile has a configured Agent Card base URL or hostname that routes to the same Gateway deployment. Private profiles can be distributed through direct configuration or an enterprise catalogue.
The tSM Agent Registry complements standard Agent Card discovery with enterprise governance. It approves and pins remote Agent Cards, Skills, interfaces, credentials, tenant availability, and rollout policy used by outbound A2A integrations.
Deterministic Skill Selection
Each A2A Script Binding declares this tSM protocol extension:
https://tsm.datalite.cz/a2a/extensions/script-binding/v1
The Agent Card advertises it in capabilities.extensions. The extension gives an inbound Message a
deterministic mapping to the binding's stable fullName. A client:
- includes the extension URI in
A2A-Extensionsandmessage.extensions; - stores
skillIdunder a metadata object named by that extension URI; - sends Script parameters as a structured Data Part.
{
"metadata": {
"https://tsm.datalite.cz/a2a/extensions/script-binding/v1": {
"skillId": "tsm.customer.balance.v1"
}
},
"extensions": [
"https://tsm.datalite.cz/a2a/extensions/script-binding/v1"
]
}
The URI namespace keeps tSM routing metadata distinct from the core A2A schema and from other extensions. The Gateway validates the Skill ID, version, availability, and authorization before routing the request.
Because this extension changes request routing, every tSM Agent Card publishes it as required:
{
"capabilities": {
"extensions": [
{
"uri": "https://tsm.datalite.cz/a2a/extensions/script-binding/v1",
"description": "Selects a published tSM Script Binding by stable Skill ID.",
"required": true
}
]
}
}
The client activates the URI in the A2A-Extensions header and message.extensions. Gateway
rejects a request that omits this required extension or its valid namespaced skillId metadata.
Register tSM in an External A2A Client
An external client registers or resolves the tSM Agent Card URL, selects a compatible interface, configures the security scheme declared by the card, and optionally allow-lists Skills. Client configuration file syntax is implementation-specific; the protocol bootstrap remains the same.
Client Configuration Example
| Client setting | Value |
|---|---|
| Agent Card URL | https://operations-agent.tsm.example.com/.well-known/agent-card.json |
| Interface URL | Exact url from a compatible supportedInterfaces entry |
| Protocol | HTTP+JSON, version 1.0 |
| Media type | application/a2a+json |
| Authentication | OAuth 2 bearer token or another scheme declared by the Agent Card |
| Required extension | https://tsm.datalite.cz/a2a/extensions/script-binding/v1 |
| Allowed Skill | Stable binding fullName, for example tsm.customer.balance.v1 |
An illustrative client registration is:
{
"agents": {
"tsm-operations": {
"agentCardUrl": "https://operations-agent.tsm.example.com/.well-known/agent-card.json",
"headers": {
"Authorization": "Bearer ${TSM_TOKEN}"
},
"mediaType": "application/a2a+json",
"allowedSkills": ["tsm.customer.balance.v1"]
}
}
}
Clients cache the Agent Card using its HTTP cache headers, revalidate its ETag, and obtain
credentials through the configured identity flow.
First Call over HTTP+JSON
First retrieve the card and select an interface URL:
curl --fail-with-body \
-H 'Authorization: Bearer <access-token>' \
https://operations-agent.tsm.example.com/.well-known/agent-card.json
If the selected interface URL is https://tsm.example.com/api/v2/a2a/operations, a non-blocking
TASK invocation is:
POST /api/v2/a2a/operations/message:send HTTP/1.1
Host: tsm.example.com
Authorization: Bearer <access-token>
Content-Type: application/a2a+json
Accept: application/a2a+json
A2A-Version: 1.0
A2A-Extensions: https://tsm.datalite.cz/a2a/extensions/script-binding/v1
{
"message": {
"messageId": "019fc72c-6cab-7f52-a6b6-31b31eb67021",
"role": "ROLE_USER",
"parts": [
{
"data": {"customerId": "9ba8a665-4ef5-41eb-90af-ff29df9a8e65"},
"mediaType": "application/json"
}
],
"metadata": {
"https://tsm.datalite.cz/a2a/extensions/script-binding/v1": {
"skillId": "tsm.customer.balance.v1"
}
},
"extensions": [
"https://tsm.datalite.cz/a2a/extensions/script-binding/v1"
]
},
"configuration": {
"returnImmediately": true
}
}
A non-blocking TASK client sets returnImmediately to true and follows the returned Task using
GetTask, streaming, or push. A client that intentionally waits for an interrupted or terminal
state sets it to false or omits it. A DIRECT binding returns its Message as soon as the Script
transaction completes. When the selected interface declares tenant, the request includes that
value in its top-level tenant field.
Parameter and Result Forms
| Script field | A2A contract |
|---|---|
paramsFormCode | Generates the structured input schema and validates incoming Data Parts |
resultFormCode | Validates the direct result or the data published with terminal COMPLETED |
The Gateway validates field names, types, media types, and size before Script execution. Governed document references carry large files instead of copying their content into Script or process variables.
In TASK mode, the initial Script result is the internal hand-off receipt. The public business
result is the payload later published as COMPLETED and validated against resultFormCode.
The #a2aTask Context Variable
TASK mode adds an invocation-specific, typed #a2aTask variable to the initial Script:
| Property | Meaning |
|---|---|
taskId | Opaque inbound A2A Task ID visible to the caller |
contextId | Opaque A2A interaction context, when present |
messageId | ID of the Message that created this Task |
skillId | Bound Agent Skill ID and version |
replyRef | Opaque, serializable correlation handle used by @a2a.reply |
expiresAt | Deadline after which replies follow the binding's timeout and reconciliation policy |
The variable is scoped to one inbound TASK invocation. The initializer forwards
#a2aTask.replyRef through one protected business field or message property. Every reply is
authorized from trusted runtime identity, tenant, Script code, and replyPolicy; possession of the
opaque value supplies correlation only.
DIRECT mode executes without an inbound Task context.
Publish Task Replies from SpEL
@a2a is a client SpEL binding available to tSM microservices. It gives Scripts a typed way to
publish an inbound Task reply while preserving their local transaction boundary:
@a2a.reply({
'replyRef': #a2aReplyRef,
'state': 'WORKING',
'payload': {'message': 'Work started'},
'eventId': #eventId
})
In the initializer, the handle is #a2aTask.replyRef. A later Script receives the same opaque value
through an explicitly named parameter in its Params Form, for example #a2aReplyRef.
Local Reply Intent: A2aReplyRequested
@a2a.reply validates the named parameter map and records an A2aReplyRequested intent in the
calling microservice's transactional outbox. The function returns an A2aReplySubmission with the
eventId and durable local submission state.
| Field | Required | Type | Meaning |
|---|---|---|---|
replyRef | Yes | string | Opaque handle copied from #a2aTask.replyRef |
eventId | No | string | Stable idempotency ID for retries and at-least-once delivery |
state | Yes | string | Requested A2A Task state without the TASK_STATE_ prefix |
payload | Yes | map | State-specific status, result, Artifact, input, or error data |
occurredAt | No | instant | Business-event time; the binding supplies the current instant when omitted |
A caller-provided eventId keeps retries deterministic. Repeating the ID with the same canonical
state and payload returns the original submission receipt; reusing it for different content is a
conflict. The Ledger serializes accepted events per Task in commit order and validates each
transition against its current authoritative state.
The local submission confirms that the reply intent committed with the business change. The Gateway reply processor performs the authoritative Task transition asynchronously.
Gateway Ledger Event: A2aTaskEvent
The Gateway consumes A2aReplyRequested, resolves its protected replyRef, authenticates the
trusted source envelope, applies replyPolicy, validates the state and payload, and appends an
enriched A2aTaskEvent to the inbound Task Ledger:
| Field | Supplied by | Meaning |
|---|---|---|
taskId | Gateway | Resolved inbound A2A Task identity |
bindingId | Gateway | Certified A2A Script Binding identity and version |
tenant | Gateway | Trusted tenant scope |
sequence | Gateway | Monotonic event order within the Task |
version | Gateway | Resulting optimistic-lock version |
state | Validated request | Accepted A2A Task state |
payloadRef | Gateway | Governed reference to the validated payload or Artifact data |
This separation keeps protocol state in the Gateway and business transactions in domain microservices. Accepted versions and classified rejections are visible in Task operations and audit.
Reply States
The Gateway assigns SUBMITTED when it creates an inbound TASK. Reply authors publish the first
business update as WORKING, an interrupted state, or a terminal state. @a2a.reply accepts:
state | Terminal | Typical payload |
|---|---|---|
WORKING | No | Message, bounded progress value, safe metadata, or incremental Artifact |
INPUT_REQUIRED | No | Message and a versioned input Form reference |
AUTH_REQUIRED | No | Message and an authorization-flow reference; never a credential |
COMPLETED | Yes | Result data validated against resultFormCode and optional Artifacts |
REJECTED | Yes | Stable business reason and safe remediation |
FAILED | Yes | Stable error class, sanitized message, and optional operator reference |
CANCELED | Yes | Confirmed cancellation or compensation outcome |
SUBMITTED is Gateway-created lifecycle state and is therefore excluded from user-authored
@a2a.reply calls.
The payload uses one stable envelope:
| Field | Mapping |
|---|---|
message | Safe text or structured Parts in TaskStatus.message |
progress | Bounded progress metadata for WORKING |
inputFormCode | Versioned Form reference for INPUT_REQUIRED |
authorizationRef | Safe broker or approval reference for AUTH_REQUIRED |
data | Primary COMPLETED result, validated against resultFormCode and published as an Artifact Data Part |
artifacts | Governed document references with media type, checksum, access, and retention metadata |
error | Stable public code and sanitized details for REJECTED or FAILED |
For example:
// Progress from a later Script
@a2a.reply({
'replyRef': #a2aReplyRef,
'state': 'WORKING',
'payload': {
'message': 'Customer data validated',
'progress': 40
},
'eventId': #kafkaMessageId + ':validated',
'occurredAt': #validatedAt
})
// Explicit terminal business result
@a2a.reply({
'replyRef': #a2aReplyRef,
'state': 'COMPLETED',
'payload': {
'data': {
'ticketId': #ticket.id,
'status': #ticket.status
}
},
'eventId': #ticket.id + ':completed'
})
The Gateway atomically assigns the next version and enforces valid transitions, idempotency, and
exactly one terminal state. The automation owner explicitly publishes COMPLETED, REJECTED,
FAILED, or CANCELED; completion of the initializer or underlying business work is a separate
event.
Starting Different Kinds of Work
The same inbound binding can initiate any implementation that preserves replyRef.
Kafka or an Integration
The initial Script sends a durable command containing business input and #a2aTask.replyRef. For
example, a project-specific Script-to-Script binding can publish the command through a configured
integration:
@script.customerCare.startAsync({
'customerId': #customerId,
'a2aReplyRef': #a2aTask.replyRef
})
The consumer handles the business work and invokes a configured reply Script with
#a2aReplyRef. A transactional outbox preserves an atomic business change and hand-off intent.
Ticket or Other Business Object
The initial Script creates the object and stores replyRef in a protected internal correlation
field. Event-bound Scripts publish milestones and the terminal business outcome. End users work
with the business object rather than the correlation handle.
BPMN Process
The initial Script starts an ordinary tSM process and maps the typed Task context into a protected process variable:
#runtimeService.startProcessInstanceByKey(
'CustomerCare.Resolve',
#businessKey,
{
'customerId': #customerId,
'a2aTask': #a2aTask
}
)
Script and Service Tasks can use #a2aTask.replyRef with @a2a.reply. The process author models
which milestones publish WORKING, when a Task requests input or authorization, and which end
paths publish COMPLETED, REJECTED, FAILED, or CANCELED. This explicit reply keeps the A2A
lifecycle aligned with the intended business outcome.
Continuation and Cancellation
When an existing Task receives another Message, the Gateway authenticates the caller, verifies its
taskId and contextId, validates the Parts, and invokes continuationScript with #a2aTask and
the new parameters. The Script correlates the data to the underlying work.
For CancelTask, a configured cancellationScript forwards the accepted request to the worker,
ticket, integration, or process. It publishes CANCELED after cancellation or compensation is
confirmed. A binding without a cancellation Script reports TaskNotCancelableError and preserves
the current Task state.
The Ledger serializes a confirmed CANCELED reply with concurrent COMPLETED, REJECTED, or
FAILED replies. The first valid terminal transition becomes authoritative and later transitions
are rejected with the current Task state.
Transaction and Delivery Guarantees
- A
DIRECTinvocation runs in its own Script transaction. - An inbound
TASKand its idempotent initializer job are persisted atomically before the initial Script executes. - With
returnImmediately: true, Gateway returns the created Task immediately after that commit. With blocking mode, the protocol request observes the Ledger until an interrupted or terminal state. - A successful initializer commits a durable hand-off or local outbox intent.
- Reconciliation retries an initializer job or resolves a
SUBMITTEDTask whose initializer did not record a hand-off or reply before its deadline. - Local database changes and outgoing commands share a transactional outbox where atomic hand-off is required.
A2aReplyRequestedevents are delivered at least once and deduplicated byeventId.- The Gateway-owned Inbound A2A Task Ledger is authoritative;
GetTask, streaming, and push are projections of the same persisted state. - The Gateway classifies stale versions, conflicting event IDs, invalid transitions, schema failures, and reply-policy violations while preserving the current Task state.
- Reconciliation finds Tasks with no accepted hand-off, progress deadline, or terminal reply and applies the configured recovery policy.
An initializer exception rolls back its local changes and moves the inbound Task to a safe
FAILED state. External effects use their own idempotency or compensation policy.
Security and Operations
- Agent Card discovery publishes enabled Skills according to their visibility policy.
- Invocation checks the binding privilege and access to referenced business objects.
replyRefprovides opaque correlation; identity, tenant, Script code, andreplyPolicyprovide authorization.- Reply, continuation, read, subscribe, and cancellation operations recheck tenant and resource authorization.
- Parameters, replies, progress, and Artifacts are schema-, size-, classification-, and retention-limited.
- Credentials stay in managed identity and secret stores rather than Messages, Task metadata,
replyRef, process variables, or logs. - Operations expose orphaned Tasks, reply failures, timeouts, duplicate or out-of-order events, outbox lag, and Tasks awaiting an explicit terminal reply.