LemonCliRunners.ClaudeSubagent (lemon_cli_runners v0.1.0)

View Source

High-level API for using Claude (Claude Code CLI) as a collaborating subagent.

This module provides a convenient interface for spawning Claude CLI sessions and interacting with them over time. Unlike one-shot LLM calls, Claude subagents maintain state across multiple prompts, enabling iterative collaboration.

Features

  • Long-lived sessions: Keep talking to the same Claude session
  • Session persistence: Resume sessions after interruptions
  • Event streaming: Process events as they happen
  • Progress tracking: Monitor tools, commands, file changes

Quick Start

# Start a new session
{:ok, session} = ClaudeSubagent.start(
  prompt: "Create a GenServer that manages a counter",
  cwd: "/path/to/project"
)

# Process events
for event <- ClaudeSubagent.events(session) do
  case event do
    {:started, resume_token} ->
      IO.puts("Session: #{resume_token.value}")

    {:action, action, :completed, ok: true} ->
      IO.puts("Completed: #{action.title}")

    {:completed, answer, _opts} ->
      IO.puts("Done: #{answer}")
  end
end

# Send a follow-up prompt to continue the conversation
{:ok, session2} = ClaudeSubagent.continue(session, "Now add decrement functionality")

Session Resume

Sessions can be resumed after the process terminates:

# Get the resume token from a completed session
token = ClaudeSubagent.resume_token(session)

# Later, resume the session
{:ok, session} = ClaudeSubagent.resume(token, "Continue from where we left off")

Event Types

Events are normalized into a simple format:

  • {:started, resume_token} - Session began
  • {:action, action, phase, opts} - Action lifecycle (phase = :started | :updated | :completed)

  • {:completed, answer, opts} - Session ended (opts may include :resume, :error, :usage)

Summary

Types

A Claude subagent session

Normalized event from the subagent

Functions

Collect all events and return the final answer.

Continue an existing session with a follow-up prompt.

Get the event stream as an enumerable of normalized events.

Resume an existing Claude session with a new prompt.

Get the resume token from a session.

Run a Claude task synchronously and return the answer.

Start a new Claude subagent session.

Types

session()

@type session() :: %{
  pid: pid(),
  stream: LemonAgent.EventStream.t(),
  resume_token: LemonCore.ResumeToken.t() | nil,
  token_agent: pid() | nil,
  cwd: String.t()
}

A Claude subagent session

subagent_event()

@type subagent_event() ::
  {:started, LemonCore.ResumeToken.t()}
  | {:action, action :: map(), phase :: atom(), opts :: keyword()}
  | {:completed, answer :: String.t(), opts :: keyword()}
  | {:error, reason :: term()}

Normalized event from the subagent

Functions

collect_answer(session)

@spec collect_answer(session()) :: String.t()

Collect all events and return the final answer.

This is a convenience function that processes all events and returns the final answer string. Useful when you don't need to track progress.

Example

{:ok, session} = ClaudeSubagent.start(prompt: "What is 2+2?", cwd: ".")
answer = ClaudeSubagent.collect_answer(session)
IO.puts(answer)  # "4" or similar

continue(session, prompt, opts \\ [])

@spec continue(session(), String.t(), keyword()) ::
  {:ok, session()} | {:error, term()}

Continue an existing session with a follow-up prompt.

This is a convenience wrapper around resume/2 that extracts the resume token from a completed session.

Example

{:ok, session1} = ClaudeSubagent.start(prompt: "Create a module")
_events = ClaudeSubagent.events(session1) |> Enum.to_list()

{:ok, session2} = ClaudeSubagent.continue(session1, "Add a public function")

events(session)

@spec events(session()) :: Enumerable.t()

Get the event stream as an enumerable of normalized events.

Events are transformed from internal CLI runner events to a simpler format. The stream completes when the session ends.

Event Format

  • {:started, resume_token} - Session began, token can be used for resume
  • {:action, action, phase, opts} - Action lifecycle event
    • action is a map with :id, :kind, :title, :detail
    • phase is :started, :updated, or :completed
    • opts may include ok: boolean() for completed phase
  • {:completed, answer, opts} - Session ended
    • opts may include :resume, :error, :usage
  • {:error, reason} - Error occurred

resume(token, opts)

@spec resume(
  LemonCore.ResumeToken.t(),
  keyword()
) :: {:ok, session()} | {:error, term()}

Resume an existing Claude session with a new prompt.

Example

token = %ResumeToken{engine: "claude", value: "session_abc123"}

{:ok, session} = ClaudeSubagent.resume(token,
  prompt: "Continue implementing the delete method",
  cwd: "/home/user/project"
)

resume_token(session)

@spec resume_token(session()) :: LemonCore.ResumeToken.t() | nil

Get the resume token from a session.

The token is populated after the session starts and can be used to resume the session later. The token is updated as events are processed, so call this after processing events.

run!(opts)

@spec run!(keyword()) :: String.t()

Run a Claude task synchronously and return the answer.

This is a convenience function that starts a session, waits for completion, and returns the answer.

Options

  • :prompt - The task prompt (required)
  • :cwd - Working directory
  • :timeout - Timeout in ms
  • :on_event - Optional callback fn event -> :ok end for progress tracking

Example

answer = ClaudeSubagent.run!(
  prompt: "Explain what this code does: ...",
  cwd: "/path/to/project",
  on_event: fn event -> IO.inspect(event) end
)

start(opts)

@spec start(keyword()) :: {:ok, session()} | {:error, term()}

Start a new Claude subagent session.

Options

  • :prompt - The initial prompt/task (required)
  • :cwd - Working directory (default: current directory)
  • :timeout - Session timeout in ms (default: :infinity)
  • :model - Optional model override (passed to Claude CLI --model)

Returns

{:ok, session} on success, {:error, reason} on failure.

Example

{:ok, session} = ClaudeSubagent.start(
  prompt: "Implement a binary search tree",
  cwd: "/home/user/project"
)