# Code Mode tool surface

> The model-visible exec and wait contracts, the hidden tool catalog, and name collisions

- 网址：https://funcoding.ai/agents/openclaw/tools/code-mode/tool-surface/
- 来源：OpenClaw 官方文档原文（英文），MIT 许可，同步于 2026-10-11
- 官方原文：https://docs.openclaw.ai/zh-CN/tools/code-mode/tool-surface

---
## Model-visible tools

When code mode is active, the model sees `exec`, `wait`, and any required
direct-only tool. Every other enabled tool is hidden from the model-facing
tool list and registered in the code-mode catalog.

Use `exec` for tool orchestration, data joining, loops, parallel nested calls,
and structured transforms. Use `wait` only when `exec` returns a resumable
`waiting` result.

## `exec`

`exec` starts a code-mode cell in a fresh JavaScript context and returns one
result. Variables, functions, and imports never carry over between cells.
`await store(key, value)` and `await load(key)` keep small JSON values across
cells and turns in the same session, including restarts. See the
[session store](https://funcoding.ai/agents/openclaw/tools/code-mode/guest-api/#session-store) for limits and commit
rules. Input code is model generated and must be treated as hostile.

Model-facing input:

```typescript
type CodeModeExecInput = {
  title: string;
  code: string;
  restartSafe?: boolean;
  awaitResults?: boolean;
};
```

Rules:

- Every new cell requires a nonblank `title` of at most 120 characters. Use a
  short purpose, usually 3–7 words, such as "Inspect the dependency graph".
  Describe intended work without claiming success or including secrets. The
  Control UI displays this title on the execution row. Nested tools retain their
  own summaries; `wait` needs no title and resumes activity under the original cell.
- `code` is the required model-facing JavaScript field and must be non-empty.
- `command` is accepted as an exec-compatible alias for hook policies and
  trusted rewrites (the normal OpenClaw shell exec tool also uses a `command`
  field). Blank caller aliases are treated as absent; a hook or trusted policy
  that invalidates one populated alias (blank or non-string) invalidates both so
  execution fails closed. When both aliases are non-empty, their values must match.
- Write plain JavaScript. TypeScript annotations, interfaces, and other
  TypeScript-only syntax are not accepted. Tool signatures and `API.read`
  declarations remain available as documentation for composing calls.
- `API.read(path)` returns `{ path, description, content, bytes }`, not a string.
  Use `(await API.read(path)).content` for the declaration text.
- The retired `language` and `typecheck` fields are rejected with `invalid_input`.
  Tool arguments are validated when each call reaches its normal execution owner;
  Code Mode does not typecheck the whole program before execution.
- Do not set `restartSafe` on a new `exec`. Set it to `true` only when OpenClaw
  explicitly requests replay after a gateway restart, and never for `write`,
  `edit`, `exec`, or any mutation. Every catalog call must be explicitly
  replay-safe. OpenClaw rejects unmarked catalog tools and namespace
  surfaces that are not proven replay-safe. A generic exec surface is not
  replay-safe merely because one command appears read-only; use audited read,
  grep, or find tools.
  Suspended results are marked replay-safe so
  [restart recovery](https://funcoding.ai/agents/openclaw/gateway/restart-recovery/) can reconstruct an interrupted
  turn from its transcript instead of restoring the process-local continuation.
  Recovery remains limited to audited read-only core tools and explicitly
  replay-safe plugin tools. `store` and `load` are unavailable in `restartSafe`
  cells. Leave the field omitted for ordinary calls.
- `exec` rejects `import`, `require`, dynamic import, and module-loader
  patterns.
- `exec` never exposes the normal shell `exec` implementation recursively.
- Outer code-mode `exec` hook events carry `toolKind: "code_mode_exec"` and
  `toolInputKind: "javascript"`, so policies can
  distinguish code-mode cells from shell-style `exec` calls that share the
  same tool name.

Result:

```typescript
type CodeModeResult = CodeModeCompletedResult | CodeModeWaitingResult | CodeModeFailedResult;

type CodeModeCompletedResult = {
  status: "completed";
  value: unknown;
  warnings?: string[];
  output?: CodeModeOutput[];
  telemetry: CodeModeTelemetry;
};

type CodeModeWaitingResult = {
  status: "waiting";
  runId: string;
  reason: "pending_tools" | "yield";
  pendingToolCalls?: CodeModePendingToolCall[];
  output?: CodeModeOutput[];
  telemetry: CodeModeTelemetry;
};

type CodeModeFailedResult = {
  status: "failed";
  error: string;
  code?: CodeModeErrorCode;
  output?: CodeModeOutput[];
  telemetry: CodeModeTelemetry;
};
```

`exec` returns `waiting` when the guest suspends with resumable state that still
needs a model-visible continuation — an explicit `yield_control(...)`, or a
bridge tool call that has not resolved within the exec deadline. The result
includes a `runId` for `wait`. Native-channel exec approvals are different:
while the operator decision is pending, OpenClaw suspends both the Code Mode
execution budget and the owning agent-run budget. The original `exec` remains
in flight, then resumes with exactly its unused budget after approval resolves;
it does not return `pending_tools` or require model polling through `wait`.
If the guest explicitly yields while a sibling call awaits approval, the cell
keeps observing that approval while parked. Its next `wait` pauses for the
pending decision too. Each call receives only its own unused execution budget;
parked time and earlier calls' approval pauses do not add execution credit.
Cancellation, owner checks, and continuation expiry remain unchanged.
Bridge requests — `catalog.search`, handle `describe()`, callable tool handles,
and namespace calls including MCP — are auto-drained inside the same
`exec`/`wait` call while they resolve within the deadline, so a compact code
block that awaits several tools runs to completion in one model turn instead of
forcing one model tool call per await.
A bridged shell `exec` without `yieldMs` or `background: true` waits for the
remaining call budget (`tools.codeMode.timeoutMs`, default 10 s) minus a resume
margin before backgrounding, so commands that finish within that window return
inline in the same turn. Late sequential calls background sooner and still
return their process handle so the guest can resume inline.

`exec` returns `completed` only when the guest VM has no pending work and the
final value is JSON-compatible after OpenClaw's output adapter runs.

New `exec` and `wait` result text uses compact JSON to leave more of the context
budget for tool data. Status, continuation, replay safety, telemetry, and
structured `details` fields are preserved. Text inside JSON strings keeps its
original whitespace. The TUI displays these results as literal text so Markdown
syntax and long-token formatting cannot change their values. URLs remain visible
as text rather than becoming Markdown links.

### Required results

Use `awaitResults: true` when this program’s results are required to finish the
current task. The existing cell owner keeps the tool call open while the VM is
parked, then resumes that exact continuation when pending tool results settle.
It does not return a `waiting` handle or make model polling calls. Completed
actions are not replayed. Failure, Stop, owner replacement, and existing run
and tool deadlines still end the operation.

`awaitResults` pauses only off-VM tool waiting. Preparation, guest execution,
checkpointing, and restoration share the original execution allowance; each
settlement does **not** grant a fresh allowance. Output, memory, pending-call,
and active-cell limits are unchanged. `yield_control` cannot abandon an
unfinished required program; finish the program or cancel the owning run.
Ordinary cells retain their existing explicit-yield behavior.

Required cells collect ordinary shell commands to completion. Explicit
`background: true` remains the opt-out for intentionally detached servers.
A shell `exec({awaitResults: true, ...})` or `agents_wait({awaitResults: true, ...})`
inside an ordinary cell also makes that cell required. These declarations do
not enable `tools.exec.notifyOnExit`.

For required collector results, await `agents.run(...)` in a required cell,
or call `agents_wait({ids, awaitResults: true})`. Ordinary announcing children
still use their existing `sessions_yield` handoff. An accepted background
handle is not a terminal result: this feature does not infer obligations from
plan text or silently turn every asynchronous service into required work.

### Source in session history

In the built-in OpenClaw runtime, the JSON Code Mode tool executes the original
input. Session history preserves computations such as `const API_TOKEN = computeToken();`
and boolean or null initializers in the outer call's JavaScript
`code` and `command` fields, while masking credential literals, recognizable
tokens, registered secrets, and configured redaction patterns. Credential
assignments use full masks so repeated storage redaction stays stable.

This treatment does not extend to shell commands, nested tool calls, unrelated
argument strings, or assistant prose. Large or unrecognized source syntax
remains subject to diagnostic masking. Stored source is a redacted record, not
a place to recover credentials; no additional setting is required.
This applies to new calls; already-redacted source cannot be reconstructed.
The Copilot runtime's separate transcript journal does not yet preserve this
source structure. Native Codex uses a separate freeform source path; this
behavior does not describe its storage.

## `wait`

`wait` continues a suspended code-mode VM.

Input:

```typescript
type CodeModeWaitInput = {
  runId: string;
};
```

Output is the same `CodeModeResult` union returned by `exec`.

`wait` exists because nested OpenClaw tools can be slow, interactive, or stream
partial updates; the model should not need to keep one long `exec` call open
while the host waits for ordinary external work. Native-channel exec approvals
are the exception: they stay inside the original `exec` so approval authority
remains bound to the admitted run.

Fast inline host exchanges retain the same execution context. Explicit yield or
an exhausted call budget can suspend it without replaying tools. Node retains a
live worker context; QuickJS snapshots its VM and can release the worker.
QuickJS worker-pool pressure can also park a cell internally within the same
call. Actual QuickJS checkpoints enforce `maxSnapshotBytes`, so a large live
heap may complete inline but fail when it must genuinely park.

Both executors use the same `wait` contract:

1. `exec` evaluates code until completion, failure, or suspension.
2. On suspension, the selected executor retains its continuation and OpenClaw
   records pending host work.
3. When pending work settles, `wait` resumes the same executor. Node continues
   its live context; QuickJS restores its snapshot and re-registers callbacks.
4. OpenClaw delivers nested tool results and lets JavaScript continuations run.
5. `wait` returns `completed`, `failed`, or another `waiting` result.

Continuations are runtime state, not user artifacts: their ownership lives only
in an in-process map (no database or disk write), they expire, and they are
scoped to the run and session that created them. QuickJS snapshots are
size-limited; Node retains live worker memory instead. See
[Code Mode executors](https://funcoding.ai/agents/openclaw/tools/code-mode/executors/#understand-waits-and-limits).
One cell owner spans initial execution, suspension, and every resume. Canceling
the owning run or current tool call, or closing its tool catalog at attempt
teardown, cancels active workers and pending host work and releases parked
continuations, even if no `wait` call follows. Catalog description refreshes and
client tool additions do not close the owner. An external operation that ignores
cancellation may still finish, but cannot resume the closed guest, emit later
guest output, or start another guest tool call.

The process-wide limit of 64 slots applies to suspended cells and their reserved
resume slots. A resume keeps its slot until it completes or parks again; an
initial execution reserves a slot before dispatching host work and retains it
through live execution and internal parking. A cell with no host work does not
consume a slot.

Snapshot TTL measures idle time while a cell is parked. An admitted `wait` keeps
the cell alive under its call deadline while pending tools settle. If that call
returns `waiting` again, parking starts a fresh snapshot TTL.

`wait` fails (as a `failed` result) when:

- `runId` is unknown or its continuation already expired.
- the caller is not in the same run/session scope as the suspended run.
- a `wait` is already in flight for that `runId`.
- the selected executor cannot resume (for example, its worker exited or QuickJS restore fails).
- a QuickJS checkpoint would exceed `maxSnapshotBytes`. Ordinary oversized successful output is truncated and remains successful.

## Tool catalog

The hidden catalog includes tools after effective policy filtering, in this
order: OpenClaw core tools, bundled plugin tools, external plugin tools, MCP
tools, then client-provided tools for the current run.

Catalog ids remain opaque host-only routing identities. They are stable within
one run and deterministic across equivalent tool sets when possible, but they
are never included in the prompt, guest metadata, handle descriptions, or
errors. Policy, approvals, telemetry, replay safety, and namespace dispatch
continue to use them internally.

Before the worker starts, OpenClaw projects one effective winner per exact tool
name and computes its final guest callable name. This matches direct-mode
precedence: later client tools win an exact-name shadow, while plugin conflict
enforcement remains unchanged. The finalized projection is carried through
bridge calls and continuation resume; consumers do not reconstruct it from the
catalog.

The catalog omits code-mode control tools (`exec`, `wait`, `tool_search`,
`tool_describe`, `tool_call`) and direct-only tools. Controls
must not recurse through the catalog; direct-only tools remain model-visible
because their structured results cannot cross the JSON guest bridge.

MCP entries stay in the run-scoped catalog so policy, approvals, hooks,
telemetry, transcript projection, and exact tool ids remain shared with
normal tool execution. `catalog.search(...)` searches native and MCP capabilities
together and returns callable handles. MCP matches identify their final
`MCP.<server>.<tool>` path and declaration file; invoking a handle uses that
same namespace dispatcher. `catalog.all()` lists only native and client handles.
Remote descriptions and schemas stay out of the trusted quick index.

## Tool Search interaction

Code mode supersedes the OpenClaw Tool Search model surface for runs where it
is active.

When Code Mode engages through forced `true` or `"auto"` activation:

- OpenClaw does not expose `tool_search`, `tool_describe`, or `tool_call` as
  model-visible tools.
- The same cataloging idea moves inside the guest runtime.
- The guest runtime receives bare async globals plus callable search/describe
  handles for native tools, plus on-demand MCP search handles.
- MCP calls use the generated `MCP` namespace, directly or through a search
  handle; handle `describe()` requests the exact `$api()` header and schema.
- Nested calls dispatch through the same OpenClaw executor path that Tool
  Search uses.

See [Tool Search](https://funcoding.ai/agents/openclaw/tools/tool-search/) for the OpenClaw structured catalog surface
that code mode supersedes for active runs.

## Tool names and collisions

The model-visible `exec` tool is the code-mode tool. If the normal OpenClaw
shell `exec` tool is enabled, it is hidden from the model and cataloged like
any other tool.

Inside the guest runtime:

- An exact JavaScript-safe tool name stays exact: `web_search(...)` and
  `sessions_spawn(...)`.
- Invalid identifier characters become `_`; a still-invalid first character
  gets the `tool_` prefix. For example, `llm-task` becomes `llm_task` when that
  name is free.
- JavaScript reserved words, specialized globals, and normalized collisions
  receive a deterministic short suffix derived from the host-only identity.
- Exact safe names win their unsuffixed spelling. A raw tool never overwrites
  `catalog`, `MCP`, `API`, `nodes`, `skills`, `namespaces`, `results`, `store`,
  `load`, output/timer helpers,
  or optional Swarm globals.
- The normal shell `exec` tool is callable as the `exec(...)` guest global when
  policy allows it. The code-mode control `exec` is not recursively available
  inside the guest.
