Skip to main content
StiggLangChainCallbackHandler governs a LangChain or LangGraph run. Add it to the run’s callbacks, and LangChain passes it to every agent in the run, including agents started by other agents. Each model call and paid tool is then checked before it runs, and its usage is reported as the agent that made it. For how Stigg governs a team of agents and what it’s used for, see Agent frameworks.

Key terms

Quick start

You need a LangChain or LangGraph app, a Stigg server API key, and a credit type in Stigg, here credits, that your customers have.
1

Install the SDK

@langchain/core 1.x is an optional peer dependency. The handler uses the copy your app already has.
2

Create the Stigg client

Create it once, when your server starts:
3

Create the handler

Create the handler once too, and tell it what to check each step against:
4

Pass the handler and the customer on every run

Your agent needs no changes. Add the handler to callbacks, and the customer the run is for to metadata:
The customer changes per request, so it goes on the run, not on the handler. Take it from the logged-in user on your server. See Naming the customer safely. A process that serves one customer can set a default in the handler’s stiggContext: { customerId } instead. A run with neither is denied before its first step.
5

Check that it works

Run the agent once. Then confirm in Stigg that the customer’s credits balance went down. If it didn’t, the token counts the handler reports aren’t converted into credits yet. See The credit balance does not go down.To see each decision while you test, add a hook to the handler:
Each step of a run is then handled as follows: A step that fails reports no usage.

Naming the customer safely

The customer ID decides who pays for a run. If it comes from the user’s request, a user can send another customer’s ID and charge their usage to that customer. Always set it on your server, from the logged-in user.
  • In your own server, set metadata.stiggContext.customerId where you call invoke, from your session or auth layer.
  • On LangGraph Platform, the client sends the run’s metadata, so the client could name any customer. Set stiggContext in your custom auth handler from the authenticated identity, and overwrite any value the client sent. The client can also name the run, so also set rootAgent on the handler.
  • With defaultPlanId, every new customer ID a run names becomes a customer on that plan. Make the default plan a free one, so a made-up ID never starts a paid subscription.
The SDK also limits what a run can set. On invoke, the run’s stiggContext can set only customerId and correlationId. Any other field throws, because a client can write that metadata. Set entitlements, mappers and agent IDs on the handler or under agents.

Configurations

Each configuration below adds to the one before it.

Metering only

With no entitlement, nothing is checked or denied. Every model call is reported, and the agents run as they would without Stigg. By default each call reports LangChain’s own counters: input_tokens, output_tokens and total_tokens. Create meters for those names in Stigg, or convert them with a usageMapper.

One budget for the whole team

Every model call, by the lead and by every other agent, is checked against the customer’s credits, and its usage is reported against them.
  • usageMapper converts a model call’s token counts into what is reported, here credits. See Usage mappers.
  • ingestMode: 'sync' reports each call before the next check, so fast agents can’t run far past the limit. See Ingest modes.

A budget per agent

To give each agent its own budget, register the agents in Stigg, then tell the handler which LangChain agent is which Stigg agent. Step 1. Register the agents. Each agent is a Stigg entity of type agent. Create a parent agent whose budget caps the whole team, then one agent per team member with its own budget under that parent. Run this once per customer, for example when the customer signs up:
Step 2. Map LangChain’s agents to Stigg’s. Under agents, each key is an agent’s name in LangChain, and its agentId is the Stigg agent registered in step 1:
  • attributionKey is the key your agent entity type uses to identify an agent, here agentId. It’s set once on the entity type in Stigg. See Entity types.
  • rootAgent names the agent that the top-level run always counts as. See the rules below.
Each step is now checked against its agent’s budget and the parent’s. When the researcher runs out, its steps are denied, and the lead and writer keep working. For more on budgets and parents, see Usage governance. The handler decides which agent a step belongs to by these rules:
  • Agents are matched by name. A key in agents must be the name given to createAgent or createReactAgent, or the node’s name in a hand-built LangGraph graph.
  • A step belongs to the nearest named agent above it. If the lead starts the researcher, the researcher’s steps count against the researcher, even when the lead passes its own config along. The lc_agent_name in a run’s metadata never decides which agent a step counts against, because a caller can set it.
  • Name every agent. An agent with no name, or with a name agents doesn’t list, counts as the nearest listed agent above it, usually the one that started it.
  • Set rootAgent. Whoever calls invoke can name the top-level run, so without rootAgent the caller could pick whose budget pays. With rootAgent: 'lead', the top-level run always counts as the lead.
  • An agent’s settings are merged over the shared ones, field by field. An agent that sets only agentId still uses the shared entitlement and usageMapper. Each step is checked once and reported once.
  • An agent missing from agents uses the shared settings alone, so its spend counts against the customer, not an agent, unless the shared settings name an agent. The SDK logs a warning once.
  • idempotencyKey is not allowed in any of these settings, because every step would share it and only the first would be reported. The SDK keys each step by its own ID.

