Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/vs/platform/agentHost/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,13 @@ For every provider, migration and discovery partition the same native catalog: m

### Server-tool creation provenance

Treat a session as the user-visible unit of work. `create_session` requires a relationship: `currentSession` creates a peer chat for tasks in the current plan or deliverable, sharing its workspace, lifecycle, and aggregate diff; `independent` creates a top-level session for a separate deliverable that needs its own workspace, provider, or lifecycle. A title is required for both relationships and is applied before the initial prompt starts.
Treat a session as the user-visible unit of work. `create_session` requires a relationship: `currentSession` creates a peer chat for tasks in the current plan or deliverable, sharing its workspace, lifecycle, and aggregate diff; `independent` creates a top-level session for a separate deliverable that needs its own workspace, provider, or lifecycle, or for an explicitly requested worktree. A title is required for both relationships and is applied before the initial prompt starts.

Sessions created by the `create_session` server tool record only the creating session, chat, and turn as immutable, provider-neutral creation provenance in the initial session summary `_meta` bag, before the session is published or its first prompt starts. The reference supports related-session placement, source identification and session-list presentation; it does not define a hierarchy, grant communication privileges, or trigger lifecycle notifications.

`list_sessions` exposes a session's configured project URI separately from its primary and additional working directories. `create_session` accepts those URIs directly and can resolve a unique project display name, preferring the configured project root over a transient worktree. Ambiguous names require an explicit project URI.

An independent session inherits the creating session's host-owned isolation selection independently of provider-owned configuration. The target workspace still constrains the effective selection, so a folder that cannot support Git worktrees resolves to folder isolation.
An independent session inherits the creating session's host-owned isolation selection independently of provider-owned configuration; otherwise it uses worktree isolation. The optional `worktree` argument overrides that selection. Agents must set it only when the user explicitly asks to create a worktree (`true`) or work without one (`false`); otherwise they must omit it. With `false`, an exact linked-worktree root reported by Git resolves to its primary checkout before session creation. Nested and ordinary additional workspace folders are preserved. The option is invalid with `currentSession`, whose chats share the existing workspace. The target workspace still constrains the effective selection, so a folder that cannot support Git worktrees resolves to folder isolation.

