Skip to content

evals: attach an existing Agent Host session - #333454

Closed
Ross Wollman (rwoll) wants to merge 1 commit into
mainfrom
rwoll/evaluation-session-attachment
Closed

evals: attach an existing Agent Host session#333454
Ross Wollman (rwoll) wants to merge 1 commit into
mainfrom
rwoll/evaluation-session-attachment

Conversation

@rwoll

Copy link
Copy Markdown
Member

Summary

Adds a private, unlisted --attach-to-evaluation-session <uri> argument for evaluation harnesses.

The argument opens the exact existing remote Agent Host session in an Agents window as a secondary client. The window contributes its client tools, including the integrated browser tools, while the external driver retains ownership of evaluated turns and tool approval decisions.

When the argument is omitted, existing window routing, session handling, approval behavior, and active-client publication remain unchanged.

Example

code-insiders --attach-to-evaluation-session \
  'remote-example-provider:/session-id'

Implementation

  • The argument implies an Agents window for both initial and forwarded launches.
  • Attachment is restricted to the exact canonical remote session and connection.
  • Existing workspace-trust checks run before the session is opened.
  • Pending client-tool confirmations for the attached session are left to the external driver.
  • Approved running calls use the existing client-tool execution path.
  • Active-client publication waits for the complete client tool-set registration, so the first publication contains the full browser inventory.
  • The window does not originate evaluated prompts, turns, drafts, cancellations, or renames.

Validation

  • Focused product tests, transpilation, layering checks, and linting pass.
  • Two exact-build browser runs published the complete browser inventory initially and preserved tool ownership across later idempotent updates.
  • Both runs completed openBrowserPage and readPage, returned controlled page content, and executed no controller-owned tools.
  • Default, assisted, and auto-approve permissions pass in interactive and autopilot execution, both headlessly and with the Agents window attached.
  • Shell execution, explicit denial isolation, and a browser page mutation pass end to end.
  • Protocol identity, stale-response, duplicate-delivery, and cancellation coverage passes.

Copilot AI balanced review requested due to automatic review settings August 31, 2026 03:57
@vs-code-engineering

Copy link
Copy Markdown
Contributor

📬 CODENOTIFY

The following users are being notified based on files changed in this PR:

Robo (@deepak1556)

Matched files:

  • src/vs/code/electron-main/app.ts

roblourens
roblourens previously approved these changes Aug 31, 2026

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 review overview

Review tier: Balanced
Findings: 3 Low severity

New issues introduced by this change (3)
Severity Finding
Low severity src/​vs/​sessions/​contrib/​providers/​remoteAgentHost/​electron-browser/​evaluationSessionAttachment.contribution.ts — This resolves services through IInstantiationService.invokeFunction as a service locator. VS Code…
Low severity src/​vs/​sessions/​contrib/​providers/​remoteAgentHost/​electron-browser/​evaluationSessionAttachment.contribution.ts — This callback displays every attachment failure in the notification UI, but the errors created by…
Low severity src/​vs/​sessions/​contrib/​providers/​remoteAgentHost/​browser/​evaluationSessionAttachment.ts — These Pick-based service facades create production-only subset interfaces to simplify the test…
What changed in this PR

Adds a private CLI flow for attaching an Agents window to an existing remote Agent Host evaluation session.

Changes:

  • Routes the new argument to an Agents window and validates attachment identity.
  • Coordinates tool publication and external approval ownership.
  • Suppresses local draft synchronization and adds focused coverage.
