recalled.dev
Core concepts

Agent audit

Recalled is built for human and AI agent actions, side by side. In 2026, half the actions in a typical SaaS are taken by AI agents (Claude, GPT, custom agents wired with tool calls), and the same audit log records them with the same hash chain, the same signatures, the same dashboard. Two small conventions make it the system of record for what your agents did, when, and on whose behalf.

The pattern

The agent itself does not call Recalled. Your backend orchestrates the agent, runs the tool calls, and logs the resulting actions. From Recalled's point of view, an agent is just an actor with a different actor.type.

Two conventions:

  1. actor.type is set to "agent" (or "ai_agent", pick one and stick with it).
  2. metadata.triggered_by_user carries the human user id who started the conversation that led to this action.
ts
client.events.emit({
  action: "file.deleted",
  actor: {
    type: "agent",
    id: "claude-sonnet-4.6",
    name: "Support Triage Agent",
  },
  organization: "acme_corp",
  targets: [{ type: "file", id: "f_42", name: "old-report.pdf" }],
  metadata: {
    triggered_by_user: "user_123",
    conversation_id: "conv_xyz",
    tool_call_id: "call_abc",
    reasoning: "user asked to clean up old uploads",
    confidence: "high",
    model: "claude-sonnet-4.6",
    tokens_used: 1240,
    result: "success",
  },
});

Three events per tool call

For full traceability, log three events around each agent tool call:

ts
// 1. The agent decided to call a tool
client.events.emit({
  action: "agent.tool_called",
  actor: { type: "agent", id: "claude-sonnet-4.6", name: "Support Agent" },
  targets: [{ type: "tool", id: "delete_file" }],
  metadata: {
    triggered_by_user: "user_123",
    conversation_id: "conv_xyz",
    tool_args: { file_id: "f_42" },
  },
});

// 2. The action itself (same as a human action)
client.events.emit({
  action: "file.deleted",
  actor: { type: "agent", id: "claude-sonnet-4.6", name: "Support Agent" },
  targets: [{ type: "file", id: "f_42" }],
  metadata: { triggered_by_user: "user_123", on_behalf_of: "user_123" },
});

// 3. The tool returned to the agent
client.events.emit({
  action: "agent.tool_returned",
  actor: { type: "agent", id: "claude-sonnet-4.6", name: "Support Agent" },
  targets: [{ type: "tool_call", id: "call_abc" }],
  metadata: {
    triggered_by_user: "user_123",
    result: "success",
    duration_ms: 142,
  },
});

If the cardinality is a problem at scale, drop the tool_called and tool_returned events and keep only the action itself with rich metadata. The action event alone is enough for accountability.

Filtering: humans vs agents

Once actor.type=agent is set consistently, you can filter the dashboard or the API to see only agent activity, only human activity, or everything for one user:

  • All agent activity this month: ?actor_type=agent (filter by metadata if you need it; today the API filters by actor_id)
  • All actions a specific human triggered, including those carried out by agents on their behalf: filter by metadata.triggered_by_user (planned, today match by hand using list_events and post-filter)
  • Audit trail for a specific agent: ?actor_id=claude-sonnet-4.6

Receipts: replayable evidence

When an agent says "I deleted that file for you", you want it to back the claim with proof. Recalled issues a receipt for any event:

bash
curl https://api.recalled.dev/v1/events/$EVENT_ID/receipt \
  -H "Authorization: Bearer rec_live_..."

You get a JSON object the agent can paste back into its reply, with a view_url (public webpage) and a verification_url (no-auth JSON endpoint). The recipient can verify cryptographically that the event happened, in what order, and untampered, without an API key.

Inside Claude Desktop, Cursor or any MCP client connected to Recalled, the get_event_receipt tool returns the same object so the agent can cite it without going through HTTP itself.

What auditors actually want to see

If a customer or auditor questions an agent action three months later, here is what makes it bulletproof:

  1. The action itself is in the audit log with actor.type=agent.
  2. The receipt verifies green: hash chain intact, HMAC signature valid.
  3. metadata.triggered_by_user ties the action to the human who initiated it.
  4. metadata.reasoning, metadata.confidence, metadata.model give context.
  5. metadata.conversation_id lets you pull the whole conversation if needed.

Public verification page

Every receipt has a public URL like https://recalled.dev/receipts/<event-id>. The page strips actor PII and metadata (the public viewer never sees who or what), and shows only:

  • The action name
  • The actor type (agent, user, service, etc.)
  • The timestamp
  • The hashes and signature
  • A green or red banner whether the cryptographic proof verifies

Hand this URL out instead of dashboard access. Anyone can confirm an event existed without accessing your project.

Privacy

The public receipt page never exposes:

  • Actor name, email, id, IP, user agent
  • Event metadata
  • Other events in the chain

It only proves that the specific event id, with that exact action verb and timestamp, was recorded by Recalled and has not been tampered with.