A hand-built LangGraph graph

In a hand-built StateGraph, each node is an agent. Use the same handler, with agents keyed by node name:
Name each node after the agent it runs, including a node that runs a subgraph. A tool that runs in its own node, such as LangGraph’s ToolNode, counts against the agent whose model asked for it, not to the tools node.

Tools

Model calls are always checked and reported. A tool is only checked and reported when tools lists it, because most tools, such as a calculator or a lookup in your own database, cost nothing.
  • usage prices a tool call. It receives { name, input, output }, with the tool’s arguments and result, and returns amounts by feature or credit ID. A model call’s usageMapper does the same job from token counts. If usage throws, the error goes to onError and the run continues.
  • Avoid counting usage twice. A common pattern wraps an agent in a tool, such as tool(() => researcher.invoke(...)), so the lead can call it. The researcher’s model calls are already reported as the researcher, so reporting the tool too would count the same work twice. If '*' would cover that tool, set it to false.
  • Per agent. agents.<name>.tools is merged over the shared tools, name by name. An agent can add its own paid tool, or set a shared one to false. '*': false only opts the agent out of the catch-all.
  • Attribution. A tool counts against the agent whose model asked for it.
  • Packaged tools. Add '*' so packaged tools such as TavilySearch are always covered, including after the graph resumes. The handler names these tools from the model’s request, so their own entries apply too.
  • Each tool call is reported once, keyed by the model’s tool-call ID. A retried or resumed call is checked again but reported under the same key.
  • Entries are validated when the handler is built. An entry with neither an entitlement nor a usage is rejected, and so is { currencyId, usage }. Write that as { entitlement: { currencyId }, usage }.

When access is denied

What your code can do depends on which step was denied: Stigg reports a denial by throwing an AccessDeniedError from inside the step, and your code decides what to do with it. LangChain often wraps that error in its own, so use stigg.denial(err) to find it. It returns the denial, with a message you can show, or undefined for any other error. In this example the researcher runs as a tool of the lead. When the researcher is out of budget, a cheaper generalist agent, with no paid search, answers instead:
At the top of the graph, catch the denial and show it to the user:
In a hand-built LangGraph graph, catch the denial inside the node, save it in the graph’s state, and use a conditional edge to route around it, such as state.refused ? 'generalist' : 'writer'. If Stigg can’t be reached to check, the SDK follows checkErrorAction, which lets the step run by default. See Fallback strategy. Reporting usage never throws into the run.

Observability

  • Hooks. hooks: { onDecision, onIngest } are called for every check and every report. context.agent and context.tool say which agent and tool the step belonged to.
  • Tracing. Stigg emits OpenTelemetry spans, so each check and each report appears in any tracing tool that reads OpenTelemetry, next to the agents’ own steps. A check shows up as a stigg.check span and a report as a stigg.ingest span. A span is one timed entry in a trace. A denied step’s span is marked as an error. This needs @opentelemetry/api, an optional peer dependency.
  • Flushing. Call await stigg.flush() before the process exits, so no report is lost.
Some tools export only the spans of libraries they recognize. In those, add Stigg’s tracer name, STIGG_TRACER_NAME, to the filter. For example, in Langfuse:

Best practices

  • Use chat models for exact metering. Chat models report usage for every call. For LangChain’s text-completion models, avoid streaming, and expect a batch of prompts to report its whole usage on the first prompt. A call with no usage goes to onError instead of being reported as zero.
  • Set the customer on your server. The customer ID decides who pays for a run. Take it from the logged-in user, never from the request, or a user can charge their usage to another customer. See Naming the customer safely.
  • Keep parallel spend within budget. Each check reads the balance at that moment, so steps that start together can each pass. Keep limits a little below the true cap, or reserve credits for the whole task. See Caching and accuracy.
  • Choose small credit units. Agent budgets count whole units and round each call up, so define your credit type in small units, such as thousandths of a dollar, to keep rounding negligible. See Usage governance.
  • Roll out without denials. Start with metering only, or register agents with no limit, then add limits once usage looks right. See Observe-only rollout.