Documentation

Simple guide: connect Cartha, run an agent, see traces, memory, costs, and guardrails in the dashboard.

1. Quick Start

Start Here

Do 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

terminal
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

main.py
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"}],
})
Still learning? Prefer the explicit path in Core SDK: @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

After one run: Agents shows your agent (or framework agent), Traces shows a timeline of LLM / tool steps, Costs shows spend if costs were recorded.

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.

support_agent.py
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

Agent name 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.

If a framework is not fully auto-instrumented yet, wrap your entrypoint with @cartha.trace and your tools with @cartha.tool. Same dashboard.

OpenAI

instrument() / wrap_openaiLLM + cost stepsStreaming paths

LangGraph

instrument() / observe()Graph run tracesLifecycle events

CrewAI

Use @trace + @toolWorks with any CrewManual memory APIs

Custom Python

@cartha.trace@cartha.toolllm_call / remember

Anthropic / Gemini / LiteLLM

instrument() when installedOr record via llm_callSame costs UI

AutoGen / PydanticAI / others

Decorator pathExplicit tool_callGrowing auto support

Example — OpenAI only

openai_only.py
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-Code

When 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(...).

budget_example.py
@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.

terminal
$ 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.py

cartha 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 These

Three ideas cover almost everything: connect, auto-patch, or watch one graph.

cartha.init(...)

Connects the SDK to your workspace. Call once at process start.

init
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"]).

instrument
cartha.instrument()                 # all available
cartha.instrument(["openai"])       # only OpenAI

cartha.observe(graph)

Prefer this when you only want one LangGraph app instrumented.

observe
app = graph.compile()
app = cartha.observe(app)
app.invoke({"messages": [...]})

What you see in the dashboard

After 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 APIs

When 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.

wrap_openai
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.

trace
@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

Dashboard → Agents shows 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.

tool
@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)           # ToolNotAuthorized

remember / recall (scoped memory)

Scopes: user, agent, team, org. Always pass the real user_id so customers stay isolated.

memory
# 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 note

What you see in the dashboard

Memory page lists entries per user/scope. Isolation demos use two user ids and prove no cross-read.

llm_call (any model)

Not using OpenAI? Call your model yourself, then record the step so costs and traces still work.

llm_call
# 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

budget_handle
from cartha import BudgetExceeded

try:
    await handle_ticket("user_1", "refund please")
except BudgetExceeded as e:
    print("Stopped by Cartha:", e)
    # notify user / fallback agent

7. 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).

Your code (OpenAI / CrewAI / custom)
cartha-sdk (instrument or decorators)
becomes
Trace + steps (tools, LLM, memory)
Budgets · policies · allow-lists
Dashboard at cartha.in

Simple map of what you see

Trace started / finished

One agent run from start to end.

Tool step

A function decorated with @cartha.tool ran.

LLM step

A model call (wrap_openai or llm_call).

Memory store / recall

remember() / recall() calls.

Cost

Spend that feeds budget breakers.

Error / BudgetExceeded

Run 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)
mental_model.txt
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

ProviderAuto instrumentManual llm_callCosts
OpenAI✅ wrap / instrument
Gemini / othersPartial✅ recommended✅ via llm_call
Anthropic / LiteLLMGrowing

Governance (all stacks)

FeatureHowDashboard
Traces@trace / instrumentTraces
Tools@tool + allowed_toolsTrace steps
Memory isolationremember / recall scopesMemory
Hard budgetsbudget_usd on @traceCosts + failed run
PoliciesDashboard policiesPolicies

10. Examples

Copy, set your key, run, then open Traces.

A. Minimal (no OpenAI)

minimal.py
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

openai_memory.py
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)

langgraph_app.py
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 Traces

D. CrewAI-style entry

crew_entry.py
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")
Wrap the entrypoint with @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
Backward compatible: Keep @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.