Skip to content

feat(adk): auto memory middleware#987

Draft
N3kox wants to merge 55 commits into
feat/session_loopfrom
feat/auto_memory_mw
Draft

feat(adk): auto memory middleware#987
N3kox wants to merge 55 commits into
feat/session_loopfrom
feat/auto_memory_mw

Conversation

@N3kox

@N3kox N3kox commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

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

codecov Bot commented Apr 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.13423% with 574 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.75%. Comparing base (3952b3d) to head (bc20cee).

Files with missing lines Patch % Lines
adk/middlewares/automemory/automemory.go 64.11% 164 Missing and 113 partials ⚠️
adk/middlewares/automemory/internal/backend.go 29.60% 76 Missing and 12 partials ⚠️
adk/middlewares/automemory/dream/dream.go 60.27% 34 Missing and 24 partials ⚠️
adk/middlewares/automemory/local_backend.go 49.01% 37 Missing and 15 partials ⚠️
adk/middlewares/automemory/inmemory_backend.go 55.55% 38 Missing and 10 partials ⚠️
adk/middlewares/automemory/dream/config.go 38.70% 10 Missing and 9 partials ⚠️
adk/middlewares/automemory/dream/transcript.go 65.62% 6 Missing and 5 partials ⚠️
adk/middlewares/automemory/coordinator.go 88.88% 4 Missing and 3 partials ⚠️
adk/middlewares/automemory/dream/store.go 86.79% 4 Missing and 3 partials ⚠️
adk/chatmodel.go 82.35% 3 Missing ⚠️
... and 2 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #987      +/-   ##
==========================================
- Coverage   82.95%   81.75%   -1.20%     
==========================================
  Files         162      174      +12     
  Lines       23103    24638    +1535     
==========================================
+ Hits        19165    20143     +978     
- Misses       2681     3044     +363     
- Partials     1257     1451     +194     

☔ 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.

@N3kox N3kox force-pushed the feat/auto_memory_mw branch 4 times, most recently from ce5ea9f to b205f20 Compare May 13, 2026 11:51
@N3kox N3kox changed the title feat: auto memory middleware feat(adk): auto memory middleware May 13, 2026
@N3kox N3kox changed the base branch from main to alpha/09 May 13, 2026 11:54
@N3kox N3kox force-pushed the feat/auto_memory_mw branch from b205f20 to b3675fb Compare May 13, 2026 11:55
Base automatically changed from alpha/09 to main May 19, 2026 10:04
@N3kox N3kox force-pushed the feat/auto_memory_mw branch 2 times, most recently from b342ae0 to 883b949 Compare May 22, 2026 09:48
Change-Id: I09b9754cf51f10fe662fb6cb33935ad92a4d1656
…mance

- Add HumanReadableSerializer that produces human-readable JSON output
- Add GobSerializer for comparison benchmarks
- Refactor serialization_test.go to use table-driven tests for both serializers
- Add comprehensive benchmarks comparing InternalSerializer, HumanReadableSerializer, and GobSerializer

Performance improvements over InternalSerializer:
- 30-68% faster marshal/unmarshal operations
- 17-49% less memory allocation
- 30-76% fewer allocations
- 33-59% smaller serialized output size

The HumanReadableSerializer uses standard JSON encoding with type annotations
only for interface{} fields, making the output both human-readable and
type-preserving for round-trip serialization.~

Change-Id: Ifeae12484fc73b74067a0ac3a496e73e0674b975
Change-Id: Iecbcfd8ab5905e61df9a5578e8d3c99387dace85
Change-Id: I5ac5d8b60be92a28d1e57929f03fa4201649fbd0
Change-Id: I318ff16a6bd81bb57b38791eeea17b3385b1fde4
Change-Id: Iefb047967d4cb4796edd363e7e6ba0a9c4f49148
Change-Id: I3de2ba37914fe779cd880b38ce067acf5fbde7c8
The runner previously wrote the interrupt checkpoint inline, before the
session event persister had flushed. This risks a checkpoint that
references events not yet durable in the SessionStore.

Introduce deferredRunnerCheckpoint: on the interrupt/cancel path the
checkpoint payload is captured but not written until finalize() confirms
persister.closeAndWait succeeded. If event persistence failed, the
checkpoint write is skipped entirely (fail-closed).

Also adds regression tests for checkpoint ordering invariants and the
InMemoryStore checkpoint round-trip.

