From d1306506aa7f504b0e91c9c6ed7314afbf99978e Mon Sep 17 00:00:00 2001 From: t Date: Wed, 19 Aug 2026 19:02:30 -0600 Subject: Add background Lua workflows, inline child prompts, and layered config subagents.lua now starts a workflow on its own coroutine and returns a session-scoped id immediately, so fan-out continues while the primary keeps working; completion wakes the primary, and later calls read immutable records from subagents.workflows. Every ctx:agent takes a workflow-unique name so those records are addressable. A session_start guidance message tells the primary when to reach for run vs lua. Children no longer inherit the primary's system context: a child starts from the fixed child-role instruction plus its profile, and subagents.run/ctx:agent accept an inline system_prompt instead of a profile. The now-redundant `agent` form of subagents.models is gone. Concurrency defaults to five and is configurable through [subagents] max_concurrent in any layered config.toml; turn boundaries reap only settled jobs so background workflows survive, while interrupt and session end cancel. Config roots come from panto.ext.dirs.layers rather than a hand-rolled XDG lookup, which picks up the base and git-ignored local layers for both agents/ and workflows/. TOML workflows tighten up: the subagents.workflow tool takes a discovered name only (inline `steps` duplicated subagents.lua at less power), an optional top-level `output` array chooses the reported steps and their order instead of the terminal set, a step with no workflow input gets no empty input heading, and a workflow naming an undiscovered agent is rejected at discovery rather than part-way through a run. --- spec/test_jobs.lua | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) (limited to 'spec/test_jobs.lua') diff --git a/spec/test_jobs.lua b/spec/test_jobs.lua index de36ab5..25faf6e 100644 --- a/spec/test_jobs.lua +++ b/spec/test_jobs.lua @@ -129,20 +129,20 @@ return { end) end }, - { "the gate runs four at a time and queues the rest", function() + { "the gate runs five at a time and queues the rest", function() with_jobs(function() local start, built = starter() local handles = {} - for _, name in ipairs({ "a", "b", "c", "d", "e", "f" }) do + for _, name in ipairs({ "a", "b", "c", "d", "e", "f", "g" }) do handles[#handles + 1] = assert(start(name)) end - assert(#built == 4, "the gate holds at four running, saw " .. #built) - assert(handles[5]:result() == nil, "a queued child has not settled") + assert(#built == 5, "the gate holds at five running, saw " .. #built) + assert(handles[6]:result() == nil, "a queued child has not settled") local results = jobs.await(handles, "all") - assert(#built == 6, "the queue drains as slots free up, saw " .. #built) - assert(#results == 6, "every child reports") - assert(results[5].text == "e" and results[6].text == "f", "queued children keep their place") + assert(#built == 7, "the queue drains as slots free up, saw " .. #built) + assert(#results == 7, "every child reports") + assert(results[6].text == "f" and results[7].text == "g", "queued children keep their place") end) end }, @@ -150,20 +150,20 @@ return { with_jobs(function() local start, built = starter() local handles = {} - for _, name in ipairs({ "a", "b", "c", "d", "e" }) do + for _, name in ipairs({ "a", "b", "c", "d", "e", "f" }) do handles[#handles + 1] = assert(start(name)) end - handles[5]:cancel() + handles[6]:cancel() - local result = handles[5]:result() + local result = handles[6]:result() assert(type(result) == "table", "a cancelled queued child settles immediately") assert(result.status == "cancelled", tostring(result.status)) assert(result.error == "cancelled before the child started", tostring(result.error)) - assert(#built == 4, "the queued child was never built") - assert(not contains(built, "e"), "the queued child was never built") + assert(#built == 5, "the queued child was never built") + assert(not contains(built, "f"), "the queued child was never built") - jobs.await({ handles[1], handles[2], handles[3], handles[4] }, "all") - assert(#built == 4, "a cancelled child does not start when a slot frees up") + jobs.await({ handles[1], handles[2], handles[3], handles[4], handles[5] }, "all") + assert(#built == 5, "a cancelled child does not start when a slot frees up") end) end }, @@ -202,16 +202,16 @@ return { with_jobs(function() local start, built, made = starter() local handles = {} - for _, name in ipairs({ "a", "b", "c", "d", "e" }) do + for _, name in ipairs({ "a", "b", "c", "d", "e", "f" }) do handles[#handles + 1] = assert(start(name, { settle = 5 })) end jobs.cancel_all() - for _, name in ipairs({ "a", "b", "c", "d" }) do + for _, name in ipairs({ "a", "b", "c", "d", "e" }) do assert(made[name]._cancel_requested, "running child " .. name .. " was not cancelled") end - assert(#built == 4, "cancel_all must not start the queued child") - assert(handles[5]:result().status == "cancelled", "the queued child settles cancelled") + assert(#built == 5, "cancel_all must not start the queued child") + assert(handles[6]:result().status == "cancelled", "the queued child settles cancelled") local results = jobs.await(handles, "all") for index, result in ipairs(results) do -- cgit v1.3