Skip to content

FEAT: Improve conversation branching and technique reuse - #3187

Merged
Richard Lundeen (richlundeen) merged 7 commits into
microsoft:mainfrom
richlundeen:richlundeen-conversation-navigation
Oct 15, 2026
Merged

Richard Lundeen (richlundeen) merged 7 commits into
microsoft:mainfrom
richlundeen:richlundeen-conversation-navigation

Conversation

@richlundeen

@richlundeen Richard Lundeen (richlundeen) commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Description

Make conversation branching and reuse easier in Co-PyRIT. This builds on the technique.execute scanner introduced in #3118.

  • Add a Copy or Branch menu to every saved turn, including system turns. Each action has a visible explanation. Copy to prompt copies only that message. Branch conversation and Branch attack copy history through that turn, open the conversation panel, and select the copy at its full attack/conversation URL. A branch icon replaces the reply icon.
  • Move the final user turn from a saved chat-editor draft into the prompt box. Keep the prompt editable without a target, but keep Send disabled. Remove the target-warning box and preserve target-free attacks when copying within the same attack. Keep text and attachment metadata, including text-only video remix IDs, through restoration, editing, request construction, and retry recovery.
  • Add Use in scan to the copy menu. It saves a target-free branch and opens /scanner/technique.execute?prepended_conversation_id={id} with the field filled in. Its explanation names the destination. No clipboard step or extra dialog is required. URL loading and navigation to a different conversation initialize the normal scanner parameter state.
  • Add Add new conversation and Edit conversation beside the scanner's conversation ID. The editor uses Add to attack to save all turns to a new conversation and update the field without changing the source or sending prompts. Preview and launch stay blocked while history loads or is being edited.
  • Add an Apply converters to conversation switch beside the editor control. It uses the declared default, is off by default in technique.execute, and is disabled without a conversation ID. Conversion happens during the scan, not when editing the source.
  • Add a read-only saved-conversation lookup for the scanner editor. Only main and active related chat conversations are available; internal preparation/adversarial conversations are excluded. Upgraded history with a NULL ownership link is resolved through a verified main/pruned attack reference without changing stored rows.

Registry behavior is unchanged. Opening the scanner or conversation editor does not launch a scan. The merge from origin/main preserves both conversation-loading safeguards and the new dataset-validation launch safeguards.

Screenshots

Save a simulated chat and continue from the prompt box. The final user turn moves into the composer. Send stays disabled without a target, with no target-warning box.

Saved simulated conversation with its final user turn in the prompt box and Send disabled

Copy or branch from any turn, including the system prompt. The compact menu explains all four actions: Copy to prompt, Branch conversation, Branch attack, and Use in scan. Explanations are visible without hover and available to screen readers.

Copy or Branch menu on a system turn with visible explanations for copying one message, branching history into the same or a new attack, and opening Technique.execute

Reuse history in the scanner. The conversation ID is filled from the URL. Creation, editing, and the converter switch sit beside that field.

Scanner parameters with the conversation ID filled in, Add new conversation and Edit conversation buttons, and the history-converter switch

Edit the saved history from scanner configuration. Add to attack saves a new copy and updates the field. There is no Send button in this editor.

Scanner conversation editor showing saved system, user, and simulated assistant turns with an Add to attack button

All screenshots were captured from the running local application with safe sample text. No prompts were sent to a target and no scan was launched.

Tests and Documentation

Updated the frontend conversation guide and backend API guide. Regression coverage includes per-turn branching, target-free copies, final-user-turn placement, scanner editing, URL prefill, converter defaults, saved-history visibility, accessible menu descriptions and keyboard selection, upgraded SQLite history, and text-only remix metadata. Browser checks use the new labels and verify mobile descriptions and touch targets.

Exact commands and results for the merged code and review fixes

From the repository root:

$env:UV_NO_SYNC='1'
uv run --no-sync pre-commit run --all-files
# Passed all hooks, including production type checks.

uv run --no-sync pytest tests\unit\backend\test_conversation_editor.py tests\unit\memory\test_attack_analytics_lock_retry.py -q
# Passed: 146 tests. Pydantic emitted 22 fixture UUID serializer warnings.

uv run --no-sync ty check tests\unit\backend\test_conversation_editor.py
# Passed.

From frontend:

npm run type-check
# Passed.

npm test -- --runInBand --runTestsByPath src\components\Chat\MessageList.test.tsx src\components\Chat\ChatInputArea.test.tsx src\components\Chat\ChatWindow.test.tsx src\components\Chat\ConversationEditor.test.tsx src\components\Scenarios\ScenarioDetail.test.tsx src\utils\conversationDraft.test.ts src\utils\messageMapper.test.ts
# 557 passed; the new remix test failed because its test fixture used MemoryRouter instead of a data router.
# Corrected that fixture, then ran the failed test directly:

npm test -- --runInBand --runTestsByPath src\components\Chat\ChatWindow.test.tsx --testNamePattern 'text-only video remix'
# Passed: one selected test. Other tests were not selected.

npx eslint src\components\Chat\ChatInputArea.tsx src\components\Chat\ChatInputArea.test.tsx src\components\Chat\ChatWindow.tsx src\components\Chat\ChatWindow.test.tsx src\components\Scenarios\ScenarioDetail.tsx src\components\Scenarios\ScenarioDetail.test.tsx src\utils\conversationDraft.ts src\utils\conversationDraft.test.ts src\utils\messageMapper.ts --max-warnings 0
# Passed. The menu component and changed browser files also passed targeted ESLint checks.