Change-Id: Ib29263add4a261513f1349a9173a913c74ee65ef
Resume previously assumed the agent_tool interrupt state was always the
JSON envelope (agentToolInterruptState) introduced for SessionID-based
event filtering. Pre-envelope checkpoints stored raw gob bridge bytes,
so json.Unmarshal failed outright and resume errored.

Try the JSON envelope first; on parse failure or empty BridgeCheckpoint,
treat the raw bytes as the legacy bridge checkpoint and synthesize a
fresh childSessionID. Pre-envelope checkpoints predate session
persistence and have no parent-session filter to coordinate with, so
the synthesized ID is harmless.

Un-skip the v0.7.37, v0.8.2, v0.8.3, v0.8.4 compat fixtures so the
resume path is now exercised for real on-disk legacy bytes.

Change-Id: I5000424ba028e9fb9fe3843ea9673a3dd23a7860
Merge AfterCursor and PageToken into a single `After` field in
LoadEventsOptions. Rename NextPageToken to `Next` in LoadEventsResult.
Rename afterEventCursor to afterCursor in LoadLatestTurnEnd returns.

One concept, one name: the caller seeds After from LoadLatestTurnEnd,
then passes res.Next back as After on subsequent pages.

Change-Id: Icd0960940b7f69938e2b4a5062d220c709bc0e66
The Options suffix implies a functional-options pattern; Request better
reflects the struct-pointer parameter style and pairs with LoadEventsResult.
Also removes dead variable in conformance test.

Change-Id: Ia28d8a1f92f4307182508dfba56e2b3c454acfa4
…ializer

Correct the tool-call middleware ordering in chatmodel.go doc comments
(cancelMonitor wraps around user handlers, not inside). Add architectural
doc comment to newTypedInvokableAgentToolRunner explaining why AgentTool
lacks its own SessionStore. Remove the unused GobSerializer type alias
from schema/serialization.go (already aliased internally). Update
conformance test variable names to match LoadEventsRequest rename.

Change-Id: I2251e190c9d343dbd1e57cbebfc236840be4a0fa
…igurable page size

AppendEvents failures in the session persister now retry with exponential
backoff (default 3 retries, 50ms initial delay, 2x multiplier, 25% jitter)
before latching the error. This prevents transient store failures from
causing irrecoverable session log corruption.

Also extracts the hard-coded page-size=100 in reconstructFromEventLog and
replayTailEvents into the configurable LoadPageSize field on
SessionPersistenceConfig (default 100, preserving current behavior).

New config fields on SessionPersistenceConfig:
- MaxFlushRetries (default 3, set to -1 to disable)
- FlushRetryInitialBackoff (default 50ms)
- LoadPageSize (default 100)

Change-Id: I407b6038fd61df74420a5ddd16ce8ec0f8860948
…preservation

When a SessionStore is configured, the Runner now reuses the exact tool
list from the previous turn's TurnEndState to feed the model, ensuring
byte-exact prompt cache hits across turns. The ToolSearch middleware
skips its initialization strip logic when it detects pre-seeded tool
infos via the new ToolInfosPreSeededKey RunLocalValue.

Users can opt out with WithRefreshToolInfos() when tools have genuinely
changed between turns.

Change-Id: I95585e105d41689f08116325478f21552482b79b
Change-Id: Id35944259eaee9254bc9ffa7288fe6bc43bc021b
…ion mode

When sessions are enabled, TurnEndState.Messages carries the previous
turn's system message. The Runner prepends this history to the new input,
then defaultGenModelInput unconditionally prepends a fresh system message,
causing duplication. Strip the leading system message from history before
prepending the fresh instruction so that dynamic SessionValues are always
re-evaluated without duplicating the system prompt.

Change-Id: Id14acc6aa5f2941ea64c4de82393a36bcea2fb36
…to single file

Merge three scattered test files (human_readable_test.go,
human_readable_edge_test.go, schema/toolinfo_humanreadable_test.go) into
one unified file in internal/serialization. Replace schema.ToolInfo usage
with a local mock type to eliminate the circular dependency, and remove
duplicate test cases that exercised identical code paths.

Change-Id: I0b1fa9d001201382917abb8bf4b3bd220ffdf13d
…izer

FileStore now writes Data directly (no base64) and rejects payloads
containing raw \n or \r. This preserves human-readability for the
default HumanReadableSerializer while clearly documenting that binary
serializers (Gob, protobuf) are incompatible with FileStore.

