> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stigg.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Google ADK

> Govern a multi-agent Google ADK graph with one Runner plugin: every model call and paid tool is checked before it runs, and its usage is reported as the agent that made it.

`StiggAdkPlugin` governs a Google ADK graph. Add it to a Runner's `plugins`, and ADK runs it for every agent the Runner reaches, including sub-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](https://docs.stigg.io/documentation/ai-agents/overview.md#agent-frameworks).

## Quick start

You need a Google ADK app, a Stigg server API key, and a credit type in Stigg, here `credits`, that your customers have.

**1. Install the SDK**

```bash theme={null}
npm install @stigg/agent-sdk-js
```

`@google/adk` 2.x is an optional peer dependency. The plugin uses the copy your app already has.

**2. Create the Stigg client**

Create it once, when your server starts:

```typescript theme={null}
import { StiggAgentSDK } from '@stigg/agent-sdk-js';

const stigg = new StiggAgentSDK(process.env.STIGG_SERVER_API_KEY);
```

**3. Create the plugin**

Tell the plugin what to check each step against, and how to find the customer a step is for:

```typescript theme={null}
import { StiggAdkPlugin } from '@stigg/agent-sdk-js/adk';

const stiggPlugin = new StiggAdkPlugin({
  stigg,
  stiggContext: { entitlement: { currencyId: 'credits' } },
  stepContext: ({ adkContext }) => ({ customerId: customerOf(adkContext.userId) }),
});
```

ADK has no per-run metadata, so `stepContext` names the customer. It's called for every step, and `customerOf` stands for your own lookup from the logged-in user. See [Naming the customer safely](#naming-the-customer-safely).

**4. Add the plugin to the Runner**

Your agents need no changes. Put the plugin first in `plugins`:

```typescript theme={null}
import { InMemoryRunner } from '@google/adk';

// Your existing ADK agent tree.
const runner = new InMemoryRunner({ agent: coordinator, appName, plugins: [stiggPlugin, ...otherPlugins] });

for await (const event of runner.runAsync({ userId, sessionId, newMessage })) {
  // ...
}
```

ADK stops at the first plugin that answers a step, so a plugin before Stigg's can hide that step from Stigg.

**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 plugin reports aren't converted into credits yet. See [The credit balance does not go down](https://docs.stigg.io/documentation/ai-agents/usage-metering.md#the-credit-balance-does-not-go-down).

To see each decision while you test, add a hook to the plugin:

```typescript theme={null}
hooks: { onDecision: (decision, context) => console.log(context.agent, decision.hasAccess) },
```

## 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 resolve it on your server, from the logged-in user.

* **`adkContext.userId`** is safe only when your server sets it from the login. ADK's own API server lets the client choose `userId`.
* **Session state** is never safe, because the client can send a `stateDelta` with every run.
* **A default customer** in the plugin's `stiggContext: { customerId }` is charged for any step whose `stepContext` returns nothing. Set one only in a process that serves one customer.

The agent a step belongs to comes from the agent tree you built, not from the request, so a client can't change whose budget pays.

## Configurations

