Get API key →

OpenClaw spend limit, one ceiling per session

OpenClaw pluginClawHub 0.1.0

One ceiling per OpenClaw session. Before every tool call, and before every model turn on OpenClaw's embedded and CLI runners, the plugin asks AgentBill whether the session's running total plus an estimate of what comes next would pass it. When it would, preflight answers approved: false, the plugin hands that refusal to OpenClaw's gate hook, and OpenClaw does not run the tool or send the turn. On the Codex and Copilot harnesses OpenClaw does not run before_agent_run, so only tool calls are asked.

Install

openclaw plugins install clawhub:@agentbill/openclaw

The package is @agentbill/openclaw on ClawHub. Its plugin id, and the key under plugins.entries in your OpenClaw config, is agentbill.

Then give it a key. Create a free one (1,000 preflight calls a month, no card) and put it in the plugin config, or set AGENTBILL_API_KEY in the Gateway's environment and leave apiKey out.

{
  "plugins": {
    "entries": {
      "agentbill": {
        "enabled": true,
        "hooks": { "allowConversationAccess": true },
        "config": {
          "apiKey": "agb_...",
          "ceilingUnits": 500000
        }
      }
    }
  }
}

agb_... stands for your key. ceilingUnits is each session's ceiling, in the unit described below.

Set allowConversationAccess, or model turns go unasked

The hooks.allowConversationAccess line is not optional. Unless the operator sets it, OpenClaw does not register before_agent_run or llm_output for a plugin it does not bundle, and it registers the other five hooks without saying so. Tool calls are then asked about and model turns are not, and in tokens mode nothing is recorded, so the ceiling never moves. The plugin checks the flag when it loads, and when it is missing it logs an error naming the command to run:

openclaw config set plugins.entries.agentbill.hooks.allowConversationAccess true

Restart the Gateway, then list the typed hooks the host accepted:

openclaw plugins inspect agentbill --runtime

The Gateway log line [agentbill] ceiling 500000 tokens per session, ... conversation hooks allowed means it is on. Uninstalling the plugin removes its whole plugins.entries.agentbill entry, the flag with it, so set it again after a reinstall.

With the flag set, model turns are asked only where OpenClaw runs before_agent_run, which is its embedded and CLI runners. On the Codex and Copilot harnesses OpenClaw does not run that hook, so only tool calls are asked and each model turn goes to the model unasked.

What one unit is

units: "tokens" is the default. After a model turn runs, the plugin records the usage total OpenClaw reports for it in its llm_output hook, which covers every model call in the turn, so what counts against the ceiling is the host's own number, not an estimate you write. Before a call it reserves an estimate: estimateUnits for a session's opening call, then the session's running average of what those reports came to. Tool calls record nothing in tokens mode; the model calls around them are what cost.

units: "calls" counts one unit for each llm_output report and one for each tool call. OpenClaw's embedded runner makes that report once per model turn (once per attempt, if it retries one), however many model calls the turn made, so a ceiling of 40 is forty turns and tool calls together, not forty provider requests.

The plugin measures no provider and no tool. It records what OpenClaw already counts.

What a refusal looks like

Before a model turn, preflight answers approved: false with task_ceiling_exceeded. The plugin returns its sentence from OpenClaw's before_agent_run gate, OpenClaw does not send the prompt to the model, and the session shows the sentence inside a label of OpenClaw's own:

Sample, a session past its ceiling

AgentBill refused (task_ceiling_exceeded): openclaw:agent:main:discord:1234 is at 501200/500000 tokens. Raise the ceiling at https://agentbill.dev/app or start a new session.

Before a tool call, the plugin returns the same sentence from before_tool_call, OpenClaw does not run the tool, and the model receives the sentence as the tool's result.

The session itself stays open. The next inbound message starts a new turn, and that turn asks again: raise the ceiling in the console and it goes through, or start a new session, which has a ceiling of its own.

Loops, subagents and fan-out

A tool loop inside one session is one task_ref already. Every tool call asks before it runs, and on the embedded and CLI runners every new model turn asks before the model sees it. The model calls inside a turn are not asked one by one: their usage is recorded after they run, from the total OpenClaw reports, so a turn can end past the ceiling, as the session in the sample above did. A refused tool call goes back to the model as the tool's result, and that model call runs too. Once the total is past the ceiling, the next tool call is refused, and so is the next turn wherever turns are asked. The ceiling has no clock. It does not refill at the top of an hour or a month, so a session that has spent it is refused until you raise it.

A subagent draws on its parent's ceiling when OpenClaw reports which session spawned it: the plugin reads requesterSessionKey from the subagent_spawned context and links the child to the parent's task_ref, so a fan-out consults one number. When OpenClaw does not report it, the child session gets a ceiling of its own rather than none.

How it maps onto OpenClaw's hooks

HookKindWhat the plugin does
session_startobserveOpens the session's task_ref, openclaw: plus the session key.
subagent_spawnedobserveLinks the child session to the parent's task_ref.
before_agent_rungateCalls preflight. On a refusal, returns the refusal sentence to OpenClaw. Run by the embedded and CLI runners only.
before_tool_callgateCalls preflight. On a refusal, returns the refusal sentence to OpenClaw.
llm_outputobserveRecords the usage total OpenClaw reports.
after_tool_callobserveRecords toolCallUnits, when above zero.
session_endobserveForgets the session.

Hook names and kinds are OpenClaw's, from its hook reference for openclaw 2026.9.4.

When AgentBill cannot be reached, or its own quota is spent

failMode is closed by default: if AgentBill cannot be reached, the plugin refuses the call and says why. Set it to open to let calls run, with a log line, while AgentBill is unreachable. timeoutMs caps each request, and the plugin keeps it under the 15 seconds OpenClaw allows a gate hook, so a refusal arrives with a reason rather than as a timeout.

AgentBill's own monthly quota is a different thing from your ceiling. When it is spent, preflight answers free_tier_exceeded or plan_limit_exceeded, and the plugin lets the call run and logs one warning per session with the upgrade link. Our billing state is never the reason a turn of yours is refused.

Raising or lowering one session's ceiling

ceilingUnits applies when a session's task is opened. After that the ceiling is the server's, and a later value in the plugin config is not applied to a session that already has one. Change it in the console, or with PUT /tasks/:task_ref/ceiling, where the task_ref is openclaw: followed by the session key:

curl -X PUT "https://agentbill.dev/tasks/openclaw:agent:main:discord:1234/ceiling" \
  -H "Authorization: Bearer $AGENTBILL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ceiling_units":800000}'

A ceiling below what the session has already used and reserved is answered with 409 ceiling_below_committed, which carries the smallest value that would be accepted.

What it does not do

Source and listing

The listing is on ClawHub at clawhub.ai/agentbill/plugins/openclaw. The source is in plugins/openclaw in the AgentBill repository, and its README carries the full options table.

Get API key →