Hacktoberfest 2026: die Issues, die Maintainer für den Oktober markiert haben – offen und einsteigerfreundlich. Hacktoberfest-Issues durchsuchen

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

Offen
#147 14 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Maintainer antworten meist innerhalb von 1 Tag

Dieses Issue hat noch niemand übernommen.

Bewertung

Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Anfängerfreundlichkeit
38/100
Issue-Typ
Feature
Klarheit
Klar beschrieben
Aktivitätsstatus
Aktiv
Tech-Stack
rust

Rechercherichtung

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.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

更正说明:本条初版把问题描述成「持久化只到 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」缩到「一条消息」。

Vorherrschende Sprache
Rust
Sterne
4
Forks
0
Ø Merge
5 Std. 32 Min.
Gemergte PRs (30 T.)
7

Entwicklungsumgebung

  • Enthält ein Dockerfile oder eine Docker-Compose-Datei
  • Keine Pull-Request-Vorlage
  • Kein Beitragsleitfaden

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus jeffkit/recursive

Alle Issues in jeffkit/recursive

Ähnliche Issues

Weitere Issues zu Rust

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.