summaryrefslogtreecommitdiff
path: root/subagents/workflow.lua
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-08-17 23:35:36 -0600
committert <t@tjp.lol>2026-08-18 00:40:46 -0600
commit372ef8ff40991644ec2654c61328f31779f4ad21 (patch)
tree9bc69a578995e14f154202badcdbe0021a39c467 /subagents/workflow.lua
parent7f8fdd8e868fb5fad71eacdf0c4fd0a97fe9c6ee (diff)
Validation-pass fixes; DESIGN.md describes the shipped seam
jobs: a child whose wake pipe cannot be armed is refused up front instead of started into a state nothing can wake (the luv-less drain loop survives only for hosts without a loop); a closing child keeps its concurrency slot and its id's exclusivity until the pump actually exits, so teardown can no longer over-admit new children or let two turns share one session file. Resume-metadata reads honor the plain-error contract on a malformed store. workflow: the built-in schema subset validator is the only validator — the jsonschema probe made behavior depend on an undeclared rock (see rockspec: that dependency is deliberately rejected); dead exports and the unreachable half of the structured-output guard are gone, keeping the empty-arguments provider case. DESIGN.md's seam sections now describe the shipped division: binding-level async jobs and tool control, host-level resolve_model/ExtHost/turn events/ component handles, rock-level policy; protocol bodies run to completion on the loop thread and cannot yield, cancellation is scoped to the stream that opened it, and a child's compaction leaves protocol sessions alone.
Diffstat (limited to 'subagents/workflow.lua')
-rw-r--r--subagents/workflow.lua47
1 files changed, 16 insertions, 31 deletions
diff --git a/subagents/workflow.lua b/subagents/workflow.lua
index b1d3e46..3cf58d9 100644
--- a/subagents/workflow.lua
+++ b/subagents/workflow.lua
@@ -40,12 +40,14 @@
-- * Handles the callback never awaited are awaited ("all") after it returns,
-- purely so no child is orphaned; those results are discarded.
-- * Structured output is decoded from result.structured_json and validated
--- against output.schema. Validation prefers the `jsonschema` rock and falls
--- back to the small built-in subset validator below when it is absent --
--- that rock pulls in lrexlib-pcre, which needs a system PCRE and fails to
--- build on stock macOS, and a failed rock install would otherwise take the
--- whole extension down silently. A validation failure turns the result into
--- status "failed"; it is never reported as a successful structured result.
+-- against output.schema by the one validator below: the JSON Schema subset
+-- a child's output tool actually uses, ignoring keywords it does not know.
+-- There is deliberately no second, rock-dependent path -- `jsonschema` needs
+-- lrexlib-pcre and a system PCRE that stock macOS lacks, so it is not a
+-- declared dependency, and a validator picked by whether a rock happens to
+-- be installed would make the same output pass here and fail there. A
+-- validation failure turns the result into status "failed"; it is never
+-- reported as a successful structured result.
-- * The host seam is reached through `require("panto").ext` at call time, not
-- aliased at load time, matching subagents/spawn.lua so a test can install a
-- fake `panto` module before the first call rather than before the require.
@@ -58,8 +60,6 @@ local M = {}
local workflow_mt = { __name = "subagents.workflow" }
-M.workflow_mt = workflow_mt
-
-- ---------------------------------------------------------------------------
-- Host seam access
-- ---------------------------------------------------------------------------
@@ -117,16 +117,15 @@ local function json_encode(value)
return tostring(value)
end
-M.json_decode = json_decode
M.json_encode = json_encode
-- ---------------------------------------------------------------------------
-- Schema validation
-- ---------------------------------------------------------------------------
--- Built-in fallback validator: the JSON Schema subset that structured child
--- output actually uses. Anything it does not understand is ignored rather than
--- rejected, so an unrecognized keyword never fails a legitimate result.
+-- The validator: the JSON Schema subset that structured child output actually
+-- uses. Anything it does not understand is ignored rather than rejected, so an
+-- unrecognized keyword never fails a legitimate result.
local function is_array_like(value)
local count = 0
for key in pairs(value) do
@@ -259,23 +258,6 @@ local function check_schema(value, schema, path)
return true
end
-local function validator_for(schema)
- local ok, jsonschema = pcall(require, "jsonschema")
- if ok and type(jsonschema) == "table" and jsonschema.generate_validator then
- local generated_ok, generated = pcall(jsonschema.generate_validator, schema)
- if generated_ok and type(generated) == "function" then
- return generated
- end
- end
- return function(value)
- return check_schema(value, schema, "output")
- end
-end
-
-M.validate = function(value, schema)
- return validator_for(schema)(value)
-end
-
-- ---------------------------------------------------------------------------
-- Result shaping
-- ---------------------------------------------------------------------------
@@ -312,8 +294,11 @@ local function shape_result(result, handle)
return shaped
end
+ -- An empty tool input is a real provider case (a chat-style provider
+ -- finalizes an argument-less call with ""), and it is not a validation
+ -- failure: nothing was produced to validate.
local raw = shaped.structured_json
- if type(raw) ~= "string" or raw == "" then
+ if raw == nil or raw == "" then
return fail(shaped, "structured output missing: the child produced no structured result")
end
@@ -322,7 +307,7 @@ local function shape_result(result, handle)
return fail(shaped, "structured output failed validation: " .. tostring(decoded))
end
- local valid, message = validator_for(schema)(decoded)
+ local valid, message = check_schema(decoded, schema, "output")
if not valid then
return fail(shaped, "structured output failed validation: " .. tostring(message or "schema mismatch"))
end