Docs Product

Limits

Every other check in KIFF asks whether one action is allowed: is the entity in the right state, does this actor have the permission, are the parameters valid, does this need an approval. A limit asks the one question no single action can answer, what has this agent already done, and is that enough?

An action can be correct in every way and still be the one that should not run, because of the twenty that came before it. That is what a limit catches.

What a limit is

A limit, a mandate: is a bounded, revocable grant of authority to one named agent:

refund-agent may run AUTO_REFUND and ISSUE_CREDIT, up to a total of 110000 summed over the amount parameter, each calendar day.

Four parts:

  • A subject: the agent this binds. It must match the actor_id the agent already sends when it proposes an action.
  • Grants: the actions it covers. An action you leave out is still governed by the contract; it just does not draw on this ceiling.
  • A quantity: either count (how many times those actions run) or sum(<parameter>) (the total of one numeric parameter the actions declare).
  • A ceiling and a window: how much, resetting each calendar day, or over any rolling 24 hours, or any rolling hour.

What happens at the ceiling

The action is refused. It is not sent for approval.

This is deliberate. A cap that produces an approval request someone clicks through at the end of a long day is not a cap; it is a notification with extra steps, and it re-creates exactly the rubber-stamping that let the decision drift to the machine in the first place.

A proposal past the ceiling comes back as:

{
  "outcome": "blocked",
  "reasons": ["mandate_limit_reached"],
  "message": "this agent has no authority left under mandate limit-refund-agent: ..."
}

If an agent needs more room, you raise the limit: one deliberate act that leaves a record, rather than approving past it one action at a time. That is the same decision, made visible and attributable instead of ambient.

Some properties worth knowing

A limit constrains; it never confers. An agent with no mandate is unaffected. This is the opposite of a capability system, and it is why you can adopt limits one agent at a time without a first mandate breaking anything already running.

Several limits can cover one action, and the tightest binds. Each is checked; the first that refuses decides. There is no precedence rule to reason about, and no way to widen an agent’s authority by adding another mandate.

A limit that cannot be read refuses. If the ledger is unreachable, the action is blocked rather than allowed, because allowing means acting on authority nobody could confirm. The Limits page marks such a limit unavailable rather than showing a comforting zero, an unused limit and an unreadable one are very different facts.

A revoked limit refuses; it does not disappear. Revoking does not return the agent to “unbounded”. A proposal under a revoked mandate is blocked with mandate_expired.

Draws are recorded when an action is authorized, not when it executes. KIFF decides; your system executes, and reports no outcome back. An authorization your code chose not to run still counts against the window. A retry of the same proposal does not draw twice.

A limit binds the approved path too. When an operator grants an approval, the re-decision runs the same check. Approval does not buy authority.

Observe mode

A limit is checked even when the domain is in observe. In observe your runtime executes anyway, so the money is spent and the draw is real - and counting it is exactly what lets you see what your agent would have spent before you enforce anything.

That is the recommended on-ramp: issue a limit deliberately high, run for a week, read the statement, and set the real number from your own traffic instead of from a guess.

The statement

Limits in the dashboard shows, for each agent: the ceiling, how much has been drawn in the current window, what remains, and every individual draw with the proposal that caused it.

It reads like a card statement for a machine, which is the point. The question “what has this agent been doing with the authority we gave it?” should have an answer you can hand to someone else.

Setting one

Limits → Issue a limit in the dashboard. The form offers the actions your domain declares and only the parameters it types as a number, so a quantity that cannot be summed is not offered in the first place.

Or over HTTP, with a management credential:

curl -sS -X POST https://api.kiff.dev/v1/me/mandates \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "id": "limit-refund-agent",
        "subject": "refund-agent",
        "domain": "refund-control",
        "grants": [{"action": "AUTO_REFUND"}],
        "aggregates": [
          {"quantity": "sum(amount)", "limit": 110000, "window": "calendar_day"}
        ]
      }'

Issuing and revoking require a management identity, the signed-in account owner or an admin. An agent’s own runtime key is refused. A credential that could grant itself authority would make every limit advisory.

Re-issuing the same id updates that limit in place; draws already recorded still count, so raising a ceiling grants the difference rather than a fresh window.

What this is not

  • Not a rate limit. A rate limit protects your infrastructure from volume. A limit bounds consequence: what an agent is permitted to do, in the units the action is measured in.
  • Not a spend tracker. Nothing here reads your ledger or your bank. It counts what KIFF authorized, and says so on the statement.
  • Not portable. A mandate is enforced by your KIFF tenant. Signing one so a third party could verify it independently is a later question.

See also

  • Decisions, the proposal flow a limit is checked in, and the outcomes it can produce.
  • Govern, the per-control evidence view, and the observe/enforce posture a limit is checked under either way.
  • Protected Controls, how one consequential action is expressed, which is what a limit’s grants name.