Etch

Integrations

Etch is framework-agnostic. If your agent calls Anthropic or OpenAI, uses LangChain / LangGraph / CrewAI / AutoGen / LlamaIndex, speaks MCP, or emits OpenTelemetry gen_ai.* traces, you can audit it with one of the four ingestion layers below. All four converge on the same authenticated audit chain, and all four surface in the same Sessions view.

Install

pip install etch-client

The etch-client package bundles all four integration layers. Import only what you use. Currently in beta, ping the operator for a pre-release wheel.

Layer 1. LLM-provider wrappers

Drop-in replacements for the Anthropic and OpenAI Python SDKs. Every messages.create or chat.completions.create records to Etch. Works with LangGraph, CrewAI, AutoGen, and every other framework that uses these SDKs under the hood, because the wrapping happens at the SDK layer.

Anthropic

from etch.integrations.anthropic_client import EtchAnthropic

client = EtchAnthropic(
    api_key="sk-ant-...",
    etch_app_token="ep_live_...",
    etch_project_id="my-project",
)

response = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=1024,
    messages=[{"role": "user", "content": "hi"}],
)

OpenAI + any OpenAI-compatible provider

Any provider that speaks the OpenAI chat-completions API works via EtchOpenAI. Pass provider_label so the dashboard attributes each call to the right upstream instead of showing everything as openai:....

from etch.integrations.openai_client import EtchOpenAI

# OpenAI
client = EtchOpenAI(
    api_key="sk-...",
    etch_app_token="ep_live_...", etch_project_id="my-project",
)

# Groq
client = EtchOpenAI(
    api_key="gsk_...",
    openai_kwargs={"base_url": "https://api.groq.com/openai/v1"},
    provider_label="groq",
    etch_app_token="ep_live_...", etch_project_id="my-project",
)

# OpenRouter
client = EtchOpenAI(
    api_key="sk-or-...",
    openai_kwargs={"base_url": "https://openrouter.ai/api/v1"},
    provider_label="openrouter",
    etch_app_token="ep_live_...", etch_project_id="my-project",
)

# HuggingFace Inference Providers (router)
client = EtchOpenAI(
    api_key="hf_...",
    openai_kwargs={"base_url": "https://router.huggingface.co/v1"},
    provider_label="hf",  # or "hf-together" / "hf-fireworks" / etc.
    etch_app_token="ep_live_...", etch_project_id="my-project",
)

# Perplexity, Fireworks, Cerebras, together.ai, DeepSeek direct, etc.
# same shape: set base_url + provider_label.

What gets recorded per call: model, input message count, input_hash, output_hash, input/output tokens, stop_reason, tool_calls_seen. Full prompt content is NOT sent to Etch by default, only hashes. Opt in with persist_full_content=True.

What the dashboard shows: per-provider filtering via the Models column on the Sessions list. A call routed through Groq shows as groq:llama-3.3-70b; through OpenRouter as openrouter:anthropic/claude-4.7; through the HF router as hf-together:meta-llama/Llama-3.3-70B, etc.

Layer 2. OpenTelemetry receiver

Every gen_ai.* span becomes an event. Covers LangChain, LangGraph, LlamaIndex, Semantic Kernel, PydanticAI, Griptape, Bedrock anything emitting the OTel GenAI semantic conventions. Two integration shapes:

Hosted (recommended), point your collector at etch.systems

# In your OTel collector config (OTLP/HTTP exporter):
exporters:
  otlphttp/etch:
    endpoint: https://etch.systems/projects/my-project/otel/v1/traces
    headers:
      authorization: Bearer ep_live_...
    encoding: json

Or point your app's OTel SDK directly at the same URL. No sidecar required. Etch parses the OTLP/HTTP JSON payload and records every span.

Self-hosted, run the receiver in your infra

from starlette.applications import Starlette
from etch.integrations.otel_receiver import mount_otel_starlette

app = Starlette()
mount_otel_starlette(
    app,
    etch_app_token="ep_live_...",
    etch_project_id="my-project",
)
# Point your collector at http://your-host/otel/v1/traces

Status: OTLP/HTTP JSON path shipping today. Full OTLP-protobuf ingest is on the roadmap.

Layer 3. MCP proxy

Sit between an agent and any upstream MCP server(s). Every tools/call is recorded, then forwarded transparently. Works with Claude Code, Cline, Cursor, and any MCP-native client.

Hosted, point your agent's MCP config at etch.systems

# In your agent's MCP client config:
{
  "mcpServers": {
    "filesystem-audited": {
      "url": "https://etch.systems/projects/my-project/mcp-proxy",
      "headers": {
        "Authorization": "Bearer ep_live_...",
        "X-Etch-MCP-Upstream": "https://your-filesystem-mcp.internal",
        "X-Etch-MCP-Upstream-Name": "filesystem",
        "X-Etch-MCP-Upstream-Authorization": "Bearer <upstream-token>"
      }
    }
  }
}

Every tools/call is transparently forwarded to the URL in the upstream header. Etch records the call name, argument hashes, and result status, then returns the upstream's response byte-for-byte.

Self-hosted, in-process middleware

from etch.integrations.mcp_proxy import EtchMCPProxy

proxy = EtchMCPProxy(
    upstream_base_url="http://your-filesystem-mcp:8000",
    upstream_name="filesystem",
    etch_app_token="ep_live_...",
    etch_project_id="my-project",
)

# Point your agent at proxy.forward_tools_call(...) OR
# use mount_proxy_starlette() to expose an HTTP endpoint.

Layer 4, framework SDK shims

