# req_managed_agents_host
Durable single-node session host for [`req_managed_agents`](https://hex.pm/packages/req_managed_agents).
Turns RMA's per-call `ReqManagedAgents.Session` into a supervised, crash-survivable,
idle-detachable hosted session keyed by a caller-supplied external id. One public
function — `ReqManagedAgents.Host.send_message/3` — finds, starts, or reattaches the
right session, runs it to a terminal, and returns the RMA `SessionResult`, with the
session's upstream identity durably persisted so a BEAM restart resumes the conversation.
## Installation
```elixir
def deps do
[
{:req_managed_agents_host, "~> 0.1.0"}
]
end
```
## Quickstart
Every call needs a `:provider` (a `ReqManagedAgents.Provider`), a `:handler` (a
`ReqManagedAgents.Handler`), and a `:store` — a `{module, opts}` pair naming a
`ReqManagedAgents.Host.Store` implementation.
### In-memory (ETS)
Fastest option; the locator is gone if the owning process stops. Good for a single
long-lived node where losing in-flight session pointers on a crash is acceptable.
```elixir
{:ok, _pid} = ReqManagedAgents.Host.Store.ETS.start_link(name: :sessions)
store = {ReqManagedAgents.Host.Store.ETS, name: :sessions}
opts = [provider: MyProvider, handler: MyHandler, store: store]
{:ok, %ReqManagedAgents.SessionResult{text: text}} =
ReqManagedAgents.Host.send_message("customer-42", "hello", opts)
# same external id later — even after the SessionServer idle-detached or crashed —
# reattaches the same upstream session instead of minting a new one
{:ok, _result} = ReqManagedAgents.Host.send_message("customer-42", "what's next?", opts)
```
### File-backed (DETS)
Use this when the locator itself must survive a full BEAM restart, not just a process
crash — e.g. a deploy or a node bounce. The upstream session id is written to disk on
every turn, so re-opening the same file after a restart reattaches exactly where the
conversation left off.
```elixir
{:ok, _pid} =
ReqManagedAgents.Host.Store.DETS.start_link(name: :sessions, file: "/var/data/sessions.dets")
store = {ReqManagedAgents.Host.Store.DETS, name: :sessions, file: "/var/data/sessions.dets"}
opts = [provider: MyProvider, handler: MyHandler, store: store]
{:ok, _result} = ReqManagedAgents.Host.send_message("customer-42", "hello", opts)
# ...BEAM restarts; the same store is reopened from the same file on boot...
{:ok, _result} = ReqManagedAgents.Host.send_message("customer-42", "still there?", opts)
```
### Idle-detach
Sessions idle-detach after `idle_timeout_ms` (default 300_000ms / 5 minutes) of inactivity —
the `SessionServer` stops, but the `Locator` row (and thus the upstream session id) survives,
so the next `send_message/3` for the same external id starts a fresh server and reattaches:
```elixir
opts = [provider: MyProvider, handler: MyHandler, store: store, idle_timeout_ms: 60_000]
```
## Durability model
- **Live handle vs. durable state.** A `SessionServer` is a reattachable *live handle*, not
the durable record — the `Locator` (backed by your chosen `Store`) is. The server's child
spec is `restart: :temporary`: an idle-detach or a crash both simply end the process, and
neither is auto-restarted by the `SessionSupervisor` — the next `send_message/3` starts a
fresh one and reattaches through the `Locator`.
- **Crash isolation.** Each external id's session runs under its own `DynamicSupervisor`
child; one session crashing never affects another.
- **`Store.ETS` vs. `Store.DETS`.** Pick `ETS` for process-crash isolation only; pick `DETS`
when the locator must also survive a BEAM/node restart.
## License
Apache-2.0. See [LICENSE](LICENSE).