Skip to main content

Limits and latency

Every number on this page is a default you can change, a bound you cannot, or a measurement. Where it is a measurement that has not been taken on release hardware yet, it says so rather than giving you a figure to plan against.

The four tiers and their budgets​

The checks run in tiers, in order, and stop as soon as one of them decides. The expensive tier is only reached by text the cheap ones did not already settle.

TierChecks in itDefault budgetWhat the budget is spent on
structurestructure50 msLength, invisible and look-alike characters, encoded content. Local string work.
patterndenylist, allowlist, secrets, pii400 msA few hundred expressions plus personal-data detection over up to 64 KiB of text.
classifierinjection, toxicity500 msTwo small text classifiers, one pass each.
semanticpolicy1500 msA dedicated safety model reads the text and judges it across ten categories. The only tier that costs tokens.

The budget is per check within that tier, not for the tier as a whole, and it is what a check has before it is treated as failed. Set any of them:

platformctl agents security set research-buddy \
--budget-ms structure=25 --budget-ms pattern=250 --budget-ms semantic=800

Bounds: 1 to 60000 ms. One millisecond is not useful but it is not wrong either; sixty seconds is the point past which your own client timeout has already fired and the budget is a fiction.

What a turn actually costs​

Measured at release

The observed added latency per turn - median and tail, per tier, on release hardware - is measured at release and published with the release notes. The numbers above are the time budgets a check is allowed, not the time it takes, and quoting a budget as a latency figure would be misleading in both directions: the fast tiers finish in a small fraction of theirs, and the semantic tier's real cost depends on text length and on how busy the safety model is.

What you can rely on today, because it is structural rather than measured:

  • An agent on the off preset adds exactly zero. No call is made.

  • The first three tiers are local text work on a CPU. No accelerator, no model server, no network hop beyond the one call.

  • Only the semantic tier involves a model, and tier_reached on every verdict tells you whether it ran. A verdict whose tier_reached is classifier did not pay for it.

  • Every verdict carries its own latency_ms, and each scanner carries its own. Your own feed is the authoritative measurement for your own traffic:

    platformctl agents security verdicts research-buddy --limit 200 -o json

Two policy choices dominate the cost, and both are opt-in:

SettingEffect on latency
input.semantic.enabledOn in standard. This is the model call; it is the difference between tens of milliseconds and hundreds.
output.semantic.enabledOff in standard, on in strict. A second model call, on the reply. The single most expensive thing a policy can ask for.
tools.enabledOff in every preset. Adds a round trip per tool call, doubled when both arguments and results are checked.

If latency matters more than the semantic judgement, turn the judgement off and keep everything else:

platformctl agents security set research-buddy --semantic off

That leaves credentials, personal data, injection and abuse checks running at pattern-and-classifier speed.

Text size​

LimitValueEnforced by
text in one check65536 bytesThe platform. 413 with code text_too_large.
input.structure.max_charsdefault 65536, settable 1–1048576Your policy. Your own decision and your own refusal message.

The platform cap is in bytes, not characters, and the policy setting is in characters. A message of 65536 emoji is 262144 bytes and over the platform's line regardless of what max_chars says - which is why the byte cap exists: the byte budget is what protects the service.

Raising max_chars above 65536 therefore does not raise the platform cap. It is useful in the other direction: set it lower to refuse long text under your own policy, with your own refusal message, rather than letting a 413 surface to whatever is calling you.

For documents larger than the cap, split and check each part, keeping the same session_id and passing the mapping_ref forward through the parts so placeholder ordinals stay consistent across the whole document. See the standalone API.

history is read by the semantic tier and does not count toward the text cap. Keep it to the turns that matter - it is text a model reads, so it costs tokens and time.

Personal data caps​

LimitValueWhat happens at it
Distinct values per session200New values are masked to <PII_{TAG}_OTHER>, which is not restorable. The verdict's reason says so.
Mapping reference lifetimeone hour by default, set by your platform operatorA reference past its expiry is refused with 403 and code mapping_invalid.
Entity types in entitiesno fixed cap; nine are detected by defaultA type the detector does not know is simply never found.

The 200 cap exists because the mapping reference travels in every request and response of the conversation. An unbounded one grows until the conversation stops working, so it is bounded where a real conversation will not reach it. Two hundred distinct people, addresses and numbers in one conversation is a batch job wearing a chat interface; if that is your workload, use one session per document rather than one session per run.

