Issho.Work (Issho v0.4.0)

Copy Markdown View Source

What an agent is doing in a room, seen by the people in it, and their hand on it: a task, its steps and how far it is, and Pause, Resume and Stop.

{:ok, claude} = Issho.Agents.join(MyApp.Issho, agent, "board:42", on_behalf_of: ada)

:ok = Issho.Work.start(claude, "Plan the release", ["Read the board", "Group the cards", "Write the summary"])
:ok = Issho.Work.progress(claude, "Read the board", :done)
:ok = Issho.Work.finish(claude, "Grouped 14 cards in 3 columns.")

The task is in the agent's state in the room ("task", see Issho.Awareness), so each page shows it as it goes (Issho.Components.agents/1), and each start and finish is an event of the room ("agent.started", "agent.finished"). Agents that reach your app over MCP do the same with the start_task, progress and finish_task tools.

Pause, Resume, Stop

The person an agent acts for may pause, resume or stop it in a room, and so may anyone your policy allows :interrupt (Issho.Policy). A paused or stopped agent changes nothing more in the room: its next transaction is refused ({:error, {:interrupted, ...}}), and its next tool call, but those that only read, says who paused or stopped it and what to do. A transaction is whole: what the agent changed before is there, nothing is half done. A paused agent waits for "agent.resumed"; a stopped one finishes (finish/2 lets it go on in another task). Each is an event of the room: "agent.interrupted" (data: %{agent: key, action: "pause" | "stop"}) and "agent.resumed", by the person who did it.

An agent whose work runs under Issho (Issho.Agents.run/5) is stopped at once: its work ends wherever it is. One that crashes says so (failed/2).

What is yours

Issho keeps the task, its state and its events; it decides nothing of what a page shows of them. Issho.Components.agents/1 is a default, to use or to leave: the task is in each agent's state in the room (Issho.Awareness), and its events are the room's (Issho.Events), to draw your own way.

Summary

Functions

Says the agent's work failed: its task, if it has one, is "failed", and the room hears "agent.failed" (data: %{agent: key, title: title, reason: kind}). The people in the room learn only the kind of failure ("error", "timeout", "killed" or "exit"), not its details, which may say more than they should know; your app has them in the [:issho, :agent, :failed] telemetry event (metadata :room, :agent, :reason), to log or to tell in its own words.

Finishes the task, with a few words of what was done; a stop is over with it.

Pauses, resumes or stops the agent key in a room, for by (an actor or an identity), from your server: no policy is asked. From a person's page, the agents bar does it through their session, under the policy.

Says how far the task is: a step (its label or its index) is :done (the next one pending becomes active), :active, :skipped or :pending. Options: :add, more steps at the end; :note, a few words of where it is.

Starts a task: its title and its steps (labels; the first is active). A task the agent had is replaced. A paused agent starts none until it is resumed ({:error, {:interrupted, ...}}); a stopped one may.

Types

action()

@type action() :: :pause | :resume | :stop

step_state()

@type step_state() :: :pending | :active | :done | :skipped

Functions

failed(participant, reason)

@spec failed(GenServer.server(), term()) :: :ok

Says the agent's work failed: its task, if it has one, is "failed", and the room hears "agent.failed" (data: %{agent: key, title: title, reason: kind}). The people in the room learn only the kind of failure ("error", "timeout", "killed" or "exit"), not its details, which may say more than they should know; your app has them in the [:issho, :agent, :failed] telemetry event (metadata :room, :agent, :reason), to log or to tell in its own words.

Issho.Agents.run/5 calls it when the work it runs crashes; call it yourself for a work that fails another way.

finish(participant, summary \\ nil)

@spec finish(GenServer.server(), String.t() | nil) :: :ok | {:error, term()}

Finishes the task, with a few words of what was done; a stop is over with it.

interrupt(instance, room, key, action, opts)

@spec interrupt(atom(), String.t(), String.t(), action(), keyword()) :: :ok

Pauses, resumes or stops the agent key in a room, for by (an actor or an identity), from your server: no policy is asked. From a person's page, the agents bar does it through their session, under the policy.

progress(participant, step, state \\ :done, opts \\ [])

@spec progress(
  GenServer.server(),
  String.t() | non_neg_integer() | nil,
  step_state(),
  keyword()
) ::
  :ok | {:error, term()}

Says how far the task is: a step (its label or its index) is :done (the next one pending becomes active), :active, :skipped or :pending. Options: :add, more steps at the end; :note, a few words of where it is.

start(participant, title, steps \\ [])

@spec start(GenServer.server(), String.t(), [String.t()]) :: :ok | {:error, term()}

Starts a task: its title and its steps (labels; the first is active). A task the agent had is replaced. A paused agent starts none until it is resumed ({:error, {:interrupted, ...}}); a stopped one may.