Skip to content

feat(adk): add persistent sessions, background tasks, and long-term memory - #1358

Open
hi-pender wants to merge 185 commits into
mainfrom
alpha/10
Open

hi-pender wants to merge 185 commits into
mainfrom
alpha/10

Conversation

@hi-pender

@hi-pender hi-pender commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Release overview

This PR merges alpha/10 into main, extending ADK with persistent conversation timelines, independently managed background work, and long-term memory.

The main capabilities are:

  • Continue a conversation from persisted state. Session events capture inputs, outputs, history edits, and turn lifecycle information so later turns can reconstruct the conversation.
  • Keep long-running work independent of the parent turn. Tools, Shell commands, and sub-agents can use foreground/background execution policies, report progress, and deliver completion notifications. Capable executors can recover or resume work through explicit protocols.
  • Reuse and consolidate memory across conversations. AutoMemory selects and injects relevant memory and extracts new memory; Dream provides manual or scheduled consolidation.

Supporting changes align middleware, message handling, serialization, and Compose execution with these capabilities. The feature inventory below covers alpha-side changes; main-only changes and behavior already present on main are excluded from the summary.

What changes

Persistent sessions and turn execution

Add typed session event logs with persistence, pagination, replay, message mutations, and rollback, along with file and in-memory store implementations. Runner integrates session history with message IDs, turn correlation, usage, and model/tool attempt events.

TurnLoop execution is reorganized around run, stop, preemption, and checkpoint handling. Related fixes address cancellation/resume behavior, tool-result ordering across turns, terminal stream errors, and checkpoint memory amplification. These changes make agreement between live state and replay a central runtime contract.

Background tasks and reusable sub-agents

Add task management and store protocols for lifecycle transitions, leases, heartbeats, recovery, and parent notifications, plus middleware for querying and stopping tasks.

Execution adapters cover local work, managed tools, Shell commands, and sub-agents. Foreground/background handoff, progress projection, durable input requests, and worker handoff have explicit boundaries. Recovery depends on the configured store and executor capabilities; external tools must satisfy the relevant idempotency and checkpoint contracts.

Reusable sub-agent middleware supports independent sessions and foreground/background execution. Deep Agent composition uses the new sub-agent and task-control components. Filesystem and Shell changes add incremental output, multi-modal file handling, long-running Bash integration, and backend-owned command timeouts.

Long-term memory

AutoMemory adds memory indexing, topic selection, synchronous/asynchronous injection, and post-conversation extraction, with configurable backends and prompts. Asynchronous injection and persisted replay use the same ordering semantics.

Dream adds manual and scheduled memory consolidation, session-touch tracking, coordination locks, optional session-history search, and custom handlers.

Middleware and context consistency

Skill, ToolSearch, and AGENTS.md reminders become part of persisted history. Skill refresh and reminder customization improve how these instructions are maintained across turns.

Reduction, Summarization, and tool-history repair emit or preserve the history changes needed for replay. Related improvements retain message metadata, reuse summarization prompt prefixes, preserve loaded skills, and repair orphaned or duplicate tool results. PlanTask tool descriptions are also refined.

Shared APIs and execution foundations

Add model response-format options and shared JSON/Gob serialization. Extend typed agent contexts and tool direct-return controls, including a compatibility alias for the default ChatModelAgentContext spelling.

Schema changes improve streamed concatenation, retain tool-result metadata, and prevent media formatting from mutating shared templates. Compose changes refine tool-node middleware and interruption/checkpoint behavior, make WithForceNewRun independent of option order, and return a validation error for missing Workflow branch endpoints.

Review map

The boundaries combine package ownership with end-to-end functionality: use a package where its public interface has a distinct responsibility, and group files across packages where one runtime behavior needs to be reviewed together.

Review area Modules included Main code locations
Conversation lifecycle Session persistence and timeline; TurnLoop and cancellation/resume adk/session*.go, adk/session/, adk/runner.go, adk/turn_loop*.go, adk/interrupt.go
Agent execution ChatModelAgent and middleware invocation adk/chatmodel.go, adk/handler.go, adk/runctx.go, adk/wrappers.go, retry/failover implementations
Task lifecycle Background task management and controls; local execution; recoverable tools adk/backgroundtask/, adk/backgroundtask/local/, adk/backgroundtask/tool/, adk/middlewares/backgroundtask/
Delegation and external execution Sub-agent execution; filesystem and Shell; Deep Agent composition adk/agent_tool.go, adk/middlewares/subagent/, adk/backgroundtask/subagent/, adk/filesystem/, adk/middlewares/filesystem/, adk/backgroundtask/shell/, adk/prebuilt/deep/
Memory AutoMemory; Dream adk/middlewares/automemory/, adk/middlewares/automemory/dream/
Instructions and history Skill; ToolSearch; AGENTS.md; PatchToolCalls; Reduction; Summarization; PlanTask Corresponding packages under adk/middlewares/, including shared reminder insertion helpers
Shared foundations Message schema; structured output; serialization; Compose schema/, components/model/option.go, internal/serialization/, compose/

Checks across module boundaries

