Docs / Python SDK
Python SDK reference.
Published to PyPI as mesedi. Source at mesedi-ai/mesedi/sdk-python.
Importing mesedi never requires any framework (LangChain, LangGraph, OpenAI Agents, CrewAI, Anthropic) to be installed. Framework integrations live behind optional extras and are tree-shaken out of the base install.
Install
pip install mesediconfigure(api_key=...)
Sets up the module-level client. Call once at process start. For local backend development against localhost:8080, pass an explicit base_url=. Otherwise the SDK posts to the Mesedi production backend.
import os
import mesedi
mesedi.configure(api_key=os.environ["MESEDI_API_KEY"])
# Or, for local dev:
# mesedi.configure(api_key="mesedi_sk_dev_local_only",
# base_url="http://localhost:8080")
@mesedi.wrap
Decorate any function as an agent execution. The SDK records start, completion (or crash), wall-clock duration, and a stable crash signature so identical exceptions cluster into a single failure group instead of paging you for every retry.
@mesedi.wrap
def run_my_agent(query: str) -> str:
# your agent logic
return "answer"
run_my_agent("hello")
For each @wrap-decorated call:
- On entry:
POST /executionswithstatus="started". - On normal return:
PATCH /executions/{id}withstatus="completed"andduration_ms. - On exception:
PATCHwithstatus="crashed"and acrash_signature(SHA-256-derived stable hash of exception type plus top of traceback). The original exception is then re-raised; @wrap does not swallow.
Network failures during observation never block the wrapped function. The SDK is fail-open: a Mesedi outage degrades to invisibility, not to broken production code.
Since 0.7.0, @wrap also accepts execution_mode. When set, the SDK emits an environment_declaration event as the first event of the run, stating what environment the run is SUPPOSED to be in. Well-known modes are "live", "simulation" and "staging"; the environment_misapprehension detector fires when a run declared not-live reaches a live-looking destination.
@mesedi.wrap(execution_mode="simulation")
def run_eval_harness(case: str) -> str:
...
@mesedi.tool
Decorate any function as an observed tool call. Emits a tool_call event into the surrounding execution context, with start time, end time, success or failure, and the exception class on failure. The tool-failures detector consumes these events to flag repeated failures of the same tool inside one execution.
@mesedi.tool
def fetch_user(user_id: str) -> dict:
return db.query(...)
@mesedi.wrap
def run_my_agent(query: str) -> str:
user = fetch_user("u_123") # emits tool_call event
return summarize(user)
Egress and environment declarations (0.7.0)
Two emitters added for the egress-visibility detectors. emit_egress records one outbound network contact by the agent's environment; the covert_coordination and environment_misapprehension detectors read these events. emit_environment_declaration states the run's intended mode; use it directly, or let @wrap(execution_mode=...) emit it for you.
from mesedi import emit_egress, emit_environment_declaration
emit_environment_declaration("simulation") # or "live", "staging"
# report an outbound contact from wherever the connection opens
emit_egress(
"https://api.example-corp.com/v2/users?id=42",
protocol="https",
tool_name="http_get",
bytes_out=412,
)
The destination is normalized to host or host:port before anything leaves your process: scheme, path, query string, and any credentials embedded in the URL are stripped client-side, so a secret in a URL never reaches Mesedi. Both emitters are no-ops outside an execution context, same as every other emitter.
Hard-halt (local budgets + SSE remote channel)
Optional. Cap a single execution across four axes: input tokens, output tokens, wall-clock seconds, and step count (tool calls + LLM calls + explicit checkpoint()s). Pass any subset: unset fields impose no limit on that axis. When any budget is exceeded, the SDK raises mesedi.MesediHalt at the next safe boundary (between LLM calls, tool calls, or checkpoint()s, never mid-call) so your try/finally cleanup runs and open resources release. MesediHalt inherits BaseException, not Exception, so broad except Exception handlers do not swallow it.
from mesedi import wrap, Budget
@wrap(budget=Budget(
max_wall_clock_seconds=600, # 10 min real time
max_steps=30, # 30 tool/LLM/checkpoint boundaries
max_tokens_in=200_000,
max_tokens_out=50_000,
))
def run_my_agent(query: str) -> str:
...
When a budget is supplied, the SDK also opens an SSE subscription to GET /executions/{id}/halt-stream. When an operator clicks Halt on the dashboard, the reader thread signals the BudgetTracker and the next safe-boundary check raises MesediHalt(trigger="remote_signal"). Mesedi never decides to halt on its own: operator intent (or local budget exhaustion) is always the trigger.
Fail-open by design. If the SSE subscription fails (Mesedi backend unreachable, 4xx/5xx response, network partition) the reader logs at DEBUG and returns. The wrapped agent keeps running with local budgets still enforced client-side. A Mesedi outage cannot break your production agent.
Anthropic auto-instrumentation
If your agent calls the Anthropic Python SDK directly, Mesedi can patch the client at import time so every messages.create() emits an llm_call event without any other code change. Cost-velocity and drift detectors light up the moment Anthropic calls start flowing.
import mesedi
mesedi.instrument_anthropic()
import anthropic
client = anthropic.Anthropic()
@mesedi.wrap
def run_my_agent(prompt: str) -> str:
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].text
Ollama auto-instrumentation (local runtime)
If your agent calls Ollama for local-runtime inference, mesedi.instrument_ollama() patches the native ollama client so every Client.chat() emits an llm_call event with provider="ollama". Both sync (ollama.Client) and async (ollama.AsyncClient) classes are patched. No real Ollama server is needed for Mesedi to instrument the client, patching happens at import time.
import mesedi
mesedi.instrument_ollama()
from ollama import Client
client = Client(host="http://localhost:11434")
@mesedi.wrap
def run_my_agent(prompt: str) -> str:
response = client.chat(
model="llama3.1:8b",
messages=[{"role": "user", "content": prompt}],
)
return response["message"]["content"]
Local runtime means a few detectors are architecturally inapplicable: provider_incident, infrastructure_throttled, and cost_velocity have no upstream signal to monitor. The dashboard renders a Detectors not applicable chip row explaining this for Ollama-only projects. Every other detector (data_leakage, prompt_injection, context_overflow, semantic_loop, etc.) runs on Ollama payloads identically to commercial-provider payloads.
Cost model: Ollama models price at $0/token in the canonical pricing table: local-runtime is the honest answer. For customers wanting GPU/electricity amortization or running fine-tuned variants, set per-project rates via the Custom Model Pricing card on the Settings page. Per-project overrides win over the canonical table for exact-name matches.
Context windows: The major Ollama families (llama3+, qwen2+, deepseek-r1/v3/coder, gemma2+, phi3+, codellama) ship with conservative upper-end-of-family defaults. Customers running smaller variants override per-project via the Context Overflow → custom_model_windows knob.
Streaming: Calls with stream=True currently emit no llm_call event and log a one-time INFO message explaining the omission. The chunk-aggregating wrapper that observes streaming Ollama calls ships in a follow-up sub-wave. Non- streaming calls are unaffected.
LangChain adapter
Install the optional extra:
pip install mesedi[langchain]Pass the callback handler to any LangChain invocation:
import mesedi
from mesedi.integrations.langchain import MesediCallbackHandler
@mesedi.wrap
def run_agent(question: str) -> str:
chain = build_chain()
result = chain.invoke(
{"input": question},
config={"callbacks": [MesediCallbackHandler()]},
)
return result["output"]
The handler subscribes to LangChain's on_llm_start / on_llm_end / on_tool_start / on_tool_end hooks and emits llm_call and tool_call events with the same wire format as a hand-written @mesedi.tool pair. Detectors see no difference between the two paths.
LangGraph adapter
LangGraph builds on langchain-core, so the LangGraph handler subclasses the LangChain one and adds node-and-handoff translation on top. Install the LangChain extra alongside LangGraph itself; there is no separate mesedi[langgraph] extra.
pip install mesedi[langchain] langgraphOne call on the compiled graph wires everything up:
import mesedi
from mesedi.integrations.langgraph import instrument_langgraph
graph = build_my_graph() # a CompiledStateGraph
graph = instrument_langgraph(graph) # patched in place
@mesedi.wrap
def run_agent(question: str) -> str:
result = graph.invoke({"question": question})
return result["answer"]
instrument_langgraph patches invoke, ainvoke, stream and astream to inject the handler into the callback config, non-destructively, so any callbacks you already pass keep working. It returns the same graph object, so re-assigning in place is the intended usage. Outside a @mesedi.wrap context the handler no-ops, which makes it safe to instrument once at module load.
On top of llm_call and tool_call, it emits a checkpoint at every node entry with the node name and a hash of the canonical state, which is what lets the semantic_loop detector catch a graph revisiting the same logical state, plus an agent_handoff when the graph invokes a compiled sub-graph.
Not covered yet: async streaming hooks (astream_events), LangGraph's Checkpointer persistence layer, which Mesedi emits parallel to rather than reading, and interrupt(), where you bridge to mesedi.pause_for_human yourself.
OpenAI Agents SDK adapter
The OpenAI Agents SDK exposes a RunHooks interface. Mesedi implements it. There is no Mesedi extra to install; you need the SDK itself.
pip install mesedi openai-agentsimport mesedi
from agents import Agent, Runner
from mesedi.integrations.openai_agents import MesediRunHooks
@mesedi.wrap
async def run_user_request(question: str) -> str:
result = await Runner.run(
triage_agent,
question,
hooks=MesediRunHooks(),
)
return result.final_output
Emits a checkpoint on every agent start and end, an agent_handoff on every transfer between agents, and a tool_call per tool invocation. That covers the topology, handoff and tool detectors.
One limitation worth knowing before you adopt it: the hooks do not emit llm_call events, because the Agents SDK dispatches model calls through its own runner and does not expose a per-call hook. So the drift, identical-call, similar-call and cost-velocity detectors, which all read llm_call, do not fire on an OpenAI-Agents-only deployment. For Anthropic-backed runs, adding mesedi.instrument_anthropic() restores that surface.
CrewAI adapter
Install the optional extra:
pip install mesedi[crewai]One line attaches Mesedi to a Crew:
import mesedi
from mesedi.integrations.crewai import instrument_crew
@mesedi.wrap
def run_my_crew(question: str) -> str:
crew = build_crew()
instrument_crew(crew)
return str(crew.kickoff(inputs={"question": question}))
instrument_crew is idempotent and does three things: attaches the LangChain callback handler to each agent's LLM (CrewAI uses LangChain under the hood), sets a step callback to emit crewai.agent_action / crewai.agent_finish checkpoint events per agent step, and sets a task callback to emit crewai.task_completed per finished task. The dashboard timeline ends up showing LLM and tool detail interleaved with CrewAI's higher-level reasoning rhythm.
Lower-level building blocks
The decorators are sugar over a lower-level client API. You rarely need it, but it's there:
mesedi.MesediClient: the HTTP client underconfigure().mesedi.Event,mesedi.EventType: build event payloads directly.mesedi.Execution,mesedi.Status: build execution payloads directly.mesedi.flush(timeout=5.0): block until the background shipper drains. Useful in test suites and short-lived scripts where the process exits before the async shipper has shipped.
What's next?
TypeScript SDK reference covers the matching surface for Node and the Vercel AI SDK adapter.
HTTP API reference covers what the SDK posts on the wire, so you can instrument from any language.
Failure classes and playbooks explains what each detector is looking for in the telemetry the SDK ships.