Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

feat(agui): threadId 即会话——服务端持有 transcript + 内核事件级持久化(业务后端不必组装 messages)

Đang mở
#147 14 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
38/100
Loại issue
Tính năng
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
rust
Lĩnh vực
backend-api-design

Hướng nghiên cứu

Start with src/http/handlers.rs near build_session_runtime_parts and src/http/agui.rs near build_agui_runtime; compare their CompositeSink setup with existing SessionPersistenceSink wiring in the CLI and TUI. Then trace the existing persist_transcript_per_turn and persist_run paths and relevant tests. Done means HTTP writes completed messages at event granularity without duplicate transcript entries, and the requested threadId/messages behavior is covered by tests.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

更正说明:本条初版把问题描述成「持久化只到 run 级、需要新建事件级机制」。核对代码后这个描述是错的 ——
内核已经有事件级持久化(SessionPersistenceSink,CLI/TUI 在用)。真实缺口是
HTTP 通道没有接这个 sink。已按实际代码重写。诉求不变,但方案小得多。

一、真实缺口:HTTP 通道没接内核已有的事件级持久化

内核侧已经做对了

src/run_core.rs:303(push_message / push_tool_result)的文档写得非常明确:

Push a message onto the transcript and announce it as [AgentEvent::MessageAppended] in the same breath,
so persistence sinks (SessionPersistenceSink) write the row to transcript.jsonl immediately instead
of waiting for the turn to end
. A process killed mid-turn (SIGKILL / SIGTERM force-exit) therefore
keeps every already completed step's messages, not just those that survived to turn end.

SessionPersistenceSink(src/session/writer.rs:708,lib.rs:139 导出)就是那个 sink。

但只有 CLI / TUI 接了它

crates/recursive-cli/src/main.rs:2672, 2955, 3814, 3862   ← CLI run/repl/loop
crates/recursive-cli/src/cli/resume.rs:715                ← CLI resume
crates/recursive-tui/src/backend.rs:849, 910              ← TUI

HTTP 侧完全没有接。 实测两处的 CompositeSink 组成:

位置 接的 sink
src/http/handlers.rs:2070-2075(REST 会话) EnvelopeSink + MetricsSink + Langfuse
src/http/agui.rs:1036-1043(AG-UI) ChannelSink + MetricsSink

没有 SessionPersistenceSink。

后果

通道 崩溃窗口
CLI / TUI 单条消息(每个 MessageAppended 落盘)
HTTP / REST 一整个 turn —— 靠 drive_turn 末尾的 persist_transcript_per_turn
HTTP / AG-UI 一整个 run —— 靠 agui.rs:1227 run 结束后的 persist_run

