Docs navigation

Claude agent

A Claude tool-use loop with ProofLedger woven through it: the run is traced, every tool_use block is logged as a signed event, and risky tools are policy-checked before execution.

TypeScript

import Anthropic from "@anthropic-ai/sdk";
import { ProofLedger, PolicyBlockedError } from "@proofledger/sdk";

ProofLedger.enable({
  apiKey: process.env.PROOFLEDGER_API_KEY,
  agentName: "claude-support-agent",
  owner: "support",
  framework: "claude-code",
  model: "claude-opus-4-8",
  policyMode: "enforce",
});
await ProofLedger.registerAgent();

const anthropic = new Anthropic();
const workflowId = "wf_ticket_4821";

await ProofLedger.logWorkflowStep({
  workflowId, eventType: "task_received",
  action: "Resolve ticket #4821",
});

let messages = [{ role: "user" as const, content: "Refund order ORD-123" }];

for (;;) {
  const response = await anthropic.messages.create({
    model: "claude-opus-4-8",
    max_tokens: 1024,
    tools: TOOLS,
    messages,
  });

  const toolUse = response.content.find((b) => b.type === "tool_use");
  if (!toolUse) break; // final answer

  // Every tool call goes through ProofLedger BEFORE it runs.
  // In enforce mode a blocked tool throws — the loop reports and stops.
  try {
    await ProofLedger.logToolCall({
      workflowId,
      toolName: toolUse.name,
      inputSummary: JSON.stringify(toolUse.input).slice(0, 500),
      sensitiveAction: toolUse.name === "issue_refund",
    });
  } catch (err) {
    if (err instanceof PolicyBlockedError) {
      await ProofLedger.logWorkflowStep({
        workflowId, status: "failed",
        action: `Stopped: ${err.evaluation.reason}`,
      });
      throw err;
    }
    throw err;
  }

  const result = await runTool(toolUse.name, toolUse.input);
  messages = [
    ...messages,
    { role: "assistant" as const, content: response.content },
    {
      role: "user" as const,
      content: [{ type: "tool_result" as const, tool_use_id: toolUse.id,
                  content: JSON.stringify(result) }],
    },
  ];
}

await ProofLedger.logWorkflowStep({
  workflowId, status: "completed", action: "Ticket resolved",
});
await ProofLedger.flush();

Note

sensitiveAction: true on the refund tool routes it through require_approval — in enforce mode the loop pauses right there until a human approves it in the dashboard.