matrix

Python example

A complete agent: a real model, a real tool call, traced end to end. Two files, no build step. The TypeScript version is at /examples/langchain.

Run it

Save both files below into an empty folder, then:

pip install -r requirements.txt

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

python agent.py

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

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 half an hour. Traces from the Python and TypeScript SDKs are the same on the wire; the verifier cannot tell which one sent them.

requirements.txt

matrix-verify>=0.1.0
langchain>=1.0
langchain-anthropic>=1.0

Download requirements.txt

agent.py

"""A LangChain agent traced by Matrix.

    pip install -r requirements.txt

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

    python agent.py

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 half an hour.

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.

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

import json
import os
import sys

from langchain_anthropic import ChatAnthropic
from langchain.agents import create_agent
from langchain_core.tools import tool

from matrix_verify import verify
from matrix_verify.langchain import verify_callback_handler

# The SDK is fail-silent by design — a missing key would look like success —
# so this example checks up front and says so.
for name in ("MATRIX_API_KEY", "ANTHROPIC_API_KEY"):
    if not os.environ.get(name):
        print(f"Set {name} first. See the comment at the top of this file.")
        sys.exit(1)

verify.init(
    api_key=os.environ["MATRIX_API_KEY"],
    # Explicit only because self-hosters need it; the default is the same.
    endpoint=os.environ.get("MATRIX_ENDPOINT", "https://matrixverify.dev/api/traces"),
)


@tool
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email to one recipient."""
    # Replace with your real send. This one does nothing and reports success.
    return json.dumps({"status": "sent", "to": to, "subject": subject})


model = ChatAnthropic(model="claude-opus-5", max_tokens=16000)

agent = create_agent(
    model,
    tools=[send_email],
    # 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.
    system_prompt=(
        "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."
    ),
)

handler = verify_callback_handler(
    # Callbacks cannot discover which account acted. Without this, every email
    # claim comes back account_unverified.
    from_resolver=lambda tool_name: os.environ.get("AGENT_EMAIL"),
)

to = sys.argv[1] if len(sys.argv) > 1 else "dana@northwind.example"
instruction = f"Email the August invoice to {to}."
print(f"asked:      {instruction}")

try:
    result = agent.invoke(
        {"messages": [{"role": "user", "content": instruction}]},
        config={"callbacks": [handler]},
    )
    last = result["messages"][-1]
    said = last.content if isinstance(last.content, str) else "\n".join(
        block.get("text", "") for block in last.content if isinstance(block, dict)
    )
    print(f"agent said: {said}")
finally:
    # Flushes the buffer. Nothing is sent without it.
    verify.shutdown()

print("\nTrace sent. It appears at https://matrixverify.dev within about half an hour.")

Download agent.py