diff --git a/docs/tools/subagents.md b/docs/tools/subagents.md
index a5c77fe3785..3d6ddec0e43 100644
--- a/docs/tools/subagents.md
+++ b/docs/tools/subagents.md
@@ -221,6 +221,7 @@ Per-agent override: `agents.list[].subagents.delegationMode`.
If `thread: true` and `mode` is omitted, default becomes `session`. `mode: "session"` requires `thread: true`.
If thread binding is unavailable for the requester channel, use `mode: "run"` instead.
+ With `visible: true`, omit `mode`; visible sessions are persistent and do not support `mode: "run"`.
`"delete"` archives the session immediately after announce (still keeps the transcript via rename).
@@ -251,7 +252,7 @@ their latest assistant turn back to the requester; external delivery stays with
the parent/requester agent.
-With `visible: true`, `model`, `cwd`, and a same-agent `context: "fork"` are supported. A sandboxed target restricts `cwd` to that agent's workspace. Thread binding, `mode`, thinking overrides, light bootstrap context, and attachment staging are unavailable on this path because visible sessions are persistent dashboard sessions created through `sessions.create`. Visible spawning is also rejected when inherited tool restrictions cannot be carried into the dashboard session. See [Managed worktrees](/concepts/managed-worktrees) for checkout naming, setup, cleanup, and restore behavior.
+With `visible: true`, `model`, `cwd`, and a same-agent `context: "fork"` are supported. A sandboxed target restricts `cwd` to that agent's workspace. Thread binding, `mode`, thinking overrides, `lightContext`, `attachments`, and `attachAs` are unavailable on this path because visible sessions are persistent dashboard sessions created through `sessions.create`. Visible spawning is rejected when the requester was itself spawned with an inherited tool allowlist or denylist; that restriction is fixed at spawn time and has no config override. Session listing and addressing obey `tools.sessions.visibility`; the default `tree` scope covers the current session and its own spawn subtree. See [Managed worktrees](/concepts/managed-worktrees) for checkout naming, setup, cleanup, and restore behavior.
### Task names and targeting
diff --git a/src/agents/tool-description-presets.ts b/src/agents/tool-description-presets.ts
index 2126ceb0e95..b467e02743f 100644
--- a/src/agents/tool-description-presets.ts
+++ b/src/agents/tool-description-presets.ts
@@ -72,6 +72,8 @@ export function describeSessionsSpawnTool(options?: {
options?.threadAvailable
? '`mode="run"` one-shot; `mode="session"` persistent/thread-bound only on supporting requester channel.'
: '`mode="run"` one-shot background.',
+ '`visible=true`: persistent dashboard session; subagent only; omit `mode` (no `mode="run"`), `thread`, `thinking`, `lightContext`, `attachments`, `attachAs`; inherited tool allow/denylist blocks it at spawn with no config override.',
+ "Session listing/addressing obeys `tools.sessions.visibility` (`tree` default: current + own spawn subtree).",
"Inherits parent workspace. Native task arrives as first `[Subagent Task]`.",
'Native transcript needed: `context="fork"`; else omit/isolated.',
"Use fresh child for sidecar/parallel batch reads, multi-step search, data collection; avoid quick lookup/single read unless policy prefers.",
@@ -81,9 +83,9 @@ export function describeSessionsSpawnTool(options?: {
return baseDescription.join(" ");
}
return [
- ...baseDescription.slice(0, 3),
+ ...baseDescription.slice(0, 5),
'`runtime="acp"` ids: codex, claude, gemini, opencode, or configured ACP.',
- ...baseDescription.slice(3),
+ ...baseDescription.slice(5),
].join(" ");
}
diff --git a/src/agents/tools/sessions-spawn-tool.test.ts b/src/agents/tools/sessions-spawn-tool.test.ts
index 8020d131dbe..86270a764b8 100644
--- a/src/agents/tools/sessions-spawn-tool.test.ts
+++ b/src/agents/tools/sessions-spawn-tool.test.ts
@@ -382,12 +382,26 @@ describe("sessions_spawn tool", () => {
it("advertises visible sessions with terse UI guidance", () => {
const tool = createSessionsSpawnTool();
const schema = tool.parameters as {
- properties?: { visible?: { description?: string }; worktree?: unknown };
+ properties?: Record<
+ string,
+ { anyOf?: unknown; description?: string; enum?: string[] } | undefined
+ >;
};
expect(schema.properties?.visible?.description).toBe(
- "visible: user sees session in UI. Use when user asked or talks via web/app.",
+ "Persistent UI session; subagent only; omit mode/thread/thinking/lightContext/attachments/attachAs; unavailable with inherited tool allow/denylist.",
);
+ expect(tool.description).toContain("`visible=true`: persistent dashboard session");
+ expect(tool.description).toContain('no `mode="run"`');
+ expect(tool.description).toContain("inherited tool allow/denylist");
+ expect(tool.description).toContain("`tools.sessions.visibility`");
+ expect(schema.properties?.runtime?.description).toContain("visible=true");
+ expect(schema.properties?.mode?.description).toContain("Omit with visible=true");
+ expect(schema.properties?.lightContext?.description).toContain("unavailable with visible=true");
+ expect(schema.properties?.attachments?.description).toContain("unavailable with visible=true");
+ expect(schema.properties?.attachAs?.description).toContain("unavailable with visible=true");
+ expect(schema.properties?.mode?.enum).toEqual(["run"]);
+ expect(schema.properties?.mode?.anyOf).toBeUndefined();
expect(schema.properties?.worktree).toBeDefined();
});
@@ -477,7 +491,7 @@ describe("sessions_spawn tool", () => {
await expect(
tool.execute("hidden-worktree", { task: "inspect", worktree: true }),
- ).rejects.toThrow("worktree options require visible=true");
+ ).rejects.toThrow("Parameters require visible=true: worktree");
expect(hoisted.spawnSubagentDirectMock).not.toHaveBeenCalled();
});
@@ -616,32 +630,32 @@ describe("sessions_spawn tool", () => {
[
"thinking",
{ thinking: "high" },
- "thinking unavailable with visible=true: thinking overrides are not wired to the sessions.create path",
+ "Parameters unavailable with visible=true: thinking: thinking overrides are not wired to the sessions.create path",
],
[
"thread",
{ thread: true },
- "thread unavailable with visible=true: visible sessions route to the dashboard, not a channel thread",
+ "Parameters unavailable with visible=true: thread: visible sessions route to the dashboard, not a channel thread",
],
[
"mode",
{ mode: "session" },
- "mode unavailable with visible=true: visible sessions are persistent dashboard sessions",
+ "Parameters unavailable with visible=true: mode: visible sessions are persistent dashboard sessions",
],
[
"lightContext",
{ lightContext: true },
- "lightContext unavailable with visible=true: bootstrap staging is not wired to the sessions.create path",
+ "Parameters unavailable with visible=true: lightContext: bootstrap staging is not wired to the sessions.create path",
],
[
"attachments",
{ attachments: [{ name: "note.txt", content: "hello" }] },
- "attachments unavailable with visible=true: attachment staging is not wired to the sessions.create path",
+ "Parameters unavailable with visible=true: attachments: attachment staging is not wired to the sessions.create path",
],
[
"attachAs",
{ attachAs: { mountPath: "inputs" } },
- "attachAs unavailable with visible=true: attachment staging is not wired to the sessions.create path",
+ "Parameters unavailable with visible=true: attachAs: attachment staging is not wired to the sessions.create path",
],
] as const)("rejects visible %s overrides with a reason", async (_name, override, message) => {
const tool = createSessionsSpawnTool({ agentSessionKey: "agent:main:main" });
@@ -651,6 +665,26 @@ describe("sessions_spawn tool", () => {
).rejects.toThrow(message);
});
+ it("reports every unsupported visible parameter in one error", async () => {
+ const tool = createSessionsSpawnTool({ agentSessionKey: "agent:main:main" });
+
+ await expect(
+ tool.execute("visible-unsupported-many", {
+ task: "inspect",
+ runtime: "acp",
+ thinking: "high",
+ thread: true,
+ mode: "run",
+ lightContext: true,
+ attachments: [{ name: "note.txt", content: "hello" }],
+ attachAs: { mountPath: "inputs" },
+ visible: true,
+ }),
+ ).rejects.toThrow(
+ 'Parameters unavailable with visible=true: runtime: supports runtime="subagent" only; thinking: thinking overrides are not wired to the sessions.create path; thread: visible sessions route to the dashboard, not a channel thread; mode: visible sessions are persistent dashboard sessions; lightContext: bootstrap staging is not wired to the sessions.create path; attachments: attachment staging is not wired to the sessions.create path; attachAs: attachment staging is not wired to the sessions.create path',
+ );
+ });
+
it("denies visible sessions when tool restrictions cannot carry forward", async () => {
const callGateway = vi.fn();
const tool = createSessionsSpawnTool({
@@ -667,7 +701,8 @@ describe("sessions_spawn tool", () => {
expect(result.details).toMatchObject({
status: "forbidden",
- error: "Visible sessions unavailable with inherited tool restrictions.",
+ error:
+ "Visible sessions unavailable with inherited tool restrictions. This session was spawned with a tool allow/denylist; visible sessions require an unrestricted session.",
});
expect(callGateway).not.toHaveBeenCalled();
});
diff --git a/src/agents/tools/sessions-spawn-tool.ts b/src/agents/tools/sessions-spawn-tool.ts
index 72661ac1b11..765c5906f0b 100644
--- a/src/agents/tools/sessions-spawn-tool.ts
+++ b/src/agents/tools/sessions-spawn-tool.ts
@@ -143,29 +143,40 @@ function createSessionsSpawnToolSchema(params: {
label: Type.Optional(Type.String()),
runtime: optionalStringEnum(
params.acpAvailable ? SESSIONS_SPAWN_RUNTIMES : (["subagent"] as const),
+ { description: 'Runtime; visible=true requires "subagent".' },
),
agentId: Type.Optional(Type.String()),
model: Type.Optional(Type.String()),
- thinking: Type.Optional(Type.String()),
+ thinking: Type.Optional(
+ Type.String({ description: "Thinking override; unavailable with visible=true." }),
+ ),
cwd: Type.Optional(Type.String()),
...(params.threadAvailable
? {
thread: Type.Optional(
Type.Boolean({
- description: 'Bind new chat thread when supported; true defaults mode="session".',
+ description:
+ 'Bind new chat thread when supported; true defaults mode="session"; unavailable with visible=true.',
}),
),
}
: {}),
- mode: optionalStringEnum(spawnModes),
- cleanup: optionalStringEnum(["delete", "keep"] as const),
+ mode: optionalStringEnum(spawnModes, {
+ description: params.threadAvailable
+ ? '"run" one-shot; "session" persistent/thread-bound. Omit with visible=true.'
+ : '"run" one-shot. Omit with visible=true; visible sessions are persistent.',
+ }),
+ cleanup: optionalStringEnum(["delete", "keep"] as const, {
+ description: "Hidden session cleanup; visible=true always keeps the session.",
+ }),
sandbox: optionalStringEnum(SESSIONS_SPAWN_SANDBOX_MODES),
context: optionalStringEnum(SUBAGENT_SPAWN_CONTEXT_MODES, {
- description: "Native: omit/isolated clean; fork only needing requester transcript.",
+ description:
+ "Native: omit/isolated clean; fork only needing requester transcript; visible fork requires same agent.",
}),
lightContext: Type.Optional(
Type.Boolean({
- description: "Light bootstrap; subagent only.",
+ description: "Light bootstrap; subagent only; unavailable with visible=true.",
}),
),
...(params.swarmEnabled
@@ -187,15 +198,18 @@ function createSessionsSpawnToolSchema(params: {
encoding: Type.Optional(optionalStringEnum(["utf8", "base64"] as const)),
mimeType: Type.Optional(Type.String()),
}),
- { maxItems: 50 },
+ { maxItems: 50, description: "Inline snapshots; unavailable with visible=true." },
),
),
attachAs: Type.Optional(
- Type.Object({
- // Where the spawned agent should look for attachments.
- // Kept as a hint; implementation materializes into the child workspace.
- mountPath: Type.Optional(Type.String()),
- }),
+ Type.Object(
+ {
+ // Where the spawned agent should look for attachments.
+ // Kept as a hint; implementation materializes into the child workspace.
+ mountPath: Type.Optional(Type.String()),
+ },
+ { description: "Attachment mount hint; unavailable with visible=true." },
+ ),
),
...(params.acpAvailable
? {
diff --git a/src/agents/tools/sessions-spawn-visible.ts b/src/agents/tools/sessions-spawn-visible.ts
index bf606a08cc9..7c317c9e3a3 100644
--- a/src/agents/tools/sessions-spawn-visible.ts
+++ b/src/agents/tools/sessions-spawn-visible.ts
@@ -30,7 +30,8 @@ import { reserveVisibleChildSlot } from "./sessions-spawn-visible-admission.js";
export const VISIBLE_SESSIONS_SPAWN_SCHEMA = {
visible: Type.Optional(
Type.Boolean({
- description: "visible: user sees session in UI. Use when user asked or talks via web/app.",
+ description:
+ "Persistent UI session; subagent only; omit mode/thread/thinking/lightContext/attachments/attachAs; unavailable with inherited tool allow/denylist.",
}),
),
worktree: Type.Optional(Type.Boolean({ description: "Visible session worktree" })),
@@ -94,18 +95,30 @@ export async function maybeSpawnVisibleSession(params: {
const worktreeName = readStringParam(params.raw, "worktreeName");
const worktreeBaseRef = readStringParam(params.raw, "worktreeBaseRef");
if (params.raw.visible !== true) {
- if (worktree || worktreeName || worktreeBaseRef) {
- throw new ToolInputError("worktree options require visible=true");
+ const visibleOnlyParams = [
+ ["worktree", worktree],
+ ["worktreeName", worktreeName],
+ ["worktreeBaseRef", worktreeBaseRef],
+ ] as const;
+ const providedVisibleOnlyParams = visibleOnlyParams
+ .filter(([, value]) => value !== undefined && value !== false)
+ .map(([name]) => name);
+ if (providedVisibleOnlyParams.length > 0) {
+ throw new ToolInputError(
+ `Parameters require visible=true: ${providedVisibleOnlyParams.join(", ")}`,
+ );
}
return undefined;
}
- if (params.runtime !== "subagent") {
- throw new ToolInputError('visible=true supports runtime="subagent" only');
- }
const modelOverride = normalizeToolModelOverride(readStringParam(params.raw, "model"));
const requestedCwd = readStringParam(params.raw, "cwd");
const spawnedCwd = requestedCwd ? resolveUserPath(requestedCwd) : undefined;
const unsupported = [
+ [
+ "runtime",
+ params.runtime === "subagent" ? undefined : params.runtime,
+ 'supports runtime="subagent" only',
+ ],
[
"thinking",
readStringParam(params.raw, "thinking"),
@@ -133,10 +146,12 @@ export async function maybeSpawnVisibleSession(params: {
"attachment staging is not wired to the sessions.create path",
],
] as const;
- const unsupportedEntry = unsupported.find(([, value]) => value !== undefined);
- if (unsupportedEntry) {
+ const unsupportedEntries = unsupported.filter(([, value]) => value !== undefined);
+ if (unsupportedEntries.length > 0) {
throw new ToolInputError(
- `${unsupportedEntry[0]} unavailable with visible=true: ${unsupportedEntry[2]}`,
+ `Parameters unavailable with visible=true: ${unsupportedEntries
+ .map(([name, , reason]) => `${name}: ${reason}`)
+ .join("; ")}`,
);
}
@@ -147,7 +162,8 @@ export async function maybeSpawnVisibleSession(params: {
) {
return {
status: "forbidden",
- error: "Visible sessions unavailable with inherited tool restrictions.",
+ error:
+ "Visible sessions unavailable with inherited tool restrictions. This session was spawned with a tool allow/denylist; visible sessions require an unrestricted session.",
};
}
const ownership = resolveSubagentSpawnOwnership({
diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.discord-group.json b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.discord-group.json
index e2c491f54c4..cdcc42ce7d7 100644
--- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.discord-group.json
+++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.discord-group.json
@@ -138,13 +138,14 @@
"type": "function"
},
{
- "description": "Spawn clean child; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot; `mode=\"session\"` persistent/thread-bound only on supporting requester channel. Inherits parent workspace. Native task arrives as first `[Subagent Task]`. Native transcript needed: `context=\"fork\"`; else omit/isolated. Use fresh child for sidecar/parallel batch reads, multi-step search, data collection; avoid quick lookup/single read unless policy prefers. After spawn, do non-overlap work. Run result returns; session output stays thread.",
+ "description": "Spawn clean child; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot; `mode=\"session\"` persistent/thread-bound only on supporting requester channel. `visible=true`: persistent dashboard session; subagent only; omit `mode` (no `mode=\"run\"`), `thread`, `thinking`, `lightContext`, `attachments`, `attachAs`; inherited tool allow/denylist blocks it at spawn with no config override. Session listing/addressing obeys `tools.sessions.visibility` (`tree` default: current + own spawn subtree). Inherits parent workspace. Native task arrives as first `[Subagent Task]`. Native transcript needed: `context=\"fork\"`; else omit/isolated. Use fresh child for sidecar/parallel batch reads, multi-step search, data collection; avoid quick lookup/single read unless policy prefers. After spawn, do non-overlap work. Run result returns; session output stays thread.",
"inputSchema": {
"properties": {
"agentId": {
"type": "string"
},
"attachAs": {
+ "description": "Attachment mount hint; unavailable with visible=true.",
"properties": {
"mountPath": {
"type": "string"
@@ -153,6 +154,7 @@
"type": "object"
},
"attachments": {
+ "description": "Inline snapshots; unavailable with visible=true.",
"items": {
"properties": {
"content": {
@@ -176,11 +178,12 @@
"type": "array"
},
"cleanup": {
+ "description": "Hidden session cleanup; visible=true always keeps the session.",
"enum": ["delete", "keep"],
"type": "string"
},
"context": {
- "description": "Native: omit/isolated clean; fork only needing requester transcript.",
+ "description": "Native: omit/isolated clean; fork only needing requester transcript; visible fork requires same agent.",
"enum": ["isolated", "fork"],
"type": "string"
},
@@ -191,10 +194,11 @@
"type": "string"
},
"lightContext": {
- "description": "Light bootstrap; subagent only.",
+ "description": "Light bootstrap; subagent only; unavailable with visible=true.",
"type": "boolean"
},
"mode": {
+ "description": "\"run\" one-shot; \"session\" persistent/thread-bound. Omit with visible=true.",
"enum": ["run", "session"],
"type": "string"
},
@@ -202,6 +206,7 @@
"type": "string"
},
"runtime": {
+ "description": "Runtime; visible=true requires \"subagent\".",
"enum": ["subagent"],
"type": "string"
},
@@ -217,14 +222,15 @@
"type": "string"
},
"thinking": {
+ "description": "Thinking override; unavailable with visible=true.",
"type": "string"
},
"thread": {
- "description": "Bind new chat thread when supported; true defaults mode=\"session\".",
+ "description": "Bind new chat thread when supported; true defaults mode=\"session\"; unavailable with visible=true.",
"type": "boolean"
},
"visible": {
- "description": "visible: user sees session in UI. Use when user asked or talks via web/app.",
+ "description": "Persistent UI session; subagent only; omit mode/thread/thinking/lightContext/attachments/attachAs; unavailable with inherited tool allow/denylist.",
"type": "boolean"
},
"worktree": {
diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.heartbeat-turn.json b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.heartbeat-turn.json
index 7b1d28123c9..959b1e26b4b 100644
--- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.heartbeat-turn.json
+++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.heartbeat-turn.json
@@ -138,13 +138,14 @@
"type": "function"
},
{
- "description": "Spawn clean child; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot background. Inherits parent workspace. Native task arrives as first `[Subagent Task]`. Native transcript needed: `context=\"fork\"`; else omit/isolated. Use fresh child for sidecar/parallel batch reads, multi-step search, data collection; avoid quick lookup/single read unless policy prefers. After spawn, do non-overlap work while run result returns.",
+ "description": "Spawn clean child; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot background. `visible=true`: persistent dashboard session; subagent only; omit `mode` (no `mode=\"run\"`), `thread`, `thinking`, `lightContext`, `attachments`, `attachAs`; inherited tool allow/denylist blocks it at spawn with no config override. Session listing/addressing obeys `tools.sessions.visibility` (`tree` default: current + own spawn subtree). Inherits parent workspace. Native task arrives as first `[Subagent Task]`. Native transcript needed: `context=\"fork\"`; else omit/isolated. Use fresh child for sidecar/parallel batch reads, multi-step search, data collection; avoid quick lookup/single read unless policy prefers. After spawn, do non-overlap work while run result returns.",
"inputSchema": {
"properties": {
"agentId": {
"type": "string"
},
"attachAs": {
+ "description": "Attachment mount hint; unavailable with visible=true.",
"properties": {
"mountPath": {
"type": "string"
@@ -153,6 +154,7 @@
"type": "object"
},
"attachments": {
+ "description": "Inline snapshots; unavailable with visible=true.",
"items": {
"properties": {
"content": {
@@ -176,11 +178,12 @@
"type": "array"
},
"cleanup": {
+ "description": "Hidden session cleanup; visible=true always keeps the session.",
"enum": ["delete", "keep"],
"type": "string"
},
"context": {
- "description": "Native: omit/isolated clean; fork only needing requester transcript.",
+ "description": "Native: omit/isolated clean; fork only needing requester transcript; visible fork requires same agent.",
"enum": ["isolated", "fork"],
"type": "string"
},
@@ -191,10 +194,11 @@
"type": "string"
},
"lightContext": {
- "description": "Light bootstrap; subagent only.",
+ "description": "Light bootstrap; subagent only; unavailable with visible=true.",
"type": "boolean"
},
"mode": {
+ "description": "\"run\" one-shot. Omit with visible=true; visible sessions are persistent.",
"enum": ["run"],
"type": "string"
},
@@ -202,6 +206,7 @@
"type": "string"
},
"runtime": {
+ "description": "Runtime; visible=true requires \"subagent\".",
"enum": ["subagent"],
"type": "string"
},
@@ -217,10 +222,11 @@
"type": "string"
},
"thinking": {
+ "description": "Thinking override; unavailable with visible=true.",
"type": "string"
},
"visible": {
- "description": "visible: user sees session in UI. Use when user asked or talks via web/app.",
+ "description": "Persistent UI session; subagent only; omit mode/thread/thinking/lightContext/attachments/attachAs; unavailable with inherited tool allow/denylist.",
"type": "boolean"
},
"worktree": {
diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json
index 5e8295c804c..5659da9c46c 100644
--- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json
+++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/codex-dynamic-tools.telegram-direct.json
@@ -138,13 +138,14 @@
"type": "function"
},
{
- "description": "Spawn clean child; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot background. Inherits parent workspace. Native task arrives as first `[Subagent Task]`. Native transcript needed: `context=\"fork\"`; else omit/isolated. Use fresh child for sidecar/parallel batch reads, multi-step search, data collection; avoid quick lookup/single read unless policy prefers. After spawn, do non-overlap work while run result returns.",
+ "description": "Spawn clean child; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot background. `visible=true`: persistent dashboard session; subagent only; omit `mode` (no `mode=\"run\"`), `thread`, `thinking`, `lightContext`, `attachments`, `attachAs`; inherited tool allow/denylist blocks it at spawn with no config override. Session listing/addressing obeys `tools.sessions.visibility` (`tree` default: current + own spawn subtree). Inherits parent workspace. Native task arrives as first `[Subagent Task]`. Native transcript needed: `context=\"fork\"`; else omit/isolated. Use fresh child for sidecar/parallel batch reads, multi-step search, data collection; avoid quick lookup/single read unless policy prefers. After spawn, do non-overlap work while run result returns.",
"inputSchema": {
"properties": {
"agentId": {
"type": "string"
},
"attachAs": {
+ "description": "Attachment mount hint; unavailable with visible=true.",
"properties": {
"mountPath": {
"type": "string"
@@ -153,6 +154,7 @@
"type": "object"
},
"attachments": {
+ "description": "Inline snapshots; unavailable with visible=true.",
"items": {
"properties": {
"content": {
@@ -176,11 +178,12 @@
"type": "array"
},
"cleanup": {
+ "description": "Hidden session cleanup; visible=true always keeps the session.",
"enum": ["delete", "keep"],
"type": "string"
},
"context": {
- "description": "Native: omit/isolated clean; fork only needing requester transcript.",
+ "description": "Native: omit/isolated clean; fork only needing requester transcript; visible fork requires same agent.",
"enum": ["isolated", "fork"],
"type": "string"
},
@@ -191,10 +194,11 @@
"type": "string"
},
"lightContext": {
- "description": "Light bootstrap; subagent only.",
+ "description": "Light bootstrap; subagent only; unavailable with visible=true.",
"type": "boolean"
},
"mode": {
+ "description": "\"run\" one-shot. Omit with visible=true; visible sessions are persistent.",
"enum": ["run"],
"type": "string"
},
@@ -202,6 +206,7 @@
"type": "string"
},
"runtime": {
+ "description": "Runtime; visible=true requires \"subagent\".",
"enum": ["subagent"],
"type": "string"
},
@@ -217,10 +222,11 @@
"type": "string"
},
"thinking": {
+ "description": "Thinking override; unavailable with visible=true.",
"type": "string"
},
"visible": {
- "description": "visible: user sees session in UI. Use when user asked or talks via web/app.",
+ "description": "Persistent UI session; subagent only; omit mode/thread/thinking/lightContext/attachments/attachAs; unavailable with inherited tool allow/denylist.",
"type": "boolean"
},
"worktree": {
diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md
index 946cc215dc3..52cf1b87681 100644
--- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md
+++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/discord-group-codex-message-tool.md
@@ -216,8 +216,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the
"roughTokens": 0
},
"dynamicToolsJson": {
- "chars": 55806,
- "roughTokens": 13952
+ "chars": 56822,
+ "roughTokens": 14206
},
"openClawDeveloperInstructions": {
"chars": 3559,
@@ -228,8 +228,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the
"roughTokens": 7021
},
"totalWithDynamicToolsJson": {
- "chars": 83892,
- "roughTokens": 20973
+ "chars": 84908,
+ "roughTokens": 21227
},
"userInputText": {
"chars": 1442,
diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md
index 5cb8d4862ec..8d05c4fa11e 100644
--- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md
+++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-direct-codex-message-tool.md
@@ -216,8 +216,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the
"roughTokens": 0
},
"dynamicToolsJson": {
- "chars": 55533,
- "roughTokens": 13884
+ "chars": 56514,
+ "roughTokens": 14129
},
"openClawDeveloperInstructions": {
"chars": 2450,
@@ -228,8 +228,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the
"roughTokens": 6642
},
"totalWithDynamicToolsJson": {
- "chars": 82101,
- "roughTokens": 20526
+ "chars": 83082,
+ "roughTokens": 20771
},
"userInputText": {
"chars": 1033,
diff --git a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md
index 588231b77e5..89e53071874 100644
--- a/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md
+++ b/test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path/telegram-heartbeat-codex-tool.md
@@ -217,8 +217,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the
"roughTokens": 0
},
"dynamicToolsJson": {
- "chars": 56823,
- "roughTokens": 14206
+ "chars": 57804,
+ "roughTokens": 14451
},
"openClawDeveloperInstructions": {
"chars": 2469,
@@ -229,8 +229,8 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the
"roughTokens": 6777
},
"totalWithDynamicToolsJson": {
- "chars": 83930,
- "roughTokens": 20983
+ "chars": 84911,
+ "roughTokens": 21228
},
"userInputText": {
"chars": 1271,