chat(): sign the state of a suspended run so a resume without persistence cannot change it
Maintainer thường phản hồi trong vòng 1 ngày
@AlemTuzlak đang làm issue này rồi.
Từ ngày 13/10/2026.
Đánh giá
Issue này chưa được đánh giá.
Mô tả
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.
- Ngôn ngữ chính
- TypeScript
- Star
- 3.2k
- Fork
- 365
- Merge trung bình
- 1 ngày 9 giờ
- Pull request đã merge (30 ngày)
- 242
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của TanStack/ai
-
withPersistence does not save activity rows when a run is stopped or failsCó thể đã có người làm @tombeckenham đã nhận 5 ngày trước. Đang mởwaiting-on: maintainer
TanStack/ai#1804 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Activity events that a chat middleware emits are streamed but never recorded or persistedCó thể đã có người làm @AlemTuzlak đã nhận 5 ngày trước. Đang mởwaiting-on: maintainer
TanStack/ai#1803 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
withPersistence stores the durability batch hint on a stopped or failed assistant messageCó thể đã có người làm @tombeckenham đã nhận 5 ngày trước. Đang mởhas-pr waiting-on: maintainer
TanStack/ai#1802 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Discriminate status-dependent record and option types (RunRecord, SessionIndexEntry, InputState, durable-only options)Có thể đã có người làm @jherr đã nhận 5 ngày trước. Đang mởenhancement waiting-on: maintainer
TanStack/ai#1799 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
ai-harness: an input with no principal runs with the owner session's principalCó thể đã có người làm @tombeckenham đã nhận 6 ngày trước. Đang mởwaiting-on: maintainer
TanStack/ai#1797 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
Issue tương tự
-
agent/scanner help wanted hive/claimed-by-scanner kind/bug stage/020-claimed
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 90/100
hivecommons/rationguard#315 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
agent-ready area: ui-sdk priority: p3 type: chore
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
schlessera/brain-kit#2534 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug lane:gh-5
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
LLM-4-People/bridlewise-testing#122 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug priority: P2
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement priority:low ready-for-dev
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 67/100
Maintainer thường phản hồi trong vòng 1 ngày