---

Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/agentHost/node/agentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,7 @@ export class AgentService extends Disposable implements IAgentService {
&& readSessionWorkspaceless(this._stateManager.getSessionState(session.toString())?._meta),
listSessions: () => this.listSessions(),
getSession: session => this._getSessionMetadata(session),
getWorktreeRoots: workspace => this._gitService.getWorktreeRoots(workspace),
createSession: config => this.createSession(config),
getModels: () => {
const models: IAgentModelInfo[] = [];
Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/agentHost/node/agentServiceFoundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class AgentServiceCallbackAdapter implements IAgentServiceCallbackBinder
canConvertWorkspace: session => this.value.sessionServerToolAccessor.canConvertWorkspace(session),
listSessions: () => this.value.sessionServerToolAccessor.listSessions(),
getSession: session => this.value.sessionServerToolAccessor.getSession(session),
getWorktreeRoots: workspace => this.value.sessionServerToolAccessor.getWorktreeRoots(workspace),
createSession: config => this.value.sessionServerToolAccessor.createSession(config),
getModels: () => this.value.sessionServerToolAccessor.getModels(),
getCreationDefaults: source => this.value.sessionServerToolAccessor.getCreationDefaults(source),
Expand Down
31 changes: 25 additions & 6 deletions src/vs/platform/agentHost/node/shared/sessionServerTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@ const createSessionInputSchema: ToolDefinition['inputSchema'] = {
relationship: {
type: 'string',
enum: [...createSessionRelationshipValues],
description: 'Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle.',
description: 'Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree.',
},
prompt: { type: 'string', description: 'Initial prompt to send to the new session.' },
workspace: { type: 'string', description: 'For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`.' },
worktree: { type: 'boolean', description: 'Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session\'s isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`.' },
title: { type: 'string', maxLength: 200, description: 'Short title for the new chat or independent session.' },
model: { type: 'string', description: 'Optional model ID or display name. Defaults to the current chat\'s model. For `currentSession`, the model must belong to the current session\'s provider; for `independent`, the model selects the new session\'s provider.' },
},
Expand Down Expand Up @@ -173,7 +174,7 @@ export const sessionServerToolDefinitions: IAgentServerToolDefinition[] = [
{
name: SessionServerToolName.CreateSession,
title: 'Create Session',
description: 'Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session\'s workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.',
description: 'Create delegated work and start it with an initial prompt, either in a new chat sharing the current session\'s workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.',
inputSchema: createSessionInputSchema,
annotations: { readOnlyHint: false },
},
Expand Down Expand Up @@ -216,6 +217,7 @@ export function currentSessionUri(toolCallChannel: ProtocolURI): URI {
interface ICreateSessionArgs {
readonly relationship?: unknown;
readonly workspace?: unknown;
readonly worktree?: unknown;
readonly prompt?: unknown;
readonly title?: unknown;
readonly model?: unknown;
Expand All @@ -229,6 +231,7 @@ export type IResolvedCreateSessionArgs = {
} | {
readonly relationship: 'independent';
readonly workspace: URI;
readonly worktree?: boolean;
readonly prompt: string;
readonly title: string;
readonly model?: IAgentModelInfo;
Expand All @@ -240,6 +243,7 @@ export interface IAgentServiceSessionServerToolAccessor {
readonly canConvertWorkspace: (session: URI) => boolean;
readonly listSessions: () => Promise<readonly IAgentSessionMetadata[]>;
readonly getSession: (session: URI) => Promise<IAgentSessionMetadata | undefined>;
readonly getWorktreeRoots: (workspace: URI) => Promise<readonly URI[]>;
readonly createSession: (config: IAgentCreateSessionConfig) => Promise<URI>;
readonly getModels: () => readonly IAgentModelInfo[];
readonly getCreationDefaults: (source: URI) => ISessionCreationDefaults | undefined;
Expand Down Expand Up @@ -515,11 +519,15 @@ export function getCreateSessionArgs(rawArgs: unknown, sessions: readonly IAgent
validateRenameTitle(title, SessionServerToolName.CreateSession);
const workspace = getOptionalString(args.workspace, 'workspace', SessionServerToolName.CreateSession);
const modelName = getOptionalString(args.model, 'model', SessionServerToolName.CreateSession);
const worktree = getOptionalBoolean(args.worktree, 'worktree', SessionServerToolName.CreateSession);
const model = resolveModel(modelName, models, relationship === 'currentSession' ? currentProvider : undefined);
if (relationship === 'currentSession') {
if (workspace !== undefined) {
throw new Error(`Invalid ${SessionServerToolName.CreateSession} input: workspace is only valid when relationship is "independent".`);
}
if (worktree !== undefined) {
throw new Error(`Invalid ${SessionServerToolName.CreateSession} input: worktree is only valid when relationship is "independent"; chats in the current session share its workspace.`);
}
return {
relationship,
prompt,
Expand All @@ -530,6 +538,7 @@ export function getCreateSessionArgs(rawArgs: unknown, sessions: readonly IAgent
return {
relationship,
workspace: resolveWorkspace(getRequiredString(workspace, 'workspace', SessionServerToolName.CreateSession), sessions),
...(worktree !== undefined ? { worktree } : {}),
prompt,
title,
...(model !== undefined ? { model } : {}),
Expand Down Expand Up @@ -817,21 +826,31 @@ export async function applyCreateSessionTool(accessor: ISessionServerToolAccesso
if (parentDepth >= maxSessionSpawnDepth) {
throw new Error(`Refusing to create a session: recursion limit reached (max spawn depth ${maxSessionSpawnDepth}). This session was itself created ${parentDepth} level(s) deep.`);
}
let workspace = args.workspace;
if (args.worktree === false) {
const [primaryRoot, ...linkedRoots] = await accessor.getWorktreeRoots(workspace);
if (primaryRoot && linkedRoots.some(root => isEqual(root, workspace))) {
workspace = primaryRoot;
}
}
const defaults = source ? accessor.getCreationDefaults(source) : undefined;
const provider = args.model?.provider ?? defaults?.provider;
const inheritsSourceProvider = provider !== undefined && provider === defaults?.provider;
const inheritedProviderConfig = inheritsSourceProvider ? defaults?.config : undefined;
const isolation = defaults?.project !== undefined && isEqual(defaults.project, args.workspace)
? defaults.isolation
: 'worktree';
let isolation: 'folder' | 'worktree' | undefined = 'worktree';
if (args.worktree !== undefined) {
isolation = args.worktree ? 'worktree' : 'folder';
} else if (defaults?.project !== undefined && isEqual(defaults.project, workspace)) {
isolation = defaults.isolation;
}
const configValues = inheritedProviderConfig === undefined && isolation === undefined
? undefined
: {
...inheritedProviderConfig,
...(isolation !== undefined ? { [SessionConfigKey.Isolation]: isolation } : {}),
};
const config: IAgentCreateSessionConfig = {
workingDirectories: args.workspace ? [args.workspace] : undefined,
workingDirectories: [workspace],
...(provider !== undefined ? { provider } : {}),
...(args.model !== undefined ? { model: { id: args.model.id } } : defaults?.model !== undefined ? { model: defaults.model } : {}),
...(configValues !== undefined ? { config: configValues } : {}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@
},
{
"name": "create_session",
"description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.",
"description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.",
"input_schema": {
"type": "object",
"properties": {
Expand All @@ -767,7 +767,7 @@
"currentSession",
"independent"
],
"description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle."
"description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree."
},
"prompt": {
"type": "string",
Expand All @@ -777,6 +777,10 @@
"type": "string",
"description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`."
},
"worktree": {
"type": "boolean",
"description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`."
},
"title": {
"type": "string",
"description": "Short title for the new chat or independent session.\n\n{maxLength: 200}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@
},
{
"name": "create_session",
"description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.",
"description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.",
"input_schema": {
"type": "object",
"properties": {
Expand All @@ -767,7 +767,7 @@
"currentSession",
"independent"
],
"description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle."
"description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree."
},
"prompt": {
"type": "string",
Expand All @@ -777,6 +777,10 @@
"type": "string",
"description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`."
},
"worktree": {
"type": "boolean",
"description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`."
},
"title": {
"type": "string",
"description": "Short title for the new chat or independent session.\n\n{maxLength: 200}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@
},
{
"name": "create_session",
"description": "Create delegated work and start it with an initial prompt. Set `relationship` to `currentSession` when the task belongs to the current plan or deliverable; this creates a new chat that shares the current session's workspace, lifecycle, and aggregate diff. Set it to `independent` only for a separate deliverable that needs its own workspace, provider, or top-level lifecycle.",
"description": "Create delegated work and start it with an initial prompt, either in a new chat sharing the current session's workspace, lifecycle, and aggregate diff, or in an independent session. Only supply `worktree` when the user explicitly requests working with or without a new worktree; never combine it with `currentSession`.",
"input_schema": {
"type": "object",
"properties": {
Expand All @@ -767,7 +767,7 @@
"currentSession",
"independent"
],
"description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks. Use `independent` only for a separate deliverable that needs its own workspace and top-level lifecycle."
"description": "Whether this work belongs to the current session or is independently managed. Use `currentSession` for tasks from the current plan or deliverable, including parallel or delegated tasks, unless the user explicitly requests a worktree. Use `independent` for a separate deliverable that needs its own workspace, provider, or top-level lifecycle, or for an explicitly requested worktree."
},
"prompt": {
"type": "string",
Expand All @@ -777,6 +777,10 @@
"type": "string",
"description": "For `independent` work: unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Required for `independent` and invalid for `currentSession`."
},
"worktree": {
"type": "boolean",
"description": "Override isolation for the new independent session. Set true only when the user explicitly asks to create a worktree, or false only when the user explicitly asks to work without one. Omit to preserve the existing isolation behavior: inherit the creating session's isolation for the same project, otherwise use worktree isolation. Only valid with relationship `independent`; omit for `currentSession`."
},
"title": {
"type": "string",
"description": "Short title for the new chat or independent session.\n\n{maxLength: 200}"
Expand Down
Loading