.. SPDX-License-Identifier: CC-BY-4.0
SPDX-FileCopyrightText: Netresearch DTT GmbH
.. include:: /Includes.rst.txt
.. _administration-a2a-server:
==========
A2A server
==========
nr-llm can answer other agents that speak the `Agent2Agent protocol
`__ (A2A) 1.0 over JSON-RPC.
It publishes selected LLM configurations as skills, and a registered remote
client sends a message to one of them. The message becomes an agent run that a
worker executes; the client receives the result as an A2A task
(:ref:`ADR-280 `, :ref:`ADR-282 `, :ref:`ADR-283 `).
This page covers stage 3 of spec 051. A client cannot continue a task with a
second message yet (stage 4), and streaming, push notifications and file
parts are not offered (stages 5a to 5c).
Switch it on
============
#. Set :guilabel:`Answer A2A requests (server role)`
(``a2a.server.enabled``) in the extension configuration. Off, no site
answers an A2A request, whatever its settings say.
#. Name the base URL in the settings of each site that should answer, for
example in :file:`config/sites//settings.yaml`:
.. code-block:: yaml
nrllm:
a2a:
baseUrl: 'https://www.example.org/a2a'
The JSON-RPC endpoint is this URL; the agent card is published at
:file:`https://www.example.org/a2a/.well-known/agent-card.json`. A site
without the setting answers neither. Every URL in the card and in a
response is built from this setting, never from the request's ``Host``
header; a request to another host, scheme or port is not an A2A request
and passes on to the site.
#. Run a worker. A task is accepted only while a worker can be shown to run:
a ``messenger:consume`` consumer with a heartbeat, or a tick of
``nrllm:agent:work`` within the last 120 seconds. Schedule
``nrllm:agent:work`` every minute on an installation without a consumer.
``nrllm:agent:status --transport=sweep --minimum-workers=1`` checks the
sweep. Without a worker, ``SendMessage`` answers HTTP 503 with
``WORKER_UNAVAILABLE`` and stores nothing.
Publish a skill
===============
Create an :guilabel:`A2A Publication Profile` record (administrators only).
Skill id, name, description, tags, examples
What the agent card lists for the skill. The skill id is how a client
names it.
Domain handler
What turns the client's message into the run and the run's answer into
the result. ``nr_llm.text`` sends the message text to the LLM
configuration and returns the answer as one text artifact: a text check or
a summary, with the instructions of the configuration's system prompt.
LLM configuration
The configuration the runs use. If its answer level keeps no result
(``metadata`` or ``none``, set on its provider), the profile cannot be
public, and a call is refused before a task exists with
``RESULT_NOT_RETAINABLE`` (:ref:`ADR-282 `). A configuration that
is switched off, or restricted to backend groups the client's technical
user is not in, refuses a call with ``PROFILE_UNAVAILABLE``. The agent card
lists a skill only while its handler is available and its configuration
is switched on and keeps a result.
Tools
Comma-separated tool names the run may use. A profile that may not write
offers only tools that declare that they only read: a list with any other
tool is not stored, and an existing list stays as it was. Empty: no tools.
Active task quota
How many tasks of this skill, from all clients together, may be queued or
running at once. ``0``: no limit of its own.
Public
Listed on the card and callable. A profile that is not public cannot be
called by any client.
Stage 3 accepts and returns ``text/plain`` only.
Register a client
=================
Create an :guilabel:`A2A Client` record (administrators only):
Technical backend user
Every task of the client runs as this backend user, with exactly its
permissions. It must be enabled and must not be an administrator; the
record cannot be enabled otherwise, and a request is refused if the user
changed since. Prepare a user for this purpose.
Workspace
The workspace the client's runs act in; ``0`` is live.
Skills it may call
The publication profiles the client may use. A client allowed one skill
needs to name none; a client allowed several names the skill in each
message, as ``SendMessageRequest.metadata`` member
``https://nr-llm.netresearch.de/a2a/ext/profile/v1`` with the skill id as
value. Otherwise the call is refused with ``PROFILE_REQUIRED``.
Task limit
How many of the client's tasks may be queued or running at once.
Enabled, expires
A disabled or expired client is refused like an unknown one.
Then issue its token:
.. code-block:: bash
vendor/bin/typo3 nrllm:a2a:client:token
The command prints the token once. The record keeps only its public prefix
and its SHA-256; nobody can read the token from the database. Running the
command again rotates the token: the old one stops working at once, and the
client keeps its tasks, which belong to the record, not to the token. The
client sends the token as ``Authorization: Bearer ``.
What a client gets
==================
``SendMessage``
Creates a task and a queued run. The request never executes the run.
Without ``returnImmediately``, the request waits until the task is
finished, failed, cancelled, rejected or waiting for input, at most
``a2a.server.blockingWaitSeconds`` (default 20, at most 100). After that
it answers HTTP 504 with JSON-RPC ``-32603``, reason
``BLOCKING_DEADLINE_EXCEEDED`` and the task id; the task goes on and is
read with ``GetTask``. A repeated ``messageId`` of the same client returns
the task the first one created.
``GetTask``, ``ListTasks``, ``CancelTask``
Only the client's own tasks. Another client's task and a task that does
not exist are the same ``-32001``. ``ListTasks`` sorts by status time,
newest first, pages with an opaque token bound to the client, its skills
and the filter, and leaves out artifacts unless ``includeArtifacts`` is
true.
Task states
``SUBMITTED`` while queued, ``WORKING`` while running (also while the run
waits for an approval in the backend or for a remote agent), ``COMPLETED``
with the answer, ``FAILED`` with a fixed message that names no internal
detail, ``CANCELED``, and ``REJECTED`` when the input was refused before
the run started. The result is kept at the run's answer level and purged
with the run (:ref:`ADR-282 `).
Limits and protection
=====================
- **Proxy timeouts.** A waiting request holds a PHP-FPM worker. Keep
``a2a.server.blockingWaitSeconds`` below the read timeout of every proxy in
front of PHP-FPM; a proxy that cuts the request earlier leaves the client
with a transport error and the task unchanged.
- **Waiting requests.** At most ``a2a.server.maxWaitingRequests`` (default 4)
blocking requests wait at the same time on the installation; one more is
refused with HTTP 503, ``WAITING_CAPACITY_EXHAUSTED``. A request with
``returnImmediately`` takes no waiting slot.
- **Request size.** A body above ``a2a.server.maxRequestBytes`` (default
1 MiB) is refused with HTTP 413 before it is parsed; a message has at most
16 parts.
- **Failed logins.** A failed authentication is logged with the source
address, never with the token, and counted per address: after 10 failures
in 15 minutes a failed request from that address gets HTTP 429 until the
window passes. A request with a valid token is not limited. The rate
limiter is ``nrllm-a2a-auth``; override it like TYPO3's own limiters in
``$GLOBALS['TYPO3_CONF_VARS']['SYS']['rateLimiter']['nrllm-a2a-auth']``.
Behind a reverse proxy, name it in
``$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP']``; otherwise every
client counts as the proxy's address and shares one limit.
- **Refusals** name their reason (``error.data`` holds a
``google.rpc.ErrorInfo``) and, where a retry may help, a ``Retry-After``
header.