JuliusBrussee/caveman · error

cave_incomplete_evidence

cave_incomplete_evidence

Error message

cave_incomplete_evidence: Pi emitted no final assistant message

What it means

'cave_incomplete_evidence: Pi emitted no final assistant message' is thrown when the Pi agent run ended without producing any assistant message and without a runtime stopReason (runtime.ts:2106-2107). The framework treats 'stopped before the first model call' as the honest outcome — empty answer, zero usage — rather than fabricating missing evidence, so it throws instead of returning a result.

Solutions

  1. Verify the provider credentials and model id are valid — a pre-call rejection yields no assistant message.
  2. Check Pi/provider adapter version pinning matches the runtime-identity expectation (src/runtime-identity.ts).
  3. Log raw Pi events (the stream yields { type: "pi" } events) to see what the provider actually returned before agent_end.
  4. Retry once to rule out a transient empty stream; if persistent, capture events and report against the provider adapter.

Example fix

// before: swallowing the stream without observing what Pi emitted
for await (const e of streamAgent(agent, input)) { /* ignore */ }

// after: inspect pi events to diagnose why no assistant message arrived
for await (const e of streamAgent(agent, input)) {
  if (e.type === "pi") console.log(e.event.type);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate inputs and provider config before the run so the first call can actually happen.
if (!apiKey || !modelId) throw new Error("provider not configured");

Try / catch

try {
  const result = await runAgent(agent, input);
} catch (e) {
  if (e instanceof Error && e.message.startsWith("cave_incomplete_evidence")) {
    // provider ended before any assistant message — check credentials/model, retry once
  }
}

Prevention

When it happens

Trigger: pi.prompt(input) terminates (agent_end) without any assistant turn_end event and no stopReason was set by hooks/budget/deadline — e.g. the provider stream ended immediately, the prompt was rejected before the first call, or the Pi loop finished abnormally without emitting a message.

Common situations: Provider returns an empty/immediately-closing stream; invalid request shape rejected pre-flight by the provider; pinned adapter/upstream version drift changes Pi event emission; misconfigured model id that fails before any assistant message is produced.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/f29c68d53a609123. Report an issue: GitHub.

Appendix: source

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

    }
    if (ladderFailure) throw ladderFailure;
    if (spendFailure) throw spendFailure;
    if (usageFailure !== undefined &&
        (efficiencyPlan !== undefined ||
          executionContext.spendLedgers.length > 0 ||
          usageFailure.message === "cave_provider_model_identity_mismatch" ||
          usageFailure.message === "cave_provider_identity_missing")) {
      throw usageFailure;
    }
    if (pendingSpendReservations.length > 0) {
      markSpendIncomplete(executionContext.spendLedgers);
      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) {

View on GitHub (pinned to 3ee70a1026)