Concepts

The core ideas behind AgentReceipt. Understanding these will help you get the most out of the SDK and the dashboard.

Sessions

A session is one end-to-end agent run. One invoice processed, one ticket handled, one report generated. Sessions group events together into a single receipt. Name your sessions clearly. The name shows up in the dashboard and in the receipt summary.

When you use a wrapper like wrapOpenAI, the SDK creates a session automatically based on the sessionName you provide. If you need more control, use startSession to manage sessions manually.

Session status

Each session has a status that is computed automatically:

StatusMeaning
runningLast event was less than 5 minutes ago.
completeNo new events for 5 minutes and no errors.
errorAt least one event with type "error" exists.

Event limit

Each session can hold up to 200 events. If a session hits 200 events, the ingest endpoint stops accepting new events for that session and records a system error event. The receipt shows exactly where and why it stopped. If your agent runs need more than 200 events, start a new session at a logical boundary in your workflow.

Events

An event is a single action within a session. Every LLM call, tool execution, decision, and human review is recorded as an event. Events are immutable once written.

TypeWhen it fires
llm_callAny LLM completion, streaming or not.
tool_callAny function wrapped with trackTool.
decisionAny call to trackDecision.
human_reviewAny call to trackHumanReview.
errorAny error captured by the SDK or recorded by the system.

Each event stores the input, output, duration, and a SHA-256 hash. Raw input and output payloads are stored separately in Cloudflare R2, not in the main database. This keeps the event metadata small and queryable while allowing large payloads.

Receipts

A receipt is the human-readable view of a completed session. It shows a timeline of every event, a plain-English summary generated by AI, and a hash chain verification badge. Receipts are read-only. Nothing can be edited after the fact.

The summary is generated the first time someone views a receipt. It uses the event names and types (not the raw payloads) to produce a 3-5 sentence description of what the agent did. The summary is cached so it only needs to be generated once.

You can see what a receipt looks like on the demo page.

Hash chain

Every event stores a SHA-256 hash of its own data combined with the hash of the previous event. This creates a chain. If any event is modified after the fact, the chain breaks and the dashboard shows "Tampered" instead of "Verified".

The chain is verified every time someone loads a receipt. The verification checks each event's hash against a freshly computed hash of the same data plus the previous event's hash. If any hash does not match, the entire chain is marked as tampered.

This means even AgentReceipt itself cannot silently modify your data. Tampering is always detectable. If you need to prove to an auditor that your agent logs have not been altered, the hash chain verification badge is that proof.

The first event in a session has no previous hash. Its hash is computed from its own data only. Every subsequent event's hash depends on the event before it, forming the chain.

Compliance metadata

Any event can be tagged with a compliance object. This lets you flag events that contain sensitive data, categorize them, and set custom retention periods.

{
  containsPII?: boolean
  dataCategory?: string   // e.g. "health", "financial", "personal"
  retentionOverride?: number  // days
}

Events tagged with containsPII: true show a PII badge in the receipt. Your compliance team can filter for these events to review what sensitive data your agent handled.

The retentionOverride field sets a custom retention period for that event, overriding the project default. Use this for healthcare data (HIPAA requires 6 years) or any data with specific retention requirements.

compliance-example.ts
await session.trackTool(
  'fetch-patient-record',
  { patientId: 'P-12345' },
  async () => {
    return await ehr.getPatient('P-12345')
  },
  {
    compliance: {
      containsPII: true,
      dataCategory: 'health',
      retentionOverride: 2190  // 6 years in days
    }
  }
)