# `Nous.Tools.RunCode`
[🔗](https://github.com/nyo16/nous/blob/v0.17.1/lib/nous/tools/run_code.ex#L1)

Code Mode's transport tool: runs a model-authored program that calls tools.

One `run_code` call replaces a chain of individual tool calls — the program
loops, branches and fans out in a single round trip, and only what it logs or
returns re-enters the conversation. The tools it may call are declared in the
SDK carried in this tool's description (see `Nous.CodeMode.Sdk`), and are
reached through bindings derived from the same permission policy that governs
a direct call (`Nous.CodeMode.bindings/4`).

## The runtime provider

`Nous.CodeRuntime.JS` ships in this repository and runs programs in an
embedded V8 isolate. It needs the optional `:tyrex` dependency:

    {:tyrex, "~> 0.4"}

    config :nous, :code_runtime, {Nous.CodeRuntime.JS, timeout_ms: 30_000}

Any other provider behind `Nous.CodeRuntime` works the same way. With none
configured this tool returns a clear error naming the configuration it needs;
it does not crash, and it does not pretend to have run anything. `mode: :both`
quietly behaves as `:native` when nothing is configured, so that error is only
reachable when an operator explicitly asked for `mode: :code`.

## Approval is per sub-call, not per program

Approving a `run_code` call approves running *that program*. It does not
approve whatever the program then decides to call: each sub-call to a tool with
`requires_approval: true` consults the approval handler on its own, with the
real tool name and the real arguments. With no handler in the context such a
tool is refused rather than run — the same default-deny `Nous.ToolExecutor`
applies at every other entry point.

## Result shape

A run that finishes returns `%{logs: [String.t()], result: json}`. A run that
fails returns `%{logs: [String.t()], error: %{kind: String.t(), message:
String.t()}}` — failure is data, not a tool error, because a program that
throws is the normal case: the model reads the failure and writes a better
program. `{:error, _}` is reserved for a call that never ran at all — no
provider, a missing `description`, a request the seam refused.

Logs are returned whether the run succeeded or failed, so a program killed by
its deadline still reports what it managed to say.

## Sub-calls go through one lane

Every tool call the program makes is submitted to a `Nous.CodeMode.Scheduler`
started for this run and torn down with it: one driver lane with a
`max_parallel` ceiling, an exclusivity barrier for tools that do not declare
themselves `concurrency_safe?/1`, and two independent argument snapshots per
call. The bindings never reach `Nous.ToolExecutor` on their own.

The scheduler records each sub-dispatch as a bookkeeping `:tool_call` event the
moment it starts. A tool is handed a `%Nous.RunContext{}`, which carries no
session log, so those events leave here as `:log_event` operations on a
`Nous.Tool.ContextUpdate` — this tool's third return shape — and the agent
runner appends them to the real `Nous.Agent.Context`. Being bookkeeping, they
project to no message: a program making forty tool calls adds forty audit
records and nothing whatsoever to what the model reads.

## Approval and audit

`description` is required and must be non-empty. It is what a human reads in
an approval prompt and what an audit log records; an empty one turns both into
a shrug, so it is rejected. The tool's own `requires_approval` is false —
gate it through `Nous.Permissions` (it is an `:execute`-category tool) rather
than here, because every tool the program calls is *also* gated, and a second
unconditional prompt in front of the first buys nothing.

# `__tool_schema__`

```elixir
@spec __tool_schema__() :: map()
```

Return the full tool schema definition for introspection.

Includes parameter declarations, category, and tags.

# `run`

```elixir
@spec run(Nous.RunContext.t(), map(), keyword()) ::
  {:ok, map()}
  | {:ok, map(), Nous.Tool.ContextUpdate.t()}
  | {:error, String.t()}
```

Run a program, with the tool set and dispatch this call is scoped to.

`Nous.CodeMode.run_code_tool/3` builds the closure that supplies `opts`; the
bare `execute/2` above is the same call with none, which is what a direct
`Nous.ToolExecutor.execute/3` outside the runner gets.

Returns `{:ok, output, %Nous.Tool.ContextUpdate{}}` when the program made at
least one sub-call, carrying one `:log_event` operation per sub-dispatch in
submission order; a program that called nothing returns the plain `{:ok,
output}`, because an update with no operations is noise.

## Options

  * `:tools` — every tool in scope *before* the permission policy filtered
    it. Granted ones become real closures, denied ones error stubs.
  * `:policy` — the `Nous.Permissions.Policy` that decides which is which.
  * `:dispatch` — a `t:Nous.CodeMode.dispatch/0` the scheduler runs sub-calls
    through; defaults to `Nous.CodeMode.direct_dispatch/3`. It replaces what
    the lane calls, never the lane itself.
  * `:call_id` — the model's tool-call id, so each sub-dispatch event
    correlates back to this call. Defaults to a minted per-run token.
  * `:runtime` — `{module, config}` overriding `config :nous, :code_runtime`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
