ExAthena.Tools.SpawnAgent (ExAthena v0.19.0)

Copy Markdown View Source

Synchronously run a sub-agent-loop with its own prompt, tools, and budget.

Useful for delegating a bounded task (exploring a codebase, summarising a file) to a fresh conversation with its own message history — so the parent loop doesn't pay the token cost of the sub-task's intermediate steps.

Arguments:

  • prompt (required) — the sub-agent's opening message.
  • agent (optional) — name of an ExAthena.Agents definition (e.g. "explore"). The definition's model, provider, tools, permissions, mode, isolation, and system-prompt body apply automatically; explicit args still override.
  • tools (optional) — list of tool names to expose to the sub-agent; defaults to whatever the parent had (minus PlanMode + SpawnAgent to avoid loops).
  • max_iterations (optional, default 25, floored UP to 25) — worker loop budget.
  • system_prompt (optional) — system prompt override for the sub-agent.

Workers ALWAYS run in the parent's working directory — there is no model-facing cwd arg (models passed junk paths and workers explored the wrong project). Hosts may still override via ctx.assigns[:spawn_agent_opts][:cwd].

Inherits the parent's provider / model / permissions unless overridden in ctx.assigns[:spawn_agent_opts].

Guardrail inheritance

A child is never MORE privileged than its parent: the parent's confinement roots, disallowed_tools / allowed_tools, can_use_tool approval callback, phase, and deny-capable PreToolUse hooks are clamped onto every spawn. Agent definitions and model args may narrow these further but can never widen them — see inherit_guardrails/2 for the exact combination rules and docs/13-agents-and-subagents.md for the policy.

Worktree isolation

When the chosen agent definition declares isolation: :worktree and the parent's cwd is a clean git repo with git on PATH, the subagent runs in a freshly-created worktree under ~/.cache/ex_athena/worktrees/<sess>/<name>-<n>. If safety checks fail, the subagent transparently falls back to :in_process — no error.

Summary

Functions

Whether a brief carries a dictated implementation rather than an outcome.

Cap a worker's report, saying so when it cuts.

Functions

dictated_code?(prompt)

@spec dictated_code?(String.t() | nil) :: boolean()

Whether a brief carries a dictated implementation rather than an outcome.

The orchestrator holds no read tools, so code it writes into a brief was composed without seeing the target file. Used both here (to tell the worker to reconcile it) and by the orchestrate mode (to steer the orchestrator back to describing outcomes).

truncate_result(text, max)

@spec truncate_result(String.t(), pos_integer() | any()) :: String.t()

Cap a worker's report, saying so when it cuts.

A bare "…" told the orchestrator nothing: 15% of live reports were being silently clipped, and it responded by re-requesting whole files (one run spent 6 of 28 spawns on "report the FULL contents of…"). Naming the loss lets it ask for the missing part instead of the whole thing again.