Skip to content

Python: preserve message roles in ClaudeAgent._format_prompt - #7895

Open
Praveen Kumar Mittal (mittalpk) wants to merge 1 commit into
microsoft:mainfrom
mittalpk:fix-claude-agent-format-prompt-roles
Open

Python: preserve message roles in ClaudeAgent._format_prompt#7895
Praveen Kumar Mittal (mittalpk) wants to merge 1 commit into
microsoft:mainfrom
mittalpk:fix-claude-agent-format-prompt-roles

Conversation

@mittalpk

Copy link
Copy Markdown

Fixes #7894.

ClaudeAgent._format_prompt() joined every message's text together with no role information at all, so a multi-agent orchestration (SequentialBuilder/ConcurrentBuilder) that hands an agent the accumulated conversation — including other agents' and the user's turns — loses all of that structure by the time Claude sees it. Everything reads as one undifferentiated blob sent as a single user-role turn.

I originally proposed sending real per-message roles through the SDK's async-iterable query() input (see the issue discussion), but testing that against the actual CLI showed the streaming-input protocol rejects any role other than user outright (Error: Expected message role 'user', got 'assistant') — it generates its own assistant turns and won't accept synthetic ones. So the fix here instead labels each message with its role as visible text ([user]: ... / [assistant]: ...) when there's more than a single plain user turn, so Claude can still tell who said what. The common single-user-message case is left exactly as it was, to avoid changing behavior for the overwhelming majority of calls.

Verification:

  • Added two tests: one confirming multi-message input keeps role labels, one confirming the single-user-turn case is unchanged. Confirmed the new multi-role test fails on unpatched code and passes after the fix (git stash isolation).
  • Full packages/claude/tests/ suite passes (72 tests).
  • ruff check / ruff format --check clean on both changed files.
  • mypy on the changed file shows the same 2 pre-existing, unrelated errors present on a clean main checkout (confirmed via git stash) — nothing introduced by this change.

_format_prompt() joined every message's text with no role information,
so in multi-agent orchestration (SequentialBuilder/ConcurrentBuilder)
Claude couldn't tell its own prior turns apart from another agent's
turns or the user's actual instruction.

The Claude Agent SDK's streaming-input protocol only accepts
user-role turns (it generates its own assistant turns), so per-message
roles can't be sent on the wire. Instead, when the input is more than
a single plain user turn, each message is now prefixed with its role
as text (e.g. "[assistant]: ...") so Claude can still tell the turns
apart. The common single-user-turn case is left unchanged.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@agent-framework-automation agent-framework-automation Bot added the python Usage: [Issues, PRs], Target: Python label Aug 26, 2026
@github-actions github-actions Bot changed the title python: preserve message roles in ClaudeAgent._format_prompt Python: preserve message roles in ClaudeAgent._format_prompt Aug 26, 2026
@eavanvalkenburg

Copy link
Copy Markdown
Member

Please use the standard PR template Praveen Kumar Mittal (@mittalpk)

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

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: ClaudeAgent flattens multi-agent conversation history into one undifferentiated user turn

3 participants