summaryrefslogtreecommitdiff
path: root/spec/test_workflow.lua
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-08-19 19:02:30 -0600
committert <t@tjp.lol>2026-08-19 19:03:52 -0600
commitd1306506aa7f504b0e91c9c6ed7314afbf99978e (patch)
treee5a7295dbc566a3679e99e14dae13376b0469e84 /spec/test_workflow.lua
parent94e3fd8358bbdb5d6ed81aed475fab7fc73e2097 (diff)
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.
Diffstat (limited to 'spec/test_workflow.lua')
-rw-r--r--spec/test_workflow.lua41
1 files changed, 23 insertions, 18 deletions
diff --git a/spec/test_workflow.lua b/spec/test_workflow.lua
index 5d58c01..2e66448 100644
--- a/spec/test_workflow.lua
+++ b/spec/test_workflow.lua
@@ -35,7 +35,7 @@ end
local function three_children(ctx)
local handles = {}
for index, name in ipairs({ "alpha", "beta", "gamma" }) do
- handles[index] = ctx:agent({ agent = name, prompt = "work on " .. name })
+ handles[index] = ctx:agent({ name = name, agent = name, prompt = "work on " .. name })
end
return handles
end
@@ -100,27 +100,27 @@ return {
with_host(function(handle, profiles)
handle.queue_for("alpha", { output = "just me" })
local result = workflow.execute(workflow.workflow(function(ctx)
- return ctx:agent({ agent = "alpha", prompt = "go" }):await()
+ return ctx:agent({ name = "only", agent = "alpha", prompt = "go" }):await()
end), "input", { profiles = profiles })
assert(result.status == "completed", tostring(result.status))
assert(result.output == "just me", tostring(result.output))
end)
end },
- { "the gate keeps four children in flight and queues the rest", function()
+ { "the gate keeps five children in flight and queues the rest", function()
with_host(function(handle, profiles)
local results = workflow.execute(workflow.workflow(function(ctx)
local handles = {}
- for index = 1, 5 do
- handles[index] = ctx:agent({ agent = "alpha", prompt = "task " .. index })
+ for index = 1, 6 do
+ handles[index] = ctx:agent({ name = "job-" .. index, agent = "alpha", prompt = "task " .. index })
end
- assert(#handle.runs == 4, "four turns run at once, saw " .. #handle.runs)
+ assert(#handle.runs == 5, "five turns run at once, saw " .. #handle.runs)
return ctx:await(handles, "all")
end), "input", { profiles = profiles })
- assert(#results == 5, "every child reports")
- assert(#handle.runs == 5, "the queued child starts once a slot frees up")
- assert(handle.max_live == 4, "never more than four at once, peaked at " .. handle.max_live)
+ assert(#results == 6, "every child reports")
+ assert(#handle.runs == 6, "the queued child starts once a slot frees up")
+ assert(handle.max_live == 5, "never more than five at once, peaked at " .. handle.max_live)
end)
end },
@@ -129,8 +129,8 @@ return {
handle.queue_for("beta", { output = "B" })
local results = workflow.execute(workflow.workflow(function(ctx)
- local first = ctx:agent({ agent = "alpha", model = "openai:ghost", prompt = "a" })
- local second = ctx:agent({ agent = "beta", prompt = "b" })
+ local first = ctx:agent({ name = "first", agent = "alpha", model = "openai:ghost", prompt = "a" })
+ local second = ctx:agent({ name = "second", agent = "beta", prompt = "b" })
return ctx:await({ first, second }, "all")
end), "input", { profiles = profiles })
@@ -146,7 +146,7 @@ return {
with_host(function(handle, profiles)
handle.queue_for("alpha", { status = "failed", error = "provider refused", resumable = false })
local result = workflow.execute(workflow.workflow(function(ctx)
- return ctx:agent({ agent = "alpha", prompt = "a" }):await()
+ return ctx:agent({ name = "failed", agent = "alpha", prompt = "a" }):await()
end), "input", { profiles = profiles })
assert(result.status == "failed")
assert(result.error == "provider refused", tostring(result.error))
@@ -162,8 +162,8 @@ return {
handle.queue({ output = "the first turn wins" })
local results = workflow.execute(workflow.workflow(function(ctx)
- local first = ctx:agent({ id = "0198-child", prompt = "a" })
- local second = ctx:agent({ id = "0198-child", prompt = "b" })
+ local first = ctx:agent({ name = "first", id = "0198-child", prompt = "a" })
+ local second = ctx:agent({ name = "second", id = "0198-child", prompt = "b" })
return ctx:await({ first, second }, "all")
end), "input", { profiles = profiles })
@@ -182,6 +182,7 @@ return {
handle.queue_for("alpha", { structured_json = '{"items":["x","y"]}' })
local result = workflow.execute(workflow.workflow(function(ctx)
return ctx:agent({
+ name = "worker",
agent = "alpha",
prompt = "split it",
output = { description = "Return the work items.", schema = ITEMS_SCHEMA },
@@ -219,6 +220,7 @@ return {
handle.queue_for("alpha", { structured_json = '{"nope":1}' })
local result = workflow.execute(workflow.workflow(function(ctx)
return ctx:agent({
+ name = "worker",
agent = "alpha",
prompt = "split it",
output = { schema = ITEMS_SCHEMA },
@@ -249,6 +251,7 @@ return {
handle.queue_for("alpha", { structured_json = '{"items":["x"]}' })
local result = workflow.execute(workflow.workflow(function(ctx)
return ctx:agent({
+ name = "worker",
agent = "alpha",
prompt = "split it",
output = { schema = ITEMS_SCHEMA },
@@ -268,6 +271,7 @@ return {
handle.queue_for("alpha", { structured_json = "" })
local result = workflow.execute(workflow.workflow(function(ctx)
return ctx:agent({
+ name = "worker",
agent = "alpha",
prompt = "split it",
output = { schema = ITEMS_SCHEMA },
@@ -283,6 +287,7 @@ return {
handle.queue_for("alpha", { output = "prose, not a tool call" })
local result = workflow.execute(workflow.workflow(function(ctx)
return ctx:agent({
+ name = "worker",
agent = "alpha",
prompt = "split it",
output = { schema = ITEMS_SCHEMA },
@@ -299,8 +304,8 @@ return {
handle.queue_for("gamma", { output = "C" })
local first_status = workflow.execute(workflow.workflow(function(ctx)
- local rejected = ctx:agent({ agent = "alpha", model = "openai:ghost", prompt = "a" })
- local live = { rejected, ctx:agent({ agent = "beta", prompt = "b" }) }
+ local rejected = ctx:agent({ name = "rejected", agent = "alpha", model = "openai:ghost", prompt = "a" })
+ local live = { rejected, ctx:agent({ name = "live", agent = "beta", prompt = "b" }) }
local result, remaining = ctx:await(live, "first")
assert(#remaining == 1, "the pending sibling stays outstanding")
assert(handle.polls == 0, "a cached result must not reach the job machinery")
@@ -326,7 +331,7 @@ return {
{ "handles the callback never awaited are settled before returning", function()
with_host(function(handle, profiles)
workflow.execute(workflow.workflow(function(ctx)
- ctx:agent({ agent = "alpha", prompt = "orphan" })
+ ctx:agent({ name = "orphan", agent = "alpha", prompt = "orphan" })
return "done"
end), "input", { profiles = profiles })
assert(#handle.jobs == 1, "one child ran")
@@ -337,7 +342,7 @@ return {
{ "an unknown agent inside a workflow is a workflow error", function()
with_host(function(handle, profiles)
local ok, err = pcall(workflow.execute, workflow.workflow(function(ctx)
- ctx:agent({ agent = "ghost", prompt = "go" })
+ ctx:agent({ name = "ghost", agent = "ghost", prompt = "go" })
end), "input", { profiles = profiles })
assert(not ok, "an unknown profile is a programmer error, not a child failure")
has(tostring(err), "unknown agent 'ghost'")