Guides

Conversation Evidence Packs, in ten minutes

Prove what was said, what kind of conversation it was, whether the capture was complete and which commitments Fidacy allowed or blocked. The transcript stays inside your infrastructure.

1. Install (proof works with no account)

npm i @fidacy/session

Hashing, digests, export and the tamper check run offline. Anchoring and the gate need a free API key (assess:write) from app.fidacy.com.

2. Wire the chain

import fs from "node:fs";
import { createSession } from "@fidacy/session";

const session = createSession({
  kind: "conversation",
  label: "case-4711",
  subject: "agent:support",
  evidence: {
    sessionId: "session-4711",
    conversationType: "insurance_claim",
    channel: "web_chat",
    caseRef: "case-4711",
    language: "en-US",
    jurisdiction: "US-FL",
  },
});

// your chat loop: one line per message (content never leaves your infra)
session.add("user", userMessage);
session.add("assistant", botReply);

Using the Vercel AI SDK? Zero lines per message instead:

import { wrapLanguageModel } from "ai";
import { createSession, sessionMiddleware } from "@fidacy/session";

const session = createSession({
  kind: "conversation",
  label: ticketId,
  subject: "agent:support",
  evidence: {
    sessionId: ticketId,
    conversationType: "customer_support",
    channel: "web_chat",
    caseRef: ticketId,
  },
});
const model = wrapLanguageModel({
  model: openai("gpt-5"),
  middleware: sessionMiddleware(session), // every prompt + reply lands in the chain
});

What the pack binds

The manifest binds the transcript digest to a session ID, conversation type, channel, case reference, language, jurisdiction, start and end, role counts, completeness and commitment decisions. These are controlled fields and identifiers, not conversation text or customer data.

3. Gate what the bot can commit to

const verdict = await session.gate(mandate, {
  apiKey: process.env.FIDACY_ENGINE_API_KEY!,
  kind: "message_send",
});
if (!verdict.allowed) refusePolitely(); // deny-by-default: review is NOT a yes

The verdict is recorded into the chain, so the anchored receipt itself proves the gate ran. On any engine error the call throws and your bot refuses: fail closed, never fail open.

Mandate gallery

The mandate is a plain object describing the commitment. Common shapes:

Refund (kind message_send):

{ type: "refund", amount: 900, currency: "USD", case: "case-4711" }

Quote above a threshold (kind message_send):

{ type: "quote", amount: 12500, currency: "EUR", product: "policy-home", case: "q-2231" }

Claim approval (kind claim_document):

{ type: "claim_approval", amount: 4300, currency: "USD", claim: "clm-8891" }

Document send (kind message_send):

{ type: "document_send", document: "policy-terms-v3.pdf", recipient: "customer", case: "case-4711" }

4. Close with the evidence pack

const pack = await session.anchorEvidence({
  apiKey: process.env.FIDACY_ENGINE_API_KEY!,
  status: "complete",
  reason: "normal_close",
});

await fs.promises.writeFile("case-4711.evidence.json", JSON.stringify(pack, null, 2));
customerLink = session.verifyUrl(); // verifies the full manifest digest

Your customer verifies at fidacy.com/verify with no account: paste the pack or its link. The browser recomputes the transcript chain, the structured context and the signed-receipt binding. Nothing uploads. Changing one message, the case type or the completeness breaks the match before any anchor lookup is attempted.

Honest scope. The @fidacy/session SDK hashes transcript content locally. A complete pack proves that this exported transcript and its declared context were sealed together. It does not by itself prove that every conversation in a deployment was captured. Use Control Coverage to disclose the live observing boundary and gaps.