The plugin takes the same `stiggContext`, `agents`, `tools` and `hooks` options as the [LangChain handler](https://docs.stigg.io/documentation/ai-agents/orchestrators/langchain.md#configurations). Each configuration below adds to the one before it.

### Metering only

```typescript theme={null}
new StiggAdkPlugin({ stigg, stiggContext: {}, stepContext });
```

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 ADK's token counts under Gemini's names, such as `promptTokenCount` and `candidatesTokenCount`. See the [Gemini page](https://docs.stigg.io/documentation/ai-agents/providers/gemini.md) for what each count includes, then create meters for them in Stigg or convert them with a `usageMapper`.

### One budget for the whole team

```typescript theme={null}
import { unitsPerTokens } from '@stigg/agent-sdk-js';

new StiggAdkPlugin({
  stigg,
  stiggContext: {
    entitlement: { currencyId: 'credits' },
    usageMapper: unitsPerTokens('credits', 1_000),   // one credit per 1,000 tokens
    ingestMode: 'sync',
  },
  stepContext,
});
```

Every model call, by every agent the Runner reaches, is checked against the customer's credits, and its usage is reported against them.

### A budget per agent

Register a parent agent and one agent per team member in Stigg, as in [Register the agents](https://docs.stigg.io/documentation/ai-agents/orchestrators/langchain.md#a-budget-per-agent). Then key `agents` by the `name` given to each `LlmAgent`:

```typescript theme={null}
const credits = { currencyId: 'credits' };

const stiggPlugin = new StiggAdkPlugin({
  stigg,
  stiggContext: { attributionKey: 'agentId', entitlement: credits, usageMapper, ingestMode: 'sync' },
  agents: {
    coordinator: { stiggContext: { agentId: 'agent-coordinator' } },
    researcher: { stiggContext: { agentId: 'agent-researcher' }, tools: { web_search: credits } },
  },
  stepContext,
});
```

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 coordinator keeps working. What `stepContext` returns is merged over the plugin's and the agent's settings, and a tool's price from `tools` still applies over it.

## Tools

Paid tools are listed under `tools`, as in the [LangChain handler](https://docs.stigg.io/documentation/ai-agents/orchestrators/langchain.md#tools). A tool counts against the agent whose model asked for it. Three kinds of tools work differently in ADK:

* **ADK's control tools**, `transfer_to_agent`, `exit_loop`, `set_model_response` and `finish_task`, are never checked, not even by `'*'`. They do no work, and denying one would break the agent.
* **Gemini's built-in tools**, such as `GOOGLE_SEARCH`, run inside the model call, so they're counted in the call's tokens.
* **An agent wrapped in an `AgentTool`** runs in a Runner of its own, without the plugin, so its model calls aren't governed. The plugin warns once. Add the agent as a sub-agent instead.

## When access is denied

ADK doesn't let a plugin throw, so the plugin answers a denied step instead:

| Denied step | What happens |
| - | - |
| A paid tool | The tool never runs. Its result is a short error, such as `web_search is not available: BudgetExceeded.`, and the model carries on without it. |
| A model call | The model is never called. The agent answers with what `onRefusal` returns, or ends its turn in an error response with `errorCode: 'STIGG_ACCESS_DENIED'`. |

Use `onRefusal` to hand a denied agent's turn to another agent. In this example, a researcher that runs out of budget hands its turn to a cheaper `generalist` agent, which answers without search:

```typescript theme={null}
const stiggPlugin = new StiggAdkPlugin({
  stigg,
  stiggContext,
  agents,
  stepContext,
  onRefusal: ({ agent }) => (agent === 'researcher' ? { transferTo: 'generalist' } : undefined),
});
```

* **`transferTo`** must name an agent ADK lets the denied one transfer to. Any other name throws, as a configuration error.
* **An agent hands off at most once per run**, so two denied agents can't pass the turn back and forth.
* **Any other `LlmResponse`** you return is used as the agent's answer.

To show the denial to the user, read it from the run's events with `stiggPlugin.denialOf(event)`. It returns the decision behind a denied step, with a `message` you can show, or `undefined` for any other event:

```typescript theme={null}
for await (const event of runner.runAsync({ userId, sessionId, newMessage })) {
  const decision = stiggPlugin.denialOf(event);
  if (decision) showUpgradePrompt(decision.message);   // your own UI
}
```

A denied model call's decision travels in its event's `customMetadata`, which the model never sees.

If Stigg can't be reached to check, the SDK follows `checkErrorAction`, which lets the step run by default. See [Fallback strategy](https://docs.stigg.io/documentation/ai-agents/access-enforcement.md#fallback-strategy). Reporting usage never throws into the run.

## How usage is reported

* **A model call** is reported when its last response arrives, before ADK hands that event to you. A caller that stops reading after the answer still has the call reported.
* **A streamed call** is reported once, with its final total.
* **A call with no finish reason** is reported at the agent's next step, or when the agent or the run ends.
* **A failed model call or tool** reports nothing.

## Observability

Hooks, tracing spans and flushing work as they do in the LangChain handler. See [Observability](https://docs.stigg.io/documentation/ai-agents/orchestrators/langchain.md#observability).

## Best practices

* **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. Return it from `stepContext`. See [Naming the customer safely](#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](https://docs.stigg.io/documentation/ai-agents/access-enforcement.md#reserving-credits-for-a-task) for the whole task. See [Caching and accuracy](https://docs.stigg.io/documentation/ai-agents/access-enforcement.md#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](https://docs.stigg.io/documentation/ai-agents/usage-governance.md).
* **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](https://docs.stigg.io/documentation/ai-agents/usage-governance.md#observe-only-rollout).


## Related topics

- [JavaScript](https://docs.stigg.io/api-and-sdks/changelog/agent-sdk/javascript.md)
- [AI agents](https://docs.stigg.io/documentation/ai-agents/overview.md)
- [Usage governance](https://docs.stigg.io/documentation/ai-agents/usage-governance.md)
- [Providers and frameworks](https://docs.stigg.io/documentation/ai-agents/providers/overview.md)
- [Access enforcement](https://docs.stigg.io/documentation/ai-agents/access-enforcement.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.