# `ClaudeWrapper.DuplexSession.Adapter`
[🔗](https://github.com/genagent/claude_wrapper_ex/blob/v0.14.4/lib/claude_wrapper/duplex_session/adapter.ex#L1)

Transport seam for `ClaudeWrapper.DuplexSession`.

A `DuplexSession` drives the `claude` subprocess through exactly three
active operations -- open the transport, write a line to it, close it --
and consumes inbound message shapes delivered to its own process
mailbox:

  * `{handle, {:data, chunk}}` -- raw stdout bytes
  * `{handle, {:exit_status, code}}` -- the subprocess exited
  * `{:EXIT, handle, reason}` -- the transport process exited

The optional Forcola adapter also sends acknowledged
`{handle, {:data, chunk, ref}}` and `{handle, {:stderr, line, ref}}`
messages, followed by `{handle, {:terminal, evidence}}`. The session
acknowledges each line before the adapter requests another.

where `handle` is the value the adapter's `open/1` returned (and which
the session stores as its transport handle). An adapter implements the
three operations; the inbound side is just a matter of sending those
message shapes to the `:owner` given at `open/1`.

Two implementations ship:

  * `ClaudeWrapper.DuplexSession.Adapter.Port` -- the default; a real
    `claude` subprocess over an Erlang port.
  * `ClaudeWrapper.DuplexSession.Adapter.Test` -- a controllable
    in-process double for network-free tests (see `ClaudeWrapper.Test`).

# `handle`

```elixir
@type handle() :: term()
```

The opaque transport handle returned by `open/1`.

# `open_opts`

```elixir
@type open_opts() :: keyword()
```

Options passed to `open/1`. Always carries:

  * `:config` -- the `t:ClaudeWrapper.Config.t/0`
  * `:args` -- the resolved CLI argument list
  * `:owner` -- the session pid that inbound messages are sent to

Adapter-specific keys (from the session's `:adapter_opts`) are merged in.

# `ack`
*optional* 

```elixir
@callback ack(handle(), reference()) :: :ok
```

Optionally acknowledge a pull-delivered line after processing it.

# `close`

```elixir
@callback close(handle()) :: :ok
```

Shut the transport down. Idempotent; the handle may already be dead.

# `command`

```elixir
@callback command(handle(), iodata()) :: :ok | {:error, term()}
```

Write iodata (one NDJSON line, newline included) to the transport.

# `open`

```elixir
@callback open(open_opts()) :: {:ok, handle()} | {:error, term()}
```

Start the transport. Returns an opaque handle the session stores.

# `shutdown`
*optional* 

```elixir
@callback shutdown(handle()) :: {:ok, term()} | {:error, term()}
```

Optionally shut down while retaining structured transport evidence.

---

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