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.
Summary
Functions
A value the agent kept in the room, or default.
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.
Takes the agent out of the room.
The participant of the agent with this key in the room, if it is 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.
The rooms the agent with this key is in, in the cluster.
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.
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.
Functions
A value the agent kept in the room, or default.
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.
Takes the agent out of the room.
The participant of the agent with this key in the room, if it is 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.
The rooms the agent with this key is in, in the cluster.
@spec run(atom(), Issho.Actor.t(), String.t(), (pid() -> 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).
@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.