How to Use

Complete guide for developers and operators: connect the SDK, instrument agents, and use the dashboard. Base URL for the cloud product: https://cartha.in

1. What Cartha is

Cartha is an ops and governance layer for AI agent fleets. You keep your own agent code (OpenAI, tools, business logic). You add a few Cartha lines so every run is registered, traced, and (optionally) budgeted and memory-scoped.

  • Not a chatbot builder — you bring the agents.
  • Not only logs — scopes, hard budgets, policies, cost per task.
  • SDK-first — Python package cartha-sdk.

2. Quickstart (minimum)

2.1 Create an account & API key

  1. Open Login and sign in (Google / GitHub).
  2. Go to Keys (or Settings) and copy your API key (cartha_...).

2.2 Install

pip install cartha-sdk
# optional — auto LLM tracing:
pip install openai

2.3 Environment variables

# macOS / Linux
export CARTHA_API_KEY="cartha_..."
export CARTHA_API_BASE="https://cartha.in"

# Windows PowerShell
$env:CARTHA_API_KEY="cartha_..."
$env:CARTHA_API_BASE="https://cartha.in"

Use https://cartha.in — not api.cartha.in (that host may not resolve).

2.4 Minimum code

import cartha

cartha.init()

@cartha.trace(id="my_agent", team="support")
def run_agent(user_id: str, prompt: str):
    # your existing logic
    return "done"

run_agent("u1", "hello")

After you call the function: the agent appears under Agents, a run appears under Traces, heartbeats keep “last seen” fresh while the process is alive.

Minimum integration ≈ 25% of product value (fleet + run shell). Use the easy path below for tools, LLM, cost, and budgets.

3. Easy full integration (recommended)

Use helpers so you rarely write tool_call / llm_call by hand.

import cartha

cartha.init()
client = cartha.wrap_openai()   # auto: LLM steps + tokens + $

@cartha.tool()                  # auto: tool steps + failures
def crm_lookup(user_id: str) -> dict:
    return {"plan": "pro", "duplicate": True}

@cartha.trace(id="support", team="support", budget_usd=0.5)
def handle(user_id: str, ticket: str) -> str:
    cartha.remember_sync(user_id=user_id, content=ticket, scope="user")
    hits = cartha.recall_sync(user_id=user_id, context=ticket, scope=["user", "team"])
    data = crm_lookup(user_id)
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"{ticket}\n{data}\n{hits}"}],
    )
    return r.choices[0].message.content or ""

print(handle("user_42", "I was charged twice"))
You writeCartha records
@cartha.trace(...)Agent, heartbeat, run start/finish, nest, optional budget
@cartha.tool()Tool steps (success + failure)
wrap_openai() + create()LLM steps, tokens, estimated cost (feeds budgets)
remember / recallScoped memory (you choose what to store)

4. Agents & traces in detail

@cartha.trace parameters

  • id — agent key in the dashboard. Optional; defaults to the function name. Use a stable id in production.
  • team — team id for memory/cost grouping. Default: CARTHA_TEAM_ID or default_team.
  • budget_usd — hard spend ceiling for the run.
  • allowed_tools — only these tool names may run.
  • nest=True — child agents auto-link under the parent.
@cartha.trace(id="billing_agent", team="support", budget_usd=0.5, allowed_tools=["crm_lookup"])
def billing(...):
    ...

Works on sync and async functions. Call the function for anything to appear — decorating alone does nothing.

5. Tools & LLM

Easy

@cartha.tool()
def crm_lookup(user_id: str) -> dict:
    return db.get(user_id)

@cartha.tool("search_docs")
async def search(q: str) -> list:
    return await index.search(q)

client = cartha.wrap_openai()  # needs: pip install openai
r = client.chat.completions.create(model="gpt-4o-mini", messages=[...])

Manual (any model / custom)

await cartha.tool_call(tool_name="crm_lookup", output=data, status="success")
await cartha.llm_call(
    model="gpt-4o-mini",
    input=prompt,
    output=text,
    tokens_in=100,
    tokens_out=40,
    total_cost_usd=0.002,
)
# sync:
cartha.tool_call_sync(...)
cartha.llm_call_sync(...)

Without tool/LLM recording, traces stay thin (start/finish only) and budgets may not trip on real model spend.

6. Memory (scopes & denials)

Memory is enforced on the server (not only in the SDK). Prefer going only through Cartha APIs — do not give agents raw DB/index credentials that skip filters.

# Store
await cartha.remember(user_id="u1", content="Prefers email", scope="user", confidence=0.9)
# scopes: "user" | "agent" | "team" | "org"

# Recall (clamped to this agent's readable scopes)
hits = await cartha.recall(
    user_id="u1",
    context="contact preference",
    scope=["user", "team"],
    top_k=5,
)

