diff options
Diffstat (limited to 'subagents/spawn.lua')
| -rw-r--r-- | subagents/spawn.lua | 82 |
1 files changed, 28 insertions, 54 deletions
diff --git a/subagents/spawn.lua b/subagents/spawn.lua index caf65aa..9117fdd 100644 --- a/subagents/spawn.lua +++ b/subagents/spawn.lua @@ -14,12 +14,14 @@ -- two fields resolve independently: a call may override reasoning while -- inheriting the model. -- --- A new child's conversation starts with the primary's effective system --- context, then the fixed child-role instruction, then — when the profile has --- a body — the profile prompt as a further system message. That profile --- message carries the immutable manifest metadata (owning primary session id + --- profile name); workflow-local profiles also carry an inline marker so the --- progress replay path can expose only prompts the caller explicitly supplied. +-- A new child's conversation starts with the fixed child-role instruction, +-- then — when the profile has a body — the profile prompt as a further system +-- message. The primary's system messages and dialogue are never copied: the +-- profile defines the child's system context rather than augmenting the +-- primary agent's prompt. That profile message carries the immutable manifest +-- metadata (owning primary session id + profile name); workflow-local profiles +-- also carry an inline marker so the progress replay path can expose only +-- prompts the caller explicitly supplied. -- The per-turn user metadata records the effective model/reasoning, the -- per-outer-call card sequence, terminal presentation status, and — when the -- spawn happened inside a bound extension tool — that outer tool call id. @@ -130,7 +132,7 @@ end -- build_spec(input, profiles) -> spec | nil, err -- --- input = { agent | id, prompt, model?, reasoning?, output? } +-- input = { agent | system_prompt | id, prompt, model?, reasoning?, output? } function M.build_spec(input, profiles) if type(input) ~= "table" then return nil, "expected a table of arguments" @@ -148,11 +150,14 @@ function M.build_spec(input, profiles) if err then return nil, err end - if agent and id then - return nil, "pass exactly one of `agent` (start a new child) or `id` (continue one), not both" + local system_prompt + system_prompt, err = optional_string(input.system_prompt, "system_prompt") + if err then + return nil, err end - if not agent and not id then - return nil, "pass exactly one of `agent` (start a new child) or `id` (continue one)" + local selectors = (agent and 1 or 0) + (system_prompt and 1 or 0) + (id and 1 or 0) + if selectors ~= 1 then + return nil, "pass exactly one of `agent` (start from a profile), `system_prompt` (start without a profile), or `id` (continue one)" end local model @@ -182,6 +187,12 @@ function M.build_spec(input, profiles) if not profile then return nil, string.format("unknown agent '%s'; known: %s", agent, M.agent_names(profiles)) end + elseif system_prompt then + profile = { + name = "subagent", + body = system_prompt, + inline = true, + } end -- child_store_dir returns the session info alongside the directory on @@ -208,17 +219,18 @@ function M.build_spec(input, profiles) spec.model = model or profile.model spec.reasoning = reasoning or profile.reasoning + local inline = profile.inline == true or profile.layer == "workflow" local system_messages = { { text = M.CHILD_ROLE } } if profile.body and profile.body:match("%S") then local manifest = { owner = info_or_err.session_id, agent = profile.name } - if profile.layer == "workflow" then manifest.inline = true end + if inline then manifest.inline = true end system_messages[#system_messages + 1] = { text = profile.body, metadata = { subagents = manifest }, } end spec.system_messages = system_messages - if profile.layer == "workflow" then + if inline then spec.presentation_system_prompt = profile.body end @@ -262,44 +274,6 @@ local function read_stored(conv) return defaults, manifest end --- The primary's effective system context, which every new child starts with. A --- replace-mode system block supersedes everything before it, exactly as the --- primary's own provider sees it. -local function primary_system_texts() - local primary = host().agent - if primary == nil then - return {} - end - local ok, conv = try(primary.conversation, primary) - if not ok or conv == nil then - return {} - end - local read, messages = try(conv.messages, conv) - if not read or type(messages) ~= "table" then - return {} - end - - local texts = {} - for _, message in ipairs(messages) do - if message.role == "system" then - local parts = {} - for _, block in ipairs(message.blocks or {}) do - if block.mode == "replace" then - texts, parts = {}, {} - end - if type(block.text) == "string" and (block.type == "system" or block.type == "text") then - parts[#parts + 1] = block.text - end - end - local text = table.concat(parts, "\n") - if text ~= "" then - texts[#texts + 1] = text - end - end - end - return texts -end - -- Everything the primary can call except the delegation tools themselves. The -- decls carry opaque source tags, so a child registering them reaches the same -- handlers on the same runtime. @@ -323,9 +297,6 @@ end local function seed_conversation(agent, spec) local conv = agent:conversation() - for _, text in ipairs(primary_system_texts()) do - conv:add_system_message(text) - end for _, message in ipairs(spec.system_messages or {}) do if message.metadata ~= nil then conv:add_system_message(message.text, { metadata = message.metadata }) @@ -527,6 +498,9 @@ function M.spawn(spec) pcall(agent.set_message_metadata, agent, turn_index, turn_metadata) end card:done(result.status, result.error) + if type(spec.on_settle) == "function" then + pcall(spec.on_settle, result) + end return result end |
