matrix

LangChain example

A complete agent: a real model, a real tool call, traced end to end. Two files, no build step.

Run it

Save both files below into an empty folder, then:

npm install

export MATRIX_API_KEY=sk_...        # from matrixverify.dev, shown once
export ANTHROPIC_API_KEY=sk-ant-...
export AGENT_EMAIL=you@yourdomain.com

node agent.mjs

On Windows PowerShell, set each variable with $env:MATRIX_API_KEY="sk_..." instead of export.

What happens

The agent is asked to email an invoice. It calls send_email, the tool reports success, and the agent tells you it sent it. The tool in this example sends nothing — it returns success without doing anything, which is the failure Matrix exists to catch. Swap in your real send when you are done looking.

The trace — instruction, tool call with its arguments, and the agent’s claim — appears on your findings page within about ten minutes. Whether the claim can be checked against a mailbox depends on one thing: during early access, connecting your mailbox is done by hand. Until it is, an email claim backed by a send call comes back inconclusive, not guessed. Get in touch to connect yours.

package.json

{
  "name": "matrix-langchain-example",
  "private": true,
  "type": "module",
  "description": "A LangChain agent traced by Matrix — npm install && node agent.mjs",
  "scripts": {
    "start": "node agent.mjs"
  },
  "engines": {
    "node": ">=20"
  },
  "dependencies": {
    "@langchain/anthropic": "^1.5.10",
    "@langchain/core": "^1.2.11",
    "@matrixverify/verify": "^0.1.0",
    "langchain": "^1.5.11",
    "zod": "^4.5.4"
  }
}

Download package.json

agent.mjs

/**
 * A LangChain agent traced by Matrix.
 *
 *   npm install
 *   MATRIX_API_KEY=sk_... ANTHROPIC_API_KEY=sk-ant-... AGENT_EMAIL=you@yourdomain.com node agent.mjs
 *
 * The agent is asked to email an invoice. It calls send_email, the tool reports
 * success, and the agent says it sent it. The trace — what it was asked, what
 * it called with which arguments, and what it claimed — lands on your findings
 * page within about ten minutes.
 *
 * THE send_email TOOL BELOW SENDS NOTHING. It returns success without doing
 * anything, which is the failure Matrix exists to catch. Replace it with your
 * real send once you have seen a trace arrive.
 */
import { ChatAnthropic } from "@langchain/anthropic";
import { createAgent, tool } from "langchain";
import { z } from "zod";
import { verify } from "@matrixverify/verify";
import { verifyCallbackHandler } from "@matrixverify/verify/langchain";

// The SDK is fail-silent by design — a missing key would look like success —
// so this example checks up front and says so.
for (const name of ["MATRIX_API_KEY", "ANTHROPIC_API_KEY"]) {
  if (!process.env[name]) {
    console.error(`Set ${name} first. See the comment at the top of this file.`);
    process.exit(1);
  }
}

verify.init({
  apiKey: process.env.MATRIX_API_KEY,
  // Explicit, so this works on @matrixverify/verify 0.1.0 as well, where the
  // default pointed at localhost.
  endpoint: process.env.MATRIX_ENDPOINT ?? "https://matrixverify.dev/api/traces",
});

const sendEmail = tool(
  async ({ to, subject }) => {
    // Replace with your real send. This one does nothing and reports success.
    return JSON.stringify({ status: "sent", to, subject });
  },
  {
    // Matrix matches tool names like send_email, gmail.send_email, email.send.
    // A tool named something else is reported as a setup problem.
    name: "send_email",
    description: "Send an email to one recipient.",
    schema: z.object({
      to: z.string().describe("Recipient email address"),
      subject: z.string(),
      body: z.string(),
    }),
  }
);

// --- model ---
const model = new ChatAnthropic({ model: "claude-opus-5", maxTokens: 16000 });
// --- end model ---

const agent = createAgent({
  model,
  tools: [sendEmail],
  // A plain one-sentence report is what Matrix reads as the claim. Phrasing
  // like "Emailed the August invoice to dana@…" is recognised; a vaguer
  // "all done!" gives it nothing to verify.
  systemPrompt:
    "You are an operations assistant. Use your tools to do what is asked. " +
    "When you are done, reply with one plain sentence stating exactly what you " +
    "did, including the recipient's email address.",
});

const handler = verifyCallbackHandler({
  // Callbacks cannot discover which account acted. Without this, every email
  // claim comes back account_unverified.
  fromResolver: () => process.env.AGENT_EMAIL ?? null,
});

const to = process.argv[2] ?? "dana@northwind.example";
const instruction = `Email the August invoice to ${to}.`;
console.log(`asked:      ${instruction}`);

try {
  const result = await agent.invoke(
    { messages: [{ role: "user", content: instruction }] },
    { callbacks: [handler] }
  );
  const last = result.messages.at(-1);
  const said =
    typeof last?.content === "string"
      ? last.content
      : (last?.content ?? [])
          .filter((block) => block.type === "text")
          .map((block) => block.text)
          .join("\n");
  console.log(`agent said: ${said}`);
} finally {
  // Flushes the buffer. Nothing is sent without it.
  await verify.shutdown();
}

console.log("\nTrace sent. It appears at https://matrixverify.dev within about ten minutes.");

Download agent.mjs