File Description
src/​vs/​code/​electron-main/​app.ts Routes initial attachment launches.
src/​vs/​platform/​launch/​electron-main/​launchMainService.ts Routes forwarded attachment launches.
src/​vs/​platform/​environment/​common/​argv.ts Defines the argument and routing helper.
src/​vs/​platform/​environment/​node/​argv.ts Registers CLI parsing.
src/​vs/​platform/​environment/​test/​common/​argv.test.ts Tests CLI routing.
src/​vs/​sessions/​sessions.desktop.main.ts Loads the attachment contribution.
src/​vs/​sessions/​contrib/​providers/​remoteAgentHost/​electron-browser/​evaluationSessionAttachment.contribution.ts Starts attachment during restoration.
src/​vs/​sessions/​contrib/​providers/​remoteAgentHost/​browser/​evaluationSessionAttachment.ts Implements validation and lifecycle.
src/​vs/​workbench/​contrib/​chat/​browser/​agentSessions/​agentHost/​evaluationSessionAttachmentService.ts Tracks attachment identity and readiness.
src/​vs/​sessions/​contrib/​providers/​agentHost/​browser/​baseAgentHostSessionsProvider.ts Adds a publication barrier hook.
src/​vs/​sessions/​contrib/​providers/​remoteAgentHost/​browser/​remoteAgentHostSessionsProvider.ts Gates remote active-client publication.
src/​vs/​workbench/​contrib/​chat/​browser/​tools/​clientToolSetsContribution.ts Exposes tool-set reconciliation.
src/​vs/​workbench/​contrib/​chat/​browser/​agentSessions/​agentHost/​agentHostSessionHandler.ts Defers confirmations and draft publication.
src/​vs/​sessions/​contrib/​providers/​remoteAgentHost/​test/​electron-browser/​evaluationSessionAttachment.test.ts Covers attachment lifecycle and identity.
src/​vs/​sessions/​contrib/​providers/​remoteAgentHost/​test/​browser/​remoteAgentHostSessionsProvider.test.ts Covers publication gating and invalidation.
src/​vs/​workbench/​contrib/​chat/​test/​browser/​agentSessions/​agentHostClientTools.test.ts Covers inventory and tool ownership.
src/​vs/​workbench/​contrib/​chat/​test/​browser/​agentSessions/​agentHostChatContribution.test.ts Covers draft suppression.
src/​vs/​platform/​agentHost/​test/​node/​agentSideEffects.test.ts Verifies canonical approved execution requests.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +29 to +33
return instantiationService.invokeFunction((accessor: ServicesAccessor) => ({
sessionsManagementService: accessor.get(ISessionsManagementService),
sessionsService: accessor.get(ISessionsService),
connectionsService: accessor.get(IAgentHostConnectionsService),
attachmentService: accessor.get(IEvaluationSessionAttachmentService),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This lazy resolution is intentional. The contribution returns before invoking getStartupServices when the private argument is absent, which keeps the no-flag path from resolving or touching the session/connection services. invokeFunction is used here as a narrow, flag-gated lifetime boundary rather than as a general service locator.

Comment on lines +21 to +23
readonly sessionsManagementService: Pick<ISessionsManagementService, 'getSession' | 'onDidChangeSessions'>;
readonly sessionsService: Pick<ISessionsService, 'canOpenSession' | 'openSession'> & { readonly activeSession: IObservable<ISession | undefined>; readonly initialRestoreComplete: IObservable<boolean> };
readonly connectionsService: Pick<IAgentHostConnectionsService, 'connections' | 'resolveSessionResource'>;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This interface is a functional dependency bundle for the lifecycle helper, not a registered DI service. Production supplies the canonical services; the Pick types make the helper’s actual dependencies explicit and keep its unit tests focused. This pattern is also used elsewhere in the codebase, so I am keeping the narrower contract.

Example:

    code-insiders --attach-to-evaluation-session \
      'remote-example-provider:/session-id'

The private argument opens the exact session in an Agents window while the external driver retains turn and tool-approval ownership.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rwoll
Ross Wollman (rwoll) force-pushed the rwoll/evaluation-session-attachment branch from ef555c7 to da3e36e Compare August 31, 2026 20:09
@eleanorjboyd

Copy link
Copy Markdown
Member

Critical issues

🤖 [P1] Attached sessions remain writable.
AgentHostChatSession.isReadOnly ignores attachment mode and still exposes interrupt, fork, and rename callbacks. Consequently, the attached window can submit prompts, cancel the driver’s active turn, fork, or rename the evaluated session—contradicting the claimed ownership boundary.

🤖 [P1] Workspace trust fails open while metadata is unhydrated.
Attachment calls canOpenSession immediately after the session first appears. However, canOpenSession explicitly allows opening when workspace metadata has not hydrated. A cached session can therefore open and publish client tools before its remote workspace receives a trust decision.

🤖 [P1] A restored target can publish its active client before attachment restrictions exist.
Startup waits for initialRestoreComplete before calling attach. If the target was already active during restore, normal publication reaches the attachment barrier before any attachment exists, so _prepareActiveClientPublication returns no barrier. The external driver may observe an incomplete client-tool inventory and begin a turn early.

🤖 [P1] Valid startup attachments can fail during connection hydration.
resolveEvaluationSessionIdentity checks the remote connection only once. Cached sessions can surface before their connection is live, so startup fails nondeterministically instead of waiting for IAgentHostConnectionsService.onDidChangeConnections.

@rwoll
Ross Wollman (rwoll) marked this pull request as draft August 31, 2026 21:43
const services = getServices();
let attachment: IEvaluationSessionAttachment | undefined;
try {
await waitForState(services.sessionsService.initialRestoreComplete, complete => complete, undefined, token);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review: Waiting for initialRestoreComplete lets persisted restore activate this target before the attachment gate exists, so the provider can publish its first active-client snapshot ungated. Reopening the same restored session may not emit another active-session change, and readiness changes do not trigger a resync, leaving the external driver with an incomplete inventory. Register a provisional gate before restore can activate the target, then bind the exact backend identity later, or explicitly resynchronize publication after binding.

if (chatURI !== undefined) {
this._ensurePendingMessageSubscription(sessionResource, resolvedSession);
this._ensureDraftSyncSubscription(sessionResource, resolvedSession, chatURI);
if (!this._isEvaluationSessionAttached(resolvedSession)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review: This attachment check suppresses only draft synchronization. The session remains locally interactive, so Submit, Stop, and Rename can still dispatch ChatTurnStarted, ChatTurnCancelled, and SessionTitleChanged, racing the external evaluation driver. Gate these mutation paths for the exact attached identity while preserving execution of externally preapproved Running client tools.

throw new Error(localize('evaluationSessionAttachment.changedBeforePublication', "The active evaluation session changed before publication was ready."));
}
services.reconcileClientToolSets();
attachment.markActiveClientPublicationReady();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review: whenWorkbenchRestored excludes Eventually contributions. With Automations enabled, AutomationToolsContribution has not registered its four client tools when reconciliation runs, yet this releases the first active-client snapshot that signals the driver to start its turn. Wait for required late tool producers, or instantiate them explicitly, before marking publication ready.

return;
}

const preparation = this._prepareActiveClientPublication(cached, token);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review: For a sessionSchemeAlias provider, this new gate keys readiness to cached.backendUri, but the later publication reconstructs the channel from cached.agentProvider and rawId. Those schemes differ for alias-backed sessions, so the inventory can be sent to the UI URI instead of the attached backend AHP channel and never reach the driver. Publish to cached.backendUri and add an alias-provider regression test.

try {
await waitForState(services.sessionsService.initialRestoreComplete, complete => complete, undefined, token);
const session = await waitForExactSession(services.sessionsManagementService, resource, token);
const identity = resolveEvaluationSessionIdentity(resource, session, services.connectionsService);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review: waitForExactSession can return a persisted or seeded session before its remote connection is live, but resolveSessionResource is live-only. This one-shot resolution then fails before openSession can trigger on-demand connection, and the lifecycle never retries. Establish or await the owning provider connection before resolving and validating the exact identity.

}
shouldDeferConfirmation(identity: IEvaluationSessionIdentity & { readonly clientId: string }, request: { readonly kind: SessionInputRequestKind; readonly clientId: string; readonly toolCall: { readonly status: ToolCallStatus } }): boolean {
return this.isAttached(identity)
&& request.kind === SessionInputRequestKind.ToolClientExecution

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review: This defers only client ToolClientExecution requests. Server-owned tools use ToolConfirmation and still reach _awaitToolConfirmation, allowing this attached window to dispatch ChatToolCallConfirmed before the external driver; the first answer changes the run. Keep rendering server tools, but suppress local confirmation dispatch for the exact evaluation attachment and add coverage for that path.

await waitForState(services.sessionsService.initialRestoreComplete, complete => complete, undefined, token);
const session = await waitForExactSession(services.sessionsManagementService, resource, token);
const identity = resolveEvaluationSessionIdentity(resource, session, services.connectionsService);
if (!await raceCancellationError(services.sessionsService.canOpenSession(session), token)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review: canOpenSession is not a fail-closed trust gate here: it returns true for an already-active restored session and while workspace metadata is undefined. A driver-approved Running client-tool request can then execute without passing through the normal send-time folder-trust check. Wait for authoritative workspace or quick-chat metadata and perform an attachment-specific trust check before attaching or publishing client tools.

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.

8 participants