Change-Id: I608b444bbc7c83b7c7cb21fee1195f1e77f68aad
@N3kox N3kox force-pushed the feat/auto_memory_mw branch 3 times, most recently from b562910 to bc20cee Compare May 25, 2026 07:04
shentongmartin and others added 23 commits May 25, 2026 15:28
RunID was a redundant concept alongside TurnID. This removes it entirely
from SessionEvent and the runner state, making TurnID the sole turn-correlation
identifier.

On Resume, the interrupted turn's TurnID is now recovered from the event log
tail (events after the last committed TurnEnd), ensuring resumed events carry
the same TurnID as the original interrupted run. Fresh Runs always generate
new TurnIDs regardless of in-flight state.

Also adds doc comments for TurnID semantics and 6 attack tests covering
the new behavior.

Change-Id: Iea8665219a2b18837f5b62440e901b1b35ba0d29
Remove SpanEvent.DurationMS, rename FirstChunkDurationMS to TTFTMS,
remove LifecycleEvent.Reason, add SessionErrorTypeFatal constant with
fatal error emission on stopReason=="failed", and change
UserInterruptEvent to fire on cancelled (not graph-interrupt).

Change-Id: I2b5d97c16062982ff418eb9e83d6a55546a59a39
Prevents serializing null when the pointer is nil, keeping JSONL
output clean and consistent with the other optional event fields.

Change-Id: Ic28d77108a2891e4e6c195c123a1ad73bd3f6c72
The struct now covers loading, serialization, and flush tuning —
"Persistence" was too narrow. Field renamed accordingly:
SessionPersistence -> Session in TypedRunnerConfig and TurnLoopConfig.

Change-Id: I4d8089e1f0ffd34bae8a4dc34c8b5a562c41fc03
Introduce SpanKindTool with paired span.tool_call_start / span.tool_call_end
events that mirror the existing model span pair. ToolSpanMeta carries the
operational fields (call ID, tool name, evaluated permission, link IDs to
the parent model span and to the assistant / tool result messages) but never
the tool input or output content — those already live on the corresponding
messages, so persistence cost drops to O(1) per call instead of duplicating
the full input/output payloads in observation events.

Schema: add SpanKindTool, SessionEventSpanToolCallStart / End, and
ToolSpanMeta. Enforce Span.Model XOR Span.Tool in ClassifySessionEvent.
Delete AgentObservationEvent / AgentThinkingEvent / AgentToolUseEvent /
AgentToolResultEvent and their SessionEventAgent* constants; remove the
SessionEvent.AgentObservation field.

Emission: rewrite the four tool-call wrappers to emit start/end spans, and
remove the thinking observation. ID plumbing crosses the model→tool boundary
via toolBoundary{ModelSpanID, AssistantMessageEventID} stashed on the
per-run typedChatModelAgentExecCtx; tool wrappers read it to populate the
span's ParentSpanID and AssistantMessageEventID. The streamable wrappers
attach end-span emission to the consumer's stream copy via
schema.WithOnEOF / schema.WithErrWrapper (Copy(2)) instead of a goroutine
drainer, eliminating the race where the agent's event generator could close
before a background drainer's emission landed.

Tests cover end-to-end persistence (start/end pair, no observation kinds),
the streaming EOF path, and the new mutual-exclusion classification check.

Change-Id: I59089e65498ca5be6b0f50aa7011dfe4a906250b
Tool spans are now anchored to a single SpanID across interrupt boundaries,
so a permission-gated tool call produces one logical start+end pair even
when start fires on the original run and end fires on a later resume.

The wrapper at chain position #1 (typedEventSenderToolWrapper) now keys
in-flight span identity off tCtx.CallID, snapshots the parent model
SpanID and assistant message EventID into typedState, and defers end-span
emission when the inner endpoint returns an interrupt-shape error.
On resume, the entry round-trips via gob and the matching end span
reuses the original SpanID / StartEventID / parent IDs. The previous
in-memory cross-turn carrier on typedChatModelAgentExecCtx is removed
along with its mutex.

ToolSpanMeta.EvaluatedPermission is removed: under the new lifecycle
the start span fires before permission decides, so the field cannot
meaningfully be populated. Gate state for interrupted calls will be
conveyed via the forthcoming AgentInterrupt event; for non-interrupted
calls it remains observable in the tool result message content.

