Job-Scorer / lib /agent-debug-trace.ts
zimejin's picture
Cursor
Make agent turns outcome-driven: soft-fail after signal, auto-cycle, resume-first.
de1f194
Raw History Blame Contribute Delete
3.41 kB
import type { AgentRunLog } from "@/lib/agent-run-log-types";
/** Markdown export for pasting into an LLM — safe for client bundles (no Redis). */
export function buildDebugTrace(run: AgentRunLog): string {
const lines: string[] = [];
const ts = new Date(run.startedAt).toISOString();
lines.push("# Job Scorer Agent Debug Trace");
lines.push(`Run ID: \`${run.runId}\``);
lines.push(`User: \`${run.userId}\``);
if (run.sessionId) lines.push(`Session: \`${run.sessionId}\``);
lines.push(`Started: ${ts}`);
lines.push(`Duration: ${(run.durationMs / 1000).toFixed(1)}s`);
lines.push(`Stopped because: **${run.stoppedBecause}**`);
lines.push(`Credit cost: ${run.creditCost}`);
lines.push(`Result: ${run.finalResultSummary ?? "none"}`);
if (typeof run.autoCycleCount === "number") {
lines.push(`Auto-cycle count: ${run.autoCycleCount}`);
}
if (run.resumeAttached) {
lines.push(`Resume attached: yes`);
}
if (run.errorMessage) lines.push(`Run error: ${run.errorMessage}`);
if (run.rawErrorMessage && run.rawErrorMessage !== run.errorMessage) {
lines.push(`Raw run error: ${run.rawErrorMessage}`);
}
lines.push("");
lines.push("## Completion Steps (LLM decisions)");
for (const step of run.completionSteps) {
lines.push(`### Step ${step.stepIndex + 1} — ${step.model || "?"} (${step.provider})`);
lines.push(`Duration: ${step.durationMs}ms`);
if (step.assistantReasoningText) {
lines.push("**Model reasoning before tool call:**");
lines.push(`> ${step.assistantReasoningText.replace(/\n/g, "\n> ")}`);
}
if (step.toolCalls?.length) {
lines.push("**Tool calls chosen:**");
for (const tc of step.toolCalls) {
lines.push(`- \`${tc.name}\`: \`${JSON.stringify(tc.input).slice(0, 200)}\``);
}
} else if (!step.errorMessage) {
lines.push("*No tool call — final response turn*");
}
if (step.errorMessage) {
lines.push(`**Error:** ${step.errorMessage}`);
}
if (step.rawErrorMessage && step.rawErrorMessage !== step.errorMessage) {
lines.push(`**Raw error:** ${step.rawErrorMessage}`);
}
if (step.messages?.length) {
lines.push(`<details><summary>Full message array (${step.messages.length} messages)</summary>`);
lines.push("");
lines.push("```json");
lines.push(JSON.stringify(step.messages, null, 2).slice(0, 8000));
lines.push("```");
lines.push("</details>");
}
lines.push("");
}
lines.push("## Tool Timeline");
for (const t of run.tools) {
const status = t.status === "ok" ? "✓" : t.status === "error" ? "✗" : "⚠";
const who = t.backend ? `backend: ${t.backend}` : t.provider ? `provider: ${t.provider}` : "unknown provider";
lines.push(`${status} **${t.tool}** — ${t.durationMs}ms — ${who}`);
if (t.resultSummary) lines.push(` Result: ${t.resultSummary}`);
if (t.rawResultSummary && t.rawResultSummary !== t.resultSummary) {
lines.push(` Raw: ${t.rawResultSummary}`);
}
if (t.creditCost !== undefined && t.creditCost > 0) lines.push(` Credits: ${t.creditCost}`);
if (t.input && Object.keys(t.input).length > 0) {
lines.push(` Input: \`${JSON.stringify(t.input).slice(0, 300)}\``);
}
}
lines.push("");
lines.push("---");
lines.push("*Job Scorer debug trace — paste into Claude for diagnosis*");
return lines.join("\n");
}