# Wrapping your app (/docs/sdk/wrapping)



Wrapping an entrypoint does two things: it groups every LLM call inside into one **trace**, and it
attaches **identity** (`user_id`, `paid_plan`, `session_id`) so your rules can target that traffic.
You get the same tagging four ways — pick by shape.

## The four wrappers [#the-four-wrappers]

| Wrapper    | Shape                                                                       | Recorded as |
| ---------- | --------------------------------------------------------------------------- | ----------- |
| `workflow` | Decorator (Python) / higher-order function (Node) — wrap a whole entrypoint | a chain     |
| `session`  | A block you open around some work                                           | an agent    |
| `agent`    | A `session` recorded as an agent — a dynamic, LLM-driven loop               | an agent    |
| `chain`    | A `session` recorded as a chain — a fixed, linear pipeline                  | a chain     |

"Recorded as" is just the label the run carries in the dashboard: **agent** for work that
decides its own next step, **chain** for a fixed pipeline. It changes nothing about behaviour —
pass `kind="agent"` / `kind="chain"` to `workflow` or `session` (or use `agent`/`chain`) if the
default label doesn't fit the shape of your code.

Reach for `workflow` first — it's the least intrusive. Use `session` / `agent` / `chain` when you
want to wrap a *region* of code rather than a whole function, or to be explicit about whether the
run is a dynamic agent or a linear chain.

## workflow [#workflow]

Wrap the function that handles one request or run. Every LLM call inside is tagged.

```python
import token_police as tp

@tp.workflow(name="support_agent")
def handle(user_id: str, paid_plan: str, session_id: str, query: str):
    ...   # all LLM calls here share one trace + this identity
```

Node — same idea, as a wrapper:

```typescript
import * as tp from "token-police";

const handle = tp.workflow({ name: "support_agent" }, async (req) => {
  // ...
});
```

## session / agent / chain [#session--agent--chain]

Open a block around the work instead of wrapping a function:

```python
with tp.session(name="nightly_report", user_id="batch", kind="chain"):
    ...   # calls in here belong to this session
```

```typescript
await tp.session({ name: "nightly_report", userId: "batch" }, async () => {
  // ...
});
```

`tp.agent(...)` and `tp.chain(...)` are the same call with the anchor fixed — use `agent` for a
loop that decides its own steps, `chain` for a set pipeline. Nested scopes inherit the parent's
`session_id` and trace unless you pass new values.

## Dynamic identity: bind\_args [#dynamic-identity-bind_args]

Often you don't know `user_id` or `session_id` until the request arrives. `workflow` can read them
straight off your function's arguments — on by default.

* **Python** (`bind_args=True`) inspects the function signature and picks up arguments named
  `user_id`, `paid_plan`, `session_id`, `workflow_name`, or `metadata`. Each is used only if it's
  the right type (strings; `metadata` a dict); otherwise the static decorator value stands. Any
  binding failure falls back silently to the static values.
* **Node** (`bindArgs: true`) reads `userId`, `paidPlan`, `sessionId` and `metadata` off the
  **first argument when it's a plain object** (JS has no signature introspection). The
  snake\_case spellings `user_id`, `paid_plan` and `session_id` are accepted too. A plain-string
  first argument is deliberately not auto-bound.

```python
# user_id + session_id come from the call, not hard-coded
@tp.workflow(name="chat")
def chat(user_id: str, session_id: str, message: str): ...
```

<Callout type="info">
  To thread a multi-turn conversation into one trace, pass the **same** `session_id` on every turn.
  The full contract is on [Identity](/docs/concepts/identity).
</Callout>

## Next [#next]

<Cards>
  <Card title="Identity" href="/docs/concepts/identity" description="user_id, paid_plan, session_id and the threading contract." />

  <Card title="Sessions & traces" href="/docs/concepts/sessions-and-traces" description="How a wrapped run becomes a trace." />
</Cards>