Scenario Review together Key behavior to verify
Persist, edit, and replay a conversation Session + agent execution + history middleware Event/message identity, persistence retries, event ordering, rollback, and agreement between live and replayed history
Cancel, preempt, or resume a turn TurnLoop + Runner + Compose + sub-agents Cancellation classification, saved inputs/results, checkpoint ownership, and resume decisions
Hand off or recover a long-running task Task manager + store + execution adapter Lease authority, external side effects, checkpoint commits, recovery, notifications, and natural completion during drain
Delegate work and stream results Sub-agents + task management + Deep Agent Parent/child event isolation, independent sessions, stream ownership, and foreground/background transitions
Execute a long Shell command Filesystem/Shell + task adapters Backend timeout versus foreground waiting, incremental output, and recovery capability
Inject and compress context across turns Session + memory + instruction/history middleware Reminder anchors, asynchronous insertion order, metadata retention, and prompt-cache prefix stability

Compatibility and validation

Review public API and configuration changes together with runtime behavior, particularly typed/default aliases, Session/store implementations, recoverable tool contracts, filesystem backends, and Deep Agent configuration. The restored ChatModelAgentContext alias has downstream-facing regression coverage; this does not establish compatibility for every changed API.

The branch adds regression, integration, and store/executor conformance tests for persistence, replay, background execution, and recovery. No additional local tests were run while preparing this release PR. Module review, API compatibility assessment, and validation of the final merge result remain pending. Assess this PR's CI results for race-enabled tests, supported Go versions, benchmarks, lint, and API differences before merging.

Comment thread schema/agentic_message.go Outdated
continue
}

merged, err := concatFunctionToolResultBlocks(ret[len(ret)-1], block)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题简述: 这里逐块合并累计结果,会使无 StreamingMeta 的长工具结果流仍产生 O(n²) 复制。

导致问题的链路: 每次循环都把 ret[len(ret)-1] 中已经累计的完整文本与下一个 block 重新传给 concatFunctionToolResultBlocks。第 2 次复制前两块,第 3 次再复制前三块,以此类推。即使内层改用预分配的 strings.Builder,整个长流的累计复制量仍是平方级,与本次降低长流复制成本的目标不符。

修复建议: 先收集属于同一次调用的一整段连续 block,再一次性合并 FunctionToolResult.Content 和 Extra;不要在每收到一个 block 时重新构造此前的累计结果。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #1388。连续的 FunctionToolResult blocks 先按整段确定范围,再一次合并 Content 和 Extra,避免逐块重建累计结果;保留空 CallID/Name 的原有合并规则。

