TokenPolice
Docs
Sample apps

Support agent

A support-desk chat app in Python and Node that runs TokenPolice in enforce mode, threading each login into one dashboard session.

A support-desk chat you can run locally: a login, a chat UI, and one LLM call per message. It's the steady-state TokenPolice pattern — add the firewall, leave it in enforce, and every turn is checked before the provider call. The same app ships in two languages so you can pick the one you work in:

FolderStackPort
support_agent_langgraph_python/FastAPI + LangGraph6002
support_agent_langgraph_node/Express + LangGraph6001

Every login gets a stable session_id, so the whole conversation shows up as one session in your dashboard, and each turn is a trace attributed to the user id you logged in as — named longrun_support_agent_langgraph_python or longrun_support_agent_langgraph_node.

What TokenPolice adds

One init() before the LLM imports, plus a workflow wrapper that names each turn and attaches the user + session identity. Nothing changes in how the model is called.

import token_police as tp

tp.init(
    api_key=os.environ.get("TOKEN_POLICE_API_KEY", "tp_sk_test"),
    base_url=os.environ.get("TOKEN_POLICE_URL", "http://localhost:3001"),
    firewall="enforce",
    log_errors=True,
)

import agent   # imports LangChain — must come after tp.init

# …in agent.py, each turn is wrapped so the dashboard groups it by user + session:
@tp.workflow(name="longrun_support_agent_langgraph_python", kind="agent")
async def run_turn(user_id, session_id, provider, mode, message, history) -> dict:
    ...

tp.flush_sync()   # on shutdown
import * as tp from 'token-police';
// @langchain/core ships dual CJS+ESM builds as distinct module records.
// Static-import the ESM ones so the SDK can patch them before any langchain code runs.
import * as lcChatModels from '@langchain/core/language_models/chat_models';
import * as lcCallbacks from '@langchain/core/callbacks/manager';

tp.init({
    apiKey: process.env.TOKEN_POLICE_API_KEY || 'tp_sk_test',
    baseUrl: process.env.TOKEN_POLICE_URL || 'http://localhost:3001',
    firewall: 'enforce',
    logErrors: true,
    instrumentModules: {
        langChain: { chatModelsModule: lcChatModels, callbackManagerModule: lcCallbacks },
    },
});

// Imported AFTER tp.init — agent.ts has no top-level langchain imports.
import { runTurn } from './agent.js';

const runSupportAgent = tp.workflow(
    { name: 'longrun_support_agent_langgraph_node', kind: 'agent' },
    async (opts: { userId: string; sessionId: string; /* … */ }) => runTurn({ /* … */ }),
);

await tp.shutdown();   // on SIGINT/SIGTERM

Node needs the LangChain companion package

The Node app depends on both token-police and token-police-langchain. Without the companion package installed, the app still boots and still enforces — but no model usage is recorded at all. The two static @langchain/core imports above must also come before tp.init(). See LangChain.

Let your coding agent wire it

You don't have to add those lines by hand. Point your coding agent at the TokenPolice skill and it does it for you — see Start with your coding agent.

Run it

You'll need a TokenPolice API key (tp_sk_…) from app.tokenpolice.ai and a key for whichever provider you pick.

cd support_agent_langgraph_python
cp env.example .env       # then fill in your keys
bash setup.sh
source .venv/bin/activate
python main.py            # http://localhost:6002
cd support_agent_langgraph_node
cp env.example .env       # then fill in your keys
npm install
npm start                 # http://localhost:6001

Configuration

Everything is read from .env at startup:

VariableWhat it does
TOKEN_POLICE_API_KEYYour tp_sk_… key
TOKEN_POLICE_URLThe collector. env.example ships pointing at a local stack (http://localhost:3001) — set it to https://collect.tokenpolice.ai to use the hosted service
PROVIDERopenai | anthropic | gemini (default openai). Fill in the matching *_API_KEY and, optionally, *_MODEL_NAME
MODEprebuilt | custom (default prebuilt) — whether the app uses LangGraph's prebuilt ReAct agent or a hand-built graph. TokenPolice behaves identically either way
SESSION_IDLE_TIMEOUT_SECONDSDefault 300. How long a login's session survives with no activity before the app ends it
APP_PORTDefault 6002 (Python) / 6001 (Node)

What you'll see

A single-page support desk: a header showing the active provider and model, a login field for a user id (e.g. usr_pro_001), and a chat transcript. Send a few messages, then open your dashboard: the whole conversation is grouped under one session, each turn is its own trace attributed to that user id, and cost and token usage accrue per turn — all checked before the provider call, with the firewall in enforce.

Because the app runs in enforce, any rule you promote to Enforce takes effect on the very next turn. Try a per-session cap and watch it bite; the sibling RAG app below does exactly that, and handles the block gracefully.