AgentHarness (AgentHarness v0.2.0)

Copy Markdown View Source

Supervised logical sessions for locally installed coding-agent harnesses.

A session has a stable AgentHarness.SessionRef and one active turn at a time. Provider adapters own the external CLI lifecycle and publish normalized events through the session.

Summary

Functions

Waits for a turn's terminal event without a completion-subscription race.

Requests cancellation.

Returns provider capabilities for a live session.

Lists the currently live, PID-free session handles in deterministic ID order.

Lists stored session snapshots and whether each has a live SessionServer.

Monitors a live session from the calling process.

Cascades deletion of a non-live session aggregate from a Store.

Responds to a structured provider request with exactly-once local ownership.

Starts a supervised logical session for provider.

Accepts one turn locally and starts provider admission asynchronously.

Returns the live session snapshot.

Stops an idle session.

Returns a replay-followed-by-live stream for a turn.

Subscribes the calling process, or :pid, to session or turn events.

Removes an event subscription.

Functions

await(turn, opts \\ [])

@spec await(
  AgentHarness.Turn.t(),
  keyword()
) :: {:ok, term()} | {:error, term()}

Waits for a turn's terminal event without a completion-subscription race.

The event-wait timeout defaults to :infinity. Do not call this blocking function from a GenServer callback; subscribe and monitor the session instead.

cancel(turn)

@spec cancel(AgentHarness.Turn.t()) :: :ok | {:error, term()}

Requests cancellation.

:ok records the intent and schedules at most one provider command; it is not provider acknowledgement. The session remains cancelling until the provider emits a terminal event. A later provider-command failure retires the session and is observed through events and the session monitor.

capabilities(session_ref)

@spec capabilities(AgentHarness.SessionRef.t()) ::
  AgentHarness.Capabilities.t() | {:error, term()}

Returns provider capabilities for a live session.

list_sessions()

@spec list_sessions() :: [AgentHarness.SessionRef.t()]

Lists the currently live, PID-free session handles in deterministic ID order.

list_stored_sessions(opts \\ [])

@spec list_stored_sessions(keyword()) :: {:ok, [map()]} | {:error, term()}

Lists stored session snapshots and whether each has a live SessionServer.

The Store defaults to the built-in Memory instance. Pass store: {module, owner} to inspect a custom Store; a PID-free session handle does not retain that ownership information after its process stops.

monitor(arg1)

@spec monitor(AgentHarness.SessionRef.t() | AgentHarness.Subscription.t()) ::
  {:ok, reference()} | {:error, :session_not_found}

Monitors a live session from the calling process.

The caller receives the standard {:DOWN, monitor_ref, :process, pid, reason} message if the session exits. This is the non-blocking lifecycle signal to use from an orchestrator GenServer.

purge_session(session_or_id, opts \\ [])

@spec purge_session(
  AgentHarness.SessionRef.t() | String.t(),
  keyword()
) :: :ok | {:error, term()}

Cascades deletion of a non-live session aggregate from a Store.

The Store defaults to the built-in Memory instance. Pass :store again for a custom Store because a PID-free stopped handle does not retain its owner.

respond(request, response)

@spec respond(AgentHarness.Request.t(), AgentHarness.Response.t()) ::
  :ok | {:error, term()}

Responds to a structured provider request with exactly-once local ownership.

The call waits for provider acknowledgement, while the owning SessionServer remains responsive to status, cancellation, and shutdown commands. A second in-flight response returns {:error, :response_in_progress}; a definite provider rejection releases the claim. Uncertain acknowledgement returns {:error, {:provider_command_uncertain, reason}}, fails the active turn, and retires the session. The public response-call timeout must remain longer than the per-session provider-command watchdog.

start_session(provider, opts \\ [])

@spec start_session(
  atom(),
  keyword()
) :: {:ok, AgentHarness.SessionRef.t()} | {:error, term()}

Starts a supervised logical session for provider.

Authentication remains the responsibility of the locally installed CLI. Provider opening and initial Store finalization have separate bounded phases (:startup_timeout and :startup_finalization_timeout). Independent session handshakes are not serialized; call from a supervised task when the caller itself must remain responsive. Before returning success, the caller and SessionServer complete a two-way readiness acknowledgement so a queued ready message cannot return a handle to a server that already died.

start_turn(session, input, opts \\ [])

@spec start_turn(AgentHarness.SessionRef.t(), term(), keyword()) ::
  {:ok, AgentHarness.Turn.t()} | {:error, term()}

Accepts one turn locally and starts provider admission asynchronously.

The returned turn initially has status :starting. A session rejects a second concurrent turn. If the local SessionServer call times out, the error includes the stable turn handle for reconciliation.

status(session_ref)

@spec status(AgentHarness.SessionRef.t()) :: map() | {:error, term()}

Returns the live session snapshot.

stop_session(session, opts \\ [])

@spec stop_session(
  AgentHarness.SessionRef.t(),
  keyword()
) :: :ok | {:error, term()}

Stops an idle session.

Set force: true to interrupt an active turn locally and close the session. Forced shutdown does not call the provider cancellation command; it kills in-flight provider tasks and retires the local provider session.

stream(turn, opts \\ [])

@spec stream(
  AgentHarness.Turn.t(),
  keyword()
) :: {:ok, Enumerable.t()} | {:error, term()}

Returns a replay-followed-by-live stream for a turn.

The stream must be consumed by the process that calls this function. It ends after a terminal turn event included by the replay cursor or delivered live. The per-event timeout defaults to :infinity; pass a finite :timeout for bounded callers. A completed turn with no terminal event in the requested replay returns {:error, :replay_unavailable}.

subscribe(target, opts \\ [])

@spec subscribe(
  AgentHarness.SessionRef.t() | AgentHarness.Turn.t(),
  keyword()
) :: {:ok, AgentHarness.Subscription.t()} | {:error, term()}

Subscribes the calling process, or :pid, to session or turn events.

:from may be :latest, :start, or {:after, sequence}.

unsubscribe(subscription)

@spec unsubscribe(AgentHarness.Subscription.t()) :: :ok | {:error, term()}

Removes an event subscription.