Documentation
Simple guide: connect Cartha, run an agent, see traces, memory, costs, and guardrails in the dashboard.
1. Quick Start
⭐ Start HereDo this once. Then run any agent code you already have. Cartha records the run so you can open the dashboard and see what happened.
Step A — Install & key
pip install cartha-sdk # From https://cartha.in → Keys / Settings export CARTHA_API_KEY="cartha_..." export CARTHA_API_BASE="https://cartha.in"
Step B — Two lines at app startup
import cartha cartha.init(api_key="cartha_...", api_base="https://cartha.in") cartha.instrument() # Your normal OpenAI / framework code runs below from openai import OpenAI client = OpenAI() client.chat.completions.create({ model="gpt-4o-mini", messages=[{"role": "user", "content": "Hello"}], })
@cartha.trace + @cartha.tool. That gives you a named agent, budgets, and tool allow-lists. instrument() is the zero-code shortcut for supported LLM clients.What you see in the dashboard
Step C — Recommended full example (named agent)
This is the pattern most teams use in production. Easy to read and fully visible in the UI.
import os
import cartha
cartha.init(
api_key=os.environ["CARTHA_API_KEY"],
api_base="https://cartha.in",
)
client = cartha.wrap_openai() # auto LLM + cost steps
@cartha.tool()
def crm_lookup(user_id: str) -> dict:
return {"plan": "pro"}
@cartha.trace(
id="support_agent",
team="support",
budget_usd=0.50,
allowed_tools=["crm_lookup"],
)
async def handle_ticket(user_id: str, ticket: str) -> str:
await cartha.remember(
user_id=user_id,
content="Ticket: " + ticket,
scope="user",
)
hits = await cartha.recall(
user_id=user_id,
context=ticket,
scope=["user", "team"],
top_k=5,
)
data = crm_lookup(user_id)
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": ticket + "\nCRM: " + str(data) + "\nMemory: " + str(hits),
}],
)
return r.choices[0].message.content or ""What you see in the dashboard
support_agent, a full trace with tool + memory + LLM steps, and costs under the budget of $0.50.2. Supported Frameworks
Use whatever stack you already have. Cartha sits beside it. Depth of automatic patching varies by framework—OpenAI and LangGraph are the most mature today. Everything else works with the Core SDK decorators.
@cartha.trace and your tools with @cartha.tool. Same dashboard.OpenAI
LangGraph
CrewAI
Custom Python
Anthropic / Gemini / LiteLLM
AutoGen / PydanticAI / others
Example — OpenAI only
import cartha
from openai import OpenAI
cartha.init(api_key="cartha_...", api_base="https://cartha.in")
cartha.instrument(["openai"]) # only patch OpenAI
client = OpenAI()
client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Summarize today's tickets"}],
)3. Automatic Features
⚡ Zero-CodeWhen you call cartha.instrument(), Cartha tries to monkey-patch installed libraries so normal API calls become traces. Think of it as “turn on recording for this process.”
Discovery
- Detect installed SDKs (e.g. OpenAI, LangGraph) and patch what is available.
- Print a short status banner so you know what got instrumented.
Governance (with Core SDK)
Hard budgets, tool allow-lists, and policies work best with explicit @cartha.trace(...).
@cartha.trace(id="research", team="ops", budget_usd=0.25)
async def research(question: str):
# If LLM spend crosses $0.25 → BudgetExceeded, run stops
...Observability
- LLM calls become cost + model steps on a trace.
- Tool calls show inputs/outputs (when decorated or discovered).
- Failures show as error steps so you can open the bad run in Traces.
4. CLI Reference
After pip install cartha-sdk, you get a cartha command for health checks.
cartha doctor
Checks Python, API key env, and which frameworks are installed.
$ cartha doctor 🔍 Analyzing environment... ✓ Python version: 3.12.x ✓ API Key: Present ✓ CARTHA_API_BASE: https://cartha.in 📦 Frameworks: - openai: available - langgraph: available (if installed)
cartha instrument
Run a script with auto-instrument env set (handy for demos).
cartha instrument python app.pycartha config
Prints whether CARTHA_API_KEY / base are set (does not print the full secret).
cartha version
Shows installed SDK version (e.g. 0.4.x).
5. Core SDK
⭐ Use TheseThree ideas cover almost everything: connect, auto-patch, or watch one graph.
cartha.init(...)
Connects the SDK to your workspace. Call once at process start.
cartha.init(
api_key="cartha_...", # or env CARTHA_API_KEY
api_base="https://cartha.in", # or env CARTHA_API_BASE
)cartha.instrument()
Patches supported installed libraries (e.g. OpenAI chat completions, LangGraph runs). Optional list: instrument(["openai"]).
cartha.instrument() # all available cartha.instrument(["openai"]) # only OpenAI
cartha.observe(graph)
Prefer this when you only want one LangGraph app instrumented.
app = graph.compile()
app = cartha.observe(app)
app.invoke({"messages": [...]})What you see in the dashboard
init + a real API call, new rows appear under Traces. If nothing shows up, check the API key and that api_base is https://cartha.in.6. Advanced Instrumentation
🔧 Manual APIsWhen to use this section
Custom agents, non-OpenAI models, hard budgets, tool allow-lists, or memory isolation. These APIs are the most reliable path for production governance.
cartha.wrap_openai()
Wrap one OpenAI client so every chat.completions.create becomes an LLM step with cost.
from openai import OpenAI
import cartha
cartha.init(api_key="cartha_...", api_base="https://cartha.in")
client = cartha.wrap_openai(OpenAI()) # or wrap_openai() and it creates a client
# Normal OpenAI code — now traced
client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Hello"}],
)@cartha.trace(...)
Marks one agent run: registers the agent, starts/finishes a trace, optional budget and allowed_tools.
@cartha.trace(
id="support_agent",
team="support",
budget_usd=0.50,
allowed_tools=["crm_lookup"],
)
async def handle_ticket(user_id: str, ticket: str) -> str:
...
return "done"What you see in the dashboard
support_agent. Each call creates a Trace. Over budget → failure with BudgetExceeded.@cartha.tool()
Records tool inputs/outputs. Prefer async tools inside async agents. Tools not listed in allowed_tools raise ToolNotAuthorized before the function body runs.
@cartha.tool()
async def crm_lookup(user_id: str) -> dict:
return {"user_id": user_id, "plan": "pro"}
@cartha.tool()
async def initiate_wire(amount: float) -> dict:
return {"status": "sent"} # dangerous — keep off allow-list for support
@cartha.trace(id="support", allowed_tools=["crm_lookup"])
async def run(user_id: str):
await crm_lookup(user_id) # OK
# await initiate_wire(100) # ToolNotAuthorizedremember / recall (scoped memory)
Scopes: user, agent, team, org. Always pass the real user_id so customers stay isolated.
# Store a fact about this customer only
await cartha.remember(
user_id="alice_42",
content="Prefers email follow-ups",
scope="user",
confidence=0.9,
)
# Search only allowed scopes
hits = await cartha.recall(
user_id="alice_42",
context="how should I contact them?",
scope=["user", "team"],
top_k=5,
)
# Bob using user_id="bob_99" will not see Alice's user-scoped noteWhat you see in the dashboard
llm_call (any model)
Not using OpenAI? Call your model yourself, then record the step so costs and traces still work.
# reply = your_gemini_or_local_model(prompt)
await cartha.llm_call(
model="gemini-flash",
input=prompt,
output=reply,
tokens_in=120,
tokens_out=80,
total_cost_usd=0.0002,
)Handle budget stop
from cartha import BudgetExceeded
try:
await handle_ticket("user_1", "refund please")
except BudgetExceeded as e:
print("Stopped by Cartha:", e)
# notify user / fallback agent7. Universal Runtime Model
Different frameworks have different APIs. Cartha maps them into one simple story so the dashboard always looks the same: a run, with steps (tool, LLM, memory, cost, error).
Simple map of what you see
Trace started / finishedOne agent run from start to end.
Tool stepA function decorated with @cartha.tool ran.
LLM stepA model call (wrap_openai or llm_call).
Memory store / recallremember() / recall() calls.
CostSpend that feeds budget breakers.
Error / BudgetExceededRun stopped or step failed.
8. Architecture
Cartha is designed to stay out of your business logic. You add a few lines; your agent still calls the same models and tools.
- Your process — agent code + cartha-sdk
- Cartha API — stores traces, memory, costs; enforces policies and plan limits
- Dashboard — same login as keys; for humans to inspect runs
- Fail-open telemetry — if recording fails, prefer not to crash your agent (governance errors like budgets/policies still raise on purpose)
Agent app --SDK--> cartha.in API --> Postgres / Redis / vectors
\
--> Dashboard (you look here)9. Capability Matrix
What you can rely on today. Prefer the Core SDK path when a cell is partial.
LLM providers
| Provider | Auto instrument | Manual llm_call | Costs |
|---|---|---|---|
| OpenAI | ✅ wrap / instrument | ✅ | ✅ |
| Gemini / others | Partial | ✅ recommended | ✅ via llm_call |
| Anthropic / LiteLLM | Growing | ✅ | ✅ |
Governance (all stacks)
| Feature | How | Dashboard |
|---|---|---|
| Traces | @trace / instrument | Traces |
| Tools | @tool + allowed_tools | Trace steps |
| Memory isolation | remember / recall scopes | Memory |
| Hard budgets | budget_usd on @trace | Costs + failed run |
| Policies | Dashboard policies | Policies |
10. Examples
Copy, set your key, run, then open Traces.
A. Minimal (no OpenAI)
import asyncio, os, cartha
cartha.init(
api_key=os.environ["CARTHA_API_KEY"],
api_base="https://cartha.in",
)
@cartha.tool()
def lookup_order(order_id: str) -> dict:
return {"order_id": order_id, "status": "shipped"}
@cartha.trace(id="support_agent", team="support", budget_usd=1.0)
async def handle(user_id: str, question: str) -> str:
order = lookup_order("ORD-1")
reply = f"Order {order['order_id']} is {order['status']}."
await cartha.llm_call(
model="mock",
input=question,
output=reply,
tokens_in=10,
tokens_out=20,
total_cost_usd=0.0001,
)
return reply
if __name__ == "__main__":
print(asyncio.run(handle("user_1", "Where is my order?")))
print("→ https://cartha.in → Traces")B. OpenAI + memory
import os, cartha
cartha.init(api_key=os.environ["CARTHA_API_KEY"], api_base="https://cartha.in")
client = cartha.wrap_openai()
@cartha.trace(id="support_agent", team="support", budget_usd=0.5)
async def handle(user_id: str, ticket: str) -> str:
await cartha.remember(user_id=user_id, content=ticket, scope="user")
hits = await cartha.recall(
user_id=user_id, context=ticket, scope=["user"], top_k=3
)
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"{ticket}\nMemory: {hits}"}],
)
return r.choices[0].message.content or ""C. LangGraph (auto)
import cartha
from langgraph.graph import StateGraph
cartha.init(api_key="cartha_...", api_base="https://cartha.in")
cartha.instrument()
# ... build StateGraph ...
app = graph.compile()
# optional: app = cartha.observe(app)
app.invoke({"input": "hello"}) # run appears under TracesD. CrewAI-style entry
import cartha
# from crewai import Crew, Agent, Task
cartha.init(api_key="cartha_...", api_base="https://cartha.in")
@cartha.trace(id="research_crew", team="research", budget_usd=1.0)
def run_crew(topic: str):
# crew = Crew(agents=[...], tasks=[...])
# return crew.kickoff(inputs={"topic": topic})
return f"researched {topic}"
run_crew("agent governance")@cartha.trace so the whole crew run is one parent trace you can open in the UI.11. Migration Guide
Moving from manual wrap-only style to optional auto-instrumentation (v0.4+).
Still valid (v0.3 style)
client = OpenAI() client = cartha.wrap_openai(client)
Also fine (v0.4)
cartha.instrument() client = OpenAI() # patched if OpenAI installed
@trace, @tool, and wrap_openai. They remain the recommended path for budgets and allow-lists.12. FAQ
Do I still need @cartha.trace()?▼
For production governance—yes, recommended. instrument() can record LLM calls automatically, but @trace gives a clear agent name, team, budget_usd, and allowed_tools.
Where do I put the API key?▼
Environment variable CARTHA_API_KEY (and CARTHA_API_BASE=https://cartha.in). Get the key after login at cartha.in → Keys / Settings. Never commit keys to git.
Nothing shows in the dashboard▼
Check: (1) key is correct, (2) base is https://cartha.in not api.cartha.in, (3) you actually called a traced function or instrumented LLM, (4) refresh Traces and filter by agent id.
What is HTTP 402?▼
Plan limit (e.g. agent count or monthly steps). Delete unused agents in the dashboard or upgrade plan on the pricing page.
What is ToolNotAuthorized?▼
The tool name is not in allowed_tools on @cartha.trace. Add it to the list only if that agent should be allowed to run it.
What is BudgetExceeded?▼
Spend on that run went over budget_usd. This is intentional hard stop—catch it and fail gracefully or hand off to another agent.
Can Customer A see Customer B memory?▼
Not if you use scope="user" and different user_id values. Isolation is enforced server-side. Never reuse one user_id for all customers.
Does Cartha replace LangGraph / CrewAI?▼
No. Keep your framework. Cartha is the control plane: traces, memory scopes, budgets, tool authority, dashboard.
What if Cartha is down?▼
Telemetry is designed not to take down your app when possible. Budget and policy denials still raise—they are safety controls, not optional logs.