Skip to content

fix(#2475): store userId/sessionId directly on CallExecution instead of parsing slotKey - #2476

Open
waterWang wants to merge 1 commit into
agentscope-ai:mainfrom
waterWang:fix-2475-slotkey-slash-parsing
Open

fix(#2475): store userId/sessionId directly on CallExecution instead of parsing slotKey#2476
waterWang wants to merge 1 commit into
agentscope-ai:mainfrom
waterWang:fix-2475-slotkey-slash-parsing

Conversation

@waterWang

Copy link
Copy Markdown

Fixes #2475

Problem

When RuntimeContext.sessionId contains a / character (e.g., DingTalk openConversationId values like cidLcMOzfRs62haqnq1NO8SxeYdmc/lO8bWyidOisoKAzM=), ReActAgent persists the AgentState under a corrupted (userId, sessionId) pair because SlotRef.parse() splits the concatenated slot key at the last /.

Root Cause

SlotRef.parse() uses lastIndexOf('/') to split the concatenated key, but this is ambiguous when sessionId itself contains /:

slotKey = "u1/cidLcMOzfRs62haqnq1NO8SxeYdmc/lO8bWyidOisoKAzM="
// parsed as: userId="u1/cidLcMOzfRs62haqnq1NO8SxeYdmc", sessionId="lO8bWyidOisoKAzM="
// CORRECT: userId="u1", sessionId="cidLcMOzfRs62haqnq1NO8SxeYdmc/lO8bWyidOisoKAzM="

Fix

  1. Store userId and sessionId directly on CallExecution — the original values are now carried through the per-call scope instead of being reconstructed from the concatenated slot key.
  2. Update saveStateToSession() to use scope.userId / scope.sessionId directly.
  3. Remove SlotRef.parse() — no longer needed.

Impact

  • Conversation history replay via getAgentState(userId, sessionId) now works correctly for any session whose id contains /.
  • State-store rows no longer accumulate under corrupted user_id values.
  • Per-user queries like listSessionIds(userId) return correct results.

Testing

A regression test should verify: run a call with a sessionId containing / and assert the state is retrievable from the store under the untouched pair, with no sibling slot appearing under a polluted userId.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@oss-maintainer oss-maintainer left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review — Approved ✅

Clean, minimal fix for the SlotRef.parse() ambiguity when sessionId contains /.

What changed

  • userId and sessionId are now stored directly on CallExecution as separate fields
  • saveStateToSession() uses scope.userId / scope.sessionId directly instead of round-tripping through the concatenated slot key
  • SlotRef.parse() is removed — no longer needed
  • Constructor updated to accept the original identity values

Assessment

  • Correctness: Eliminates the root cause entirely. No more ambiguity from lastIndexOf('/') splitting.
  • Scope: 12 additions / 13 deletions in a single file — well-contained.
  • Backward compatibility: The slotKey field is preserved on CallExecution (still used as map key), so no downstream breakage.
  • Suggestion: Add a regression test with a sessionId containing / (e.g. "cidLcMOzfRs62haqnq1NO8SxeYdmc/lO8bWyidOisoKAzM=") to verify state is stored and retrievable under the correct (userId, sessionId) pair.

@oss-maintainer

This comment was marked as abuse.

@oss-maintainer

This comment was marked as abuse.

@gxgeek-n

Copy link
Copy Markdown
Contributor

@oss-maintainer noted on #2475 that "a regression test covering sessionId with / would prevent this from recurring" — I wrote one on top of this PR and verified it in both directions.

Commit: gxgeek-n@c664ed09 (branch test/2475-slotkey-sessionid-slash, based on this PR's head 0bdb6bb9)

AgentStateSlotKeySessionIdSlashTest — end-to-end through a real ReActAgent call against InMemoryAgentStateStore, 6 cases:

case asserts
sessionIdWithSlash_statePersistedUnderOriginalSlot state lands under the original (userId, sessionId)
sessionIdWithSlash_noStateUnderCorruptedSlot nothing leaks into the pair the old SlotRef.parse() derived
sessionIdWithSlash_stateReplayedAcrossCalls state survives a second call in the same session
anonymousUserSessionIdWithSlash_... __anon__ prefix path (also corrupted before)
sessionIdWithMultipleSlashes_... a/b/c/d
sessionIdWithoutSlash_unchangedBehavior plain sessionId still works — guards against over-correction

Verified both directions:

The only case green on both is the plain-sessionId one, which the bug never touched — so the suite genuinely detects the regression rather than passing vacuously.

The fixture uses a real DingTalk-style openConversationId (cidLcMOzfRs62haqnq1NO8SxeYdmc/lO8bWyidOisoKAzM=) and hard-codes the expected corrupted pair, so slot-derivation regressions fail loudly instead of silently.

Feel free to cherry-pick c664ed09 straight into this PR — that keeps fix + test together in one changeset, which seems cleaner than a separate PR that would depend on this one. Happy to open a standalone PR instead if you'd rather keep them split; also glad to adjust naming or placement (currently agentscope-core/src/test/java/io/agentscope/core/state/).

spotless:apply已跑过,格式合规。

@oss-maintainer

This comment was marked as abuse.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]:AgentState is persisted under a corrupted (userId, sessionId) slot when sessionId contains '/'

3 participants