feat(task): enabled schema override flow to keep task payloads with warnings

- Propagated `schemaOverridden` from `YieldTool` into executor `YieldItem` metadata.
- Bypassed schema validation on override or schema-builder errors and kept payload output with success exit.
- Emitted `SUBAGENT_WARNING_SCHEMA_OVERRIDDEN` so accepted override results no longer surface as `schema_violation`.
This commit is contained in:
can1357
2026-06-08 02:04:59 +02:00
parent e44bf8261b
commit c9b3c23600
4 changed files with 110 additions and 24 deletions
+34 -22
View File
@@ -311,6 +311,15 @@ export interface YieldItem {
data?: unknown;
status?: "success" | "aborted";
error?: string;
/**
* Set by the in-tool yield validator when it exhausted its retry budget
* (MAX_SCHEMA_RETRIES) and accepted a schema-invalid payload anyway.
* `finalizeSubprocessOutput` honors this by serializing the payload and
* surfacing a stderr warning, instead of re-emitting `schema_violation`
* — which would silently swap the subagent's "accepted" view for a
* different, opaque error blob in the parent's view of the result.
*/
schemaOverridden?: boolean;
}
interface FinalizeSubprocessOutputArgs {
@@ -331,7 +340,8 @@ interface FinalizeSubprocessOutputResult {
abortedViaYield: boolean;
hasYield: boolean;
}
export const SUBAGENT_WARNING_SCHEMA_OVERRIDDEN =
"SYSTEM WARNING: Subagent exhausted schema-retry budget; result was accepted despite failing the output schema.";
export const SUBAGENT_WARNING_NULL_YIELD = "SYSTEM WARNING: Subagent called yield with null data.";
export const SUBAGENT_WARNING_MISSING_YIELD =
"SYSTEM WARNING: Subagent exited without calling yield tool after 3 reminders.";
@@ -384,29 +394,31 @@ export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): Fi
rawOutput = rawOutput ? `${SUBAGENT_WARNING_NULL_YIELD}\n\n${rawOutput}` : SUBAGENT_WARNING_NULL_YIELD;
} else {
const { validator, error: schemaError } = buildOutputValidator(outputSchema);
if (schemaError) {
rawOutput = `{"error":"schema_violation","message":"invalid output schema: ${schemaError.replace(/"/g, '\\"')}"}`;
stderr = `schema_violation: invalid output schema: ${schemaError}`;
exitCode = 1;
const overridden = lastYield?.schemaOverridden === true;
const completeData = normalizeCompleteData(submitData, reportFindings, validator);
const result =
schemaError || overridden
? { success: true as const }
: (validator?.validate(completeData) ?? { success: true as const });
if (!result.success) {
const summary = summarizeValidationFailure(result, completeData, validator?.requiredFields ?? []);
const outcome = buildSchemaViolationOutcome(summary, completeData);
rawOutput = outcome.rawOutput;
stderr = outcome.stderr;
exitCode = outcome.exitCode;
} else {
const completeData = normalizeCompleteData(submitData, reportFindings, validator);
const result = validator?.validate(completeData) ?? { success: true as const };
if (!result.success) {
const summary = summarizeValidationFailure(result, completeData, validator?.requiredFields ?? []);
const outcome = buildSchemaViolationOutcome(summary, completeData);
rawOutput = outcome.rawOutput;
stderr = outcome.stderr;
exitCode = outcome.exitCode;
} else {
try {
rawOutput = JSON.stringify(completeData, null, 2) ?? "null";
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
rawOutput = `{"error":"Failed to serialize yield data: ${errorMessage}"}`;
}
exitCode = 0;
stderr = "";
try {
rawOutput = JSON.stringify(completeData, null, 2) ?? "null";
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
rawOutput = `{"error":"Failed to serialize yield data: ${errorMessage}"}`;
}
exitCode = 0;
stderr = overridden
? SUBAGENT_WARNING_SCHEMA_OVERRIDDEN
: schemaError
? `invalid output schema: ${schemaError}`
: "";
}
}
}
+10 -1
View File
@@ -20,6 +20,14 @@ export interface YieldDetails {
data: unknown;
status: "success" | "aborted";
error?: string;
/**
* Set when the yield tool exhausted its in-tool schema-retry budget
* (MAX_SCHEMA_RETRIES) and accepted the data anyway. Surfaced so the
* executor's post-mortem finalizer can honor the override instead of
* re-rejecting the same payload with `schema_violation` — keeping the
* subagent's acceptance and the parent's view of the result in lockstep.
*/
schemaOverridden?: boolean;
}
function formatSchema(schema: unknown): string {
@@ -237,7 +245,7 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
: "Result submitted.";
return {
content: [{ type: "text", text: responseText }],
details: { data, status, error: errorMessage },
details: { data, status, error: errorMessage, schemaOverridden: schemaValidationOverridden || undefined },
};
}
}
@@ -254,6 +262,7 @@ subprocessToolRegistry.register<YieldDetails>("yield", {
data: record.data,
status,
error: typeof record.error === "string" ? record.error : undefined,
schemaOverridden: record.schemaOverridden === true ? true : undefined,
};
},
shouldTerminate: event => !event.isError,