Federal invariants
Runtime contracts enforced by the web-kernel — what "federal-grade" actually means at the boundary
When we say "federal-grade", we mean a specific thing: invariants enforced at runtime — not promises, not best practices, not unit tests. Code paths that violate them block PRs, fail validators, or fail-closed at runtime. This page covers the contracts extension developers most often interact with; the platform maintains a much larger internal inventory of contracts that govern kernel internals you do not need to know about.
What an invariant is
An invariant is a named, written-down property the platform commits to keeping true. Each one has:
| Field | Example |
|---|---|
| ID | I-AH-1 |
| Name | "No fabricated IDs in tool args" |
| Surface | Federal Anti-Hallucination |
| Statement | "Tool calls whose params model contains an id-shaped field MUST not contain values not seen in conversation history" |
| Enforcement | Runtime check in SDK chat handler — ALERT at >0 violations/hour |
| Bypass | None (federal) |
The platform enforces several hundred named contracts in production. Most of them govern kernel internals; the ones below are the surface most extension developers encounter. They cluster into about a dozen domains.
The big surfaces
Identity & Tenancy (W*)
ctx.user.[imperal_id](/en/reference/glossary/) canonicalization, tenant scope checks, fail-closed defaults.
Anti-Hallucination (I-AH-*)
Six invariants protecting chat from fabricated IDs, ungrounded data claims, out-of-enum hints, narrator factual claims without backing.
Authorization (I-AA-FU-*)
Acting-user header rules + confirmation lifecycle. The earlier workflow-level AA-branch fast-path has been retired (see I-NO-WORKFLOW-LEVEL-AA-BRANCH); these remaining FU-numbered invariants are the canonical authorization-layer rules.
Audit chokepoint (I-AUDIT-*)
Every action that touches user data lands in the federal [action ledger](/en/reference/glossary/).
[Pydantic feedback loop](/en/reference/glossary/) (I-PYDANTIC-*)
Bounded retry on validation errors with structured prose feedback.
Skeleton lifecycle (I-SKELETON-*)
Continue-as-new rotation, watchdog, live-invalidate.
Context channels (I-SKELETON-*, I-FACT-LEDGER-*, I-CROSS-TURN-*)
How skeleton, [fact-ledger](/en/concepts/fact-ledger/), and ctx.cache feed the [intent classifier](/en/reference/glossary/). Bounded sizes, staleness envelopes, PII gates.
Chain orchestration (I-CHAIN-*)
Multi-step planning, typed pipe, prior outputs, scope drift validation.
Magic UX (I-MAGIC-UX-*)
Conversational chat errors — no leaked stack traces, no Pydantic class names visible to users.
Confirmation flow (I-CONFIRMATION-*)
[Pre-Authorized Action Execution](/en/reference/glossary/) — what the user sees IS what fires.
The most-cited invariants
These come up constantly. Worth memorizing.
I-AH-1 — No fabricated IDs
Tool call args MUST NOT contain id-shaped values not seen in conversation history.If the LLM hallucinates note_id="abc123" in a delete_note call, the SDK catches it and refuses. This is enforced on every retry too — so a misbehaving LLM can't fabricate an ID on the second try and slip through.
I-CONFIRMATION-EXECUTES-WHAT-USER-SAW
When a user accepts a confirmation card, the action that runs MUST be byte-identical
to the one shown on the card. NO LLM rerun. NO arg drift.The web-kernel stores the original intercepted_calls and dispatches them typed-direct on accept. Federal cornerstone of Pre-Authorized Action Execution.
I-PYDANTIC-RETRY-BUDGET
Bounded retry on Pydantic ValidationError. Max 2 retries per tool_use. Beyond that
→ standard VALIDATION_MISSING_FIELD failure.Bounds blast radius. Nobody loops forever.
I-AA-FU-7-CONFIRMATIONS-STRICT-DEP
/v1/confirmations/* endpoints reject body/query fallback for the acting user_id.
Header X-Acting-User is REQUIRED.Closed an attack class where a malicious caller could spoof acceptance for another user.
I-CONVERSATIONAL-INTENTS-CANONICAL
The set of conversational intents is a canonical frozenset in core/intent.py.
Forks/divergence MUST go through the registry — not a local copy.V14–V22, V24, V31 — Manifest validators
ERROR-severity. Block publishing at Developer Portal time. The 9 you'll meet most:
| ID | Checks |
|---|---|
| V14 | Manifest schema v3 conformance |
| V15 | Display name + description not empty/placeholder |
| V16 | Icon resolves |
| V17 | Pydantic params model is module-scope |
| V18 | Pydantic params model has no forward references |
| V19 | All @chat.function descriptions ≥ 10 chars and non-boilerplate |
| V20 | action_type ∈ {'read', 'write', 'destructive'} |
| V21 | icon.svg declared + valid (XML-validated, viewBox required, ≤100KB, no embedded raster) |
| V22 | No print() in handlers |
| V24 | Handlers use ctx.http, never ctx.skeleton.* (AST scan) |
Skeleton lifecycle — I-SKELETON-CAN-ROTATE + I-SKELETON-WATCHDOG
Skeleton workflows MUST safely continue-as-new mid-loop.
A watchdog parent re-spawns the child on any terminal state.You don't wire this — the web-kernel does. But it explains why your skeleton has a continuous "life" the user sees.
I-CONFIRMATIONS-NO-BODY
/v1/confirmations/{id}/{accept,cancel} accepts NO body parameters.
Action arguments come from web-kernel-stored intercepted_calls — not from the request.Closes args-injection at confirmation time.
I-NO-REGEX-PRODUCTION-RUNTIME
Production runtime hot paths MUST NOT use `re` module for parsing user input.
Use parsers, structured matchers, or LLM-based extraction.Born from a 2026-04 incident. The federal feedback memo on this includes the rationale.
I-PARAMS-NO-PLACEHOLDER-VALUES
Tool calls whose argument values look like LLM-emitted placeholder
sentinels (e.g. <UNKNOWN>, <TODO>, <MISSING>, <EMAIL>, <PASSWORD>,
<USER_ID>) MUST be rejected before dispatch.When the user's message omits a required field, the wrapper LLM sometimes substitutes a placeholder token instead of asking. Before this invariant, the dispatch went through; the downstream anti-fab layer caught the drift on the response side (server did not reflect 'email': requested '<UNKNOWN>', got None), but by then billing tokens had been spent, the action ledger had a target=<UNKNOWN> row, and the user saw an opaque failure.
Now the SDK guard check_placeholder_args runs as the first layer in check_guards() — before write-arg-bleed, target-scope, and 2-step confirmation guards. A placeholder hit returns an instruction-to-LLM rejection that flows back through the chat loop as a synthetic tool_result, so the LLM self-corrects by asking the user a clarifying question. Tight regex (^<[A-Z][A-Z0-9_]*>$, full-anchored, uppercase-only) — substrings inside prose and lowercase HTML/XML tags do not trip the guard.
Landed in SDK v4.2.15.
I-CHAIN-PREFLIGHT-RESPECTS-USER-TOGGLE
Pre-flight confirmation cards MUST consult kctx.confirmation_enabled
(master toggle) AND kctx.confirmation_actions (per-action-type
granularity) on BOTH the single-action and multi-step chain paths.A user who disables 2-step in their settings must see uniform behaviour across single-action writes and multi-step chains. As of 2026-05-14 this is enforced on both surfaces — the multi-step path in session_workflow.py Phase 2B-2 (long-standing) and the single-action path in hub.handle_hub_chat (closing a Sprint unified-dispatch regression from 2026-05-07 where the gate set _needs_confirmation = True unconditionally on write/destructive plans).
How they get enforced
At publish time
V14-V22+V24+V31 validators. Failing any blocks publishing — your extension never reaches the marketplace until fixed.
At runtime
Pre-flight checks on every chat turn (UNKNOWN_SUB_FUNCTION, FABRICATED_ID, scope drift). Fail-closed.
Via observability
SigNoz alerts wired to invariant violation counts. >0 fires PagerDuty for some; >threshold for others.
At PR review
Web-kernel/SDK PRs that weaken invariant text or remove enforcement code get blocked at merge time.
What this means for you
Most invariants are free
If you write extensions the recommended way (@chat.function with Pydantic, ctx.http for HTTP, key state by ctx.user.imperal_id), you satisfy almost everything automatically.
A few are about your discipline
- Multi-tenant safety — federal can't enforce that you scope state by user. You must.
- Description quality — V19 catches empty/boilerplate, but a misleading description still confuses the LLM.
- action_type accuracy — calling something
"read"when it sends an email gets caught by audit, but breaks user trust before that.
Don't bypass — design around
If you find yourself wanting to bypass an invariant, either:
- The invariant has a real subset that fits your case → use the documented pattern
- The invariant doesn't fit your case → file a Dev Portal issue with the use case
Direct bypass attempts get caught at validate time.
Looking up an error message
When your code hits a runtime error that mentions an invariant ID, this table helps you find the right concept page or guide. Errors not listed here are usually accompanied by an explanatory message — read the message first, then this page if the invariant name is opaque.
| Error or symptom | Invariant | What it means |
|---|---|---|
SkeletonAccessForbidden raised in handler | I-SKELETON-LLM-ONLY | ctx.skeleton.get() was called outside an @ext.skeleton handler; skeletons are classifier-only |
| Skeleton section never appears in classifier responses | I-SKELETON-SMALL-LIST-INLINE | Section returned a list of >5 items and the kernel collapsed it to list[N] before injection |
| Skeleton numbers are stale despite recent activity | I-SKELETON-STALENESS-ENVELOPE | Classifier sees (cached ~Xs ago) and was told to fetch fresh for specific metrics |
Tool call rejected with placeholder argument | I-PARAMS-NO-PLACEHOLDER-VALUES | LLM emitted <UNKNOWN> / <TODO> / <EMAIL> as a value; the SDK rejected the call |
Tool call rejected with fabricated id | I-AH-1 | LLM emitted an id-shaped value that was never in conversation history |
PydanticValidationError after retries exhausted | I-PYDANTIC-RETRY-BUDGET | LLM failed validation 2+ times; standard VALIDATION_MISSING_FIELD returned |
Confirmation accept rejected with X-Acting-User required | I-AA-FU-7-CONFIRMATIONS-STRICT-DEP | Acting-user header is required on /v1/confirmations/*; body fallback rejected |
| Confirmation accept executes different args than displayed | (impossible — federal I-CONFIRMATION-EXECUTES-WHAT-USER-SAW) | Kernel always dispatches the byte-identical intercepted_calls; mismatch is a federal regression |
Chain step receives <unresolved $REF> value | I-CHAIN-REF-UNRESOLVED-WRITE-HARD-FAIL | A write step depended on a prior step's output that did not resolve; hard-fail by design |
| Narrator output mentions a number not in tool result | I-NARRATOR-NO-FABRICATED-CONTENT-CLAIMS | Federal anti-fabrication regression; report to platform team |
imperal build fails with V14–V31 error | Validators reference | Static manifest check failed; the error text identifies which rule |
Where to read all of them
The full invariant ledger lives in the web-kernel and SDK source under named comments. Public surfaces covering frequent extension-developer paths:
| Doc | Surface |
|---|---|
| Skeletons | I-SKELETON-* lifecycle, compression, staleness invariants |
| Fact-ledger | I-FACT-LEDGER-* cap and PII invariants, I-DISPATCH-RESULT-DATA-PLUMBED |
| Context channels | How the three classifier-visible channels coordinate |
| Chain dispatch | I-CHAIN-* orchestration, I-PARAMS-NO-PLACEHOLDER-VALUES |
| Pydantic feedback loop | I-PYDANTIC-* (5 invariants) |
| Confirmations guide | I-CONFIRMATION-* + Pre-Authorized Action Execution |
| Validators reference | V1 through V25+ static manifest checks |
The remaining several hundred invariants govern kernel internals (workflow management, audit chokepoint, billing, observability, deploy gates). They are tracked in the platform's internal contract inventory; an extension developer rarely needs to consult them directly. If you see an invariant ID in a runtime error message and it is not covered above, the error text will explain what enforcement triggered.