diff options
| author | t <t@tjp.lol> | 2026-08-17 23:35:36 -0600 |
|---|---|---|
| committer | t <t@tjp.lol> | 2026-08-18 00:40:46 -0600 |
| commit | 372ef8ff40991644ec2654c61328f31779f4ad21 (patch) | |
| tree | 9bc69a578995e14f154202badcdbe0021a39c467 /subagents/workflow.lua | |
| parent | 7f8fdd8e868fb5fad71eacdf0c4fd0a97fe9c6ee (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.lua | 47 |
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 |
