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
- Verify the provider credentials and model id are valid — a pre-call rejection yields no assistant message.
- Check Pi/provider adapter version pinning matches the runtime-identity expectation (src/runtime-identity.ts).
- Log raw Pi events (the stream yields { type: "pi" } events) to see what the provider actually returned before agent_end.
- 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
- Validate provider credentials and model ids before running.
- Capture the yielded pi events to diagnose premature stream ends.
- Keep the Pi adapter version exactly pinned per runtime-identity.
- Retry transient empty streams once before surfacing to users.
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
- cave_provider_model_identity_mismatch
- browser function returned no result object
- cave_claude_provider_unsupported
- cave_claude_terminal_missing
- cave_fixture_terminal_evidence_missing
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)