summaryrefslogtreecommitdiff
path: root/spec/test_run.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_run.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_run.lua')
-rw-r--r--spec/test_run.lua42
1 files changed, 31 insertions, 11 deletions
diff --git a/spec/test_run.lua b/spec/test_run.lua
index 57c6e47..b2c5783 100644
--- a/spec/test_run.lua
+++ b/spec/test_run.lua
@@ -75,11 +75,15 @@ return {
end)
end },
- { "both agent and id is refused", function()
+ { "multiple selectors are refused", function()
with_host(function(handle, profiles)
local text = run.handle({ agent = "reviewer", id = "0198-x", prompt = "go" }, profiles)
has(text, "Error:")
- has(text, "not both")
+ has(text, "exactly one of `agent`")
+ assert(#handle.spawns == 0)
+
+ text = run.handle({ agent = "reviewer", system_prompt = "Be concise.", prompt = "go" }, profiles)
+ has(text, "exactly one of `agent`")
assert(#handle.spawns == 0)
end)
end },
@@ -124,6 +128,21 @@ return {
end)
end },
+ { "an inline system prompt starts a child without a profile", function()
+ with_host(function(handle, profiles)
+ local text = run.handle({ system_prompt = "Be a focused investigator.", prompt = "Find the cause." }, profiles)
+ assert(#handle.spawns == 1)
+ local messages = handle.spawns[1].system_messages
+ assert(#messages == 2, "expected role and inline prompt")
+ assert(messages[1].text == spawn.CHILD_ROLE)
+ assert(messages[2].text == "Be a focused investigator.")
+ local manifest = messages[2].metadata.subagents
+ assert(manifest.agent == "subagent", tostring(manifest.agent))
+ assert(manifest.inline == true, "inline prompts must remain identifiable on replay")
+ has(text, "agent: subagent")
+ end)
+ end },
+
{ "subagents.run presents its child prompt only when expanded", function()
with_host(function(handle, profiles)
handle.queue_for("reviewer", { events = {
@@ -171,22 +190,23 @@ return {
end)
end },
- { "the primary's system context comes first, then the role, then the profile", function()
+ { "the primary's system context and dialogue are not copied", function()
with_host(function(handle, profiles)
run.handle({ agent = "reviewer", prompt = "go" }, profiles)
local messages = handle.spawns[1].system_messages
- assert(#messages == 4, "expected two copied messages plus role and profile, saw " .. #messages)
- assert(messages[1].text == "Project context.", tostring(messages[1].text))
- assert(messages[2].text == "House style.", tostring(messages[2].text))
- assert(messages[3].text == spawn.CHILD_ROLE, "the child role follows the primary's context")
- assert(messages[4].text == "You are a reviewer.\n", "the profile body is last")
- assert(messages[1].metadata == nil, "copied context carries no manifest")
+ assert(#messages == 2, "expected only role and profile messages, saw " .. #messages)
+ assert(messages[1].text == spawn.CHILD_ROLE, "the child role is first")
+ assert(messages[2].text == "You are a reviewer.\n", "the profile defines the child context")
+ for _, message in ipairs(messages) do
+ assert(message.text ~= "Primary core prompt.", "the primary system prompt must not reach the child")
+ assert(message.text ~= "Project context.", "primary system additions must not be copied")
+ end
end, {
primary_messages = {
- { role = "system", text = "Project context." },
+ { role = "system", text = "Primary core prompt." },
{ role = "user", text = "the parent dialogue is never copied" },
{ role = "assistant", text = "nor this" },
- { role = "system", text = "House style." },
+ { role = "system", text = "Project context." },
},
})
end },