# `PtcRunner.SubAgent.Exposure`
[🔗](https://github.com/andreasronge/ptc_runner/blob/main/lib/ptc_runner/sub_agent/exposure.ex#L1)

Pure helpers for tool exposure resolution and filtering.

Tier 1a of the text-mode + PTC compute-tool plan
(`Plans/text-mode-ptc-compute-tool.md`).

Each tool may declare `expose: :native | :ptc_lisp | :both`. When the
field is missing/`nil`, the resolved value depends on the agent's mode
per the plan's "Tool Exposure Policy" table:

| `output:` | `ptc_transport:`           | default `expose:` |
|-----------|----------------------------|-------------------|
| `:text`   | not `:tool_call` (or nil)  | `:native`         |
| `:text`   | `:tool_call` (combined)    | `:native`         |
| `:ptc_lisp` | any                      | `:ptc_lisp`       |

This module is intentionally side-effect-free: validation lives in
`PtcRunner.SubAgent.Validator`; runtime wiring (LLM request build,
Lisp analyzer inventory) is wired in Tier 2/3.

# `mode`

```elixir
@type mode() :: {atom(), atom() | nil} | struct()
```

Mode descriptor accepted by `effective_expose/2` and `filter_by_expose/3`.

Either:

- `{output, ptc_transport}` tuple — `output` is `:text | :ptc_lisp`,
  `ptc_transport` is `:content | :tool_call | nil`.
- A `PtcRunner.SubAgent.Definition.t()` struct (the `:output` and
  `:ptc_transport` fields are read).

# `effective_expose`

```elixir
@spec effective_expose(PtcRunner.Tool.t(), mode()) :: PtcRunner.Tool.expose_layer()
```

Resolve a tool's effective `expose:` value for the given agent mode.

If the tool has an explicit `expose:`, returns it unchanged. Otherwise
applies the per-mode default per "Tool Exposure Policy."

## Examples

    iex> tool = %PtcRunner.Tool{name: "x", expose: nil}
    iex> PtcRunner.SubAgent.Exposure.effective_expose(tool, {:text, nil})
    :native

    iex> tool = %PtcRunner.Tool{name: "x", expose: nil}
    iex> PtcRunner.SubAgent.Exposure.effective_expose(tool, {:text, :tool_call})
    :native

    iex> tool = %PtcRunner.Tool{name: "x", expose: nil}
    iex> PtcRunner.SubAgent.Exposure.effective_expose(tool, {:ptc_lisp, :content})
    :ptc_lisp

    iex> tool = %PtcRunner.Tool{name: "x", expose: :both}
    iex> PtcRunner.SubAgent.Exposure.effective_expose(tool, {:text, nil})
    :both

# `filter_by_expose`

```elixir
@spec filter_by_expose(
  [PtcRunner.Tool.t()] | %{optional(String.t()) =&gt; PtcRunner.Tool.t()},
  mode(),
  [
    PtcRunner.Tool.expose_layer()
  ]
) :: [PtcRunner.Tool.t()]
```

Filter a tool collection to those whose effective `expose:` is in
`allowed_set`.

- `tools` — list of `PtcRunner.Tool` structs OR a map of
  `name => PtcRunner.Tool`. The output preserves the input shape's
  iteration order (lists keep order; maps are converted to a list of
  tools sorted by tool name, since map iteration order in Elixir is
  not guaranteed).
- `allowed_set` — list/MapSet of `:native | :ptc_lisp | :both`.

Returns a list of `PtcRunner.Tool` structs.

## Examples

    iex> a = %PtcRunner.Tool{name: "a", expose: :native}
    iex> b = %PtcRunner.Tool{name: "b", expose: :both}
    iex> c = %PtcRunner.Tool{name: "c", expose: :ptc_lisp}
    iex> result = PtcRunner.SubAgent.Exposure.filter_by_expose(
    ...>   [a, b, c], {:text, :tool_call}, [:native, :both]
    ...> )
    iex> Enum.map(result, & &1.name)
    ["a", "b"]

---

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