# panto-subagents Delegation and workflows for [Pantograph](https://github.com/travisp/pantograph). The `subagents` extension lets a primary agent start specialized child agents, run several at once, and continue their conversations later. ## Install ```toml [extensions] rocks = ["panto-subagents"] ``` For a local checkout, use `paths = ["/path/to/panto-subagents"]` instead. A `paths` entry loads the code but installs nothing, so put the dependencies below in panto's own rocks tree first: ```sh TREE="$(echo ~/.local/share/panto/rocks/lua-*)" luarocks --lua-version 5.4 --tree "$TREE" install toml2lua luarocks --lua-version 5.4 --tree "$TREE" install api7-lua-tinyyaml ``` (Every rock here is pure Lua, so any LuaRocks targeting Lua 5.4 can populate panto's tree. A `rocks` entry needs none of this — panto installs dependencies with the rock.) ### Dependencies Panto installs the rock's dependencies with it: - **api7-lua-tinyyaml** parses profile frontmatter. Pure Lua, nothing to install alongside it. It covers a subset of YAML — every quoting, escaping and block-scalar form profiles use, but not anchors/aliases (flagged with a warning rather than silently misread) and not plain multi-line scalars (a parse warning). Full-spec `lyaml` was the alternative, rejected because binding the system libyaml makes the rock fail to build wherever that library is absent. - **toml2lua** reads the layered `config.toml` at activation and TOML workflows afterwards. Pure Lua, nothing to install; without it activation fails. - **luv** backs profile and workflow discovery. Panto already ships it. Structured workflow output is validated against its JSON Schema by a built-in validator covering the schema subset those results use. Installing the `jsonschema` rock switches validation over to it; that rock needs a system PCRE, so it is not a declared dependency. ## Configuration At most five children run concurrently by default; additional children queue. Override the limit in any layered `config.toml` (later layers win): ```toml [subagents] max_concurrent = 10 ``` `max_concurrent` must be a positive integer. ## Agent profiles Agents are Markdown files with YAML frontmatter: ```markdown --- name: reviewer description: Reviews changes for correctness and missing tests model: anthropic:sonnet reasoning: high --- You are a focused code reviewer. Report only concrete findings. ``` Profiles are loaded recursively from `agents/` beneath every Panto config layer, lowest precedence first: the built-in base layer, `${XDG_CONFIG_HOME:-$HOME/.config}/panto/`, `.panto/`, and `.panto/local/`. A more local profile overrides an earlier one with the same name. `name` defaults to the filename. `model` and `reasoning` are optional; each value follows the same precedence: tool call, then profile, then the primary agent. Models use full `provider:model` names. ## Delegation The primary uses `subagents.run` to start a child from a profile: ```lua subagents.run { agent = "reviewer", prompt = "Review the authentication changes.", } ``` A one-off child can instead take an inline system prompt, without a saved profile: ```lua subagents.run { system_prompt = "You are a focused code reviewer.", prompt = "Review the authentication changes.", } ``` `agent`, `system_prompt`, and `id` are mutually exclusive. Every started child returns an ID. Passing it back resumes the same conversation, including after restarting Panto and resuming the primary: ```lua subagents.run { id = "0198...", prompt = "Now focus on the missing tests.", } ``` Children start a fresh conversation with the fixed child-role instruction and their profile prompt; the primary's system prompt and conversation are not copied. They share its workspace and tools except for `subagents.*`. Multiple calls in one tool batch run concurrently. Their tagged assistant and tool activity is visible in the TUI while they work. Child conversations are stored as normal JSONL sessions beneath their owning primary session, so they remain resumable without appearing in the user's `/resume` picker. A child ID cannot be resumed from another primary conversation. `subagents.models` provides bounded search and validation for configured providers, models, and model-specific reasoning levels without placing the entire model catalog in every tool prompt. ## Workflows TOML workflows define fixed dependency graphs. They are loaded from `workflows/` beneath the same config layers as profiles, then exposed as `/workflow:` commands. Ready steps run concurrently; each dependent step receives its predecessors' labeled outputs. The workflow reports its terminal steps, or exactly the steps a top-level `output = ["id", ...]` array names, in that order. The Lua workflow API handles dynamic branching and fan-out. `subagents.lua` starts model-authored workflows in a restricted environment and returns a session-scoped workflow ID immediately. Work continues on Pantograph's event loop; completion wakes the primary, and later calls can inspect field-read-only records through `subagents.workflows[id]`, including named child status and outputs. While one is live the extension emits balanced `background_work_start` and `background_work_end` events, allowing lifecycle integrations to remain busy across the primary turn boundary. Every `ctx:agent` needs a workflow-unique `name`, and the callback must return the workflow's string result. The same read-only workflow records are exposed to other extensions as `panto.ext.workflows`. Extensions can iterate this table or look up an ID; each record has `id`, `status`, `result`, `error`, and `agents`, and a `status` of `running` identifies in-flight background work. Fields are read-only, but `workflow:cancel()` requests cancellation for every running agent and `workflow.agents[name]:cancel()` requests cancellation for one; both return true only while their target is running. Inline `agents` remain scoped to workflows started by that tool call. The API can await one job or a group and supports one-turn workers whose validated tool input is their structured result. Persistent TOML workflows remain the simpler fixed-DAG surface. ## Development [mise](https://mise.jdx.dev) provides Lua 5.4 and LuaRocks: ```sh mise run deps # install the rocks into ./.rocks (gitignored) mise run check # run the specs against that tree ``` Without mise, install the same rocks with any LuaRocks targeting Lua 5.4 and run `lua spec/run.lua` from the repo root. `panto lua spec/run.lua` runs the suite inside panto's own interpreter and rocks tree, skipping whatever cases that tree has no rock for. The specs are plain Lua asserts — no framework. `spec/run.lua` loads every `spec/test_*.lua`, each of which returns an ordered array of `{ name, function }` cases. A case asserts and returns nothing to pass, or returns `"skip", reason` when an optional rock is missing; skips do not fail the run. `spec/fake_ext.lua` is a scriptable stand-in for the `panto.ext` host seam: it queues the result each spawned child settles with, records every spawn spec, tool, and command the extension produced, and settles awaits synchronously. See [DESIGN.md](DESIGN.md) for the runtime and persistence design.