# `PtcRunner.SubAgent.Loop.PtcToolCall`
[🔗](https://github.com/andreasronge/ptc_runner/blob/main/lib/ptc_runner/sub_agent/loop/ptc_tool_call.ex#L1)

Native tool-call transport handler for `ptc_transport: :tool_call` agents.

Owns the loop branch that consumes assistant turns shaped as native
`tool_calls` (the `lisp_eval` invocation) plus the direct
final-answer path. App tools are never exposed as provider-native
tools — they remain callable only from inside a PTC-Lisp program via
`(tool/name ...)`. The system prompt continues to render the full
app-tool inventory.

See `Plans/ptc-lisp-tool-call-transport.md` for the full design.

## Naming

In this module, "tool call" without qualifier refers to a *native*
tool call (the `lisp_eval` invocation on the provider wire).
PTC-Lisp `(tool/...)` invocations continue to be called "app tool
calls" and surface as `lisp_step.tool_calls`.

## Public surface

- `tool_name/0`, `tool_description/0`, `tool_schema/0`, `request_tools/1`
  — Phase 3 schema/request shape (unchanged).
- `handle_response/3` — Phase 4 entry point. Branches on the assistant
  response to either execute a single `lisp_eval` call, treat
  direct content as a final answer, or surface a paired protocol error.

# `handle_response`

```elixir
@spec handle_response(
  map(),
  PtcRunner.SubAgent.Definition.t(),
  PtcRunner.SubAgent.Loop.State.t()
) ::
  {:continue, PtcRunner.SubAgent.Loop.State.t(), PtcRunner.Turn.t()}
  | {:stop, {:ok | :error, PtcRunner.Step.t()}, PtcRunner.Turn.t() | nil,
     map() | nil}
```

Handle an assistant turn under `ptc_transport: :tool_call`.

Returns the same signal as content-mode response handling:

- `{:continue, new_state, turn}` for non-terminating turns.
- `{:stop, {:ok | :error, step}, turn, turn_tokens}` to terminate.

Branches:

- **No native tool calls**: treat direct content as the final answer
  (signature handling matrix). Markdown-fenced clojure as content
  triggers targeted feedback (R16).
- **Exactly one `lisp_eval` call**: execute, append paired
  `role: :tool` message, continue or terminate on `(return)`/`(fail)`.
- **One unknown tool call**: paired `unknown_tool` error, continue.
- **More than one native tool call**: paired `multiple_tool_calls`
  error per `tool_call_id`, continue (R12, R13).

# `request_tools`

```elixir
@spec request_tools(PtcRunner.SubAgent.Definition.t()) :: [map()] | nil
```

Build the request `tools` list for an agent.

In `:tool_call` mode, returns exactly one entry — the
`lisp_eval` schema — regardless of how many app tools the agent
declares. App tools stay in the system prompt's Tool Inventory and are
callable only from inside the sandboxed program.

In `:content` mode, returns `nil` so the request omits the `tools`
field (matching today's behavior where PTC-Lisp app tools are not
exposed as native provider tools).

# `tool_description`

```elixir
@spec tool_description() :: String.t()
```

The canonical description string for the `lisp_eval` tool in
v1 PTC `:tool_call` mode.

Delegates to `PtcRunner.PtcToolProtocol.tool_description/1` with the
`:in_process_with_app_tools` profile. Tests assert stable substrings
against this value; do not paraphrase the guidance elsewhere.

# `tool_name`

```elixir
@spec tool_name() :: String.t()
```

The reserved native tool name (`"lisp_eval"`).

# `tool_schema`

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

Build the OpenAI-format tool schema for `lisp_eval`.

Returns a single map. The intended use in `:tool_call` mode is to put
exactly this one entry in the LLM request's `tools` field — app tools
are never included.

## Examples

    iex> schema = PtcRunner.SubAgent.Loop.PtcToolCall.tool_schema()
    iex> schema["type"]
    "function"
    iex> schema["function"]["name"]
    "lisp_eval"
    iex> schema["function"]["parameters"]["required"]
    ["program"]

---

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