Change-Id: I1701d64e0cb3144134ea8f2fc7cfb62185ceb039
Add SessionEventPayload.Kind and LoadEventsRequest.Kinds so storage
backends can filter events without deserializing Data. Both InMemoryStore
and FileStore now persist and filter by Kind. reconstructSessionState
passes modelContextSessionEventKinds to skip irrelevant events. FileStore
adopts a 3-field tab-separated line format (EventID, Kind, Data).

Change-Id: I637e7a3a382bb481dba1bae0d3bf720ad063d9c9
Add durable session event for business interrupts so that crash
recovery, UI reload, and external clients can retrieve the resume
handle (CheckPointID + InterruptContexts) from SessionStore.

- New types: AgentInterruptCause, AgentInterruptEvent
- New constant: SessionEventAgentInterrupt ("agent.interrupt")
- Runner emits the event post-loop with cause inference, ToolUseID,
  and best-effort SpanEventID linkage
- ClassifySessionEvent handles the new payload
- State reconstruction explicitly excludes agent.interrupt
- First-turn interrupted TurnID recovery for resume continuity
- Comprehensive test coverage: serialization round-trip,
  classification, reconstruction exclusion, tool linkage,
  TurnLoop managed interrupt persistence
- SESSION_API_DOCUMENTATION.md added

Change-Id: I8471015e182079655f0cef566a87e697a50790cd
Change-Id: Iaaea7b25dbc3a22bf55e14c283da08f885e6e708
Change-Id: If9366c1ee599eec3ad30548629f10d1f56542f54
SessionConfig.PersistenceMode lets callers choose between the existing
low-latency async persistence and a new sync mode that guarantees every
persistable SessionEvent is appended before the corresponding AgentEvent
is delivered. In sync mode streaming message outputs are fully
materialized and delivered as non-streaming events after persistence.

Refactors the persister retry loop into appendEventsWithRetry for
shared use by both modes, and updates the runner event loop to gate
delivery on successful append when sync is active.

Change-Id: I0d5a9f14ce3773dd59f805d24fad16fcb3e423ea
Remove CheckPointID, SpanEventID, and InterruptContexts []*InterruptCtx
from AgentInterruptEvent. Replace with Contexts []*AgentInterruptContext
where each element carries its own Cause, InterruptID, Info, and ToolUseID.
This eliminates redundant Address/Parent/IsRootCause data from persisted
events and makes each context self-describing.

Change-Id: I6855daac0fb23a162fa397f8811ece014d148234
Attach the persisted SessionEvent identity to all persistable message
AgentEvents so downstream consumers (TurnLoop, onAgentEvents) can
correlate live events with their store representation without inspecting
Output.MessageOutput.Message.Role.

Three paths handled:
- Non-streaming: full SE backfilled after toSessionEventChecked.
- Sync-streaming: SE set on persistEvent after materialization.
- Async-streaming: shell SE (Kind+EventID+Timestamp, Message nil)
  attached to the live event; cleared before persistence to let
  toSessionEventChecked build the canonical SE from materialized output.

Change-Id: I4d60b9fb247844192902dcaf722f8407a46e591d
Change-Id: I9aa1ee4532a568548e9304e514c0f6166a9f1a7f
The sync-mode stream persistence tests read store.events inside the
iter.Next() loop without holding the mutex, racing with the persister
goroutine's AppendEvents call. Snapshot the slice under lock before
iterating.

Change-Id: I56fd7618aa45dd2ae7e9b70609ee696eb0554bb5
Change-Id: I2a326eb9a837a7eed4cd34e18424764e3add6e0c
Rename public Runner and TurnLoop config fields from Session to SessionConfig for clearer API semantics. Also ensure TurnLoop does not pass a typed nil checkpoint store when only SessionStore is configured.

Change-Id: I6fd2017a3a9788e4464fecc32eaccd24aae2410d
Change-Id: I6e45dfcadeb4ca725e5b5cf9257e2a309d594743
Change-Id: I4d42e16cd420690625342d5097df4dbcae07cb8b
Change-Id: Iabc283f15ab65dc8100ace045344a4a24574eea9
Change-Id: I2cc402789b17b9feb777853632ab5f19e260c527
Change-Id: I6a1df710e5f7e251d629d13a2c00cd49837e1828
@N3kox N3kox force-pushed the feat/auto_memory_mw branch from bc20cee to ce575f9 Compare May 29, 2026 08:33
@N3kox N3kox changed the base branch from main to feat/session_loop May 29, 2026 08:36
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.

2 participants