PtcRunner.PlanRunner (PtcRunner v0.7.0)

Copy Markdown View Source

Execute parsed plans with SubAgents.

Takes a normalized PtcRunner.Plan and executes each task in dependency order, using SubAgents for each task. Results flow between tasks via template expansion.

Parallel Execution

Tasks are grouped into phases by dependency level. Within each phase, all tasks execute in parallel. Phases execute sequentially.

Error Handling

Each task can specify on_failure:

  • :stop (default) - Stop execution on failure
  • :skip - Log and continue with other tasks
  • :retry - Retry up to max_retries times before failing
  • :replan - On deliberate agent failure via (fail "reason"), trigger replanning

Example

{:ok, plan} = PtcRunner.Plan.parse(llm_generated_plan)
{:ok, results} = PtcRunner.PlanRunner.execute(plan,
  llm: my_llm,
  base_tools: %{
    "search" => &MyApp.search/1,
    "fetch" => &MyApp.fetch/1
  }
)

Result Flow

Task results are available to subsequent tasks via the results map:

  • In task input: "Analyze {{results.research_task.data}}"
  • Previous task outputs are automatically injected

Options

  • llm - Required. LLM callback for all agents
  • llm_registry - Optional map of atom -> llm callback
  • base_tools - Tool implementations (map of name -> function)
  • available_tools - Tool descriptions (map of name -> description string). Used to enrich raw function tools with signatures so the LLM knows how to call them. Format: "Description. Input: {param: type}. Output: {field: type}"
  • timeout - Per-task sandbox timeout in ms (default: 30_000)
  • pmap_timeout - Per-pmap/pcalls task timeout in ms (default: same as timeout). Increase for LLM-backed tools.
  • max_turns - Max turns per agent (default: 5)
  • max_concurrency - Max parallel tasks per phase (default: 10)
  • reviews - Map of task_id => decision for human review tasks (default: %{})
  • initial_results - Pre-populated results map (default: %{}). Tasks with IDs already in this map are skipped. Used for replanning - pass completed results from previous execution to avoid re-running successful tasks.
  • on_event - Optional callback for task lifecycle events. Receives tuples like {:task_started, %{task_id: id, attempt: n}}, {:task_succeeded, %{...}}, etc.
  • builtin_tools - List of builtin tool families to enable for PTC-Lisp agents (default: []). Available: :grep (adds grep and grep-n tools). Only injected for agents running in :ptc_lisp output mode.
  • quality_gate - Enable pre-flight data sufficiency check before tasks with dependencies (default: false). When enabled, a lightweight SubAgent validates upstream results before each dependent task. On failure, triggers {:replan_required, context}.
  • quality_gate_llm - Optional separate LLM callback for quality gate checks. Falls back to the main llm if not provided.

Human Review Tasks

Plans can include type: "human_review" tasks that pause execution until a human provides a decision. When encountered:

  1. First invocation returns {:waiting, pending_reviews, partial_results}
  2. Application presents review to human, collects decision
  3. Re-invoke with reviews: %{"task_id" => decision} to continue

Example:

# First call - hits human review, pauses
{:waiting, pending, results} = PlanRunner.execute(plan, llm: llm)

# Collect human decision (app-specific)
decision = get_human_decision(hd(pending))

# Continue with the decision
{:ok, final_results} = PlanRunner.execute(plan,
  llm: llm,
  reviews: Map.put(results, hd(pending).task_id, decision)
)

Summary

Functions

Execute a parsed plan.

Types

execute_result()

@type execute_result() ::
  {:ok, %{required(String.t()) => term()}}
  | {:error, String.t(), %{required(String.t()) => term()}, term()}
  | {:waiting, [pending_review()], %{required(String.t()) => term()}}
  | {:replan_required, replan_context()}

pending_review()

@type pending_review() :: %{task_id: String.t(), prompt: String.t(), context: map()}

replan_context()

@type replan_context() :: %{
  task_id: String.t(),
  task_input: term(),
  task_output: term(),
  diagnosis: String.t(),
  completed_results: %{required(String.t()) => term()},
  agent_spec: PtcRunner.Plan.agent_spec() | nil
}

result()

@type result() :: %{
  task_id: String.t(),
  status: :ok | :error,
  value: term(),
  duration_ms: non_neg_integer()
}

Functions

execute(plan, opts)

@spec execute(
  PtcRunner.Plan.t(),
  keyword()
) :: execute_result()

Execute a parsed plan.

Runs tasks in parallel phases (respecting dependencies). Each task's results are available to subsequent tasks via template expansion.

Parameters

  • plan - Parsed %Plan{} struct
  • opts - Execution options

Returns

  • {:ok, results} - Map of task_id to task result value
  • {:error, failed_task_id, partial_results, reason} - First critical failure with partial results
  • {:waiting, pending_reviews, partial_results} - Paused at human review
  • {:replan_required, context} - Verification failed with :replan strategy