ExAthena.Orchestrator.AgentInfo (ExAthena v0.17.0)

Copy Markdown View Source

Observable state of one agent (the main loop or a subagent) in an orchestrated run — the pure functional core of ExAthena.Orchestrator.Coordinator.

All updates are pure reducers over loop events (apply_event/2) and todo-list rewrites (set_todos/2), so every state transition is unit testable without processes (jido-style logic/runtime split).

Status machine

:running  :waiting_gpu        (queue_wait events)
:running  :stalling           (repeated identical conclusions)
:running  :done | :failed     ({:done, Result} by category)
any non-terminal  :failed     (fail/1  run process died)

Terminal statuses (:done, :failed, :timeout) are sticky.

Todo ledger

The model speaks the dumb full-rewrite todo_write interface (idempotent, survives small-model slop); this module diffs successive rewrites by content to assign stable ids — the Claude Code task-ledger pattern, kept runtime-side so the model never does id bookkeeping. Invariants enforced in code: at most one :in_progress item (later ones demoted), and complete_todo/2 for runtime-derived completion (e.g. when a linked subagent succeeds).

Summary

Functions

Fold one loop event into the agent's observable state.

Flip one todo to completed by id (runtime-derived completion).

Flip the todo matching content to completed (model-facing linkage).

Mark a non-terminal agent failed (the run process died underneath it). Terminal statuses stay as they are.

Create the info record for a newly observed agent.

Apply a full todo-list rewrite from the model, diffing against the current ledger to keep ids stable (matched by content) and enforcing the single-in_progress invariant.

Types

status()

@type status() ::
  :queued | :running | :waiting_gpu | :stalling | :done | :failed | :timeout

t()

@type t() :: %ExAthena.Orchestrator.AgentInfo{
  conclusions: term(),
  cost_usd: term(),
  current_action: term(),
  depth: term(),
  finished_at: term(),
  id: term(),
  iteration: term(),
  linked_todo: term(),
  match_text: term(),
  name: term(),
  next_todo_id: term(),
  parent_id: term(),
  prompt_summary: term(),
  result: term(),
  started_at: term(),
  status: term(),
  todos: term(),
  transcript_tail: term(),
  usage: term()
}

todo()

@type todo() :: %{
  id: pos_integer(),
  content: String.t(),
  status: :pending | :in_progress | :completed,
  active_form: String.t() | nil
}

Functions

apply_event(info, arg2)

@spec apply_event(t(), term()) :: t()

Fold one loop event into the agent's observable state.

complete_todo(info, todo_id)

@spec complete_todo(t(), pos_integer()) :: t()

Flip one todo to completed by id (runtime-derived completion).

complete_todo_by_content(info, content)

@spec complete_todo_by_content(t(), String.t()) :: t()

Flip the todo matching content to completed (model-facing linkage).

fail(info)

@spec fail(t()) :: t()

Mark a non-terminal agent failed (the run process died underneath it). Terminal statuses stay as they are.

new(id, attrs \\ %{})

@spec new(term(), map()) :: t()

Create the info record for a newly observed agent.

set_todos(info, raw_todos)

@spec set_todos(t(), [map()]) :: t()

Apply a full todo-list rewrite from the model, diffing against the current ledger to keep ids stable (matched by content) and enforcing the single-in_progress invariant.