Skip to content

feat: use SystemMessageConfig with SectionOverride for worker prompts (fixes #496)#822

Open
github-actions[bot] wants to merge 8 commits intomainfrom
fix/issue-496-worker-systemmessage-sections
Open

feat: use SystemMessageConfig with SectionOverride for worker prompts (fixes #496)#822
github-actions[bot] wants to merge 8 commits intomainfrom
fix/issue-496-worker-systemmessage-sections

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot commented May 1, 2026

Summary

Refactors worker prompt construction from string concatenation in BuildWorkerPrompt() to use the SDK's SystemMessageConfig with Customize mode and SectionOverride. This places worker-specific system content in the appropriate system prompt sections instead of concatenating everything into the user message.

What Changed

Section Mapping

Content SDK Section Action
Worker charter/identity SystemPromptSections.Identity Append
Tool honesty policy SystemPromptSections.ToolEfficiency Append
Worktree note SystemPromptSections.EnvironmentContext Append
Shared context (decisions.md) SystemPromptSections.CustomInstructions Append

Code Changes

  • CopilotService.Organization.cs:

    • Added BuildWorkerSystemMessageSections() — constructs Dictionary<string, SectionOverride> for worker-specific content using the SDK's structured section system
    • Simplified BuildWorkerPrompt() — now only contains task-specific content (Original User Request + Assigned Task)
    • Updated ExecuteWorkerAsync() — no longer builds identity/worktree/shared context inline; these are delivered via system message sections set at session creation
    • Updated both group creation paths (CreateMultiAgentGroupAsync and the detailed Squad setup) to pass worker sections to CreateSessionAsync
  • CopilotService.cs:

    • Added systemMessageSections parameter to CreateSessionAsync — when provided, uses SystemMessageMode.Customize with sections instead of SystemMessageMode.Append
    • Updated BuildFreshSessionConfig — detects worker sessions via BuildWorkerSectionsForSession() and includes sections for session revival/reconnection
    • Added BuildWorkerSectionsForSession() — looks up worker meta (charter, worktree, shared context) and delegates to BuildWorkerSystemMessageSections

Test Changes

  • WorkerToolHonestyTests.cs — updated from reflection-based tests on old 5-param BuildWorkerPrompt to direct tests on:

    • BuildWorkerPrompt() — verifies only task content (no system-level content)
    • BuildWorkerSystemMessageSections() — verifies each section (Identity, ToolEfficiency, EnvironmentContext, CustomInstructions) with correct action and content
    • 8 new behavioral tests covering section inclusion/exclusion for worktree and shared context
  • WorkerSystemMessageTests.cs (integration) — verifies app initializes correctly with refactored CreateSessionAsync

Why This Is Better

  1. SDK controls section ordering — no more manual concatenation order to maintain
  2. Cleaner separation — system-level content (identity, policy) in system message vs task content in user message
  3. No accidental clobbering — each content type has its own named section
  4. Revival-safeBuildFreshSessionConfig re-derives worker sections from meta, so revived sessions get correct system messages

Risk

Low — same content, different delivery mechanism. The prompt text is identical, just delivered through SDK sections instead of string concatenation in the user message. Backward compatible: sessions created without sections fall through to the existing Append mode path.

Test Results

Warning

⚠️ Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • 192.0.2.1

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "192.0.2.1"

See Network Configuration for more information.

Generated by Agent Fix for issue #496 · ● 49.4M ·

Refactor worker prompt construction from string concatenation in
BuildWorkerPrompt() to use the SDK's SystemMessageConfig with Customize
mode and SectionOverride. This places worker-specific content in the
appropriate system prompt sections instead of the user message:

- Identity/charter → SystemPromptSections.Identity (Append)
- Tool honesty policy → SystemPromptSections.ToolEfficiency (Append)
- Worktree note → SystemPromptSections.EnvironmentContext (Append)
- Shared context → SystemPromptSections.CustomInstructions (Append)

Key changes:
- Add BuildWorkerSystemMessageSections() to construct section overrides
- Add systemMessageSections parameter to CreateSessionAsync
- Update BuildFreshSessionConfig to detect workers and add sections
- Simplify BuildWorkerPrompt to only contain task-specific content
- Update group creation flows to pass sections at session creation time

Fixes #496

Co-authored-by: copilot-agentic-workflow[bot] <224017+copilot-agentic-workflow[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

Cross-Platform Verification — PR #822

Build Results

Platform Status
Tests (macOS) ✅ success
Mac Catalyst build ✅ success
Windows build ✅ success

✅ All platforms verified


Triggered by: verify-build run

@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

🧪 Integration Test Report — PR #822

Platform Build Launch DevFlow Smoke
Linux/GTK (xvfb)
Mac Catalyst
Windows ⚠️ ⚠️

✅ All platforms passed

View full run

@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

Cross-Platform Verification — PR #822

Build Results

Platform Status
Tests (macOS) ✅ success
Mac Catalyst build ✅ success
Windows build ✅ success

✅ All platforms verified


Triggered by: verify-build run

@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

🧪 Integration Test Report — PR #822

Platform Build Launch DevFlow Smoke
Linux/GTK (xvfb)
Mac Catalyst
Windows ⚠️ ⚠️

✅ All platforms passed

View full run

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor Author

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Review Summary

Found 2 issues — one regression and one unverified SDK behavior risk.

🔴 Worker identity/context frozen at session creation time (regression)

CopilotService.Organization.cs:2506-2512

The old ExecuteWorkerAsync read meta.SystemPrompt, group.SharedContext, and worktree info dynamically on every dispatch. The refactored code bakes these into SystemMessageConfig sections at session creation time, and the SDK has no API to update the system message post-creation. This means:

  • SetSessionSystemPrompt() updates meta.SystemPrompt (used by the orchestrator's planning prompt), but the worker's own system message is stale → orchestrator routes work assuming a persona the worker doesn't reflect.
  • group.SharedContext changes after CreateMultiAgentGroupAsync are invisible to existing workers.
  • Only session revival (error recovery) refreshes sections via BuildWorkerSectionsForSession.

🟡 SystemMessageMode.Customize + Content interaction undocumented

CopilotService.cs:2829-2834

First use of Customize mode in the codebase. SDK skill reference (v0.2.2) doesn't document this mode (new in 0.3.0). If Content is ignored in Customize mode, MCP server guidance and relaunch instructions are silently lost for worker sessions.

Warning

⚠️ Firewall blocked 4 domains

The following domains were blocked by the firewall during workflow execution:

  • api.nuget.org
  • dc.services.visualstudio.com
  • pkgs.dev.azure.com
  • www.nuget.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "api.nuget.org"
    - "dc.services.visualstudio.com"
    - "pkgs.dev.azure.com"
    - "www.nuget.org"

See Network Configuration for more information.

Generated by Expert Code Review · ● 67.9M

Comment on lines +2506 to +2512
var sw = System.Diagnostics.Stopwatch.StartNew();
await EnsureSessionModelAsync(workerName, cancellationToken);

// Use per-worker system prompt if set, otherwise generic.
// Note: .github/copilot-instructions.md is auto-loaded by the SDK for each session's working directory,
// so workers already inherit repo-level copilot instructions without explicit injection here.
var meta = GetSessionMeta(workerName);
var identity = !string.IsNullOrEmpty(meta?.SystemPrompt)
? meta.SystemPrompt
: "You are a worker agent. Complete the following task thoroughly.";

// Inject shared context (e.g., Squad decisions.md) if the group has it
var group = meta != null ? Organization.Groups.FirstOrDefault(g => g.Id == meta.GroupId) : null;
var sharedPrefix = !string.IsNullOrEmpty(group?.SharedContext)
? $"## Team Context (shared knowledge)\n{group.SharedContext}\n\n"
: "";

// Inject worktree awareness if the worker has an isolated worktree
var wtInfo = meta?.WorktreeId != null
? _repoManager.Worktrees.FirstOrDefault(wt => wt.Id == meta.WorktreeId) : null;
var worktreeNote = wtInfo != null && group?.WorktreeStrategy != WorktreeStrategy.Shared
? $"\n\n## Your Worktree\nYou have an isolated git worktree at `{wtInfo.Path}` (branch: {wtInfo.Branch}). " +
"You can safely run any git operations without affecting other workers. " +
"To check out a PR: `git fetch origin pull/<N>/head:pr-<N> && git checkout pr-<N>`\n"
: "";

var workerPrompt = BuildWorkerPrompt(identity, worktreeNote, sharedPrefix, originalPrompt, task);
// Worker identity, worktree note, shared context, and tool honesty policy are now
// delivered via SystemMessageConfig sections (set at session creation/revival time).
// The user prompt contains only the task-specific content.
var workerPrompt = BuildWorkerPrompt(originalPrompt, task);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🔴 Regression: SetSessionSystemPrompt changes silently ignored for running workers

The old code read meta.SystemPrompt and group.SharedContext at dispatch time (every ExecuteWorkerAsync call). The new code bakes these values into SystemMessageConfig sections at session creation time. The SDK provides no API to update the system message after session creation, so these values are frozen for the session's lifetime.

Concrete failing scenario:

  1. User creates a group via CreateMultiAgentGroupAsync → workers get generic "You are a worker agent" identity in sections
  2. User calls SetSessionSystemPrompt("GroupName-Worker-1", "You are a security auditor.") — meta is updated
  3. Orchestrator plans tasks and sees "security auditor" persona (reads meta.SystemPrompt in BuildOrchestratorPlanningPrompt) — so it routes security-related work there
  4. ExecuteWorkerAsync sends only the task prompt; the worker's system message still says "You are a worker agent" — the persona mismatch causes the worker to ignore its specialization

The same applies to group.SharedContext changes after creation (e.g., updating team decisions mid-session). The revival path (BuildWorkerSectionsForSession) reads live values, but revival only triggers on error recovery — not on every dispatch.

Suggested fix: Either (a) keep identity/context in the user prompt so it's always fresh (mirror the old behavior alongside sections), or (b) add a session recreation or SDK system message update when SetSessionSystemPrompt or SharedContext changes for workers.

Comment thread PolyPilot/Services/CopilotService.cs Outdated
Comment on lines +2829 to +2834
SystemMessage = systemMessageSections != null
? new SystemMessageConfig
{
Mode = SystemMessageMode.Customize,
Sections = systemMessageSections,
Content = systemContent.ToString()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 Risk: Content behavior under SystemMessageMode.Customize is unverified

The SDK reference (v0.2.2) documents modes as "append/prepend/replace" — Customize is new in SDK 0.3.0 and not documented in the skill reference. This code sets both Sections and Content in Customize mode, expecting both to be used. If the SDK ignores Content in Customize mode, worker sessions silently lose:

  • MCP server guidance ("If an MCP tool call fails... suggest /mcp reload")
  • The relaunch script instructions (when working directory matches ProjectDir)

This is the first use of SystemMessageMode.Customize anywhere in the codebase, so there's no existing precedent to validate against.

Suggested fix: Add an integration test or manual verification that confirms Content is included in the final system prompt when Mode = Customize with Sections set. Alternatively, move the MCP guidance and relaunch instructions into an additional section override (e.g., SystemPromptSections.EnvironmentContext or a custom section key) so there's no dependency on Content being used alongside Sections.

Comment thread PolyPilot/Services/CopilotService.cs Outdated
Comment on lines +4424 to +4431
var meta = GetSessionMeta(sessionName);
if (meta?.Role != MultiAgentRole.Worker) return null;

var identity = !string.IsNullOrEmpty(meta.SystemPrompt)
? meta.SystemPrompt
: "You are a worker agent. Complete the following task thoroughly.";

var group = Organization.Groups.FirstOrDefault(g => g.Id == meta.GroupId);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 MODERATE — Organization.Groups/Sessions read without lock from background thread (2/3 reviewers)

BuildWorkerSectionsForSession is called from BuildFreshSessionConfig, which runs on background reconnect threads (lines ~4046, ~4078, ~4900 — all inside Task.Run or async continuations). GetSessionMeta (line 4424) reads Organization.Sessions.FirstOrDefault and line 4431 reads Organization.Groups.FirstOrDefault — both without synchronization.

Concrete scenario: A concurrent CreateMultiAgentGroupAsync on the UI thread calls Organization.Sessions.Add(...) while a worker revival is running BuildWorkerSectionsForSession on a background thread → InvalidOperationException: Collection was modified from FirstOrDefault, crashing the reconnect and orphaning the session.

Fix: Use the existing thread-safe snapshot helpers:

private Dictionary<string, SectionOverride>? BuildWorkerSectionsForSession(string sessionName)
{
    var metas = SnapshotSessionMetas();
    var meta = metas.FirstOrDefault(m => m.SessionName == sessionName);
    if (meta?.Role != MultiAgentRole.Worker) return null;
    // ...
    var groups = SnapshotGroups();
    var group = groups.FirstOrDefault(g => g.Id == meta.GroupId);
    // ...
}

Comment on lines +2509 to +2512
// Worker identity, worktree note, shared context, and tool honesty policy are now
// delivered via SystemMessageConfig sections (set at session creation/revival time).
// The user prompt contains only the task-specific content.
var workerPrompt = BuildWorkerPrompt(originalPrompt, task);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 MODERATE — Behavioral regression: dynamic→static worker context binding (3/3 reviewers)

The old ExecuteWorkerAsync re-read meta.SystemPrompt, group.SharedContext, and meta.WorktreeId at every dispatch. The new code bakes these into SystemMessageConfig.Sections at session creation time.

Concrete scenario: A user creates a Squad team, dispatches initial work, then edits decisions.md (updating SharedContext) or changes a worker's charter via SetSessionSystemPrompt. The next dispatch to the same worker still uses the original values — the update is invisible until the session dies and gets fresh-recreated.

This also affects the simple CreateMultiAgentGroupAsync path (line 131), which hardcodes empty worktree and shared context. If a user later assigns a worktree or adds shared context to the group, those values are never reflected.

Fix options:

  1. Document this as a known behavioral change ("charter/context changes take effect on next session revival")
  2. On SetSessionSystemPrompt or SharedContext change, trigger session recreation for affected workers
  3. Keep charter in the user prompt for the current-dispatch path (old approach) and use sections only for fresh/revived sessions

/// </summary>
[Collection("PolyPilot")]
[Trait("Category", "WorkerSystemMessage")]
public class WorkerSystemMessageTests : IntegrationTestBase
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟢 MINOR — Integration tests don't validate worker system message behavior (2/3 reviewers)

WorkerSystemMessageTests contains two generic smoke tests:

  • Dashboard_ShowsMultiAgentGroupCreation — verifies the dashboard has content
  • App_RespondsToApiStatus — verifies the /api/status endpoint

Neither test validates that CreateSessionAsync correctly uses SystemMessageMode.Customize with section overrides for worker sessions. Consider either renaming the class to match what it actually tests (e.g., AppSmoke_WorkerRefactorTests), or adding a test that creates a multi-agent group and verifies the worker session's configuration includes expected sections.

Comment thread PolyPilot/Services/CopilotService.cs Outdated
Comment on lines +2829 to +2834
SystemMessage = systemMessageSections != null
? new SystemMessageConfig
{
Mode = SystemMessageMode.Customize,
Sections = systemMessageSections,
Content = systemContent.ToString()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🔴 CRITICAL — Content may be silently dropped in Customize mode (3/3 reviewers)

When systemMessageSections != null, the config uses SystemMessageMode.Customize with both Sections and Content. The SDK reference documents Mode as "append/prepend/replace"Customize is not documented (possibly a newer SDK addition).

The risk: If Customize mode ignores the Content field (only using Sections), every worker session silently loses:

  1. The "CRITICAL BUILD INSTRUCTION: ALWAYS run the relaunch script" warning (built into systemContent when sessionDir == ProjectDir)
  2. All MCP server guidance appended by AppendMcpServerGuidance

Concrete scenario: A PolyPilot-directory worker makes code changes but the relaunch instruction is missing from its system prompt, so it attempts dotnet build + open separately — exactly the failure the relaunch instruction exists to prevent.

The same pattern appears in BuildFreshSessionConfig (line 4397), so revived workers are equally affected.

Fix: Before merging, empirically verify that Customize mode delivers both Content and Sections to the model. If Content is ignored, move the relaunch instructions and MCP guidance into a dedicated SectionOverride entry (e.g., appended to SystemPromptSections.EnvironmentContext). Also update the copilot-sdk-reference skill with Customize mode semantics.

Comment thread PolyPilot/Services/CopilotService.cs Outdated
{
Mode = SystemMessageMode.Customize,
Sections = systemMessageSections,
Content = systemContent.ToString()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 MODERATE · 2/3 reviewers · Content field behavior in Customize mode unverified

When systemMessageSections is provided, Content (containing MCP server failure guidance built by AppendMcpServerGuidance — the /mcp reload instructions) is set alongside Mode = Customize and Sections. The SDK docs don't explicitly document whether Content is honored when Mode = Customize. If the SDK ignores Content in Customize mode, all worker sessions lose MCP failure guidance.

Scenario: Worker session with MCP servers configured. An MCP tool fails. The worker has no /mcp reload guidance because Content was silently dropped.

Suggestion: Verify empirically that Customize mode honors both Sections and Content. If not, pipe MCP guidance into an additional named section entry in the sections dictionary.

// Worker identity, worktree note, shared context, and tool honesty policy are now
// delivered via SystemMessageConfig sections (set at session creation/revival time).
// The user prompt contains only the task-specific content.
var workerPrompt = BuildWorkerPrompt(originalPrompt, task);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 MODERATE · 3/3 reviewers · Stale sections: dynamic state → static binding

Old ExecuteWorkerAsync re-read meta.SystemPrompt, group.SharedContext, and worktree info on every dispatch call, so mid-flight decisions.md updates were automatically picked up. The new code bakes these into SystemMessageConfig sections at session creation time only.

Scenario: A squad group is created, the user edits decisions.md to add a critical constraint (e.g., "Do NOT touch the auth module"), then triggers a multi-agent dispatch. Workers' CustomInstructions section still reflects the pre-edit decisions. Similarly, worktree branch changes after session creation aren't reflected in the EnvironmentContext section.

Suggestion: Either document this as intentional (creation-time binding), re-derive sections before each dispatch if the SDK supports updating them, or fall back to including dynamic fields (SharedContext) in the user prompt alongside task content when they've changed.

Comment thread PolyPilot/Services/CopilotService.cs Outdated
? meta.SystemPrompt
: "You are a worker agent. Complete the following task thoroughly.";

var group = Organization.Groups.FirstOrDefault(g => g.Id == meta.GroupId);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 MODERATE · 3/3 reviewers (via follow-up) · Thread safety: unsynchronized Organization.Groups read

BuildWorkerSectionsForSession reads Organization.Groups (a plain List<T>) via FirstOrDefault without synchronization. Per project conventions (confirmed by the snapshot pattern at ~line 3766: var groupSnapshots = Organization.Groups.ToList()), these lists must only be enumerated from the UI thread. However, BuildFreshSessionConfigBuildWorkerSectionsForSession is called from reconnect/revival paths that run on background threads (Task.Run, EnsureSessionConnectedAsync).

Scenario: UI thread mutates Organization.Groups (adding/removing a group) concurrently with a background reconnect calling BuildWorkerSectionsForSession → list enumerator corruption.

Suggestion: Snapshot the needed values (group.SharedContext, group.WorktreeStrategy) before calling from background threads, or use SnapshotGroups() inside this method.


[Fact]
public async Task Dashboard_ShowsMultiAgentGroupCreation()
{
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟢 MINOR · 3/3 reviewers · Integration tests don't validate the feature

Both test methods are pure smoke tests — Dashboard_ShowsMultiAgentGroupCreation checks the body has content and takes a screenshot; App_RespondsToApiStatus checks agentReady. Neither creates a multi-agent group, creates a worker session, nor inspects whether the session uses SystemMessageMode.Customize with section overrides. These tests pass identically whether the PR's changes are present or reverted.

Suggestion: Either rename to reflect what's actually tested (e.g., AppBootstrapTests) or replace with tests that create a worker group and verify the session's SystemMessageConfig includes the expected sections.

github-actions Bot and others added 3 commits May 1, 2026 01:15
MergeDynamicContentIntoSections pipes relaunch instructions and MCP
guidance into EnvironmentContext section overrides instead of relying
on Content being honored alongside Sections in Customize mode.
Also uses thread-safe SnapshotSessionMetas/SnapshotGroups in
BuildWorkerSectionsForSession to prevent collection-modified crashes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
BuildWorkerPrompt now accepts optional freshIdentity and
freshSharedContext params. ExecuteWorkerAsync re-reads current
meta.SystemPrompt and group.SharedContext at dispatch time, ensuring
mid-session changes (e.g., edited decisions.md or SetSessionSystemPrompt)
are reflected even though system message sections are baked at creation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…oke tests

- Add 5 new tests: BuildWorkerPrompt with fresh identity/SharedContext,
  MergeDynamicContentIntoSections merge/create/no-op behaviors
- ConnectionRecoveryTests: add Customize mode + BuildWorkerSectionsForSession
  assertions to catch regressions in the worker branch
- Rename integration tests to AppBootstrap to honestly reflect scope

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

Commit pushed: 3b4c284

Generated by Review & Fix

@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

Review-Fix Loop — Round 1 of 3

Findings Addressed: 5/5

# Severity Finding Fix
1 🔴 CRITICAL Content may be silently dropped in Customize mode Added MergeDynamicContentIntoSections() — pipes relaunch instructions + MCP guidance into EnvironmentContext section overrides instead of relying on Content alongside Sections
2 🟡 MODERATE Stale sections — dynamic state baked at creation BuildWorkerPrompt now accepts fresh identity/SharedContext; ExecuteWorkerAsync re-reads current values at dispatch time
3 🟡 MODERATE Thread safety — unsynchronized Organization reads BuildWorkerSectionsForSession now uses SnapshotSessionMetas() + SnapshotGroups()
4 🟢 MINOR Integration tests don't validate feature Renamed to AppBootstrap* to honestly reflect scope
5 🟢 MINOR ConnectionRecoveryTests structural guard weakened Added assertions for SystemMessageMode.Customize + BuildWorkerSectionsForSession

Skipped: 0

Test Results

3682 passed, 0 failed, 0 skipped

Next Steps

Expert review round 2 and verify-build dispatched.

Warning

⚠️ Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • 192.0.2.1

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "192.0.2.1"

See Network Configuration for more information.

Generated by Review & Fix · ● 23.9M ·

@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

Cross-Platform Verification — PR #822

Build Results

Platform Status
Tests (macOS) ✅ success
Mac Catalyst build ✅ success
Windows build ✅ success

✅ All platforms verified

Previous Review History

Found 3 automated review(s) on this PR. Build verification validates that all review-driven fixes compile and pass tests across platforms.


Triggered by: verify-build run

github-actions Bot and others added 2 commits May 1, 2026 01:35
… reads

ExecuteWorkerAsync runs on background threads (dispatched via Task.WhenAll
from orchestration). It was reading Organization.Groups and
Organization.Sessions directly via GetSessionMeta/FirstOrDefault without
synchronization, risking InvalidOperationException if the UI thread mutates
these lists concurrently.

Round 1 fixed this in BuildWorkerSectionsForSession but missed the same
pattern in ExecuteWorkerAsync. Now uses SnapshotSessionMetas() and
SnapshotGroups() for thread-safe reads.

Co-authored-by: copilot-agentic-workflow[bot] <224017+copilot-agentic-workflow[bot]@users.noreply.github.com>
Addresses review finding: integration tests are smoke tests that verify
app bootstrap, not worker system message behavior. Renaming the class
and file to honestly reflect what they test.

Co-authored-by: copilot-agentic-workflow[bot] <224017+copilot-agentic-workflow[bot]@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

Commit pushed: 07729ad

Generated by Review & Fix

@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

Review-Fix Loop — Round 2 of 3

Findings Addressed: 2/2

# Severity Finding Fix
1 🟡 MODERATE Thread safety — ExecuteWorkerAsync reads Organization.Groups and Organization.Sessions directly without snapshot from background threads Uses SnapshotSessionMetas() + SnapshotGroups() — same pattern as round 1's fix to BuildWorkerSectionsForSession, but applied to ExecuteWorkerAsync which was missed
2 🟢 MINOR Integration test class name still WorkerSystemMessageTests despite being smoke tests (non-outdated review finding) Renamed class and file to AppBootstrapTests to honestly reflect scope

Skipped: 0

Test Results

3682 passed, 0 failed, 0 skipped

Next Steps

Expert review round 3 and verify-build dispatched.

Warning

⚠️ Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • 192.0.2.1

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "192.0.2.1"

See Network Configuration for more information.

Generated by Review & Fix · ● 15.3M ·

@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

Cross-Platform Verification — PR #822

Build Results

Platform Status
Tests (macOS) ✅ success
Mac Catalyst build ✅ success
Windows build ✅ success

✅ All platforms verified

Previous Review History

Found 3 automated review(s) on this PR. Build verification validates that all review-driven fixes compile and pass tests across platforms.


Triggered by: verify-build run

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

Expert Code Review — PR #822

Methodology: 3 independent reviewers with adversarial consensus

4 findings posted as inline comments (3 moderate, 1 minor)

# Severity Consensus File Finding
1 🟡 MODERATE 3/3 CopilotService.Organization.cs Duplicate identity/shared context in system sections + user prompt — token waste and conflicting stale vs. fresh instructions
2 🟡 MODERATE 3/3 CopilotService.Organization.cs Worktree info no longer refreshed at dispatch time — regression from removing per-dispatch worktree note
3 🟡 MODERATE 2/3 CopilotService.cs RecreateSessionAsync and adopted-session paths don't pass systemMessageSections — tool honesty policy lost
4 🟢 MINOR 3/3 CopilotService.cs MergeDynamicContentIntoSections mutates input dictionary in-place — fragile API contract

Discarded Findings (single reviewer only, failed consensus)

  • ConnectionRecoveryTests 3000-char structural window is nearly full — Reviewer 2 only
  • AppBootstrap_RespondsToApiStatus asserts property exists but not its value — Reviewer 2 only
  • BuildWorkerSectionsForSession shallow-copies group properties read from background thread — Reviewer 2 only

CI Status

No check runs found for the head commit.

Test Coverage

The PR includes comprehensive test updates: WorkerToolHonestyTests.cs was rewritten with 11 behavioral tests covering BuildWorkerPrompt, BuildWorkerSystemMessageSections, and MergeDynamicContentIntoSections. ConnectionRecoveryTests adds structural assertions for the new Customize mode. Integration test AppBootstrapTests.cs verifies bootstrap doesn't break. Test coverage for the new code is solid; the gaps identified above are in untested code paths (e.g., RecreateSessionAsync for workers, adopted sessions).

Generated by Expert Code Review · 3 independent reviewers with adversarial consensus

Generated by Expert Code Review · ● 20M ·

Copy link
Copy Markdown
Contributor Author

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Expert Code Review: 4 findings posted inline (3 moderate, 1 minor). See summary comment below for full details.

Generated by Expert Code Review · ● 20M

/// provided, ensuring dispatch-time changes are picked up even when sections are stale.
/// </summary>
internal static string BuildWorkerPrompt(string originalPrompt, string task,
string? freshIdentity = null, string? freshSharedContext = null)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 MODERATE · 3/3 reviewers · Worktree info no longer refreshed at dispatch time

The old ExecuteWorkerAsync re-read meta.WorktreeId and injected the worktree note into the user prompt at every dispatch. The refactored code removes this entirely — worktree context now exists only in system message sections baked at session creation time.

If the worktree's branch changes between session creation and dispatch (e.g., git checkout), or if a worktree is assigned to a simple-path worker after creation (CreateMultiAgentGroupAsync creates workers with worktreeNote: ""), the worker sees stale or missing worktree instructions with no user-prompt fallback.

Suggestion: Mirror the approach used for identity/shared context — add freshWorktreeNote as an optional parameter to BuildWorkerPrompt and recompute it in ExecuteWorkerAsync from the current meta.WorktreeId.

@@ -2710,7 +2744,7 @@ await FinalizeResumedSessionUiStateAsync(
private static Task<PermissionRequestResult> AutoApprovePermissions(PermissionRequest request, PermissionInvocation invocation)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 MODERATE · 2/3 reviewers · Other CreateSessionAsync callers for workers don't pass systemMessageSections

The new systemMessageSections parameter is only passed from 3 call sites (simple CreateMultiAgentGroupAsync, Squad setup, and BuildFreshSessionConfig revival). But at least two other paths create worker sessions without sections:

  1. RecreateSessionAsync (~line 3307) — called when a user changes a worker's model on a session with zero history. It calls CreateSessionAsync without systemMessageSections, so the recreated session uses Append mode and loses the tool honesty policy that ExecuteWorkerAsync no longer injects via the user prompt.

  2. CreateMultiAgentGroup with existing sessionNames — adopts already-created sessions as workers without recreating them. These sessions were created without sections, so they lack tool honesty, identity, and worktree sections.

Suggestion: In RecreateSessionAsync, call BuildWorkerSectionsForSession(name) before CloseSessionAsync (while meta is still available) and pass the result to CreateSessionAsync. For adopted sessions, consider recreating them with sections or ensuring tool honesty is still delivered via the user prompt as a fallback.

/// This avoids relying on <c>Content</c> being honored alongside <c>Sections</c> in
/// <see cref="SystemMessageMode.Customize"/> mode.
/// </summary>
internal static Dictionary<string, SectionOverride> MergeDynamicContentIntoSections(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟢 MINOR · 3/3 reviewers · MergeDynamicContentIntoSections mutates the input dictionary in-place

This method modifies sections[EnvironmentContext] in the passed-in dictionary and returns the same reference. Currently safe because each call site creates a fresh dictionary via BuildWorkerSystemMessageSections. However, the method name ("Merge...Into...") and the return sections pattern imply functional composition, not mutation — a future caller that caches or reuses the dictionary across retries or sessions would silently accumulate duplicate environment content.

Suggestion: Either clone the dictionary before mutating (new Dictionary<string, SectionOverride>(sections)), or add a // Mutates sections in-place remark to the XML doc to signal the side-effect.

var freshSharedContext = group?.SharedContext ?? "";
var freshIdentity = meta?.SystemPrompt;

var workerPrompt = BuildWorkerPrompt(originalPrompt, task, freshIdentity, freshSharedContext);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 MODERATE · 3/3 reviewers · Duplicate identity/shared context in system message + user prompt

When meta.SystemPrompt or group.SharedContext are non-empty (the common Squad case), the model receives the same content twice: once from the system message sections (baked at session creation) and once from the user prompt (injected here at dispatch time).

This creates two problems:

  1. Token waste proportional to charter + decisions.md size — can be significant for large team contexts.
  2. Conflicting instructions — if the values changed after session creation, the system section has the stale creation-time value and the user prompt has the fresh dispatch-time value. Models are inconsistent about which they honor when system vs. user messages diverge.

Suggestion: Pick one source of truth. If dispatch-time freshness matters, only include changed values in the user prompt (compare against what was baked into sections). Alternatively, skip duplicating identity entirely (system section always has it) and only pass shared context in the user prompt when it has actually changed.

github-actions Bot and others added 2 commits May 1, 2026 02:38
…nding 13)

Identity and shared context are now delivered exclusively via SystemMessageConfig
sections. Removes freshIdentity/freshSharedContext from BuildWorkerPrompt and
ExecuteWorkerAsync to avoid token waste and conflicting instructions when system
sections contain stale creation-time values.

Adds fresh worktree note to BuildWorkerPrompt instead (finding 10) since worktree
branch/path can change between session creation and dispatch.

Updates tests to match new BuildWorkerPrompt signature.

Co-authored-by: copilot-agentic-workflow[bot] <224017+copilot-agentic-workflow[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ding 11)

RecreateSessionAsync now calls BuildWorkerSectionsForSession before closing the
session, preserving worker identity, tool honesty, worktree, and shared context
sections when a worker's model is changed on a zero-history session.

Also adds mutation warning to MergeDynamicContentIntoSections XML doc (finding 12).

Co-authored-by: copilot-agentic-workflow[bot] <224017+copilot-agentic-workflow[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

Commit pushed: 32a4c2f

Generated by Review & Fix

@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

Review-Fix Loop — Round 2 of 3

Findings Addressed: 4 of 5

# Severity Finding Status
10 🟡 MODERATE Worktree info not refreshed at dispatch time ✅ Fixed — ExecuteWorkerAsync now recomputes worktree note from current meta.WorktreeId and passes to BuildWorkerPrompt
11 🟡 MODERATE RecreateSessionAsync doesn't pass worker sections ✅ Fixed — now calls BuildWorkerSectionsForSession before closing and passes sections to CreateSessionAsync
12 🟢 MINOR MergeDynamicContentIntoSections mutates in-place ✅ Fixed — added mutation warning to XML doc
13 🟡 MODERATE Duplicate identity/shared context in system + user prompt ✅ Fixed — removed freshIdentity/freshSharedContext from BuildWorkerPrompt; identity and shared context now delivered exclusively via sections
9 🟢 MINOR Integration tests don't validate the feature ⏭️ Skipped — tests already renamed to AppBootstrapTests with accurate doc comments; adding CDP-based feature validation is high-risk for a MINOR finding

Test Results

3683 passed, 0 failed, 0 skipped

Next Steps

Expert review and verify-build dispatched for round 3.

Warning

⚠️ Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • 192.0.2.1

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "192.0.2.1"

See Network Configuration for more information.

Generated by Review & Fix · ● 17.8M ·

@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

Cross-Platform Verification — PR #822

Build Results

Platform Status
Tests (macOS) ✅ success
Mac Catalyst build ✅ success
Windows build ✅ success

✅ All platforms verified

Previous Review History

Found 3 automated review(s) on this PR. Build verification validates that all review-driven fixes compile and pass tests across platforms.


Triggered by: verify-build run

Copy link
Copy Markdown
Contributor Author

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Expert Code Review: 1 finding posted inline (1 moderate). See summary comment for full details.

Warning

⚠️ Firewall blocked 2 domains

The following domains were blocked by the firewall during workflow execution:

  • api.nuget.org
  • dc.services.visualstudio.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "api.nuget.org"
    - "dc.services.visualstudio.com"

See Network Configuration for more information.

Generated by Expert Code Review · ● 72.6M

Comment on lines +3347 to +3354
// For worker sessions, build system message sections BEFORE closing (meta is still available).
// Without this, the recreated session loses identity, tool honesty, worktree, and shared context.
var workerSections = BuildWorkerSectionsForSession(name);

await CloseSessionAsync(name);

return await CreateSessionAsync(name, newModel, workingDir, groupId: groupId);
return await CreateSessionAsync(name, newModel, workingDir, groupId: groupId,
systemMessageSections: workerSections);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 MODERATE · 2/2 reviewers agreed · RecreateSessionAsync loses worker metadata on subsequent restarts

Worker sections are correctly captured before CloseSessionAsync (line 3349), so the immediate recreation works. However, CloseSessionAsync removes the SessionMeta (including Role = Worker, SystemPrompt, WorktreeId). After recreation, the new session's meta only has GroupId restored.

Failing scenario: User changes a worker's model → RecreateSessionWithModelAsync runs → initial session works with correct sections. App restarts → BuildFreshSessionConfigBuildWorkerSectionsForSession sees meta.Role != Worker → returns null → session revives with SystemMessageMode.Append instead of Customize, losing identity, tool honesty policy, and shared context.

Suggested fix: Preserve and restore critical worker meta fields after recreation:

var savedRole = meta?.Role;
var savedSystemPrompt = meta?.SystemPrompt;
var savedWorktreeId = meta?.WorktreeId;

await CloseSessionAsync(name);

var result = await CreateSessionAsync(name, newModel, workingDir, groupId: groupId,
    systemMessageSections: workerSections);

var newMeta = GetSessionMeta(name);
if (newMeta != null && savedRole != null)
{
    newMeta.Role = savedRole.Value;
    newMeta.SystemPrompt = savedSystemPrompt;
    newMeta.WorktreeId = savedWorktreeId;
}
return result;

@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented May 1, 2026

⚠️ Review-fix loop reached maximum rounds (3). Remaining findings (if any) require manual review.

The latest expert review found 4 findings (3 moderate, 1 minor) that were not addressed in the 3 automated rounds:

# Severity File Finding
1 🟡 MODERATE CopilotService.Organization.cs Duplicate identity/shared context in system sections + user prompt
2 🟡 MODERATE CopilotService.Organization.cs Worktree info no longer refreshed at dispatch time
3 🟡 MODERATE CopilotService.cs RecreateSessionAsync and adopted-session paths don't pass systemMessageSections
4 🟢 MINOR CopilotService.cs MergeDynamicContentIntoSections mutates input dictionary in-place

These require manual review and resolution.

Generated by Review & Fix · ● 1.5M ·

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.

feat: Use SystemMessageConfig with SectionOverride for worker prompts

0 participants