Comment thread schema/serialization.go
@@ -29,6 +29,8 @@ func init() {
RegisterName[[]*Message]("_eino_message_slice")
RegisterName[*AgenticMessage]("_eino_agentic_message")
RegisterName[[]*AgenticMessage]("_eino_agentic_message_slice")
RegisterName[*ToolInfo]("_eino_tool_info")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题简述: 在包初始化阶段为 ToolInfo 固定注册名称,会使此前合法的用户自定义注册产生 breaking change。

导致问题的链路: 旧版本允许 SDK 用户调用 schema.RegisterName[*schema.ToolInfo]("custom_name"),并可能已经用该名称生成 checkpoint。升级后,schema.init 会先把同一具体类型注册为 _eino_tool_info;用户原有注册随后会因 gob 不允许同一类型对应多个名称而 panic。同时,历史 checkpoint 中的自定义 $type 名称也无法通过当前单一 type-to-name 注册表恢复。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

确认这是 breaking change。本次保留 ToolInfo 和 []*ToolInfo 的固定名称注册。

升级后,用户此前对这些类型使用其他名称的自定义注册会发生冲突;依赖旧注册名称的历史 checkpoint 也不能直接沿用新默认名称恢复。发布时需要在 release notes 中明确说明这项不兼容变更,并说明受影响用户的注册调整及历史 checkpoint 迁移要求。

Comment thread compose/tool_node.go Outdated
inner := func(ctx context.Context, input *ToolInput) (*ToolOutput, error) {
return next(core.PopAddressSegment(ctx), input)
}
return spec.fn(inner)(mwCtx, input)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题简述: 设置 Name 后,middleware factory 会从构建 endpoint 时执行一次,变成每次工具调用都重新执行;Name 本应只增加可恢复的地址身份,不应改变 middleware 生命周期。

导致问题的链路: 当前 spec.fn(inner) 位于请求闭包内。用户给已有 middleware 增加 Name 后,每个工具调用都会重新创建 middleware endpoint,factory 内捕获的计数器、缓存、限流器等状态会反复重置,并可能在并发工具调用中重复初始化。同样的问题也存在于另外三类命名 middleware wrapper。

修复建议: 在 wrap*ToolMiddleware 构建阶段先创建稳定的 inner,执行一次 wrapped := spec.fn(inner);请求闭包只负责追加 middleware 地址并调用 wrapped(mwCtx, input)。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #1388。四类命名工具 middleware 均改为在构建 endpoint 时执行一次 factory;每次调用只处理 middleware 地址并调用已构建的 endpoint。

@hi-pender
hi-pender requested a review from N3kox October 9, 2026 05:07
Comment thread compose/graph_run.go
@@ -607,7 +609,7 @@ func (r *runner) handleInterrupt(
for _, t := range nextTasks {
cp.Inputs[t.nodeKey] = t.input
}
err = r.checkPointer.convertCheckPoint(cp, isStream)
err = r.checkPointer.convertCheckPoint(cp, isStream, is)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题简述: 这里的 checkpoint 转换可能改写 cp.RerunNodes,但此前构造的 intInfo.RerunNodes 不会同步,导致公开中断信息与实际恢复行为不一致。

导致问题的链路: 工具输出流在物化时返回 toolStreamCheckpointError,applyToolStreamCheckpoint 会删除下游 consumer,并把真正的 tools node 加入 cp.RerunNodes;resume 随后使用更新后的 checkpoint,但 ExtractInterruptInfo 和子图上报仍直接返回转换前的 intInfo。相同的遗漏也存在于 handleInterruptWithSubGraphAndRerunNodes。

修复建议: 在 convertCheckPoint 成功后,用最终的 cp.RerunNodes 更新或重新构造 intInfo.RerunNodes,并对另一条保存路径做相同处理;补充断言,确保公开 metadata、嵌套 SubGraphs 与持久化 checkpoint 一致。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #1388。两条中断处理路径都在 convertCheckPoint 完成后生成公开 RerunNodes;子图仍通过 InterruptInfo.SubGraphs 表达,不直接将 checkpoint 中的子图恢复节点混入公开 RerunNodes。

@N3kox
N3kox added this pull request to stack #1362 October 9, 2026 13:24
Comment thread adk/chatmodel.go Outdated
// instruction, so it is preserved.
if len(inputMessages) > 0 && inputMessages[0].Role == schema.System &&
!isReminderExtra(inputMessages[0].Extra) {
inputMessages = inputMessages[1:]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] 调用方传入的首条 system message 会被静默删除

问题简述: 当 Agent 配置了非空 Instruction,且 SDK 用户通过 input.Messages 显式传入普通 system message 时,该消息仅因 role 和位置符合条件就会在这里被移除。

导致问题的链路: defaultGenModelInput 删除首条非 reminder system message,随后在输入首位加入 Agent instruction。调用方传入的租户策略、安全约束等 system 内容因此不会进入最终模型输入。AgenticMessage 分支存在相同行为。

Comment thread adk/prebuilt/deep/deep.go Outdated
if instruction != "" {
if len(inputMessages) > 0 {
if msg, ok := any(inputMessages[0]).(*schema.Message); ok && msg.Role == schema.System {
inputMessages = inputMessages[1:]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Deep 会删除调用方 system message以及 middleware 注入的 system reminder

问题简述: Deep 始终具有非空 instruction,并固定使用 typedGenModelInput;这里仅根据首条消息的 role 进行删除,没有区分消息来源,也没有识别 MessageExtraKeySystemReminder。

导致问题的链路: 当调用方的 system message或 middleware 注入的 system reminder 位于输入首位时,该消息会在 Deep instruction 加入前被移除,最终无法发送给模型。AgenticMessage 分支存在相同行为。

Comment thread adk/chatmodel.go Outdated
if reflect.DeepEqual(a, b) {
return true
}
aBytes, aErr := json.Marshal(a)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

想确认一下是否确实需要引入 model_context 事件,以及目前是否已经有明确的 consumer。如果需要,还要评估它在模型调用路径上的性能成本:每次都需要遍历 ToolInfos 做 reflect.DeepEqual,比较不等时还会对两边分别执行 json.Marshal;在工具数量较多、参数 schema 较大或 ReAct 模型调用频繁时,这部分开销可能比较明显。

Comment thread adk/internal/config.go
ReminderMessageRoleUser
)

var reminderMessageRole atomic.Value

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] ReminderMessageRole 的 session 级契约实际是进程全局

问题简述: 这里将 ReminderMessageRole 保存为包级单例,但公开 API 声明它的作用域是单个 session。公开 setter 没有携带任何 session 信息,因此任一 session 的设置都会覆盖同一进程内所有 session 读取到的角色。

导致问题的链路: Session A 设置为 User 后,Session B 的 toolsearch、skill、subagent middleware 会在模型调用前读取同一个全局值,用它归一化已有 reminder并创建新 reminder;其他 session 随后设置为 System,又会反向改变 A、B 下一次模型调用看到的角色。atomic.Value 只能避免数据竞争,不能提供 session 隔离。共享进程中使用不同模型约束的 session 因而会互相改变 reminder 角色,可能导致模型拒绝请求或错误理解 reminder。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里还涉及一个更通用的扩展问题:内置 middleware 在返回前就通过 TypedSendEvent 发出了 MessageInserted 事件。用户即使包装这个 middleware,在拿到返回值后修改 message,也无法同步修改已经发出的事件;仅修改模型输入则不会改变 SessionStore 中保存的消息。

如果用户希望自定义内置 middleware 注入的消息,并让修改后的消息进入 session 历史,目前缺少事件发送前的修改入口。这可能涉及 session event / SessionStore 的整体设计,建议单独讨论消息修改与事件持久化的扩展契约。

Comment thread adk/wrappers.go Outdated
ended: ended,
msg: result,
err: err,
accepted: err == nil,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] ShouldRetry 的 Generate 路径在 verdict 前提交了错误的 span 状态

问题简述: ShouldRetry 的 Generate 路径中,这里发生在 verdict 之前,因此模型无错误返回、但随后被 ShouldRetry 拒绝的 attempt 也会被记录为 Accepted=true。

导致问题的链路: retry wrapper 先设置 suppressEventSend 并调用 event sender;本函数先发送 model end span,之后才检查 suppression。最终接受某次输出时,retry 层只补发普通 model output event,没有执行正常路径中创建 SessionEventVariant、写入 CurrentModelSpanID 和 CurrentAssistantMessageEventID 的逻辑。后续 tool span 因而会读取到空值或旧值。即使 Session Runner 之后为 output event回填 SessionEventVariant,也不会把这些 ID 写回 typed state。每次 attempt 仍有独立 span ID 和正确的 Attempt 序号,但 rejected attempt 的 Accepted 状态及最终 tool span 关联是错误的。

修复建议: 在 ShouldRetry verdict 后再提交 Generate 的 span completion 和 accepted output 状态:被拒绝的 attempt 以 Accepted=false 结束;最终接受的 attempt 统一创建 assistant event identity、更新两个 Current ID,并发送与该 model span 对应的 output event。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #1388。正常返回的 Generate attempt 在 ShouldRetry verdict 后结束 span,被拒绝时记录 Accepted=false;最终接受的输出复用完整发送逻辑,创建 assistant event 并更新 CurrentModelSpanID、CurrentAssistantMessageEventID。模型报错、中断或取消时保留立即结束 span 的原有行为。

Comment thread adk/runctx.go
var typedEvents any
rs.mtx.Lock()
events = append(events, rs.Events...)
typedEvents = rs.TypedEvents

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题简述: 锁内只复制了 TypedEvents 的 interface 值,其动态值仍指向原 slice header。解锁后的 len(*store) 和遍历会与 addTypedEvent 中的 *store = append(...) 并发读写同一个 slice header。

导致问题的链路: cancel checkpoint 保存时可能尚未等待上游 typedFlowAgent.run 完全退出,上游仍可能追加 typed event,因此 checkpoint 中保存的事件集合没有稳定的快照边界,并会触发数据竞争。

修复建议: 在持有 rs.mtx 时复制 *store 的元素,解锁后只对独立快照执行 sanitize。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #1388。在 rs.mtx 锁内复制 TypedEvents slice 元素,解锁后仅对独立列表快照执行 sanitize,避免与 addTypedEvent 并发读写同一个 slice header。

Comment thread adk/chatmodel.go Resolved
Comment thread adk/call_option.go Resolved
Comment thread adk/session.go
// uses pointer-to-slice semantics (nil = absent, non-nil = active replacement).
type SessionEvent[M MessageType] struct {
// EventID is the canonical, session-unique identity of this event.
// Assigned exactly once by the Runner at event materialization

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的公开 API 注释存在不一致和契约缺失,SDK 用户无法明确判断 EventID、TurnID 是否应该自己填写:

  1. SessionEvent.EventID 注释仍称 ID 在 Runner materialization 时分配,但当前包装器已经提前生成,注释与实现不符。
  2. 同一字段注释称 Runner 分配的是 UUIDv4,但 SessionConfig.EventIDGenerator 允许返回业务 ID。应明确 UUIDv4 只是默认规则。
  3. TypedSendEvent / SendEvent 没有说明 EventID、TurnID 的填写要求,也没有说明用户显式填写时会被保留还是覆盖、EventID 唯一性由谁负责。TurnID 字段虽然写了 runner-owned,发送接口处仍缺少直接可用的契约。

这些说明应在字段和发送接口的公开注释中统一,不能让 SDK 用户通过阅读内部实现推断。

Comment thread adk/session.go

// ValidateEmittedSessionEventKind enforces that runtime-emitted session events
// carry an explicit Kind matching their active payload.
func ValidateEmittedSessionEventKind[M MessageType](event *SessionEvent[M]) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个函数只是在 NormalizeSessionEventKind 前增加 nil 和 Kind 非空检查,当前调用也都在 adk 包内,没有独立的 SDK 使用需求。为了这层很薄的内部校验新增一个公开 API,会让用户额外理解“emitted event”的概念并增加兼容性维护负担。建议改为未导出的内部函数,不必暴露给 SDK 用户。

Comment thread adk/session.go

// ClassifySessionEvent derives the canonical event kind from the single active
// payload carried by event.
func ClassifySessionEvent[M MessageType](event *SessionEvent[M]) (SessionEventKind, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SessionEvent 已有 Kind 字段,SDK 用户可以直接读取;根据 payload 重新推导 Kind 的逻辑主要供内部补齐和一致性校验使用,当前也没有包外生产代码调用。没有必要将这个实现步骤单独暴露为公共 API,增加用户需要理解的概念和兼容性维护负担。建议将 ClassifySessionEvent 改为未导出的内部函数,一致性校验仍由发送/存储边界负责。

Comment thread adk/session.go
// mechanism: legacy data (e.g. pre-refactor "turn_end") and events written by
// newer code with kinds we don't yet understand are accepted as-is rather than
// failing replay.
func NormalizeSessionEventKind[M MessageType](event *SessionEvent[M]) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个函数承担 Kind 补齐、payload 一致性校验和旧事件兼容,主要是框架及 store 的内部处理,没有必要要求普通 SDK 用户理解和调用。内置 adk/session store 的跨包复用也不足以成为公开 API 的理由;目前没有说明第三方 store 必须使用它的契约。建议收敛为内部实现,并调整内置 store 的共享方式(例如在依赖允许时放入共享 internal 包),避免将内部兼容规则固化成公共 SDK API。

Comment thread adk/session.go Resolved
Comment thread adk/session.go
// with additional event-envelope Extra. Provider output is merged before
// EventIDGenerator runs, so custom generators can inspect the final Extra map.
// Returned keys prefixed with "_eino_" are rejected.
EventExtraProvider SessionEventExtraProvider[M]

@mrh997 mrh997 Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EventExtraProvider 的命名待讨论,注释也未说明入参字段的可用性:流式预留 ID 时,即使 Kind == message,Message 仍为 nil,EventID 也为空,调用方无法从当前契约得知这些情况。

Comment thread adk/retry_chatmodel.go Outdated
@@ -255,7 +256,15 @@ type TypedModelRetryConfig[M MessageType] struct {
// ModelRetryConfig is the default retry config type using *schema.Message.
type ModelRetryConfig = TypedModelRetryConfig[*schema.Message]

type retryableBeforeOutputError interface {

@mrh997 mrh997 Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里应删除 retryableBeforeOutputError 及 errors.As 的特殊判断,默认重试判断只保留 return err != nil。SDK 默认逻辑不应识别业务项目自定义的 IsModelTimeoutBeforeOutput() 方法来改变行为;业务侧的特殊重试条件放到已有的 ShouldRetry 中即可,不需要再增加接口或概念。

Comment thread adk/runner.go
// Capture caller-provided messages BEFORE prepending history. These will be
// emitted as session events at turn start so they appear in the event log.
sessionState.inputMessages = append([]M{}, messages...)
messages = append(append([]M{}, sessionState.latestState.Messages...), sessionState.inputMessages...)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Run 在这里已把历史拼接进独立的输入切片,但 sessionState.latestState.Messages 仍由事件处理流程持有到运行结束。两份切片起初共享消息对象;后续 Agent 摘要、删除或替换历史后,这份旧引用可能继续保留已不需要的消息,增加长时间运行的内存占用。这是运行期间的冗余引用,不是已确认的永久泄漏。

请在拼接完成后立即设置 sessionState.latestState.Messages = nil,保留 latestState 的工具上下文等字段即可。无需感知后续摘要时机,也不会影响已经传给 Agent 的输入。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #1388。Run 将历史拼入独立输入切片后,立即清空 sessionState.latestState.Messages,移除运行期间不再需要的额外历史引用,保留工具上下文字段。

Comment thread adk/runner.go
return nil, "", fmt.Errorf("failed to reconstruct session[%s]: %w", sessionID, err)
}
if reconstructResult != nil && reconstructResult.state != nil {
state.latestState = reconstructResult.state

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resume 从事件日志重建完整历史并保存在 latestState.Messages,但实际执行历史来自 checkpoint;后续只读取 latestState 的工具上下文信息,没有使用这份消息列表。sessionState 又在整个恢复执行期间被持有,导致长会话恢复时额外保留一份重建的历史消息。

请让 Resume 只重建所需的工具上下文信息;至少在准备阶段清空 latestState.Messages,避免持续保留无用消息。清空只能减少持续占用,不能消除完整历史重建本身的耗时和内存峰值。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已在 #1388 修复持续持有部分:Resume 重建完成后立即清空 latestState.Messages,仅保留后续使用的工具上下文字段。当前仍执行完整历史重建,因此尚未消除重建阶段的耗时和内存峰值;本次按最小修复范围处理,未改变现有历史重建、回滚及替换边界逻辑。

Comment thread adk/runner.go
type TypedRunnerConfig[M MessageType] struct {
Agent TypedAgent[M]
EnableStreaming bool

CheckPointStore CheckPointStore

SessionID string

@mrh997 mrh997 Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的 SessionID、SessionStore 没有字段注释,用户无法知道配置后的效果和组合要求。请明确:SessionID 由调用方提供;两者同时有效才启用消息托管,当前只配一个会静默按非托管模式运行;Run 会加载该会话当前历史并拼接本次输入,运行事件写入 SessionStore;SessionConfig 可省略并使用默认值。

Comment thread adk/turn_loop.go Outdated
@@ -680,97 +166,25 @@ type GenResumeResult[T any, M MessageType] struct {
// ResumeParams are optional parameters for resuming an interrupted agent.
ResumeParams *ResumeParams

// Decision selects whether to resume the suspended checkpoint or abandon it
// and start a fresh turn. The zero value resumes for compatibility.
Decision TurnLoopResumeDecision

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

首版先不要暴露这套恢复决策配置。当前仓库中 StartNewTurn 只有实现和测试使用,尚未体现明确的业务需求,却引入了“恢复 checkpoint / 放弃 checkpoint 并新开一轮”的公开契约及额外执行分支。请一起移除 Decision、TurnLoopResumeDecision 和仅供 StartNewTurn 使用的 GenResumeResult.Input,让 GenResume 只负责生成恢复所需的参数和待处理输入的分配;等确有放弃旧执行并开启新一轮的需求时,再设计相应 API。

Comment thread adk/turn_loop.go Outdated
TurnLoopResumeDecisionResume TurnLoopResumeDecision = iota
// TurnLoopResumeDecisionStartNewTurn abandons the checkpoint and starts a
// fresh Runner.Run turn using GenResumeResult.Input.
TurnLoopResumeDecisionStartNewTurn

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里是否有必要增加 StartNewTurn?建议考虑删除 TurnLoopResumeDecision、GenResumeResult.Decision 和 Input,让 GenResume 只负责生成恢复参数。

如果需要选择“恢复旧执行”或“开始新任务”,业务层也可以在外部判断:恢复时加载 checkpoint;开始新任务时明确废弃旧 checkpoint,再启动不加载旧断点的 TurnLoop 或调用 Runner.Run。这样可以减少公开 API 和 TurnLoop 内部的决策分支。待讨论。

Comment thread adk/interrupt.go Outdated
// InterruptSignalFromEvent returns the internal interrupt signal carried by an
// interrupt event as an error. Nil events, events without actions, and
// non-interrupt events return nil.
func InterruptSignalFromEvent[M MessageType](event *TypedAgentEvent[M]) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议删除 InterruptSignalFromEvent。当前 eino 非测试代码中没有调用方;已有内部中断传播直接读取 internalInterrupted,普通 SDK 用户则可通过 InterruptContexts 查看中断信息。这个方法返回内部 signal 作为 error,但没有说明明确的包外使用场景和后续处理契约,暂时没有必要新增公开 API。

// conversations are summarized automatically). When nil, the default note is used;
// returning "" suppresses the note entirely.
// Optional.
CustomFormatContextManagementInstruction func(ctx context.Context) string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这段 prompt 没有提供更多有效的指导,建议不追加这段说明,也没有必要为了它增加 CustomFormatContextManagementInstruction 配置。

// consolidation: Read, GlobInfo, Write, and Edit.
//
// LocalBackend and InMemoryBackend both implement this interface.
type Backend = ainternal.Backend

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里是否也需要考虑通过 Bash 操作记忆文件的使用方式,而不是把加载/整理能力限定在 Read、GlobInfo、Write、Edit 这组文件接口上?如果确有该需求,可以复用已有 filesystem.Shell 作为可选能力,并明确命令执行与 Backend 是否访问同一文件空间、MemoryDirectory 的目录限制如何生效。不要把 Execute 强制加入这个最小存储接口,否则 InMemoryBackend 等只提供文件存储的实现也会被迫承担 Shell 能力。

}

func buildPendingSnapshot[M adk.MessageType](messages []M, cursor int, toolInfos []*schema.ToolInfo) (*PendingSnapshot, error) {
raw, err := json.Marshal(messages)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AutoMemory 内的 JSON 编解码统一改用 sonic.Marshal / sonic.Unmarshal,包括这里的消息、ToolInfos,以及 coordinator 中 PendingSnapshot 的序列化和反序列化。请检查 AutoMemory 范围内的其他同类调用一起替换;本条仅针对 AutoMemory,不扩展到其他模块。

N3kox and others added 23 commits November 12, 2026 14:42
#1145)

feat(adk): add response format fallback for automemory topic selection
…tem tools (#1298)

* fix(adk): prefer automemory extraction tool definitions

* feat(adk): support custom dream agent handlers
refactor(adk): internalize conformance test helpers
chore: remove internal review and release artifacts
alpha/10 genericized ChatModelAgentContext into ChatModelAgentContext[M
MessageType] without leaving a non-generic spelling behind. Every
downstream middleware that names the type in its BeforeAgent signature
stopped compiling, because *adk.ChatModelAgentContext is no longer a
valid type expression once the type takes a parameter.

Follow the convention already used by the 19 other genericized ADK types
(ModelContext, ChatModelAgentState, Runner, AgentEvent, ...): rename the
generic type to TypedChatModelAgentContext[M] and add

    type ChatModelAgentContext = TypedChatModelAgentContext[*schema.Message]

Type aliases are source-compatible, so existing code keeps compiling
unchanged, and callers on a non-default message type gain the Typed form.

All in-repo references already carried explicit type arguments, so they
were renamed mechanically with no semantic change.

Adds adk/handler_alias_test.go in package adk_test, which declares
BeforeAgent with the bare, non-generic *adk.ChatModelAgentContext exactly
as a downstream user would and asserts the result satisfies
adk.ChatModelAgentMiddleware. That file does not compile without the
alias, so it guards the compatibility contract at build time.

Verified: go build, go vet, go test -race ./..., golangci-lint (0 issues).
apidiff no longer reports the type as gaining a type parameter.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(serialization): preserve typed persistence round trips

* fix(serialization): preserve native gob encode behavior

* fix(serialization): retain existing ToolInfo JSON contract

* fix(serialization): preserve signed zero and expand regression coverage

* fix(serialization): validate custom JSON method pairs

* refactor(serialization): clarify custom JSON marshal helper name

* refactor(serialization): use sonic with number-preserving decoding

* docs(serialization): clarify custom JSON null handling

* docs(serialization): mark human-readable serializer experimental

* docs(session): mark built-in stores experimental

* ci(ci): allow session commit scope

* test(adk): make notification conformance lease timing deterministic

The 20ms wall-clock attempt lease can expire during instrumented CI payload validation. Reuse the test clock and explicit expiry hooks to preserve lease-fencing and outbox-redelivery coverage without depending on execution speed.
* refactor(adk): reuse internal stream collection and message concatenation helpers

* refactor(schema): move message stream helpers out of ADK

* refactor(schema): distinguish complete and prefix stream concatenation

* refactor(adk): replace remaining private message stream concat helpers
"reduction",
"schema",
"session",
"skill",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Breaking API Changes Detected

Package: github.com/cloudwego/eino/compose

Incompatible changes:

  • MigrateCheckpointState: changed from func([]byte, Serializer, func(state any) (any, bool, error)) ([]byte, error) to func([]byte, github.com/cloudwego/eino/schema.Serializer, func(state any) (any, bool, error)) ([]byte, error)
  • Serializer: changed from Serializer to github.com/cloudwego/eino/schema.Serializer
  • WithSerializer: changed from func(Serializer) GraphCompileOption to func(github.com/cloudwego/eino/schema.Serializer) GraphCompileOption
Review Guidelines

Please ensure that:

  • The changes are absolutely necessary
  • They are properly documented
  • Migration guides are provided if needed

⚠️ Please resolve this thread after reviewing the breaking changes.

"reduction",
"schema",
"session",
"skill",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Breaking API Changes Detected

Package: github.com/cloudwego/eino/adk/filesystem

Incompatible changes:

  • ExecuteRequest.RunInBackendGround: removed
Review Guidelines

Please ensure that:

  • The changes are absolutely necessary
  • They are properly documented
  • Migration guides are provided if needed

⚠️ Please resolve this thread after reviewing the breaking changes.

"reduction",
"schema",
"session",
"skill",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Breaking API Changes Detected

Package: github.com/cloudwego/eino/adk/middlewares/filesystem

Incompatible changes:

  • EditFileToolDesc: value changed from "Performs exact string replacements in files.\n\nUsage
  • EditFileToolDescChinese: value changed from "在文件中执行精确的字符串替换。\n\n使用方法:\n- 在编辑之前,你必须在对话中至少使用一次 'read_file' 工具。如果你在未... to "在文件中执行精确的字符串替换。\n\n- 编辑前你必须在本次对话中 Read 过该文件,否则调用会失败。\n- old_string...
  • EnhancedReadFileDescSuffix: removed
  • EnhancedReadFileDescSuffixChinese: removed
  • ExecuteToolDesc: value changed from "\nExecutes a given command in the sandbox environment with proper ha... to "Executes a bash command and returns its output.\n\n- Working directo...
  • ExecuteToolDescChinese: value changed from "\n在沙箱环境中执行给定命令,具有适当的处理和安全措施。\n\n执行命令前,请按照以下步骤操作:\n\n1. 目录验证:\n- 如果命令... to "执行一条 bash 命令并返回其输出。\n\n- 工作目录在多次调用间保持,但优先使用绝对路径 —— 复合命令中的 cd 可能触发权...
  • GlobToolDesc: value changed from "Fast file pattern matching tool that works with any codebase size\n-... to "- Fast file pattern matching tool that works with any codebase size...
  • GlobToolDescChinese: value changed from "适用于任何代码库大小的快速文件模式匹配工具\n- 支持 glob 模式,如 "/*.js" 或 "src//.ts"... to "- 适用于任何代码库规模的快速文件模式匹配工具\n- 支持形如 "**/.js" 或 "src/**/*.ts" 的 glob...
  • GrepToolDesc: value changed from "\nA powerful search tool built on ripgrep\n\n Usage
  • GrepToolDescChinese: value changed from "\n基于 ripgrep 的强大搜索工具\n\n 使用方法:\n - 始终使用 Grep 进行搜索任务。不要将 'grep' 或 '... to "基于 ripgrep 的强大搜索工具\n\n 使用方法:\n - 搜索任务始终使用 Grep。不要将 grep 或 rg 作...
  • MiddlewareConfig: old is comparable, new is not
  • ReadFileToolDesc: value changed from "Reads a file from the filesystem. You can access any file directly b... to "Reads a file from the local filesystem.\n\n- file_path must be an ...
  • ReadFileToolDescChinese: value changed from "从文件系统读取文件。你可以使用此工具直接访问任何文件。\n假设此工具能够读取机器上的所有文件。如果用户提供了文件路径,假设该路径是有效的... to "从本地文件系统读取文件。\n\n- file_path 必须是绝对路径。\n- 默认最多读取 2000 行。\n- 当你已经知道需要...
  • WriteFileToolDesc: value changed from "Writes a file to the local filesystem.\n\nUsage
  • WriteFileToolDescChinese: value changed from "将文件写入本地文件系统。\n\n使用方法:\n- 如果提供的路径已存在文件,此工具将覆盖现有文件\n- 如果这是一个现有文件,你必须先使... to "将文件写入本地文件系统,若已存在则覆盖。\n\n何时使用:创建新文件,或完全替换一个你已 Read 过的文件。覆盖你尚未 Read 过的...
Review Guidelines

Please ensure that:

  • The changes are absolutely necessary
  • They are properly documented
  • Migration guides are provided if needed

⚠️ Please resolve this thread after reviewing the breaking changes.

Comment thread adk/agent_tool.go
import (
"context"
"encoding/json"
"errors"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Breaking API Changes Detected

Package: github.com/cloudwego/eino/adk

Incompatible changes:

  • **(TypedBaseChatModelAgentMiddleware[github.com/cloudwego/eino/schema.Message]).BeforeAgent: changed from func(context.Context, *ChatModelAgentContext) (context.Context, *ChatModelAgentContext, error) to func(context.Context, *TypedChatModelAgentContext[*github.com/cloudwego/eino/schema.Message]) (context.Context, *TypedChatModelAgentContext[*github.com/cloudwego/eino/schema.Message], error)
  • *(TypedBaseChatModelAgentMiddleware[M]).BeforeAgent: changed from func(context.Context, *ChatModelAgentContext) (context.Context, *ChatModelAgentContext, error) to func(context.Context, *TypedChatModelAgentContext[M]) (context.Context, *TypedChatModelAgentContext[M], error)
  • ChatModelAgentContext: changed from ChatModelAgentContext to TypedChatModelAgentContext[*github.com/cloudwego/eino/schema.Message]
  • *TypedChatModelAgentMiddleware[github.com/cloudwego/eino/schema.Message].BeforeAgent: changed from func(context.Context, *ChatModelAgentContext) (context.Context, *ChatModelAgentContext, error) to func(context.Context, *TypedChatModelAgentContext[*github.com/cloudwego/eino/schema.Message]) (context.Context, *TypedChatModelAgentContext[*github.com/cloudwego/eino/schema.Message], error)
  • TypedChatModelAgentMiddleware[M MessageType].BeforeAgent: changed from func(context.Context, *ChatModelAgentContext) (context.Context, *ChatModelAgentContext, error) to func(context.Context, *TypedChatModelAgentContext[M]) (context.Context, *TypedChatModelAgentContext[M], error)
  • TypedChatModelAgentMiddleware[M].BeforeAgent: changed from func(context.Context, *ChatModelAgentContext) (context.Context, *ChatModelAgentContext, error) to func(context.Context, *TypedChatModelAgentContext[M]) (context.Context, *TypedChatModelAgentContext[M], error)
  • TypedChatModelAgentMiddleware[M].BeforeAgent: changed from func(context.Context, *ChatModelAgentContext) (context.Context, *ChatModelAgentContext, error) to func(context.Context, *TypedChatModelAgentContext[M]) (context.Context, *TypedChatModelAgentContext[M], error)
Review Guidelines

Please ensure that:

  • The changes are absolutely necessary
  • They are properly documented
  • Migration guides are provided if needed

⚠️ Please resolve this thread after reviewing the breaking changes.

"reduction",
"schema",
"session",
"skill",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Breaking API Changes Detected

Package: github.com/cloudwego/eino/adk/middlewares/summarization

Incompatible changes:

  • *github.com/cloudwego/eino/adk.(TypedBaseChatModelAgentMiddleware[M]).BeforeAgent, method set of TypedMiddleware: removed
  • *github.com/cloudwego/eino/adk.(TypedBaseChatModelAgentMiddleware[M]).BeforeAgent: changed from func(context.Context, *github.com/cloudwego/eino/adk.ChatModelAgentContext) (context.Context, *github.com/cloudwego/eino/adk.ChatModelAgentContext, error) to func(context.Context, *github.com/cloudwego/eino/adk.TypedChatModelAgentContext[M]) (context.Context, *github.com/cloudwego/eino/adk.TypedChatModelAgentContext[M], error)
Review Guidelines

Please ensure that:

  • The changes are absolutely necessary
  • They are properly documented
  • Migration guides are provided if needed

⚠️ Please resolve this thread after reviewing the breaking changes.

@codecov

codecov Bot commented Nov 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.90161% with 993 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.26%. Comparing base (4a4b718) to head (269a188).

Files with missing lines Patch % Lines
adk/backgroundtask/tool/managed_tool.go 60.50% 214 Missing and 83 partials ⚠️
adk/backgroundtask/in_memory_store.go 78.64% 78 Missing and 73 partials ⚠️
adk/backgroundtask/tool/executor.go 75.97% 73 Missing and 32 partials ⚠️
adk/backgroundtask/executor.go 82.07% 62 Missing and 40 partials ⚠️
adk/backgroundtask/subagent/subagent.go 83.30% 61 Missing and 33 partials ⚠️
adk/backgroundtask/local/local.go 76.64% 60 Missing and 25 partials ⚠️
adk/backgroundtask/subagent/progress.go 75.00% 23 Missing and 15 partials ⚠️
adk/backgroundtask/local/stream.go 80.51% 23 Missing and 7 partials ⚠️
adk/backgroundtask/tool/registry.go 67.64% 14 Missing and 8 partials ⚠️
adk/backgroundtask/tool/progress.go 75.40% 8 Missing and 7 partials ⚠️
... and 7 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1358      +/-   ##
==========================================
- Coverage   83.97%   82.26%   -1.72%     
==========================================
  Files         162      216      +54     
  Lines       24181    36662   +12481     
==========================================
+ Hits        20307    30160    +9853     
- Misses       2615     4362    +1747     
- Partials     1259     2140     +881     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

8 participants