JuliusBrussee/caveman · error

cave_output_schema_invalid_json

cave_output_schema_invalid_json

Error message

cave_output_schema_invalid_json

What it means

Thrown when the agent definition declares an output schema (definition.output.schema) and the final assistant message's text is not parseable as JSON. The runtime validates structured output before returning it, so callers never receive invalid JSON typed as valid.

Source

Thrown at packages/agent/src/runtime.ts:2119

      throw new Error("cave_subagent_spend_evidence_incomplete");
    }
    // A run stopped before its first call has no assistant message, and that is
    // the honest outcome rather than missing evidence: the caller gets an empty
    // answer, zero usage, and the reason the runtime declined to spend.
    if (!finalMessage && stopReason === undefined) {
      throw new Error("cave_incomplete_evidence: Pi emitted no final assistant message");
    }
    if (finalMessage &&
        (finalMessage.stopReason === "error" || finalMessage.stopReason === "aborted")) {
      throw new Error(`cave_provider_terminal_${finalMessage.stopReason}`);
    }
    const text = finalMessage === undefined ? "" : assistantText(finalMessage);
    if (definition.output?.schema && finalMessage !== undefined) {
      let parsed: unknown;
      try {
        parsed = JSON.parse(text);
      } catch {
        throw new Error("cave_output_schema_invalid_json");
      }
      if (!Value.Check(definition.output.schema, parsed)) {
        throw new Error("cave_output_schema_mismatch");
      }
    }
    if (appliedPlan.appliedTransformIDs.length > 0 && !cacheBoundaryKnown) {
      cacheBust = true;
      markRequestPassThrough(headers, appliedPlan, "cache_boundary_unobserved");
    }
    for (const child of nestedReceipts) receipt.recordSubagent(child);
    // Built once so the result and its receipt cannot disagree about a breach.
    const runReceipt = receipt.build({
      runId,
      agentId: definition.id,
      stopReason: stopReason ?? "complete",
      meter: budgetMeter,
      ...(breakers === undefined ? {} : { breakers: breakers.recorded }),
    });

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Strengthen the output instruction — the injected <cave-output> tag says 'Return output matching declared schema'; reinforce in agent instructions that the final message must be JSON only
  2. Raise definition.output.maxTokens so the JSON is not truncated
  3. Use a model with reliable structured-output behavior
  4. Check appliedPlan for transforms routing the output segment and exclude the final answer from compression

Example fix

// before
const agent = defineAgent({
  id: "extractor",
  output: { schema: ExtractSchema, maxTokens: 128 }, // too small, JSON truncates
});

// after
const agent = defineAgent({
  id: "extractor",
  instructions: "Reply with a single JSON object and nothing else.",
  output: { schema: ExtractSchema, maxTokens: 2048 },
});
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check the model's answer shape in development by running without a schema
// and validating yourself before enabling definition.output.schema:
function isJson(text: string): boolean {
  try { JSON.parse(text); return true; } catch { return false; }
}

Type guard

function isInvalidOutputJson(e: unknown): e is Error {
  return e instanceof Error && e.message === "cave_output_schema_invalid_json";
}

Try / catch

try {
  return await agent.run(input, opts);
} catch (e) {
  if (isInvalidOutputJson(e)) {
    // retry with reinforced JSON-only instructions or higher output maxTokens
  } else throw e;
}

Prevention

When it happens

Trigger: definition.output.schema is set, a finalMessage exists, and assistantText(finalMessage) fails JSON.parse — the model answered in prose or emitted truncated JSON.

Common situations: Output max_tokens too small so JSON is cut off; instructions not demanding JSON-only output; models weak at instruction-following; transforms (e.g. text/toon compression) applied to the output segment distorting it.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/d19b7f52fecfb3c9. Report an issue: GitHub.