ADR-282: An A2A result is stored with the run's completion
- Status
-
Accepted
- Date
-
2026-11-10
- Amends
-
ADR-234 (an A2A task's run keeps its result in the completing transaction, also after an approval); ADR-242 (a fourth store follows the run's answer ceiling)
- Authors
-
Netresearch DTT GmbH
Note
The decision also covers the A2A event journal that polling, streams and push read; the title names the part the other records amend.
Context
A remote client of the server role (ADR-278) learns the result
of its task by Get, a stream or a push notification, any time after the
task reports COMPLETED. A COMPLETED task without its artifacts is a wrong
answer, not a missing one.
nr-llm keeps one kind of result today. Queued
stores the final answer of a queued run with an origin that COMPLETED, in
tx_, sealed (ADR-234). It runs after
the run has been settled, and a failure there is only logged, so a run can be
COMPLETED without its answer. A run that went through an approval keeps none.
What may be stored is decided by two ceilings per run
(ADR-270): the audit level for events and the answer level for
answers, read by Run. The answer
level follows the global level, except that the global default metadata
keeps a full answer, and a provider or task tightens it
(ADR-242). ADR-270 tightens it per actual provider attempt,
including a fallback, under a run-row lock, and scrubs a stored answer when it
tightens; a later write rereads the ceiling under that lock.
Decision
An A2A task's result is written in the transaction that completes its run.
The artifacts and the final status message go to tx_ (sealed,
like the answer column), and the task's state and the run's status change to
COMPLETED in the same transaction, under the run-row lock ADR-270 uses for
answer writes. If the result cannot be written, the run is not completed: it
fails, and the task says FAILED. A COMPLETED task without its stored result
cannot exist.
Everything a remote party sees comes from one journal, written after the
commit. Agent hands each step to its live observer before
it records it ("emit-before-persist"), and text deltas reach the observer only;
an A2A stream or webhook fed from there could announce what was never stored.
So Get, List, SSE and push read the task view and an A2A event
journal (tx_) instead. A projector writes both after the
completing transaction, from the committed state. It is idempotent per run
revision: the same revision projected twice yields its events once. A projector
failure is retried from the committed state and never executes the model or a
tool again. The journal holds only what may leave the installation (state,
status message, the stored artifacts), never a prompt, a tool argument, raw
provider output or an audit row.
The store follows the answer ceiling, not the audit level. The result is
what the task exists for, like a background answer. full keeps it,
redacted keeps the redacted text, as ADR-234 does. The audit level of the
events does not matter. This does not bypass ADR-270: the answer ceiling is
read under the lock at completion, and a tightening that scrubs answers scrubs
this store as well.
A profile that cannot keep a result is not executed. A publication profile
whose configuration resolves to an answer level of metadata (set explicitly
on its provider or task) or none is refused before a task is created, with
Unsupported (-32004) and an Error reason
RESULT_. This applies to the blocking mode too: a blocking
call that reaches its cap (ADR-283) is continued by
Get, which needs the store.
A ceiling that tightens during the run fails the task, distinguishably. A
fallback can move the answer ceiling to metadata or none after the task
was accepted. Then nothing is stored, the run ends FAILED, and the task says
FAILED with a status message saying the result could not be kept under the
installation's privacy policy, and Task. carrying the reason
RESULT_. No path returns the content, the blocking response
included. A task that COMPLETED with no artifacts is a different, legitimate
state.
An A2A run keeps its result after an approval. ADR-234's "a run that goes through an approval keeps none" does not apply to a run that belongs to an A2A task: the remote client has no other way to learn what was done.
The task's history follows the same rule. Task. holds the
client's messages and the agent's status messages; a later turn in the same
context reads it, and Get returns it. It is sealed, kept at the answer
level like the result (redacted keeps redacted text), and stored nowhere
else. A profile that cannot keep a result cannot keep a history either, so
the refusal above covers both.
The result is purged with its run. The task row and its sealed columns are
removed by the run purge (privacy.); afterwards
Get answers Task, which the specification allows for a
purged task.
Consequences
- ●● A task that says COMPLETED always has its result.
- ●● Polling, streams and webhooks show the same states in the same order, and none of them shows a state that was not committed.
- ● One privacy rule for every answer an installation keeps; nothing new to configure.
- ◑ A profile on a provider or task tightened to
metadatacannot be published; the backend says why when the profile is saved. - ◑ A fallback to a stricter provider fails a task that would otherwise have completed.
- ✕ The completion of an A2A run takes a lock and a second table write in its transaction.
- ✕ A stream lags the run by the projector's latency.
Net Score: +5
Alternatives considered
Feed streams from the executor's live callbacks. Rejected: they fire before the step is stored, so a client could see a state the database never held.
Use Queued. Rejected: it writes after
completion and only logs a failure, keeps nothing after an approval, and stores
one text where a task has artifacts.
Keep the result regardless of the answer ceiling. Rejected: it would undo ADR-270 for one store, and an operator who tightened a provider would find its output stored anyway.
Refuse only non-blocking calls under a no-storage profile. Rejected: a blocking call that reaches its cap needs the store just the same.