后端会话恢复与断线续传
Agentdown 的会话真相在后端。浏览器只保存当前连接需要的临时状态,不把 IndexedDB、runtime snapshot 或组件状态当作权威存储。
页面加载 -> GET 后端事件归档 -> adapter 恢复 runtime
-> 若 run 仍在执行,GET 只读事件流 + 最新 cursor
-> 新发送 / HITL 决策才使用 POST + 新 request id
-> 断流后用同一 request id + 最新 cursor 自动重连
-> 后端只补缺失事件 -> 前端按 SSE id 去重统一 HTTP 契约
FastAPI 与 Spring 示例后端使用相同字段:
session_id:后端会话 id。client_request_id:一次发送或 HITL 恢复操作的稳定幂等键。after_cursor:前端已经应用的最后游标。Idempotency-Key:可替代client_request_id的请求头。Last-Event-ID:可替代after_cursor的 SSE 标准请求头。
每条 SSE 都带 id: {conversation_id}:{cursor}。游标在同一会话内严格递增;客户端断开不会取消后端生产任务。
同一幂等键与相同请求体重连时,后端复用已有运行。相同幂等键承载不同请求时返回 409 Conflict;非法游标返回 422 Unprocessable Entity。
会话归档
刷新时请求:
GET /api/v1/conversations/{conversation_id}响应格式固定为 agentdown.conversation/v1:
{
"format": "agentdown.conversation/v1",
"conversation_id": "session:weather",
"provider_id": "springai",
"latest_cursor": 12,
"status": "completed",
"active_request_id": null,
"updated_at": "2026-08-09T05:00:00Z",
"events": [
{
"cursor": 12,
"event_id": "session:weather:12",
"request_id": "request:send-1",
"event": "done",
"data": { "event": "done", "data": {} },
"created_at": "2026-08-09T05:00:00Z"
}
]
}这里保存框架原始事件,而不是浏览器渲染结果。恢复时仍由 Agno、LangChain、AutoGen、CrewAI 或 Spring AI adapter 解释事件,避免后端与前端各维护一套渲染语义。
如果 status 是 running,响应必须同时提供 active_request_id。前端随后只读续接已有 run,不会重新提交原始 prompt:
GET /api/v1/conversations/{conversation_id}/events?request_id={active_request_id}
Last-Event-ID: 12这个接口没有请求体,也不会创建新运行。归档不存在或 run 不存在时返回 404。
前端接入
use*ChatSession() 会自动生成 client_request_id、推进 eventCursor、重连时携带游标,并过滤重复 SSE id。传入 recovery: {} 即可启用页面加载恢复:
const session = useSpringAiChatSession({
source: '/api/stream/springai',
conversationId: 'session:weather',
recovery: {}
})
// 页面通常无需等待;路由守卫和测试可以等待初始化确定完成。
await session.recoveryReady默认会从 /api/stream/<framework> 推导下面两个统一接口:
GET /api/v1/conversations/{conversation_id}GET /api/v1/conversations/{conversation_id}/events?request_id=...
归档返回 404 会被当成干净的新会话。已完成的会话只恢复事件;运行中的会话会继续消费只读事件流;包含 approval、interrupt 或 handoff 的会话只恢复待处理卡片,不会自动批准或执行。
普通 POST 流发生网络错误时,recovery: {} 也会默认启用自动重试。每次重试沿用同一个 client_request_id,并动态发送最新 after_cursor。调用 session.interrupt() 会立即取消这条重试链,后续是否 resume() 仍由用户决定。
页面可以直接读取:
session.eventCursor:已经应用的后端游标。session.clientRequestId:当前操作的幂等键。session.connectionState:idle / recovering / connecting / reconnecting / recovered / failed。session.recoveryError:自动归档读取或运行中续接的错误;归档404不会写入错误。session.recoveryReady:自动恢复初始化对应的 Promise。session.restoreConversation(archive):直接恢复已获取的后端事件归档。session.loadConversation(loader):保留的手动恢复入口。
可以按需关闭部分行为:
useSpringAiChatSession({
source: '/api/stream/springai',
conversationId,
recovery: {
autoRestore: false, // 不在 setup 时读取归档
autoReconnect: true // 仍为本次页面内的网络断流启用重试
}
})如果网关地址不是 /api/stream/<framework> 形状,可通过 loadArchive 与 resolveEventsSource 接入自定义路径。显式 reconnect: false 会覆盖并关闭 transport 层自动重试。
存储实现边界
仓库里的 FastAPI 和 Spring 实现是进程内参考存储,已经具备后端权威、幂等运行、断线后继续生产和按游标补发的完整语义,但服务重启后数据会消失。
线上部署应把事件日志和运行索引替换成 PostgreSQL/Redis 等持久化实现,并保留这里定义的 HTTP 契约。前端不需要随存储实现变化。