TokenPolice
Docs
Core concepts

Sessions, workflows & traces

Session, agent, chain, and workflow anchor spans — what a trace is and how multi-turn conversations thread.

When your app runs, TokenPolice records each LLM call as a span. The spans from one run form a tree — that tree is a trace. Wrapping your entrypoint gives that tree a root and a name, so the dashboard shows "the support-agent run" instead of a pile of loose calls.

A trace is one run

Every LLM call becomes a span. On its own, a call is a lone span. Wrap the code around it and those calls nest under an anchor span — one trace for the whole run, with the model calls as children. That's what lets the dashboard total the cost of a run, not just of a call.

Giving the tree a root

You get that root by wrapping the code that handles one run. Whichever wrapper you use, it carries the same identity tags (user_id, paid_plan, session_id) down to every call inside.

In practice you'll reach for @tp.workflow most of the time — it wraps a function as one trace and pulls identity straight from its arguments. Wrap a region of code instead, or label a sub-part by its shape, with session / agent / chain. The four wrappers and when to use which are in Wrapping your app.

@tp.workflow(name="support_agent")
def run_agent(user_id: str, session_id: str, query: str):
    # both calls below are children of one "support_agent" trace
    plan = client.chat.completions.create(...)
    reply = client.chat.completions.create(...)
    return reply

Threading multi-turn conversations

One run is one trace. A back-and-forth conversation is many runs — so to see them as one thread, reuse the same session_id across turns. Each turn is still its own trace, but they share a session, and the dashboard groups them into a single conversation.

Nested scopes inherit their parent's session_id and trace automatically — you only set the id at the top. Same id across turns → one conversation; a new id → a new one. See the stable-session_id contract.

Next