The reference's lifetime is sized for a conversation, not for a week. It is deliberately much shorter than any credential's.

Retention​

LimitValue
logging.retention_daysdefault 30, settable 1–365
Audit entriespermanent, append-only - not governed by retention_days
Agent logs14 days, the platform default for every workload
platformctl agents security set research-buddy --retention-days 90
platformctl guard policy set --retention-days 90 # the project-wide default

A verdict expires. The audit record that a refusal happened does not. See verdicts and audit.

Rate limits​

LimitDefault
Checks per credential, sustained200 per second
Burst400

Counted per credential and per serving instance, so the effective ceiling a caller sees is higher than the figure above. It is high on purpose: the point is to have the knob before it is needed, not to throttle ordinary use. Crossing it is 429 with code rate_limited and a Retry-After header - always an error code, never a silent allow. Your platform operator can change both numbers.

Other bounds​

FieldBound
refusal.message1024 characters
Any threshold0 to 1
budget_ms.<tier>1 to 60000 ms
logging.retention_days1 to 365
input.structure.max_chars1 to 1048576
Verdicts per feed page1 to 1000 (--limit)
Safety categories10, fixed: violent, sexual, self_harm, illegal, hate, privacy, misinformation, copyright, jailbreak, unethical
Checks8, fixed: structure, denylist, allowlist, secrets, pii, injection, toxicity, policy
Decisions4, fixed: allow, mask, flag, block
Error codes8, fixed - see the standalone API

The fixed sets are a frozen contract. Adding a value is a change we announce; renaming one is a breaking change we will not make quietly. You can branch on them.

When a tier is unavailable​

This is the important half of the page, because it is the behaviour you get on your worst day.

A check can fail for reasons that have nothing to do with the text: it runs out of its time budget, a dependency it needs is unreachable, something raises. None of those produce an HTTP error. They produce a verdict, and which verdict is your choice:

fail.<tier>The verdictThe end user seesThe record
opendecision: flag, original text carried throughtheir reply, normallya verdict whose scanner entry has error populated, reason A security check could not complete, so the request was recorded and allowed.
closeddecision: block, text: nullthe refusal messagethe same, reason A security check could not complete, so the request was not allowed.

Defaults by preset:

TierstandardstrictWhy
structureclosedclosedLocal string work. It essentially cannot fail except under real trouble, so refusing is right.
patternclosedclosedAlso local. This is the tier that catches credentials; letting text past unchecked is the wrong trade.
classifieropenclosedMore moving parts. Refusing every request because a classifier is restarting is an outage you inflicted on yourself.
semanticopenclosedDepends on a model server. Same reasoning, more so.

Set them individually:

# Refuse rather than let anything through unchecked, on every tier.
platformctl agents security set research-buddy \
--fail-pattern closed --fail-classifier closed --fail-semantic closed

# Or the same thing in one word.
platformctl agents security set research-buddy --preset strict

What each kind of unavailability looks like​

The safety model is unreachable. The semantic check obeys fail.semantic. Under standard that is open: requests continue, each one recorded as a flag with the error on the scanner entry, and the cheap tiers are all still running. Under strict it is closed and requests are refused with the refusal message. Either way model is absent from the verdict, because the tier did not run.

The whole guardrail service is unreachable. An agent is not left choosing between unguarded and broken: the harness applies the policy's fail behaviour for the tier it could not reach, so a standard agent flags and continues and a strict agent refuses. A caller of the standalone API gets 503 with code guard_unavailable and decides for itself - which is why your own policy's fail settings are the thing to get right rather than your retry loop.

A tier is slow rather than down. The budget fires and it is treated as a failure, by the same rules. Raising budget_ms for that tier trades latency for fewer of these; lowering it does the reverse.

Your policy could not be read. A running agent whose policy document did not arrive guards with conservative local defaults and says so at startup, rather than serving unguarded. That failure is visible in the agent's log and fixed by rolling a new version:

platformctl logs research-buddy | grep guardrails

Whatever the failure, the property that matters to a caller is the same: a failed check is a verdict with a decision in it. The branch you already wrote handles it, and there is no second error path to design.

Next steps​