-- The `subagents.run` tool: start one child agent, or continue one, and wait. -- -- One call handles all cases. `agent` starts a new child from that profile; -- `system_prompt` starts one without a saved profile; `id` continues a child -- this primary session started earlier. Exactly one is required, and `prompt` -- is always required — a child cannot see the parent dialogue, so the prompt -- is the only task context it gets. -- -- The call blocks until the child settles. Parallelism is ordinary tool -- batching: several subagents.run calls emitted in one batch run concurrently -- under the session-wide bound in subagents/jobs.lua, and one failure does not -- disturb its siblings. -- -- Failures are values, not exceptions. Everything the model could plausibly -- have caused — a missing prompt, both selectors at once, an unknown profile, -- an unresumable id, a child that errored or was cancelled — comes back as -- readable text. Validation that fails before a child is allocated has -- no id to report, and a new child that dies before its first assistant -- message has no durable file, so it reports `resumable: false` rather than -- promising a continuation that would not resolve. -- -- The result block is plain `key: value` lines rather than JSON: it is read by -- a model, and the field names match the design's result shape (id, agent, -- status, resumable, then the output or the error message). local jobs = require("subagents.jobs") local spawn = require("subagents.spawn") local M = {} -- A resumed child has no profile in hand; its identity comes back from the -- manifest metadata stored on its first profile system message. local function manifest_agent(result) local manifest = result.manifest if type(manifest) ~= "table" then return nil end local mine = manifest.subagents if type(mine) ~= "table" or type(mine.agent) ~= "string" then return nil end return mine.agent end -- format_result(result, agent_name) -> the model-visible block. function M.format_result(result, agent_name) local body = result.output if body == nil or body == "" then body = result.error or "" end return table.concat({ "id: " .. (result.id or "(none)"), "agent: " .. (agent_name or manifest_agent(result) or "?"), "status: " .. (result.status or "unknown"), "resumable: " .. tostring(result.resumable == true), "--- output ---", tostring(body), }, "\n") end function M.handle(input, profiles) local spec, err = spawn.build_spec(input, profiles) if not spec then return "Error: " .. tostring(err) end local handle, spawn_err = spawn.spawn(spec) if not handle then return "Error: " .. tostring(spawn_err) end local results = jobs.await({ handle }, "all") local result = results and results[1] if type(result) ~= "table" then return "Error: the subagent produced no result." end return M.format_result(result, spec.label) end return M