# Sync
cartha.remember_sync(...)
cartha.recall_sync(...)
  • Team memory only shared if agents share the same team= on @trace.
  • Org setting memory denial mode (Settings): denied_hint (default) vs silent.
  • Admins can erase memory / RTBF via API (governance); see Memory & Settings in the app.

7. Budgets (circuit breaker)

@cartha.trace(id="billing", team="support", budget_usd=0.05)
def billing(...):
    # each llm_call / wrap_openai create counts toward the cap
    ...

# On overspend:
from cartha import BudgetExceeded
try:
    ...
except BudgetExceeded as e:
    return f"Stopped: {e}"

Fail-closed: after the limit is crossed, the next spend raises — not an email hours later. Spend must be reported (via wrap_openai or llm_call / cost).

8. Multi-agent: nest & attenuated delegate

Nest (automatic)

Call one @cartha.trace agent from another — child runs link under the parent in the dashboard.

Delegate budget (parent → child)

@cartha.trace(id="boss", team="support", budget_usd=2.0, allowed_tools=["crm_lookup", "refund"])
async def boss(user_id: str, ticket: str):
    grant = await cartha.delegate(
        to_agent_id="worker",
        task_description="Handle ticket",
        budget_usd=0.5,  # must be ≤ remaining
        allowed_tools=["crm_lookup"],  # subset of parent only
    )
    try:
        return await worker(
            user_id,
            ticket,
            cartha_budget_id=grant.get("budget_id"),
            cartha_budget_max_usd=grant.get("budget_max_usd"),
            cartha_budget_tools=grant.get("budget_allowed_tools"),
        )
    finally:
        if grant.get("budget_id"):
            await cartha.close_budget(grant["budget_id"])

@cartha.trace(id="worker", team="support")
async def worker(user_id: str, ticket: str, **kwargs):
    ...

Child cannot get more money or more tools than the parent granted.

Cost per completed task (retries)

tid = cartha.task_context()
for attempt in range(3):
    try:
        await run_agent(..., cartha_task_id=tid)
        break
    except Exception:
        continue
# Dashboard → Costs → outcomes rolls retries under one task_id

9. Policies & human-in-the-loop

  1. Open Policies in the dashboard.
  2. Create a rule in plain language (e.g. block certain tools or PII).
  3. Set action: block or require human approval.
  4. In code, use @cartha.tool / tool_call — policy is checked automatically.
from cartha import PolicyViolation

try:
    await cartha.tool_call(tool_name="charge_refund", ...)
except PolicyViolation as e:
    return f"Blocked: {e.policy_name} — {e.reason}"

Pending approvals appear under Escalations. Trace Replay re-evaluates current policies on a past run.

10. Dashboard guide (operators)

PageWhat to do there
Home / DashboardTasks today, cost, active agents, needs attention, recent runs
AgentsFleet list, status, last seen, open agent detail
TracesFilter/search runs, open waterfall, run-diff, replay, live tail, export
MemorySearch memories, audit, flows
CostsSpend by agent, trends, cost per completed task
PoliciesCreate/edit rules, view policy events
EscalationsApprove/reject HITL requests
IntelligenceOrg benchmarks / summaries
KeysCreate admin/agent keys; never commit keys to git
SettingsOrg name, denial mode, GitHub repo, alerts, SDK snippet

11. What you see with different integration levels

IntegrationDashboard shows
init + @trace onlyAgents, heartbeats, thin runs (start/finish), nest
+ @tool + wrap_openaiFull timeline, tokens, costs, useful budgets
+ remember/recallMemory page, scope/denial behavior
+ budget_usd / delegateHard stop, child envelopes
+ policies in UIBlocks, replay, escalations

12. Integration checklist

  • API key set; base URL is https://cartha.in
  • cartha.init() at process start
  • @cartha.trace on each agent (stable id + team)
  • @cartha.tool on tools OR manual tool_call
  • wrap_openai() OR manual llm_call with total_cost_usd
  • remember/recall if you need shared context
  • budget_usd on expensive agents
  • Policies created in dashboard for guardrails
  • Call the agent functions (decorator alone is not enough)
  • Log in with the same org as the API key

13. FAQ

Do I need id and team on @trace?

Optional. Defaults: function name + default_team. Use explicit values in production.

Is tool_call required if I use @cartha.tool?

No — the decorator records it for you.

Why is cost still $0?

You must report spend via wrap_openai or llm_call/cost. Decorator alone does not read your OpenAI bill.

Sync vs async?

Both work. Prefer async + await for high concurrency; use *_sync helpers in sync code.

Can agents bypass memory scopes?

Not through the Cartha API. Never give tools raw database/index access that skips Cartha — enforcement lives on the server.

Ready to instrument your first agent?