Skip to content

fix(a2a): isolate persisted context histories - #83706

Draft
honor2030 wants to merge 1 commit into
NousResearch:mainfrom
honor2030:fix/a2a-context-id-collision
Draft

fix(a2a): isolate persisted context histories#83706
honor2030 wants to merge 1 commit into
NousResearch:mainfrom
honor2030:fix/a2a-context-id-collision

Conversation

@honor2030

@honor2030 honor2030 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • store new A2A conversation logs in a versioned SHA-256 namespace so distinct contextId values cannot collapse to the same filename
  • persist the original context_id so list_conversations() returns caller-visible IDs instead of hashes
  • leave legacy sanitized logs untouched but do not automatically load or list them because their original context ownership cannot be proven
  • keep the new digest namespace separate from legacy filenames so a legacy context ID equal to another context's digest cannot collide

Legacy data handling

Legacy sanitized logs are left untouched on disk, but they are not automatically loaded or listed. The old mapping was many-to-one, so even a filename that looks like a safe context ID may contain records written through another ID (for example, tenant/a and tenanta). Automatic fallback would therefore reopen the cross-context leakage this patch prevents. Manual recovery remains possible from the unchanged legacy files.

Tests

  • python -m pytest tests/plugins/test_a2a_plugin.py -o 'addopts=' -q120 passed
  • ruff check plugins/platforms/a2a/protocol.py tests/plugins/test_a2a_plugin.pypassed
  • git diff --checkpassed

I also ran tests/plugins: 1,336 passed, 3 skipped, and 15 failed outside this change's focused gate. Those failures were in optional Hindsight/FAL dependencies plus one order-dependent A2A skill assertion that passes when test_a2a_plugin.py is run in isolation; this PR does not modify those paths.

Prior work and coordination

This adapts the collision-resistant SHA-256 storage approach originally implemented by @kuangmi-bit in #64982 / 067e2137; the commit retains co-author attribution. I asked in #77526 whether they prefer carrying the fix there or as this focused follow-up. This PR is intentionally a draft pending that coordination.

Fixes #83701

@alt-glitch alt-glitch added type/bug Something isn't working comp/plugins Plugin system and bundled plugins area/memory Memory subsystem: store, providers, sync, background reviews P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 11, 2026
@honor2030

Copy link
Copy Markdown
Contributor Author

CI triage: the only failing test slice is tests/gateway/test_multiplex_busy_input_mode.py::test_profile_route_and_nonmultiplexed_resolution_preserve_boundaries. I reproduced the same failure in a clean detached worktree at the exact base commit 9829746dfe3d5077f4f076e257505ec7f8feaa65; this PR changes only A2A persistence and tests. The other 11 Python slices, A2A focused suite, lints, attribution, supply-chain checks, and Docker builds passed. I’m leaving the PR as a draft pending the coordination noted above rather than mixing an unrelated gateway fix into this patch.

Store new conversation logs under a versioned SHA-256 namespace, retain original context IDs for listing, and leave ambiguous legacy sanitized logs untouched rather than reopening cross-context history leakage.

Adapted from the collision-resistant storage approach in 067e213.

Co-authored-by: sg-architect <kuangmi@nudge.com.cn>
@honor2030
honor2030 force-pushed the fix/a2a-context-id-collision branch from 2346a31 to 9d56d25 Compare August 11, 2026 06:17

@kuangmi-bit kuangmi-bit 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.

Reviewed the fix against the local plugin code. Verdict: solid fix — SHA-256 filenames plus a v2 storage namespace eliminates both collision classes, and persisting context_id in each record makes list_conversations round-trip correct.

Bonus — this also fixes a list→recall inconsistency: previously list_conversations() returned sanitized stems (tenanta) while a2a_history expects the raw context_id (tenant/a), so a user recalling from the list could load the wrong (merged) file. With real context_ids persisted in v2 records, the round-trip is now consistent.

Two nits, neither blocking:

  1. _safe_name(None) now raises — old code tolerated None via (context_id or "default"); the new context_id.encode(...) would raise AttributeError, which the broad except in persist_message swallows silently (message dropped). Current callers all pass str (extract_context_id guarantees ""), so this is future-proofing: _safe_name(context_id or "default") (or a guard inside) keeps the defensive contract.

  2. Legacy histories are deliberately orphaned — v2-only reads mean old a2a_conversations/*.jsonl become invisible after upgrade. That is the right security posture given the ambiguity, but worth one line in the PR description (or an upgrade note) so users know old histories are archived, not lost. A one-time migration helper is a nice-to-have, not required.

The triaged CI failure (test_multiplex_busy_input_mode) is unrelated to this change and matches our own observations. Our open PR #77526 (history injection) touches the same module but only reads via load_conversation/format_history, so it is compatible with the v2 layout — no conflict expected.

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

Labels

area/memory Memory subsystem: store, providers, sync, background reviews comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: A2A contextId filename sanitization can merge separate conversations

3 participants