# `Issho.Agents`
[🔗](https://github.com/hattoriai/issho/blob/v0.4.0/lib/issho/agents.ex#L1)

Agents in rooms for front ends without a lasting connection: an MCP
server, whose every tool call is its own request, a webhook, a chat
bot. An agent joins a room on its first call and stays, present and
where it last pointed, between calls; it leaves after `:idle`
milliseconds with no call (ten minutes), or when told.

    {:ok, claude} = Issho.Agents.join(MyApp.Issho, agent, "board:42", on_behalf_of: ada)
    {:ok, _snapshot} = Issho.Participant.open(claude, "board:42")
    Issho.Participant.place(claude, %{"doc" => "board:42", "at" => %{"node" => "n7"}})

`join/4` gives the agent's `Issho.Participant`, the same one each time
for the same actor and room, and starts the idle time again. People
see the agent as they see anyone: its name, its place, its changes.

An agent in a room hears its events (`Issho.Event`), but those it made
happen itself, and keeps the last 100 of them until it takes them
(`wait/5`, the `wait_for_events` tool): what people did between its
calls, and what it waits for (someone mentions it, a document changes).

An agent is one in the cluster (`:global`, as the documents'
authorities are): a call on any node finds it, so an MCP endpoint
behind a load balancer needs no session affinity.

## Its work

Your app's own agent (a model your app calls, not an MCP client) can
run its work under Issho: `run/5` runs a function as the agent, in a
process linked to it. When a person stops the agent (`Issho.Work`), the
process is ended at once, wherever it is (waiting on the model, in the
middle of a tool), not at its next change; a pause still lets it run,
and refuses its changes. When it crashes, its task says it failed and
the room hears `"agent.failed"` (`Issho.Work.failed/2`).

Each agent in a room is a state machine (`:gen_statem`): listening, or
waiting (a call of `wait/6` that has nothing yet), with its idle time
and a wait's time as its timeouts. An agent idle for a while
hibernates: it takes little memory until it is called.

# `get`

```elixir
@spec get(atom(), String.t(), String.t(), term(), term()) :: term()
```

A value the agent kept in the room, or `default`.

# `join`

```elixir
@spec join(atom(), Issho.Actor.t(), String.t(), keyword()) ::
  {:ok, pid()} | {:error, term()}
```

The agent's participant in the room, joining it on the first call.
Options: those of `Issho.join_as/4` (`:can`, `:on_behalf_of`,
`:claims`) and `:idle`.

# `leave`

```elixir
@spec leave(atom(), String.t(), String.t()) :: :ok
```

Takes the agent out of the room.

# `participant`

```elixir
@spec participant(atom(), String.t(), String.t()) :: pid() | nil
```

The participant of the agent with this key in the room, if it is there.

# `put`

```elixir
@spec put(atom(), String.t(), String.t(), term(), term()) ::
  :ok | {:error, :not_there}
```

Keeps a value for the agent in the room, for its later calls (what it
copied, what it was asked to remember), until it leaves.

# `rooms`

```elixir
@spec rooms(atom(), String.t()) :: [String.t()]
```

The rooms the agent with this key is in, in the cluster.

# `run`

```elixir
@spec run(atom(), Issho.Actor.t(), String.t(), (pid() -&gt; term()), keyword()) ::
  {:ok, pid()} | {:error, term()}
```

Runs the agent's work in the room, joining it as `join/4` does: `fun`,
given the agent's participant, in a process linked to the agent, which
stays in the room while it runs (its idle time starts when the work
ends). One work at a time: `{:error, :running}` while one runs.

    {:ok, work} =
      Issho.Agents.run(MyApp.Issho, assistant, "board:42", fn claude ->
        :ok = Issho.Work.start(claude, "Group the cards", ["Read", "Group"])
        # ... the model, the tools ...
        Issho.Work.finish(claude, "Grouped 14 cards.")
      end, on_behalf_of: ada)

Gives `{:ok, pid}`. Monitor it to know how it ended: `:normal`;
`{:shutdown, {:stopped, by}}`, a person stopped the agent (`by`, who);
`{:shutdown, :left}`, the agent left the room; or the reason it
crashed, when the agent's task says it failed (`Issho.Work.failed/2`).

# `wait`

```elixir
@spec wait(
  atom(),
  String.t(),
  String.t(),
  :all | [String.t()],
  non_neg_integer(),
  keyword()
) ::
  {:ok, [Issho.Event.t()]} | {:error, :not_there}
```

The events of the room since the agent last took them, whose names are
one of `names` (`Issho.Events.match?/2`: a list of names, `"kind.*"`, or
`:all`), oldest first; when there is none yet, waits up to `timeout`
milliseconds for one. With `mentions_me: true`, only those whose data
mentions the agent (`mentions`, a comment's). Gives `{:ok, events}`, an empty list when none
came, or `{:error, :not_there}` for an agent not in the room.

---

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