# `PtcRunner.Session`
[🔗](https://github.com/andreasronge/ptc_runner/blob/main/lib/ptc_runner/session.ex#L1)

Stateful PTC-Lisp session for embedding applications.

A session owns only the REPL state that `PtcRunner.Lisp.run/2` already
understands: explicit `(def ...)` memory and the bounded `*1`/`*2`/`*3`
turn history. Durable chat messages, user identity, persistence, and tool
configuration stay with the embedding application. Runtime options can be
stored as session defaults or passed per evaluation.

## Examples

    iex> session = PtcRunner.Session.new(timeout: 1_000)
    iex> {{:ok, step}, session} = PtcRunner.Session.eval(session, "(def x 41)")
    iex> step.memory["x"]
    41
    iex> {{:ok, step}, _session} = PtcRunner.Session.eval(session, "(inc x)")
    iex> step.return
    42

    iex> session = PtcRunner.Session.new(history_depth: 2)
    iex> {{:ok, _}, session} = PtcRunner.Session.eval(session, "1")
    iex> {{:ok, _}, session} = PtcRunner.Session.eval(session, "2")
    iex> {{:ok, step}, _session} = PtcRunner.Session.eval(session, "*1")
    iex> step.return
    2

# `eval_result`

```elixir
@type eval_result() :: {{:ok, PtcRunner.Step.t()} | {:error, PtcRunner.Step.t()}, t()}
```

Result of `eval/3`: the normal `PtcRunner.Lisp.run/2` result paired with the
session state to use for the next turn.

# `t`

```elixir
@type t() :: %PtcRunner.Session{
  attempts: non_neg_integer(),
  history_depth: non_neg_integer(),
  memory: map(),
  preludes: [map()],
  run_opts: keyword(),
  session_id: String.t(),
  turn: non_neg_integer(),
  turn_history: [term()],
  upstream_runtime: struct() | pid() | nil
}
```

# `eval`

```elixir
@spec eval(t(), String.t(), keyword()) :: eval_result()
```

Evaluate PTC-Lisp source with this session's memory and turn history.

Session default options and per-call `opts` are passed through to
`PtcRunner.Lisp.run/2`, then the session's `:memory` and `:turn_history` are
applied. This lets embedding callers pass normal runtime options such as
`:tools`, `:context`, `:timeout`, or upstream options while the session
remains the owner of REPL state. If the session was created with
`:upstream_runtime`, evaluation goes through `PtcRunner.Upstream.Runtime`.

On success, the returned session stores `step.memory` and appends
`step.return` to the bounded history. On error, the returned session is the
original session, preserving the prior memory and history — but its `attempts`
counter still advances, since every attempt (including failed ones) is a
turn-log record.

# `new`

```elixir
@spec new(keyword()) :: t()
```

Create a new stateful PTC-Lisp session.

## Options

  * `:memory` - initial Lisp memory map (default: `%{}`)
  * `:turn_history` - initial result history, oldest first (default: `[]`)
  * `:history_depth` - maximum stored result count for `*1`, `*2`, `*3`
    references (default: `3`)
  * `:upstream_runtime` - optional `PtcRunner.Upstream.Runtime` handle used
    to evaluate programs through configured upstream MCP/OpenAPI tools
  * `:prelude_store` and `:preludes` - resolve versioned store refs at
    session start and freeze the compiled bundle for this session
  * `:session_id` - correlation id stamped on this session's turn-log events
    (default: autogenerated). The struct stays a pure value; the id only
    tags emitted turn events so a session's turns can be grouped across a
    trace (plan P2).

Any other options are stored as default `PtcRunner.Lisp.run/2` options and
merged with per-eval options.

# `preludes`

```elixir
@spec preludes(t()) :: {:ok, [map()]}
```

Returns the frozen prelude refs resolved at session start.

---

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