chat(): sign the state of a suspended run so a resume without persistence cannot change it
Los mantenedores suelen responder en 1 día
@AlemTuzlak ya está trabajando en esto.
Desde el 13/10/2026.
Evaluación
Este issue todavía no se ha evaluado.
Descripción
Without persistence, chat() trusts the client for the state of a suspended run. Sign that state on the server when the run suspends, and verify it when the run resumes.
Problem
A run that ends with outcome: interrupt waits for the client to send answers. With no persistence middleware, the server keeps nothing between the two requests. The resume reads this state back from the client:
- The pending tool calls and their arguments.
chat()rebuilds them from the message history in the request. - The subagent router plan on
SUBAGENT_STARTEDmetadata: agent names, order, and (in #1722) each agent's input.
Nothing in this state is signed. The interrupt binding under tanstack:interruptBinding holds SHA-256 hashes of the tool schemas (defaultInterruptHash in packages/ai/src/interrupt-serialization.ts). The hash has no key. It finds a schema that changed between the two requests. It does not find a client that changed the state.
Effects:
- A client can change the arguments of a pending tool call after the user saw the approval prompt.
- A client can change which agents of the chat a routed resume runs.
- A router is server code, so an app can put a trusted value in a routed input, such as a tenant id. A client can replace it. A schema check does not help, because the new value has the same shape.
With persistence, the server checks a resume against its stored record. This issue does not apply there.
Scope
All interrupts that resume through the client, not only subagents:
- Tool approvals (
needsApproval). - Client tools.
- Generic interrupts from
onInterruptBoundary. - Interrupts raised in a subagent, and the router plan.
All four leave the run through the same suspend point and come back through the same resume point. One signature at that point covers them.
Proposal
- Add one server secret to
chat(), for examplesigningSecret. - When a run suspends, build one canonical JSON value that holds:
- the thread id and the run id
- each pending interrupt: id, tool name, arguments, schema hashes
- the router plan and its inputs, if the turn has one
- an expiry time
- Compute an HMAC-SHA256 of that value and send it with the interrupts.
- On resume, build the same value from the request, compute the HMAC again, and compare. If the values differ, or the expiry time is past, fail the resume with
InterruptResumeValidationErrorand codestale.
Parts that exist:
canonicalInterruptJsongives a stable serialization.- Web Crypto has HMAC in each runtime the library supports, so no new dependency is necessary.
Open questions
- Where the signature travels. An AG-UI resume entry has an interrupt id, a status, and a payload. The signature must be on a field that each client sends back. Use an AG-UI field for this. Do not add a new wire format.
- No secret set. Option: signing is opt-in at first, and a resume with no signature works as it does now, with a warning in development. A later breaking release then requires a secret or persistence.
- Generic interrupts. Confirm that their resume path goes through the same point as tool approvals and client tools.
- Key rotation. A new secret makes each suspended run stale. Decide if
chat()accepts more than one secret for verification.
Limits
- The server still trusts the earlier message history. The signature covers the suspended state only.
- A client can send the same resume two times. The expiry time limits this. To stop it, the server must keep state.
- The client can still read the arguments and the inputs. That needs encryption, which is not part of this issue.
Related
- #1722 adds routed inputs to the plan. Until this issue is done, the safe behavior is to keep routed inputs off the wire, so a turn with routed inputs cannot resume.
- A second way to get the same result is to keep the plan on the server through
routedSubagentPersistence. That needs persistence. Signing does not.
- Lenguaje dominante
- TypeScript
- Estrellas
- 3.2k
- Forks
- 365
- Merge medio
- 1 d 9 h
- PR fusionados (30 d)
- 242
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de TanStack/ai
-
withPersistence does not save activity rows when a run is stopped or failsPosiblemente ocupada @tombeckenham la tomó hace 3 días. Abiertowaiting-on: maintainer
TanStack/ai#1804 · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
Activity events that a chat middleware emits are streamed but never recorded or persistedPosiblemente ocupada @AlemTuzlak la tomó hace 3 días. Abiertowaiting-on: maintainer
TanStack/ai#1803 · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
withPersistence stores the durability batch hint on a stopped or failed assistant messagePosiblemente ocupada @tombeckenham la tomó hace 3 días. Abiertohas-pr waiting-on: maintainer
TanStack/ai#1802 · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
Discriminate status-dependent record and option types (RunRecord, SessionIndexEntry, InputState, durable-only options)Posiblemente ocupada @jherr la tomó hace 3 días. Abiertoenhancement waiting-on: maintainer
TanStack/ai#1799 · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
ai-harness: an input with no principal runs with the owner session's principalPosiblemente ocupada @tombeckenham la tomó hace 4 días. Abiertowaiting-on: maintainer
TanStack/ai#1797 · 1 asignado ·
Los mantenedores suelen responder en 1 día
Todos los issues de TanStack/ai
Issues similares
-
[Android] Nested transforms are composed in reverse orderPosiblemente ocupada @Danyalo la tomó hoy. AbiertoRepro provided
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
software-mansion/react-native-svg#3090 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
radix-ui/primitives#4228 ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 1/5 Menos de una hora Aptitud para principiantes 95/100
rocketride-org/rocketride-server#2705 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
archon-ready archon-size-s archon-type-bug area: web
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
coleam00/Archon#4707 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
HarperFast/grafana-datasource#177 ·