# `Raxol.AgentClientProtocol.Transport.Paired`
[🔗](https://github.com/DROOdotFOO/raxol-agent/blob/raxol_agent_client_protocol-v0.1.0/packages/raxol_agent_client_protocol/lib/raxol/agent_client_protocol/transport/paired.ex#L1)

An in-process, zero-copy pair of `Raxol.AgentClientProtocol.Transport`
handles. This is the test backbone for the rest of the library and the
BEAM-local wiring for an agent/client pair that live in the same VM.

`create_pair/0` starts two lightweight `GenServer`s — one per side — and
links them to each other as peers. A message sent on the left handle is
delivered to the right handle's owner (and vice versa), preserving send
order. No JSON encoding happens anywhere in this module: Elixir maps
pass straight through by reference. Byte-level codec correctness
(encoding, framing, partial reads) is exercised by the stdio transport
instead, not here.

## Ownership

A freshly created handle has no owner (`nil`), so a supervisor can
create the pair before the `Connection` process that will adopt it
exists. Whatever arrives before an owner is set — peer frames and a
peer close — is held, and `set_owner/2` delivers it to the new owner in
arrival order before anything later: the other side may start sending
(e.g. a client's `initialize`) before this side's `Connection` has
adopted its handle. Call `set_owner/2` to adopt (or re-adopt — handoff
to a new owner is supported at any time, including mid-stream).

## Ordering

Erlang guarantees that messages sent between any two given processes
arrive in send order. `send_message/2` accepts a frame locally (a
single, non-nested `GenServer.call/2`) and hands it off to the peer's
side with `GenServer.cast/2`. Because a side handles its own incoming
`:send` calls one at a time, in arrival order, the casts it issues to
the peer land on the peer's mailbox in that same order — so a single
sending process sees strict order preserved end to end, with no
explicit sequence number needed.

This deliberately avoids a synchronous call *into* the peer from
inside a side's own `handle_call/3`: two sides calling each other
synchronously at the same time is a classic mutual-deadlock — each
side blocks inside its callback waiting on the other, and neither can
drain its mailbox to answer. Keeping the peer hop async (`cast`) is
what makes simultaneous bidirectional traffic (both ends sending at
once) safe.

## Closing

`close/1` is idempotent and marks the local side closed immediately.
It also notifies the peer (a cast), which marks *itself* closed and
delivers `{:closed, :peer_closed}` to its own owner — the side that
calls `close/1` does not message its own owner. Once a side's `closed`
flag is set (locally, by `close/1`, or asynchronously via the peer
notification), further `send_message/2` calls through it return
`{:error, :closed}`, and any `:deliver` that arrives after closing is
dropped rather than reaching the owner. The underlying processes are
not killed by `close/1`; a handle remains a valid (but permanently
closed) tuple that can still be inspected.

# `t`

```elixir
@type t() :: %Raxol.AgentClientProtocol.Transport.Paired{pid: pid()}
```

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `create_pair`

```elixir
@spec create_pair() :: {t(), t()}
```

Create a connected pair of transport handles: `{left, right}`.

Neither handle has an owner yet; inbound traffic is held until
`set_owner/2` adopts the handle.

# `set_owner`

```elixir
@spec set_owner(t(), pid()) :: :ok
```

Set (or replace) the owner process for a handle. The owner is the
process that will receive `{:acp_transport, transport_ref, ...}`
messages for this handle going forward, starting with any held since
the handle was created.

---

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