一个 agentic run 可能跑几分钟(多轮 LLM + 多个工具调用 + 可能 20+ step)。
这期间磁盘上什么都没有;进程被 OOM/SIGKILL,整个 run 的工作 + 已烧掉的 token 一起丢。
(#115 已经在关心「失败 run 的 token 也要计费」,但那是 run 结束后才写的。)

而且这顺带解决了 #92 为 AG-UI 留的那个洞

handlers.rs:498 解释了为什么 AG-UI 不能用 persist_transcript_per_turn:

AG-UI … reseeds its transcript from the client-supplied messages on every run,
so the append watermark premise ("on-disk prefix == runtime prefix") does not hold there

但 SessionPersistenceSink 根本不依赖那个前提 —— 它消费的是 MessageAppended 事件,
逐行 append 到 transcript.jsonl,与 runtime 的 transcript 前缀无关。

所以 AG-UI 那个「水位线不成立」的理由,对这条路径不适用。 AG-UI 也能拿到单条消息粒度的持久化。


二、方案(很小)

在 HTTP 两个运行时装配点,把 SessionPersistenceSink 加进 CompositeSink:

  • src/http/handlers.rs(REST,build_session_runtime_parts 附近)
  • src/http/agui.rs(AG-UI,build_agui_runtime)

要点:

  1. 复用现成 sink,不新增内核机制
  2. 需要拿到该 session 的 SessionWriter(sink 的构造参数)—— REST 侧 create_session / fork_session /
    cold_load 已经持有 session 目录与 writer 语义;AG-UI 侧 agui_session::resolve_session_dir +
    SessionWriter::open_or_create 已是 persist_run 的既有路径,可直接复用
  3. 注意与既有落盘的重复写:persist_transcript_per_turn 与 persist_run 仍在,
    需要明确二者关系 —— 建议 sink 为准(事件级),turn/run 末的批量落盘退化为兜底或移除,
    否则同一批消息会被写两次
    (runtime/builder.rs:400 已有一条相关注释:「…through their own sink, and the kernel must not double-write」)
  4. 仍不落盘流式事件:PartialToken / PartialReasoning 不进 SessionPersistenceSink
    (它本来就只匹配 MessageAppended / MessageAppendedWithAudit,见 event.rs:366)

这一条我认为是「接线」而不是「造机制」,所以成本远低于初版描述。


三、诉求(不变)

C 端的接入方通常不是纯前端,而是业务侧后端(AG-UI 官方架构里的 Secure Proxy)。两个诉求:

  1. threadId 就是会话 id —— 有它就能继续对话,不需要独立的 resume 概念;messages 可以为空,
    业务后端不必组装历史。
  2. 持久化到事件级(本节一)—— 崩溃窗口 = 单条消息,而不是一整个 run。

关于 messages 语义(契约变更,想请确认)

今天 messages 被解释为完整历史(agui.rs:528:"the server keeps no other per-thread context"),
且空 messages 直接 400:

.ok_or_else(|| PrepareAguiError::BadRequest(
    "RunAgentInput must contain at least one user message or a non-empty context item".into(),
))?

建议改为规则制(不加 mode 字段):

  • 线程已存在 ⇒ 服务端持久化的 transcript 为准;请求里的 messages 只取最后一条 user 消息作为本轮输入
  • 线程不存在 ⇒ 沿用今天的行为,从 messages 播种(CopilotKit 等标准客户端不受影响)
  • 空 messages ⇒ 线程存在时合法

代价(需写进文档):客户端侧对历史的改写/裁剪不会生效 —— 历史由服务端持有。


四、明确不做

  1. 不破坏标准 AG-UI 客户端(messages 非空 + 线程不存在 ⇒ 行为与今天一致)
  2. 不落盘流式事件
  3. 不新增 mode 字段
  4. 不与 #146(身份贯通)合并 —— 前提有重叠、诉求不同

五、外部验收判据(我会怎么验,不改产品代码)

  1. 事件级持久化(关键判据):跑一个长 run(多工具调用),在 run 进行中 SIGKILL,
    重启后断言 —— 已完成的部分在位,且 cost.json 的 token 数不为 0。
    这条能把「事件级」与「turn/run 级」直接区分开。
    同样方法对 CLI 跑一遍应已经通过(对照组,证明判据本身有效)。
  2. messages: [] 合法(RFC 1 的入口)
  3. threadId 即会话:只发 {threadId, messages: []} 或只带最后一条 user → 第二轮应记得第一轮
    (随机 nonce + 两轮之间清空磁盘旁路的排除法)
  4. 标准模式回归:messages 全量重发的既有路径行为不变
  5. 无重复写:同一批消息不应在 transcript 中出现两次

六、建议落地顺序

  1. HTTP 接 SessionPersistenceSink(REST 与 AG-UI 两处)—— 主体,但属接线
  2. 明确 sink 与 persist_transcript_per_turn / persist_run 的关系(去重或降级为兜底)
  3. messages 语义规则 + 空 messages 合法 —— 契约变更,可独立评估

关联

#57(AG-UI 落为原生 session)· #62(input.messages 语义)· #92(per-turn 持久化;
本条指出它的水位线理由对 sink 路径不适用)· #97(SSE 重放)· #115(失败 run 的 token)·
#146(身份贯通,不合并)

优先级

P1。理由:内核层已经做对了,缺的只是一处接线;而收益是 HTTP 两个通道的崩溃窗口
从「一个 run」缩到「一条消息」。

Ngôn ngữ chính
Rust
Star
4
Fork
0
Merge trung bình
5 giờ 32 phút
Pull request đã merge (30 ngày)
7

Chuẩn bị môi trường

  • Có Dockerfile hoặc tệp Docker Compose
  • Không có mẫu pull request
  • Không có hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của jeffkit/recursive

Tất cả issue của jeffkit/recursive

Issue tương tự

Thêm issue về Rust

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.