Thin adapters for the biggest agent frameworks. Use these when you want finer-grained per-node / per-tool observability than the LLM proxy provides.

LangChain

from etch.integrations.langchain_callback import EtchLangChainCallback

cb = EtchLangChainCallback(
    etch_app_token="ep_live_...",
    etch_project_id="my-project",
)

chain.invoke({"question": "..."}, config={"callbacks": [cb]})

LangGraph

from etch.integrations.base import EtchRecorder, RecorderConfig
from etch.integrations.langgraph_shim import etch_trace

recorder = EtchRecorder(RecorderConfig(
    app_token="ep_live_...",
    project_id="my-project",
))

@etch_trace(recorder, node_name="planner",
            session_id_fn=lambda state: state.get("run_id"))
def planner_node(state):
    return {"plan": "..."}

CrewAI

CrewAI routes LLM calls through LiteLLM to the underlying provider SDK. Wrap the provider client once and every agent's calls flow through Etch.

from etch.integrations.openai_client import EtchOpenAI
from crewai import Agent, Task, Crew, LLM

# Wrap the provider SDK. CrewAI-compatible LLM object.
etch_client = EtchOpenAI(
    api_key="sk-...",
    etch_app_token="ep_live_...",
    etch_project_id="my-project",
    provider_label="openai",
)
llm = LLM(model="gpt-4o", client=etch_client)

researcher = Agent(role="Researcher", goal="...", llm=llm)
crew = Crew(agents=[researcher], tasks=[Task(description="...", agent=researcher)])
crew.kickoff()

AutoGen

AutoGen accepts a raw OpenAIWrapper-compatible client via llm_config. The Etch wrapper is a drop-in replacement.

from etch.integrations.openai_client import EtchOpenAI
from autogen import AssistantAgent, UserProxyAgent

etch_client = EtchOpenAI(
    api_key="sk-...",
    etch_app_token="ep_live_...",
    etch_project_id="my-project",
    provider_label="openai",
)

assistant = AssistantAgent(
    name="assistant",
    llm_config={"model": "gpt-4o", "client": etch_client},
)
user = UserProxyAgent(name="user", human_input_mode="NEVER")
user.initiate_chat(assistant, message="...")

LlamaIndex

LlamaIndex uses provider-specific LLM classes. Pass the Etch-wrapped provider client into the constructor for full audit coverage.

from etch.integrations.anthropic_client import EtchAnthropic
from llama_index.llms.anthropic import Anthropic
from llama_index.core import VectorStoreIndex, Document

etch_client = EtchAnthropic(
    api_key="sk-ant-...",
    etch_app_token="ep_live_...",
    etch_project_id="my-project",
)

# LlamaIndex's Anthropic LLM accepts the raw client.
llm = Anthropic(model="claude-opus-4-7", client=etch_client)

index = VectorStoreIndex.from_documents([Document(text="...")])
query_engine = index.as_query_engine(llm=llm)
query_engine.query("...")

Alternative: LlamaIndex also emits OpenTelemetry gen_ai.* spans via its OpenInference tracer — see Layer 2 above.

Semantic Kernel, PydanticAI, Griptape, Bedrock Agents, Vertex AI

All emit OpenTelemetry gen_ai.* semantic-convention spans natively. Point their OTel exporter at Etch (Layer 2 above) and every request lands in the same audit chain. No per-framework wrapper needed.

Prefer a one-command install for AI coding agents?

If your workflow is agent-first (Claude Code, Cursor, Cline, Codex, etc.) rather than framework-first, skip the layers above and use the per-adapter install pages at etch.systems/docs/install. Each of the 12 supported coding agents has a stable install page with a copy-paste command.

Which layer for which framework?

FrameworkRecommended layerWhy
Custom Anthropic / OpenAI SDKLayer 1Direct wrapping, zero framework dependency.
LangChainLayer 4 callbackPer-node visibility. Layer 1 also works.
LangGraphLayer 4 @etch_tracePer-node inputs/outputs.
CrewAILayer 1CrewAI uses LiteLLM → provider SDK.
AutoGenLayer 1Same reason.
LlamaIndexLayer 2 OTelShips gen_ai.* spans natively.
Semantic Kernel, PydanticAI, GriptapeLayer 2 OTelAll emit OTel gen_ai.* semantic conventions.
Claude Code, Cline, CursorLayer 3 MCP proxyMCP-native. Layer 1 also works for the LLM step.
Bedrock AgentsLayer 2 OTelBedrock emits gen_ai.* traces to CloudWatch/OTel.
GroqLayer 1 (provider_label="groq")OpenAI-compat API.
OpenRouterLayer 1 (provider_label="openrouter")OpenAI-compat API.
HuggingFace Inference ProvidersLayer 1 (provider_label="hf")OpenAI-compat router; also per-provider (hf-together, hf-fireworks, etc.).
Perplexity / Fireworks / Cerebras / together.ai / DeepSeekLayer 1 (provider_label="<label>")All expose OpenAI-compat endpoints.

The privacy default

Every layer defaults to hash-only recording: Etch never sees your prompts, tool arguments, or response content, only cryptographic fingerprints. That means your PII stays in your infrastructure, and the audit trail proves what you did without exposing what was in the data.

Opt in to full-content persistence per layer via persist_full_content=True. Use case: regulator-facing audit archives where the content itself must be inspectable later.

What you see afterwards

Every event lands in the Sessions tab of your project's dashboard, grouped by session_id. One click on a session shows the chronological reconstruction, every LLM call, every tool call, every OTel span, in the order it actually happened.

Support

Email support@etch.systems. First-integration check-ins are on the house.