Skip to content

feat(a2a): inject conversation history on context resume (multi-turn) - #77526

Open
kuangmi-bit wants to merge 1 commit into
NousResearch:mainfrom
kuangmi-bit:feat/a2a-multiturn-injection
Open

feat(a2a): inject conversation history on context resume (multi-turn)#77526
kuangmi-bit wants to merge 1 commit into
NousResearch:mainfrom
kuangmi-bit:feat/a2a-multiturn-injection

Conversation

@kuangmi-bit

Copy link
Copy Markdown

Summary

Multi-turn support for the A2A plugin that landed in #77109: when a caller reuses a contextId, the persisted conversation is prepended to the inbound message so the agent sees the full thread — not just the latest message.

Built on top of the merged official plugin (plugins/platforms/a2a/), which already persists every exchange to disk (persist_message / load_conversation). This PR adds the missing read-back half of multi-turn.

Changes

  • protocol.format_history(context_id, limit=None) — renders prior messages as role: text lines (user: / assistant:). Empty string when the context has no history. Bounded by A2A_HISTORY_INJECTION_LIMIT env (default 20, max 200, 0 disables injection entirely).
  • adapter._prepare_task() — on inbound message/send, if history exists for the context, prepend it to the text that goes to the agent. The original message is what gets audited and persisted, so injected prefixes never accumulate in the on-disk log (verified across 3+ turns).
  • tests/plugins/test_a2a_multiturn.py — 9 tests: empty history, role rendering, limit + env override, injection on resume, original-only persistence/audit, no duplicate-prefix growth.

Why it matters

A2A peers that hold a contextId open across multiple message/send calls (the standard multi-turn pattern) currently get a stateless agent — the agent answers each message as if it were the first. With this, a resumed context behaves like a conversation thread.

Non-goals (unchanged from #64982)

  • No a2a-sdk dependency, no new deps at all (stdlib only).
  • No behavior change for new contexts (no history → no injection).
  • Turn-cap / anti-loop protection is untouched.

Verification

  • tests/plugins/test_a2a_multiturn.py: 9 passed
  • Official suite test_a2a_plugin.py + test_a2a_phase23.py: 151 passed, 0 regressions
  • ruff check clean on all touched files

Supersedes #64982 (multi-turn conversation support) — same intent, rebased onto the merged official plugin.

@alt-glitch alt-glitch added type/feature New feature or request comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have labels Aug 3, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

Two PRs address A2A multi-turn continuity. #64982 introduced a broad pre-#77109 A2A implementation including persistence and history replay, while #77526 applies the remaining history-injection change directly to the A2A implementation now on main, with bounded replay and original-only persistence/auditing.

Related pull requests

Duplicates

#64982 and #77526 overlap on persisted-history injection, but #77526 is the focused successor implementing that live remainder against the current A2A code; #64982 contains substantial superseded platform work beyond that overlap.

Suggested consolidation

Keep #77526 open with a salvage path centered on its bounded history formatter, resume-time injection, original-only persistence/auditing, and focused tests. Keep #64982 closed as superseded by #77526; this does not disregard its earlier keep_open review, because the later contributor assessment explicitly narrowed the wanted work to history injection after #77109 landed, and #77526's diff implements that requested scope.

Cross-PR triage: Reviewed 2 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 80 kB of PR diffs, 4 kB of issue/PR text, 6 kB of discussion (9 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

When a caller reuses a contextId, prepend the persisted conversation so
the agent sees the full thread instead of only the latest message.

- protocol.format_history(): render prior messages as 'role: text' lines,
  bounded by A2A_HISTORY_INJECTION_LIMIT (default 20, max 200, 0 disables)
- adapter._prepare_task(): inject history before dispatch; audit and
  on-disk persistence keep the original (un-augmented) message so
  injected prefixes never accumulate in the log
- tests: 9 new cases covering empty history, role rendering, limit/env,
  injection on resume, original-only persistence, no-duplication over
  three turns

Closes NousResearch#64982 (superseded by this rebased implementation).
@kuangmi-bit
kuangmi-bit force-pushed the feat/a2a-multiturn-injection branch from c0e4143 to 7001260 Compare August 5, 2026 11:04
@kuangmi-bit

Copy link
Copy Markdown
Author

Rebased onto current main (per the ask in #64982) — the PR now sits on top of the merged A2A v1.0 plugin with the scope narrowed exactly to the history-injection half: guard markers in protocol.py + persist-before-augment ordering in adapter.py, with E2E tests in tests/plugins/test_a2a_multiturn.py (9 passed on Python 3.11).

@teknium1 — you mentioned you'd be happy to review this scoped-down version; it's ready whenever you have a moment.

@kuangmi-bit

Copy link
Copy Markdown
Author

@teknium1 — gentle ping on this one. The branch is rebased on current main and scoped exactly to the history-injection half you flagged in #64982 (guard markers in protocol.py + persist-before-augment ordering in adapter.py, E2E tests in tests/plugins/test_a2a_multiturn.py, 9 passed on Python 3.11). Mergeable and green. No rush — whenever you have a window.

@honor2030

Copy link
Copy Markdown
Contributor

Hi @kuangmi-bit — while validating conversation persistence on current main, I reproduced a filename collision where tenant/a and tenanta share one history file. I filed #83701 with the exact repro. Your earlier #64982/commit 067e2137 already introduced the core SHA-256 idea, but this PR’s current diff only adds history injection and does not carry that fix. Would you prefer to include the collision fix here, or are you okay with a focused follow-up PR? I have a regression-tested compatibility patch prepared locally and will preserve attribution to your original work.

@alt-glitch alt-glitch added the sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state label Aug 11, 2026
@kuangmi-bit

Copy link
Copy Markdown
Author

@honor2030 — thank you for the reproduction and for preserving attribution on the SHA-256 idea. To your question: I'd prefer the focused follow-up PR (#83706) over folding the collision fix into this one — keeping this PR scoped to history injection makes both easier to review, and your fix (SHA-256 filenames + v2 namespace + persisted context_id) is more complete than a one-off sanitize change here.

One sequencing note: this PR should land on top of #83706 rather than before it — otherwise the injection path inherits the tenant/atenanta collision for any history persisted before the v2 namespace lands. I left a review on #83706 (verdict: solid fix, two small nits); happy to help land it so this can rebase cleanly after.

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

Labels

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/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants