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

Turn feedback formatting for SubAgent execution.

Formats execution results and turn state information for LLM feedback.
Supports the unified budget model with work turns and retry turns.

Uses Mustache templates from `PtcRunner.Prompts`:
- `must_return_warning/0` - Warning for final work turn
- `retry_feedback/0` - Turn info during retry phase

# `append_turn_info`

```elixir
@spec append_turn_info(String.t(), PtcRunner.SubAgent.Definition.t(), map()) ::
  String.t()
```

Append turn progress info to a feedback message.

For multi-turn agents with retry_turns, shows unified budget info.
For multi-turn agents without retry_turns, shows legacy turn count.

# `build_error_feedback`

```elixir
@spec build_error_feedback(String.t(), PtcRunner.SubAgent.Definition.t(), map()) ::
  String.t()
```

Build error feedback with appropriate turn info based on unified budget model.

This is used by the loop to format error messages with context about
work/retry budgets.

# `execution_feedback`

```elixir
@spec execution_feedback(PtcRunner.SubAgent.Definition.t() | map(), map(), map()) ::
  %{
    feedback: String.t(),
    prints: [String.t()],
    result: String.t() | nil,
    memory: %{
      changed: %{required(String.t()) =&gt; String.t()},
      stored_keys: [String.t()],
      truncated: boolean()
    },
    result_truncated: boolean(),
    visible_truncated: boolean(),
    truncated: boolean()
  }
```

Render the execution-feedback portion of a PTC-Lisp turn into a structured map.

This is the canonical execution-feedback renderer. `format/3` calls it and
then layers on `append_turn_info/3` and `append_progress/4` for content-mode
multi-turn agents. The upcoming `:tool_call` transport (Phase 4 of the
PTC-Lisp tool-call plan) reuses this function directly to populate the
`feedback` field of the `lisp_eval` tool-result JSON, ensuring loop
control scaffolding (turn budgets, `progress_fn` output) does not leak into
native tool results.

## Fields

- `:feedback` — the LLM-facing feedback string. Contains only the execution
  portion: result preview (`user=> ...`), printed `println` output, and
  changed/new memory previews (`;; items = [...]`). Does **not** include
  `append_turn_info` or `append_progress` output.
- `:prints` — the raw `lisp_step.prints` list (untruncated; truncation is
  reflected in `feedback` and the top-level `truncated` flag).
- `:result` — preview string of `lisp_step.return` (`"user=> ..."`), or
  `nil` when `lisp_step.return` is `nil` or a `Var`. Rendered
  unconditionally for the structured field — the suppression rules that
  `format/3` applies to its human-readable string (single-turn agents,
  or turns with non-empty `prints`) do **not** affect this field. Phase 4
  of the PTC-Lisp tool-call plan relies on this so the `lisp_eval`
  tool-result JSON always carries the final value.
- `:memory.changed` — map of `name => preview` for memory bindings that are
  new or whose value changed since the previous turn. String-keyed for
  direct use in tool-result JSON. Populated unconditionally regardless of
  `agent.max_turns`, for the same Phase 4 reason.
- `:memory.stored_keys` — sorted list of all currently-stored memory binding
  names (string-keyed). Fallback orientation hint when nothing changed or
  previews were truncated.
- `:memory.truncated` — `true` if any memory preview was truncated.
- `:result_truncated` — `true` if the result preview was truncated.
- `:visible_truncated` — `true` if `prints` or `result` was truncated
  (excludes memory). Used by the MCP one-shot renderer where the
  memory field is omitted entirely (issue #879) — without this the
  top-level `truncated` flag could read `true` while no truncated
  field is actually visible to the caller.
- `:truncated` — `true` if any preview (prints, result, or memory) was
  truncated.

# `format`

```elixir
@spec format(PtcRunner.SubAgent.Definition.t(), map(), map()) ::
  {String.t(), boolean(), term()}
```

Format execution result feedback for the next LLM turn.

Returns `{feedback_string, truncated?, new_progress_state}`.

Only shows explicit println output - the LLM must be intentional about what it inspects.

This is a thin wrapper around `execution_feedback/3` that additionally appends
`append_turn_info/3` and `append_progress/4` output. Tool-call transport
(Phase 4 of the PTC-Lisp tool-call plan) reuses `execution_feedback/3`
directly so loop-control scaffolding (turn budgets, custom `progress_fn`
output) does not leak into the `lisp_eval` tool-result JSON.

# `render_initial_progress`

```elixir
@spec render_initial_progress(PtcRunner.SubAgent.Definition.t(), term()) ::
  {String.t(), term()}
```

Render initial progress for the first user message.

Returns `{text, progress_state}`. Uses `progress_fn` if set, otherwise
renders the default checklist from `plan`. Returns `{"", nil}` if no plan
and no custom `progress_fn`.

---

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