$env:UV_NO_SYNC='1'
$env:E2E_FRONTEND_PORT='3108'
$env:PYRIT_BACKEND_URL='http://127.0.0.1:8001'
# PYRIT_PYTHON points to this worktree's .venv\Scripts\python.exe.
npx playwright test 'chat\.spec\.ts$' 'config\.spec\.ts$' 'touch-targets\.spec\.ts$' 'errors\.spec\.ts$' --project mock --grep 'drafting but block sending|sending after a target is set|keeps Chat message|accepted send with target processing failure' --workers 1
# Passed: four browser tests. An earlier run used an unrelated backend on port 8000 and hit the compatibility guard; rerunning against the matching backend passed.

Live local UI checks covered saving a simulated conversation, final prompt placement, the per-turn menu, direct navigation to technique.execute, and loading its history editor. No target prompts were sent and no scan was launched.

JupyText was not run because this feature does not change notebooks. The full repository test suites and live-target tests were not run.

Add per-turn branching, target-free prompt drafting, and scanner conversation editing. Open technique.execute with the saved conversation ID and expose history conversion as a toggle.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d6ee85f-fe71-4f31-93fd-d1955453f763
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d6ee85f-fe71-4f31-93fd-d1955453f763
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d6ee85f-fe71-4f31-93fd-d1955453f763
@romanlutz

Roman Lutz (romanlutz) commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

"Copy conversation" is not super intuitive to me because it doesn't tell me that we can actually fork after a certain number of turns. The icon chosen is the reply icon which adds to the confusion for me. The options aren't clear at all. Previously, the buttons allowed for tooltip so we could at least explain. This new way with dropdown doesn't. At the very least, we should explore making the dropdown a bit more custom so that it contains explanation. Maybe Fluent has some ways (see custom options or compelx options maybe) Components / Dropdown - Docs ⋅ Storybook

Perhaps worst is technique.execute because it's anyone's guess what that means.

I still very much prefer the buttons but I'm biased as I added them 🤣

Update: I actually misunderstood what these mean and checked now. This definitely needs fixing.

Comment thread pyrit/backend/services/attack_service.py Outdated
Comment thread frontend/src/utils/conversationDraft.ts Outdated
@richlundeen

Richard Lundeen (richlundeen) commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor Author

"Copy conversation" is not super intuitive to me because it doesn't tell me that we can actually fork after a certain number of turns. The icon chosen is the reply icon which adds to the confusion for me. The options aren't clear at all. Previously, the buttons allowed for tooltip so we could at least explain. This new way with dropdown doesn't. At the very least, we should explore making the dropdown a bit more custom so that it contains explanation. Maybe Fluent has some ways (see custom options or compelx options maybe) Components / Dropdown - Docs ⋅ Storybook

I found the last way confusing :D

What do you think of this (updated with tooltips)

Selection Description
Copy to prompt Copy only this message to the prompt box.
Branch conversation Copy history through this turn into the same attack.
Branch attack Copy history through this turn into a new attack.
Use in scan Open Technique.execute with history through this turn.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d6ee85f-fe71-4f31-93fd-d1955453f763
Restore upgraded conversation lookups and preserve text-only remix metadata. Keep conversation and dataset launch guards during the main merge.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d6ee85f-fe71-4f31-93fd-d1955453f763
@richlundeen

Copy link
Copy Markdown
Contributor Author

Roman Lutz (@romanlutz) Updated the menu in 84b122c: it is now Copy or Branch, with a branch icon instead of the reply icon. Each option has a visible explanation below its label using Fluent UI MenuItem.subText, so the explanation does not depend on hover. The actions are Copy to prompt, Branch conversation, Branch attack, and Use in scan. The last option explains that it opens Technique.execute with history through the selected turn. The PR description now has a screenshot of the updated menu.

@romanlutz

Copy link
Copy Markdown
Contributor

"Copy conversation" is not super intuitive to me because it doesn't tell me that we can actually fork after a certain number of turns. The icon chosen is the reply icon which adds to the confusion for me. The options aren't clear at all. Previously, the buttons allowed for tooltip so we could at least explain. This new way with dropdown doesn't. At the very least, we should explore making the dropdown a bit more custom so that it contains explanation. Maybe Fluent has some ways (see custom options or compelx options maybe) Components / Dropdown - Docs ⋅ Storybook

I found the last way confusing :D

What do you think of this (updated with tooltips)

Selection Description
Copy to prompt Copy only this message to the prompt box.
Branch conversation Copy history through this turn into the same attack.
Branch attack Copy history through this turn into a new attack.
Use in scan Open Technique.execute with history through this turn.

Yeah it's inherently complex so not surprised you found it confusing 🤣

How about
"Copy to prompt" -> "Copy to next message"
"Branch conversation" description -> replace "through" with "including"
"Branch attack" description -> "Copy history including this turn into a new attack (i.e., separate group of conversations)"
"Use in scan" -> apply a PyRIT attack technique after prepending history including this turn. [Q: Is this in a new attack? Probably?]

@romanlutz

Copy link
Copy Markdown
Contributor

FWIW I like the "copy or branch" label much better!

Keep main's cancellation barrier and its explanation when resolving the SQLite test conflict.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d6ee85f-fe71-4f31-93fd-d1955453f763
Rename Copy to prompt to Copy to next message and explain inclusive history, attack groups, and scan preparation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9d6ee85f-fe71-4f31-93fd-d1955453f763
@richlundeen
Richard Lundeen (richlundeen) added this pull request to the merge queue Oct 15, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 15, 2026
@richlundeen
Richard Lundeen (richlundeen) added this pull request to the merge queue Oct 15, 2026
Merged via the queue into microsoft:main with commit 81bfcee Oct 15, 2026
64 checks passed
@richlundeen
Richard Lundeen (richlundeen) deleted the richlundeen-conversation-navigation branch October 15, 2026 21:54
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.

2 participants