fix: keep OpenClaw control tools available when tool_search misroutes (#99561)

* fix(codex): keep OpenClaw control tools direct

* test(codex): refresh direct-tool prompt snapshots

* fix(codex): keep heartbeat direct only when available

* fix(codex): keep heartbeat tool schema stable

---------

Co-authored-by: Eva <eva@100yen.org>
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
This commit is contained in:
Eva
2026-07-04 04:34:32 +02:00
committed by GitHub
parent 8250b8d02e
commit faa4f4782e
9 changed files with 419 additions and 373 deletions
@@ -178,12 +178,13 @@ afterEach(() => {
});
describe("createCodexDynamicToolBridge", () => {
it("keeps turn-yield direct while deferring OpenClaw session spawn", () => {
it("keeps OpenClaw control-path tools direct while deferring broad tools", () => {
const bridge = createCodexDynamicToolBridge({
tools: [
createTool({ name: "web_search" }),
createTool({ name: "message" }),
createTool({ name: HEARTBEAT_RESPONSE_TOOL_NAME }),
createTool({ name: "agents_list" }),
createTool({ name: "sessions_spawn" }),
createTool({ name: "sessions_yield" }),
],
@@ -194,6 +195,7 @@ describe("createCodexDynamicToolBridge", () => {
const webSearch = specs.find((tool) => tool.name === "web_search");
const message = specs.find((tool) => tool.name === "message");
const heartbeat = specs.find((tool) => tool.name === HEARTBEAT_RESPONSE_TOOL_NAME);
const agentsList = specs.find((tool) => tool.name === "agents_list");
const sessionsSpawn = specs.find((tool) => tool.name === "sessions_spawn");
const sessionsYield = specs.find((tool) => tool.name === "sessions_yield");
@@ -212,11 +214,8 @@ describe("createCodexDynamicToolBridge", () => {
namespace: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
deferLoading: true,
});
expectDynamicSpec(sessionsSpawn, {
name: "sessions_spawn",
namespace: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
deferLoading: true,
});
expectNoNamespace(agentsList);
expectNoNamespace(sessionsSpawn);
expectNoNamespace(sessionsYield);
});
@@ -353,9 +353,14 @@ export type CodexDynamicToolBridge = {
/** Namespace attached to OpenClaw-owned dynamic tools exposed to Codex. */
export const CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE = "openclaw";
// Keep OpenClaw session spawning searchable in Codex mode so Codex's native
// spawn_agent remains the primary Codex subagent surface.
const ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES = new Set(["sessions_yield"]);
// Keep OpenClaw control-path tools directly callable even when Codex tool_search
// is unavailable or resolves a connector-only universe. Developer instructions
// still steer normal Codex subagents to native spawn_agent.
const ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES = new Set([
"agents_list",
"sessions_spawn",
"sessions_yield",
]);
const EXPLICIT_MESSAGE_PROVIDER_KEYS = ["channel", "provider"];
const EXPLICIT_MESSAGE_TARGET_KEYS = ["target", "to", "channelId"];
const EXPLICIT_MESSAGE_THREAD_KEYS = ["threadId", "thread_id", "messageThreadId", "topicId"];
@@ -1594,11 +1594,12 @@ describe("runCodexAppServerAttempt", () => {
expect(dynamicToolNames).toEqual(["message"]);
});
it("keeps searchable OpenClaw dynamic tools when code-mode-only is enabled", () => {
it("keeps OpenClaw control-path tools direct when code-mode-only is enabled", () => {
const tools = [
createRuntimeDynamicTool("message"),
createRuntimeDynamicTool("web_search"),
createRuntimeDynamicTool("heartbeat_respond"),
createRuntimeDynamicTool("agents_list"),
createRuntimeDynamicTool("sessions_spawn"),
createRuntimeDynamicTool("sessions_yield"),
];
@@ -1612,6 +1613,7 @@ describe("runCodexAppServerAttempt", () => {
const message = specs.find((tool) => tool.name === "message");
const webSearch = specs.find((tool) => tool.name === "web_search");
const heartbeat = specs.find((tool) => tool.name === "heartbeat_respond");
const agentsList = specs.find((tool) => tool.name === "agents_list");
const sessionsSpawn = specs.find((tool) => tool.name === "sessions_spawn");
const sessionsYield = specs.find((tool) => tool.name === "sessions_yield");
@@ -1621,13 +1623,15 @@ describe("runCodexAppServerAttempt", () => {
expect(webSearch?.deferLoading).toBe(true);
expect(heartbeat?.namespace).toBe(CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE);
expect(heartbeat?.deferLoading).toBe(true);
expect(sessionsSpawn?.namespace).toBe(CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE);
expect(sessionsSpawn?.deferLoading).toBe(true);
expect(agentsList).not.toHaveProperty("namespace");
expect(agentsList).not.toHaveProperty("deferLoading");
expect(sessionsSpawn).not.toHaveProperty("namespace");
expect(sessionsSpawn).not.toHaveProperty("deferLoading");
expect(sessionsYield).not.toHaveProperty("namespace");
expect(sessionsYield).not.toHaveProperty("deferLoading");
});
it("registers heartbeat response durably without advertising it on normal turns", async () => {
it("keeps the heartbeat schema deferred and stable across normal and heartbeat turns", async () => {
testing.setOpenClawCodingToolsFactoryForTests((options) => [
createRuntimeDynamicTool("message"),
...(options?.enableHeartbeatTool === true
@@ -1640,12 +1644,10 @@ describe("runCodexAppServerAttempt", () => {
const params = createParams(sessionFile, workspaceDir);
params.disableTools = false;
params.runtimePlan = createCodexRuntimePlanFixture();
params.sourceReplyDeliveryMode = "message_tool_only";
if (trigger) {
params.trigger = trigger;
}
if (trigger === "heartbeat") {
params.sourceReplyDeliveryMode = "message_tool_only";
}
return params;
};
@@ -1661,30 +1663,76 @@ describe("runCodexAppServerAttempt", () => {
const normalInstructions = testing.buildDeveloperInstructions(createRunParams(), {
dynamicTools: normalBridge.availableSpecs,
});
const heartbeatParams = createRunParams("heartbeat");
const heartbeatBridge = createCodexToolBridgeForTest(
heartbeatParams,
[createRuntimeDynamicTool("message"), createRuntimeDynamicTool("heartbeat_respond")],
registeredTools,
);
const heartbeatInstructions = testing.buildDeveloperInstructions(heartbeatParams, {
dynamicTools: heartbeatBridge.availableSpecs,
});
const nextNormalParams = createRunParams();
const nextNormalBridge = createCodexToolBridgeForTest(
nextNormalParams,
[createRuntimeDynamicTool("message")],
registeredTools,
);
const registeredToolNames = specNames(normalBridge.specs);
expect(registeredToolNames).toContain("message");
expect(registeredToolNames).toContain("heartbeat_respond");
expect(normalInstructions).toContain(
"Deferred searchable OpenClaw dynamic tools available: message.",
);
expect(normalInstructions).not.toContain(
"Deferred searchable OpenClaw dynamic tools available: heartbeat_respond",
);
const heartbeatBridge = createCodexToolBridgeForTest(
createRunParams("heartbeat"),
[createRuntimeDynamicTool("message"), createRuntimeDynamicTool("heartbeat_respond")],
registeredTools,
expect(heartbeatInstructions).toContain(
"Deferred searchable OpenClaw dynamic tools available: heartbeat_respond.",
);
const nextNormalBridge = createCodexToolBridgeForTest(
createRunParams(),
[createRuntimeDynamicTool("message")],
registeredTools,
for (const bridge of [normalBridge, heartbeatBridge, nextNormalBridge]) {
const heartbeat = flattenSpecsWithNamespace(bridge.specs).find(
(tool) => tool.name === "heartbeat_respond",
);
expect(heartbeat?.namespace).toBe(CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE);
expect(heartbeat?.deferLoading).toBe(true);
}
expect(codexDynamicToolsFingerprint(heartbeatBridge.specs)).toBe(
codexDynamicToolsFingerprint(normalBridge.specs),
);
expect(codexDynamicToolsFingerprint(nextNormalBridge.specs)).toBe(
codexDynamicToolsFingerprint(normalBridge.specs),
);
expect(specNames(heartbeatBridge.specs)).toEqual(registeredToolNames);
expect(specNames(nextNormalBridge.specs)).toEqual(registeredToolNames);
let startedThreadId: string | undefined;
const request = vi.fn(async (method: string) => {
if (method === "thread/start") {
startedThreadId = "thread-stable-heartbeat";
return threadStartResult(startedThreadId);
}
if (method === "thread/resume") {
return threadStartResult(startedThreadId);
}
throw new Error(`unexpected method: ${method}`);
});
const turns = [
{ params: createRunParams(), bridge: normalBridge },
{ params: heartbeatParams, bridge: heartbeatBridge },
{ params: nextNormalParams, bridge: nextNormalBridge },
];
for (const turn of turns) {
await startOrResumeThread({
client: { request } as never,
params: turn.params,
cwd: workspaceDir,
dynamicTools: turn.bridge.specs,
appServer: createThreadLifecycleAppServerOptions(),
});
}
expect(request.mock.calls.map(([method]) => method)).toEqual([
"thread/start",
"thread/resume",
"thread/resume",
]);
});
it("keeps message in the registered schema when disabled for an internal turn", async () => {
@@ -124,6 +124,108 @@
"name": "message",
"type": "function"
},
{
"description": "List agent ids allowed for `sessions_spawn runtime=\"subagent\"`.",
"inputSchema": {
"properties": {},
"type": "object"
},
"name": "agents_list",
"type": "function"
},
{
"description": "Spawn clean child session; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot; `mode=\"session\"` persistent/thread-bound, only when requester channel supports thread bindings. Subagents inherit parent workspace. Native subagents get task in first visible `[Subagent Task]` message. Native only: `context=\"fork\"` only when child needs current transcript; else omit or `isolated`. Use for fresh child-session work. Delegate sidecar/parallel tasks: batch file reads, multi-step searches, data collection. Avoid delegating quick lookups or single-file reads unless policy prefers delegation. After spawning, do non-overlapping work; run-mode results return, session-mode output stays in thread.",
"inputSchema": {
"properties": {
"agentId": {
"type": "string"
},
"attachAs": {
"properties": {
"mountPath": {
"type": "string"
}
},
"type": "object"
},
"attachments": {
"items": {
"properties": {
"content": {
"type": "string"
},
"encoding": {
"enum": ["utf8", "base64"],
"type": "string"
},
"mimeType": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": ["name", "content"],
"type": "object"
},
"maxItems": 50,
"type": "array"
},
"cleanup": {
"enum": ["delete", "keep"],
"type": "string"
},
"context": {
"description": "Native context. Omit/\"isolated\" for clean child; \"fork\" only when child needs requester transcript.",
"enum": ["isolated", "fork"],
"type": "string"
},
"cwd": {
"type": "string"
},
"label": {
"type": "string"
},
"lightContext": {
"description": "Light bootstrap context; runtime=\"subagent\" only.",
"type": "boolean"
},
"mode": {
"enum": ["run", "session"],
"type": "string"
},
"model": {
"type": "string"
},
"runtime": {
"enum": ["subagent"],
"type": "string"
},
"sandbox": {
"enum": ["inherit", "require"],
"type": "string"
},
"task": {
"type": "string"
},
"taskName": {
"description": "Stable alias for later targeting; lowercase letters/digits/underscores/hyphens, starts letter.",
"type": "string"
},
"thinking": {
"type": "string"
},
"thread": {
"description": "Bind spawn to new chat thread when supported. `thread=true` defaults mode=\"session\".",
"type": "boolean"
}
},
"required": ["task"],
"type": "object"
},
"name": "sessions_spawn",
"type": "function"
},
{
"description": "End current turn. Use after spawning subagents; results arrive as next message.",
"inputSchema": {
@@ -1001,16 +1103,6 @@
"name": "gateway",
"type": "function"
},
{
"deferLoading": true,
"description": "List agent ids allowed for `sessions_spawn runtime=\"subagent\"`.",
"inputSchema": {
"properties": {},
"type": "object"
},
"name": "agents_list",
"type": "function"
},
{
"deferLoading": true,
"description": "List visible sessions; filter by kind, label, agentId, search, activity. Use before sessions_history or sessions_send target selection.",
@@ -1117,100 +1209,6 @@
"name": "sessions_send",
"type": "function"
},
{
"deferLoading": true,
"description": "Spawn clean child session; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot; `mode=\"session\"` persistent/thread-bound, only when requester channel supports thread bindings. Subagents inherit parent workspace. Native subagents get task in first visible `[Subagent Task]` message. Native only: `context=\"fork\"` only when child needs current transcript; else omit or `isolated`. Use for fresh child-session work. Delegate sidecar/parallel tasks: batch file reads, multi-step searches, data collection. Avoid delegating quick lookups or single-file reads unless policy prefers delegation. After spawning, do non-overlapping work; run-mode results return, session-mode output stays in thread.",
"inputSchema": {
"properties": {
"agentId": {
"type": "string"
},
"attachAs": {
"properties": {
"mountPath": {
"type": "string"
}
},
"type": "object"
},
"attachments": {
"items": {
"properties": {
"content": {
"type": "string"
},
"encoding": {
"enum": ["utf8", "base64"],
"type": "string"
},
"mimeType": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": ["name", "content"],
"type": "object"
},
"maxItems": 50,
"type": "array"
},
"cleanup": {
"enum": ["delete", "keep"],
"type": "string"
},
"context": {
"description": "Native context. Omit/\"isolated\" for clean child; \"fork\" only when child needs requester transcript.",
"enum": ["isolated", "fork"],
"type": "string"
},
"cwd": {
"type": "string"
},
"label": {
"type": "string"
},
"lightContext": {
"description": "Light bootstrap context; runtime=\"subagent\" only.",
"type": "boolean"
},
"mode": {
"enum": ["run", "session"],
"type": "string"
},
"model": {
"type": "string"
},
"runtime": {
"enum": ["subagent"],
"type": "string"
},
"sandbox": {
"enum": ["inherit", "require"],
"type": "string"
},
"task": {
"type": "string"
},
"taskName": {
"description": "Stable alias for later targeting; lowercase letters/digits/underscores/hyphens, starts letter.",
"type": "string"
},
"thinking": {
"type": "string"
},
"thread": {
"description": "Bind spawn to new chat thread when supported. `thread=true` defaults mode=\"session\".",
"type": "boolean"
}
},
"required": ["task"],
"type": "object"
},
"name": "sessions_spawn",
"type": "function"
},
{
"deferLoading": true,
"description": "List active and recent subagents for the requester session. If sessions_yield exists, use it for completion; do not poll wait loops.",
@@ -124,6 +124,104 @@
"name": "message",
"type": "function"
},
{
"description": "List agent ids allowed for `sessions_spawn runtime=\"subagent\"`.",
"inputSchema": {
"properties": {},
"type": "object"
},
"name": "agents_list",
"type": "function"
},
{
"description": "Spawn clean child session; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot background work. Subagents inherit parent workspace. Native subagents get task in first visible `[Subagent Task]` message. Native only: `context=\"fork\"` only when child needs current transcript; else omit or `isolated`. Use for fresh child-session work. Delegate sidecar/parallel tasks: batch file reads, multi-step searches, data collection. Avoid delegating quick lookups or single-file reads unless policy prefers delegation. After spawning, do non-overlapping work while run-mode results return.",
"inputSchema": {
"properties": {
"agentId": {
"type": "string"
},
"attachAs": {
"properties": {
"mountPath": {
"type": "string"
}
},
"type": "object"
},
"attachments": {
"items": {
"properties": {
"content": {
"type": "string"
},
"encoding": {
"enum": ["utf8", "base64"],
"type": "string"
},
"mimeType": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": ["name", "content"],
"type": "object"
},
"maxItems": 50,
"type": "array"
},
"cleanup": {
"enum": ["delete", "keep"],
"type": "string"
},
"context": {
"description": "Native context. Omit/\"isolated\" for clean child; \"fork\" only when child needs requester transcript.",
"enum": ["isolated", "fork"],
"type": "string"
},
"cwd": {
"type": "string"
},
"label": {
"type": "string"
},
"lightContext": {
"description": "Light bootstrap context; runtime=\"subagent\" only.",
"type": "boolean"
},
"mode": {
"enum": ["run"],
"type": "string"
},
"model": {
"type": "string"
},
"runtime": {
"enum": ["subagent"],
"type": "string"
},
"sandbox": {
"enum": ["inherit", "require"],
"type": "string"
},
"task": {
"type": "string"
},
"taskName": {
"description": "Stable alias for later targeting; lowercase letters/digits/underscores/hyphens, starts letter.",
"type": "string"
},
"thinking": {
"type": "string"
}
},
"required": ["task"],
"type": "object"
},
"name": "sessions_spawn",
"type": "function"
},
{
"description": "End current turn. Use after spawning subagents; results arrive as next message.",
"inputSchema": {
@@ -1037,16 +1135,6 @@
"name": "gateway",
"type": "function"
},
{
"deferLoading": true,
"description": "List agent ids allowed for `sessions_spawn runtime=\"subagent\"`.",
"inputSchema": {
"properties": {},
"type": "object"
},
"name": "agents_list",
"type": "function"
},
{
"deferLoading": true,
"description": "List visible sessions; filter by kind, label, agentId, search, activity. Use before sessions_history or sessions_send target selection.",
@@ -1153,96 +1241,6 @@
"name": "sessions_send",
"type": "function"
},
{
"deferLoading": true,
"description": "Spawn clean child session; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot background work. Subagents inherit parent workspace. Native subagents get task in first visible `[Subagent Task]` message. Native only: `context=\"fork\"` only when child needs current transcript; else omit or `isolated`. Use for fresh child-session work. Delegate sidecar/parallel tasks: batch file reads, multi-step searches, data collection. Avoid delegating quick lookups or single-file reads unless policy prefers delegation. After spawning, do non-overlapping work while run-mode results return.",
"inputSchema": {
"properties": {
"agentId": {
"type": "string"
},
"attachAs": {
"properties": {
"mountPath": {
"type": "string"
}
},
"type": "object"
},
"attachments": {
"items": {
"properties": {
"content": {
"type": "string"
},
"encoding": {
"enum": ["utf8", "base64"],
"type": "string"
},
"mimeType": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": ["name", "content"],
"type": "object"
},
"maxItems": 50,
"type": "array"
},
"cleanup": {
"enum": ["delete", "keep"],
"type": "string"
},
"context": {
"description": "Native context. Omit/\"isolated\" for clean child; \"fork\" only when child needs requester transcript.",
"enum": ["isolated", "fork"],
"type": "string"
},
"cwd": {
"type": "string"
},
"label": {
"type": "string"
},
"lightContext": {
"description": "Light bootstrap context; runtime=\"subagent\" only.",
"type": "boolean"
},
"mode": {
"enum": ["run"],
"type": "string"
},
"model": {
"type": "string"
},
"runtime": {
"enum": ["subagent"],
"type": "string"
},
"sandbox": {
"enum": ["inherit", "require"],
"type": "string"
},
"task": {
"type": "string"
},
"taskName": {
"description": "Stable alias for later targeting; lowercase letters/digits/underscores/hyphens, starts letter.",
"type": "string"
},
"thinking": {
"type": "string"
}
},
"required": ["task"],
"type": "object"
},
"name": "sessions_spawn",
"type": "function"
},
{
"deferLoading": true,
"description": "List active and recent subagents for the requester session. If sessions_yield exists, use it for completion; do not poll wait loops.",
@@ -124,6 +124,104 @@
"name": "message",
"type": "function"
},
{
"description": "List agent ids allowed for `sessions_spawn runtime=\"subagent\"`.",
"inputSchema": {
"properties": {},
"type": "object"
},
"name": "agents_list",
"type": "function"
},
{
"description": "Spawn clean child session; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot background work. Subagents inherit parent workspace. Native subagents get task in first visible `[Subagent Task]` message. Native only: `context=\"fork\"` only when child needs current transcript; else omit or `isolated`. Use for fresh child-session work. Delegate sidecar/parallel tasks: batch file reads, multi-step searches, data collection. Avoid delegating quick lookups or single-file reads unless policy prefers delegation. After spawning, do non-overlapping work while run-mode results return.",
"inputSchema": {
"properties": {
"agentId": {
"type": "string"
},
"attachAs": {
"properties": {
"mountPath": {
"type": "string"
}
},
"type": "object"
},
"attachments": {
"items": {
"properties": {
"content": {
"type": "string"
},
"encoding": {
"enum": ["utf8", "base64"],
"type": "string"
},
"mimeType": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": ["name", "content"],
"type": "object"
},
"maxItems": 50,
"type": "array"
},
"cleanup": {
"enum": ["delete", "keep"],
"type": "string"
},
"context": {
"description": "Native context. Omit/\"isolated\" for clean child; \"fork\" only when child needs requester transcript.",
"enum": ["isolated", "fork"],
"type": "string"
},
"cwd": {
"type": "string"
},
"label": {
"type": "string"
},
"lightContext": {
"description": "Light bootstrap context; runtime=\"subagent\" only.",
"type": "boolean"
},
"mode": {
"enum": ["run"],
"type": "string"
},
"model": {
"type": "string"
},
"runtime": {
"enum": ["subagent"],
"type": "string"
},
"sandbox": {
"enum": ["inherit", "require"],
"type": "string"
},
"task": {
"type": "string"
},
"taskName": {
"description": "Stable alias for later targeting; lowercase letters/digits/underscores/hyphens, starts letter.",
"type": "string"
},
"thinking": {
"type": "string"
}
},
"required": ["task"],
"type": "object"
},
"name": "sessions_spawn",
"type": "function"
},
{
"description": "End current turn. Use after spawning subagents; results arrive as next message.",
"inputSchema": {
@@ -1001,16 +1099,6 @@
"name": "gateway",
"type": "function"
},
{
"deferLoading": true,
"description": "List agent ids allowed for `sessions_spawn runtime=\"subagent\"`.",
"inputSchema": {
"properties": {},
"type": "object"
},
"name": "agents_list",
"type": "function"
},
{
"deferLoading": true,
"description": "List visible sessions; filter by kind, label, agentId, search, activity. Use before sessions_history or sessions_send target selection.",
@@ -1117,96 +1205,6 @@
"name": "sessions_send",
"type": "function"
},
{
"deferLoading": true,
"description": "Spawn clean child session; default `runtime=\"subagent\"`. `mode=\"run\"` one-shot background work. Subagents inherit parent workspace. Native subagents get task in first visible `[Subagent Task]` message. Native only: `context=\"fork\"` only when child needs current transcript; else omit or `isolated`. Use for fresh child-session work. Delegate sidecar/parallel tasks: batch file reads, multi-step searches, data collection. Avoid delegating quick lookups or single-file reads unless policy prefers delegation. After spawning, do non-overlapping work while run-mode results return.",
"inputSchema": {
"properties": {
"agentId": {
"type": "string"
},
"attachAs": {
"properties": {
"mountPath": {
"type": "string"
}
},
"type": "object"
},
"attachments": {
"items": {
"properties": {
"content": {
"type": "string"
},
"encoding": {
"enum": ["utf8", "base64"],
"type": "string"
},
"mimeType": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": ["name", "content"],
"type": "object"
},
"maxItems": 50,
"type": "array"
},
"cleanup": {
"enum": ["delete", "keep"],
"type": "string"
},
"context": {
"description": "Native context. Omit/\"isolated\" for clean child; \"fork\" only when child needs requester transcript.",
"enum": ["isolated", "fork"],
"type": "string"
},
"cwd": {
"type": "string"
},
"label": {
"type": "string"
},
"lightContext": {
"description": "Light bootstrap context; runtime=\"subagent\" only.",
"type": "boolean"
},
"mode": {
"enum": ["run"],
"type": "string"
},
"model": {
"type": "string"
},
"runtime": {
"enum": ["subagent"],
"type": "string"
},
"sandbox": {
"enum": ["inherit", "require"],
"type": "string"
},
"task": {
"type": "string"
},
"taskName": {
"description": "Stable alias for later targeting; lowercase letters/digits/underscores/hyphens, starts letter.",
"type": "string"
},
"thinking": {
"type": "string"
}
},
"required": ["task"],
"type": "object"
},
"name": "sessions_spawn",
"type": "function"
},
{
"deferLoading": true,
"description": "List active and recent subagents for the requester session. If sessions_yield exists, use it for completion; do not poll wait loops.",
@@ -88,16 +88,16 @@
"developerInstructions": "<see Reconstructed Model-Bound Prompt Layers>",
"dynamicTools": [
"message",
"agents_list",
"sessions_spawn",
"sessions_yield",
"nodes",
"cron",
"tts",
"gateway",
"agents_list",
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"subagents",
"session_status",
"web_search",
@@ -227,20 +227,20 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the
"roughTokens": 0
},
"dynamicToolsJson": {
"chars": 50951,
"roughTokens": 12738
"chars": 50395,
"roughTokens": 12599
},
"openClawDeveloperInstructions": {
"chars": 3074,
"roughTokens": 769
"chars": 3045,
"roughTokens": 762
},
"totalTextOnly": {
"chars": 27692,
"roughTokens": 6923
"chars": 27663,
"roughTokens": 6916
},
"totalWithDynamicToolsJson": {
"chars": 78645,
"roughTokens": 19662
"chars": 78060,
"roughTokens": 19515
},
"userInputText": {
"chars": 1535,
@@ -427,7 +427,7 @@ Approval policy is currently never. Do not provide the `sandbox_permissions` for
````text
You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.
Deferred searchable OpenClaw dynamic tools available: agents_list, cron, gateway, nodes, session_status, sessions_history, sessions_list, sessions_send, sessions_spawn, subagents, tts, web_fetch, web_search. Use `tool_search` to load exact callable specs before use.
Deferred searchable OpenClaw dynamic tools available: cron, gateway, nodes, session_status, sessions_history, sessions_list, sessions_send, subagents, tts, web_fetch, web_search. Use `tool_search` to load exact callable specs before use.
Use Codex native `spawn_agent` for Codex subagents. Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation.
@@ -560,16 +560,16 @@ Full JSON: `codex-dynamic-tools.discord-group.json`
```json
[
"message",
"agents_list",
"sessions_spawn",
"sessions_yield",
"nodes",
"cron",
"tts",
"gateway",
"agents_list",
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"subagents",
"session_status",
"web_search",
@@ -88,16 +88,16 @@
"developerInstructions": "<see Reconstructed Model-Bound Prompt Layers>",
"dynamicTools": [
"message",
"agents_list",
"sessions_spawn",
"sessions_yield",
"nodes",
"cron",
"tts",
"gateway",
"agents_list",
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"subagents",
"session_status",
"web_search",
@@ -227,20 +227,20 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the
"roughTokens": 0
},
"dynamicToolsJson": {
"chars": 50620,
"roughTokens": 12655
"chars": 50084,
"roughTokens": 12521
},
"openClawDeveloperInstructions": {
"chars": 1965,
"roughTokens": 492
"chars": 1936,
"roughTokens": 484
},
"totalTextOnly": {
"chars": 26081,
"roughTokens": 6521
"chars": 26052,
"roughTokens": 6513
},
"totalWithDynamicToolsJson": {
"chars": 76703,
"roughTokens": 19176
"chars": 76138,
"roughTokens": 19035
},
"userInputText": {
"chars": 1033,
@@ -427,7 +427,7 @@ Approval policy is currently never. Do not provide the `sandbox_permissions` for
````text
You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.
Deferred searchable OpenClaw dynamic tools available: agents_list, cron, gateway, nodes, session_status, sessions_history, sessions_list, sessions_send, sessions_spawn, subagents, tts, web_fetch, web_search. Use `tool_search` to load exact callable specs before use.
Deferred searchable OpenClaw dynamic tools available: cron, gateway, nodes, session_status, sessions_history, sessions_list, sessions_send, subagents, tts, web_fetch, web_search. Use `tool_search` to load exact callable specs before use.
Use Codex native `spawn_agent` for Codex subagents. Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation.
@@ -537,16 +537,16 @@ Full JSON: `codex-dynamic-tools.telegram-direct.json`
```json
[
"message",
"agents_list",
"sessions_spawn",
"sessions_yield",
"nodes",
"cron",
"tts",
"gateway",
"agents_list",
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"subagents",
"session_status",
"web_search",
@@ -88,17 +88,17 @@
"developerInstructions": "<see Reconstructed Model-Bound Prompt Layers>",
"dynamicTools": [
"message",
"agents_list",
"sessions_spawn",
"sessions_yield",
"nodes",
"cron",
"heartbeat_respond",
"tts",
"gateway",
"agents_list",
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"subagents",
"session_status",
"web_search",
@@ -228,20 +228,20 @@ This is the deterministic model-bound layer stack OpenClaw can snapshot for the
"roughTokens": 0
},
"dynamicToolsJson": {
"chars": 51910,
"roughTokens": 12978
"chars": 51374,
"roughTokens": 12844
},
"openClawDeveloperInstructions": {
"chars": 1984,
"roughTokens": 496
"chars": 1955,
"roughTokens": 489
},
"totalTextOnly": {
"chars": 27024,
"roughTokens": 6756
"chars": 26995,
"roughTokens": 6749
},
"totalWithDynamicToolsJson": {
"chars": 78936,
"roughTokens": 19734
"chars": 78371,
"roughTokens": 19593
},
"userInputText": {
"chars": 1271,
@@ -428,7 +428,7 @@ Approval policy is currently never. Do not provide the `sandbox_permissions` for
````text
You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.
Deferred searchable OpenClaw dynamic tools available: agents_list, cron, gateway, heartbeat_respond, nodes, session_status, sessions_history, sessions_list, sessions_send, sessions_spawn, subagents, tts, web_fetch, web_search. Use `tool_search` to load exact callable specs before use.
Deferred searchable OpenClaw dynamic tools available: cron, gateway, heartbeat_respond, nodes, session_status, sessions_history, sessions_list, sessions_send, subagents, tts, web_fetch, web_search. Use `tool_search` to load exact callable specs before use.
Use Codex native `spawn_agent` for Codex subagents. Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation.
@@ -547,17 +547,17 @@ Full JSON: `codex-dynamic-tools.heartbeat-turn.json`
```json
[
"message",
"agents_list",
"sessions_spawn",
"sessions_yield",
"nodes",
"cron",
"heartbeat_respond",
"tts",
"gateway",
"agents_list",
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"subagents",
"session_status",
"web_search",