Skip to content

feat: auto memory middleware#987

Draft
N3kox wants to merge 72 commits intomainfrom
feat/auto_memory_mw
Draft

feat: auto memory middleware#987
N3kox wants to merge 72 commits intomainfrom
feat/auto_memory_mw

Conversation

@N3kox
Copy link
Copy Markdown
Contributor

@N3kox N3kox commented Apr 23, 2026

What type of PR is this?

Check the PR title.

  • This PR title match the format: <type>(optional scope): <description>
  • The description of this PR title is user-oriented and clear enough for others to understand.
  • Attach the PR updating the user documentation if the current PR requires user awareness at the usage level. User docs repo

(Optional) Translate the PR title into Chinese.

(Optional) More detailed description for this PR(en: English/zh: Chinese).

en:
zh(optional):

(Optional) Which issue(s) this PR fixes:

(optional) The PR that updates user documentation:

Comment thread adk/chatmodel.go Outdated

func (a *ChatModelAgent) applyBeforeAgent(ctx context.Context, ec *execContext) (context.Context, *execContext, error) {
func (a *ChatModelAgent) applyBeforeAgent(ctx context.Context, ec *execContext, agentInput *AgentInput) (context.Context, *execContext, error) {
runCtx := &ChatModelAgentContext{
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:

  • ChatModelAgentMiddleware.AfterAgent: added
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.

@N3kox N3kox force-pushed the feat/auto_memory_mw branch from 6544882 to 7a50a8c Compare April 23, 2026 13:21
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

❌ Patch coverage is 53.48837% with 660 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.37%. Comparing base (7ac01e2) to head (7a50a8c).

Files with missing lines Patch % Lines
adk/middlewares/automemory/sync.go 60.61% 237 Missing and 106 partials ⚠️
adk/middlewares/automemory/local_backend.go 0.00% 130 Missing ⚠️
adk/middlewares/automemory/backend.go 15.25% 94 Missing and 6 partials ⚠️
adk/middlewares/automemory/inmemory_backend.go 54.07% 52 Missing and 10 partials ⚠️
adk/chatmodel.go 66.66% 9 Missing and 3 partials ⚠️
adk/middlewares/automemory/coordinator.go 85.07% 7 Missing and 3 partials ⚠️
adk/middlewares/automemory/prompt.go 95.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #987      +/-   ##
==========================================
- Coverage   82.82%   79.37%   -3.46%     
==========================================
  Files         148      154       +6     
  Lines       16668    21693    +5025     
==========================================
+ Hits        13806    17219    +3413     
- Misses       1903     3386    +1483     
- Partials      959     1088     +129     

☔ View full report in Codecov by Sentry.
📢 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.

mrh997 and others added 27 commits May 6, 2026 09:37
feat(agentic_model):
- format print
- support agentic chat template
- support to compose agentic odel&agentic tools node
- support agentic tool node
- support agentic message concat
shentongmartin and others added 26 commits May 6, 2026 09:37
* fix(adk): skip saving checkpoint when TurnLoop is idle

When Stop() is called on an idle TurnLoop (no active agent run, no
unhandled items, no canceled items), the resulting checkpoint contains
no meaningful state. Skip saving such checkpoints to avoid unnecessary
store writes.

- Add isIdle check in cleanup() before checkpoint save decision
- Add TestTurnLoop_StopWhileIdle_SkipsCheckpoint test

Change-Id: I6aeaff5ed5833a971cb95298193fdb96d904baf8

* fix(internal): merge id2State in PopulateInterruptState instead of replacing

PopulateInterruptState merged id2Addr entries one by one but replaced
id2State wholesale. In a parallel workflow resume, two goroutines share
the same globalResumeInfo. If one goroutine's compose graph called
PopulateInterruptState (replacing id2State with compose-only entries)
before the other goroutine looked up its outer-level entry, the lookup
returned a zero-value InterruptState with State=nil, triggering the
'has no state' panic in ChatModelAgent.Resume.

Change id2State handling to merge entry by entry, consistent with
id2Addr.

Change-Id: Ia21f65289bff7beb2bc383fb033926ad9c92d7e7

* fix(adk): keep watching for cancel escalation after stopSig.done

When watchStopSignal entered the stopSig.done branch, it processed the
initial cancel and then blocked on <-done (turn completion), never
looping back to check notify. This meant a subsequent Stop() call with
a higher cancel mode (e.g. CancelImmediate) was never forwarded to the
agent, causing TestTurnLoop_Stop_EscalatesCancelMode to time out.

Replace the blocking <-done with an inner loop that selects on both
done and notify, so escalation signals are always delivered. Also apply
the generation-based dedup check consistent with the notify branch.

Change-Id: Ia6a04d00a2b44625ffbcb625ff0e559c12ed145f
…r agent cancellation (#929)

* fix(adk): prevent panic when orphaned tool goroutine sends event after agent cancellation

When CancelAfterChatModel times out and escalates to CancelImmediate,
GraphInterrupt fires with timeout=0. The compose graph returns immediately,
orphaning parallel tool goroutines. When an orphaned tool completes,
eventSenderToolWrapper tries to send an event via the AsyncGenerator which
is already closed, causing 'send on closed channel' panic.

- Add isImmediateCancelled() to cancelContext for checking immediateChan
- Make chatModelAgentExecCtx.send cancel-aware: skip send when immediate cancel is active
- Use trySend as safety net for the TOCTOU race window
- Route SendEvent() through execCtx.send() instead of direct generator.Send()

Change-Id: Ic7e0194c860e2692a3cddc559911ab379024f650

* test(adk): add test for orphaned tool goroutine panic after CancelImmediate

- unit_send_after_close: directly reproduces the panic by sending to a
  closed generator with isImmediateCancelled=true
- unit_send_after_close_without_cancel_ctx: verifies trySend safety net
  prevents panic even without cancelCtx
- integration_cancel_escalation_orphans_tool: end-to-end test with slow
  tool, CancelAfterChatModel timeout escalation, and orphaned goroutine

Change-Id: Ia82fa957b102ccc2ac42094d18d4b15db2a1701c

* test(adk): improve coverage for orphaned tool goroutine fix

Add test cases for:
- nil execCtx and nil generator defensive guards
- nil cancelContext in isImmediateCancelled
- TOCTOU race window (isImmediateCancelled=false but generator closed)
- SendEvent public API with closed generator
- SendEvent without exec context

Change-Id: I197c36f34675f5376cbe5f830b15db6ca873cd1f
…925)

* fix(adk): keep late turn loop items

Change-Id: Iabee0c25a83d5a25585d3592a41ca6a5fba35c2b

* docs(adk): clarify cancel wait semantics

Change-Id: Ia0a396b9cc2e43f15e85056d966f20b010dcd2b6

* feat(adk): add WithSkipCheckpoint and WithStopCause StopOptions

Add two new StopOption variants for TurnLoop.Stop():

- WithSkipCheckpoint: prevents checkpoint persistence on stop, for
  cases where the caller does not intend to resume in the future.
  The flag is sticky across escalation calls.

- WithStopCause: attaches a business-supplied reason string. Surfaced
  in TurnLoopExitState.StopCause and, after the Stopped channel
  closes, via TurnContext.StopCause(). Uses first-non-empty-wins
  semantics across multiple Stop() calls.

Thread both fields through stopSignal with proper mutex protection.
Update cleanup() to skip checkpoint save when skipCheckpoint is set.

Change-Id: Ifeat-stop-options-skip-checkpoint-stop-cause
* fix: rebase error

Change-Id: If20fa78dba82a1c177c8ec47090050ea8c1354ed

* feat(adk): add failover support for ChatModel

Change-Id: Ice1b513b4b509e7b540316da9119ff3d529c9bae

* feat(adk): add failover support for ChatModel

Change-Id: Ice1b513b4b509e7b540316da9119ff3d529c9bae

* feat(adk): add failover support for ChatModel

Change-Id: Id5483447b74322f6dd495bdd3b994c001094569d

* feat(adk): make Name and Description optional in ChatModelAgentConfig

* feat(adk): add callback lifecycle management to failoverProxyModel

- Extract prepareCallbacks method to reuse callback setup logic between
  Generate and Stream methods
- Add callbacks.ReuseHandlers with proper RunInfo (model type + component)
  before each failover model invocation so handlers receive correct identity
- Add explicit OnStart/OnEnd/OnError callback invocations in Generate and
  Stream since failoverProxyModel declares IsCallbacksEnabled() = true and
  the outer layer skips automatic callback injection

Change-Id: I0150529024125251828cf6f77c8247aa464b1f84

* fix(adk): preserve partial result in failoverProxyModel.Generate on error

Return result instead of nil when target.Generate fails, so that the
outer failoverModelWrapper can pass the partial output message to
ShouldFailover for inspection.

Change-Id: I32d86151a6e133f1a58d5e988bccf42d831a646c

* refactor(adk): use EnsureRunInfo in failoverProxyModel and separate ctx for callbacks

- Replace manual RunInfo construction + ReuseHandlers with
  callbacks.EnsureRunInfo for cleaner RunInfo setup
- Use nCtx (from EnsureRunInfo) for target model invocation and
  original ctx for OnStart/OnEnd/OnError callback lifecycle

Change-Id: I1d5982d0e1ceeaf8f6648b9c40c229b6a2b07ab8

---------

Co-authored-by: shentong.martin <shentong.martin@bytedance.com>
feat: tool search definition
…945)

- Add ToolAliases to prepareExecContext when building ToolsNodeConfig
- Add UnknownToolsHandler, ExecuteSequentially, ToolArgumentsHandler,
  and ToolAliases to applyBeforeAgent when rebuilding after BeforeAgent
  handlers modify tools
- Add tests covering argument alias remapping, name alias dispatch,
  alias preservation after handler rebuild, and handler-only tool
  registration with pre-configured aliases
feat(adk): add MultiModalRead with custom FileContentPart types

- Define FileContentPartType, FileContentPart in filesystem package
  to replace direct schema.ToolOutputPart dependency, supporting
  only Image (bytes) and File (bytes) types
- Add MultiModalReader interface and MultiModalReadRequest with Pages field
- Add multiModalReadFileArgs extending readFileArgs with PDF pages param
- Convert FileContentPart to schema.ToolOutputPart with base64
  encoding in middleware layer
- Guard against nil FileContent returned from Backend.Read and
  MultiModalRead; return human-readable fallback instead of panicking
- Reuse base64 encoding buffer across multimodal parts via base64Encoder
- Add tests for image, file, unsupported type, pages passthrough,
  schema fields, custom desc, empty data error, nil result, and routing
* feat(adk): validate pages parameter in MultiModalReadFileTool

- Add validatePages function to check format (must be "N" or "N-M")
- Reject invalid formats such as "1-", "-5", non-numeric values
- Enforce end >= start and max 20 pages per request
- Return validation error as ToolResult so the model can self-correct

* test(adk): add unit tests for validatePages function

- Cover valid formats: single page, range, same start/end, max boundary
- Cover invalid formats: trailing dash, leading dash, non-numeric, zero
- Cover logic errors: end < start, range exceeds 20 pages
Replace FunctionToolResult.Result string field with Blocks
[]*FunctionToolResultBlock to uniformly represent all tool results
(text-only and multimodal) as structured content blocks.

- Add FunctionToolResultBlock type supporting text, image, audio,
  video, and file content with String() method
- Remove FunctionToolResult.Result field; text results are now
  wrapped as FunctionToolResultBlock{Text: ...}
- Update FunctionToolResultAgenticMessage to accept blocks parameter
- Convert MessageInputPart to FunctionToolResultBlock in compose layer
- Update concatFunctionToolResults to merge via Blocks append
- Add comprehensive tests for multimodal and streaming tool results
…xt, and DeepAgent for AgenticMessage support (#988)
…1004)

refactor(adk): build ToolsNodeConfig via shallow copy + field override

Replace explicit field-by-field struct literals with a shallow copy of
the source ToolsNodeConfig followed by overriding only the fields that
need per-run isolation (Tools and ToolCallMiddlewares). New fields added
to compose.ToolsNodeConfig in the future will be forwarded automatically
instead of being silently dropped.

- applyBeforeAgent: reuse the already-cloned toolsNodeConf local instead
  of rebuilding the struct
- prepareExecContext: shallow-copy a.toolsConfig.ToolsNodeConfig then
  cloneSlice the Tools/ToolCallMiddlewares that will be appended to

No behavior change: every field is assigned the same value as before.
…base

Swap handler positions in InnermostGetsOriginalOutput subtests to match
the forward-iteration semantics from #1000. The tests assumed the old
reverse-iteration order where handlers[0] was innermost.

Change-Id: Ib319b3ea687870db9f69c4c93e1ee69369ea2fe8
…eep-copy (#1007)

fix(serialization): ensure pointer-receiver MarshalJSON is invoked in InternalSerializer

When InternalSerializer marshals a struct value that implements
json.Marshaler via pointer receiver (e.g. *ToolInfo), rv.Interface()
produces a non-addressable copy. json.Marshal then cannot call the
pointer method and falls back to default struct encoding, which skips
unexported fields — causing ParamsOneOf data loss after deepCopyState
during interrupt/resume.

Fix: pass a pointer to json.Marshal by using rv.Addr() when addressable,
or copying into reflect.New() otherwise.
@N3kox N3kox force-pushed the feat/auto_memory_mw branch 2 times, most recently from ba37df9 to 52a0b15 Compare May 7, 2026 03:05
@N3kox N3kox force-pushed the feat/auto_memory_mw branch from 52a0b15 to ce5ea9f Compare May 7, 2026 08:10
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