Federal invariants
Federal invariants — the runtime contracts Imperal Cloud enforces at the boundary so every extension stays safe, audited, and impossible to bypass at runtime.
When we say "federal-grade", we mean a specific thing: guarantees enforced at runtime — not promises, not best practices, not unit tests. Extensions that violate them fail validators at publish time or fail-closed at runtime. This page covers the guarantees extension developers most often rely on. The platform maintains a much larger inventory of internal contracts that govern how the kernel itself is built — those are not something an extension author needs to know.
What a federal guarantee is
A federal guarantee is a written-down property the platform commits to keeping true for every extension — automatically, whether or not you do anything. You don't wire them; you benefit from them. The sections below describe the ones you'll actually notice while building.
The big surfaces
Identity & tenancy
ctx.user.[imperal_id](/en/reference/glossary/) is canonical, tenant scope is checked at the boundary, and defaults fail closed.
Anti-hallucination
Chat is protected from fabricated IDs, ungrounded data claims, out-of-enum hints, and narrator factual claims without backing.
Authorization
The acting user is authenticated from a required header, and the confirmation lifecycle gates every write/destructive action.
Audit chokepoint
Every action that touches user data lands in the federal [action ledger](/en/reference/glossary/).
[Pydantic feedback loop](/en/reference/glossary/)
Bounded retry on validation errors with structured prose feedback.
Skeleton lifecycle
Skeletons keep a continuous 'life' — rotation, watchdog re-spawn, and live invalidation are all platform-managed.
Context channels
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
Multi-step planning, typed pipe between steps, prior outputs, scope-drift validation.
Magic UX
Conversational chat errors — no leaked stack traces, no Pydantic class names visible to users.
Confirmation flow
[Pre-Authorized Action Execution](/en/reference/glossary/) — what the user sees IS what fires.
The guarantees you'll notice most
These come up constantly while building. Worth knowing.
No fabricated IDs
If the agent hallucinates an ID it never actually saw — say note_id="abc123" in a delete_note call — the platform catches it and refuses. This holds on every retry too, so a misbehaving model can't fabricate an ID on the second try and slip through.
Confirmations execute exactly what you saw
When a user accepts a confirmation card, the action that runs is byte-identical to the one shown on the card. No model rerun, no argument drift. This is the cornerstone of Pre-Authorized Action Execution.
Bounded validation retry
When typed args fail validation, the agent gets at most 2 retries per tool call with structured feedback. Beyond that, the call fails with a standard VALIDATION_MISSING_FIELD. Nothing loops forever.
Acting-user is authenticated, not spoofable
Accepting or cancelling a confirmation requires an authenticated acting user — there is no body or query fallback to spoof acceptance for another user, and the confirmation endpoints take no action arguments from the request. This closes a class of confirmation-time injection and impersonation attacks.
Placeholder args are rejected before dispatch
When a user's message omits a required field, the agent sometimes substitutes a placeholder token (<UNKNOWN>, <TODO>, <EMAIL>, etc.) instead of asking. The platform rejects these before the action dispatches — so no billing tokens are spent, no misleading audit row is written, and the agent is steered to ask the user a clarifying question instead. (Genuine prose containing angle brackets, and lowercase HTML/XML tags, are not affected.) Landed in SDK v4.2.15.
Manifest validators block publishing
The federal validators run at publish time and most ERROR-severity rules block the package from reaching the marketplace. The ones you'll meet most:
| ID | Checks | Severity |
|---|---|---|
| V14 | Extension description too short or equal to app_id | ERROR |
| V15 | display_name empty, short, or equal to app_id | ERROR |
| V16 | @chat.function description shorter than 20 chars | ERROR |
| V17 | Pydantic params model is module-scope (not function-local) | ERROR |
| V18 | @chat.function declares a typed return (-> ActionResult / Pydantic model) | ERROR |
| V19 | actions_explicit=True; write/destructive tools are chain_callable=True | ERROR |
| V20 | write/destructive @chat.function declares effects | WARN |
| V21 | icon.svg declared + valid (XML-validated, viewBox required, ≤100KB, no embedded raster) | ERROR |
| V22 | Lifecycle-hook signatures match the SDK contract | ERROR |
| V24 | Handlers don't access ctx.skeleton.* outside @ext.skeleton (ERROR); write/destructive data_model recommendation (WARN) | ERROR / WARN |
| V31 | Extension(system=True) reserved for first-party Imperal authors | ERROR |
Skeletons stay alive
Your skeleton has a continuous "life" the user sees — rotation and watchdog re-spawn are platform-managed. You don't wire any of it.
Confirmation toggles apply uniformly
A user who disables 2-step confirmations in their settings sees uniform behaviour across single-action writes and multi-step chains — the gate consults the master toggle and the per-action-type granularity on both paths.
How they get enforced
At publish time
The federal validators run on every package. Failing any ERROR-severity rule blocks publishing — your extension never reaches the marketplace until fixed.
At runtime
Pre-flight checks on every chat turn (unknown tool, fabricated ID, scope drift, placeholder args). Fail-closed.
Via observability
The platform's observability pipeline tracks violation counts and alerts the platform team automatically.
At platform review
Changes to the platform that would weaken a guarantee or remove its enforcement are blocked before they ship.
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, this table maps the symptom to what happened and where to read more. Errors are usually accompanied by an explanatory message — read the message first, then this page if it's still opaque.
| Error or symptom | What it means | Where to read |
|---|---|---|
SkeletonAccessForbidden raised in handler | ctx.skeleton.get() was called outside an @ext.skeleton handler; skeletons are classifier-only | Skeletons |
| Skeleton section never appears in classifier responses | The section returned a list of more than 5 items and the platform collapsed it to a count before injection | Skeletons |
| Skeleton numbers are stale despite recent activity | The classifier was told the data is cached and to fetch fresh for specific metrics | Skeletons |
Tool call rejected with placeholder argument | The agent emitted <UNKNOWN> / <TODO> / <EMAIL> as a value; the platform rejected the call before dispatch | this page (above) |
Tool call rejected with fabricated id | The agent emitted an id-shaped value that was never in conversation history | this page (above) |
PydanticValidationError after retries exhausted | Validation failed 2+ times; standard VALIDATION_MISSING_FIELD returned | Pydantic feedback loop |
| Confirmation accept rejected with acting-user error | An authenticated acting user is required to accept a confirmation; body fallback is rejected | Confirmations |
| Confirmation accept executes different args than displayed | Impossible by design — the platform always dispatches byte-identical to what the user saw; a mismatch is a platform regression to report | Confirmations |
Chain step receives <unresolved $REF> value | A write step depended on a prior step's output that did not resolve; hard-fail by design | Chain dispatch |
| Narrator output mentions a number not in the tool result | Anti-fabrication regression; report it to the platform team | — |
imperal build fails with a V## error | A static manifest check failed; the error text identifies which rule | Validators reference |
Where to read more
Public pages covering the guarantees extension developers rely on most:
| Doc | Surface |
|---|---|
| Skeletons | Skeleton lifecycle, compression, staleness behavior |
| Fact-ledger | Fact-ledger cap and PII behavior |
| Context channels | How the three classifier-visible channels coordinate |
| Chain dispatch | Chain orchestration and placeholder-arg rejection |
| Pydantic feedback loop | Bounded validation retry |
| Confirmations guide | Confirmation flow + Pre-Authorized Action Execution |
| Validators reference | The static manifest validators |
The platform enforces many more contracts that govern how the kernel itself is built (workflow management, audit chokepoint, billing, observability, deploy gates). Those are internal — an extension developer never needs to consult them. If a runtime error message is not covered above, its text will explain what triggered.
What's next
Web-kernel context (ctx)
Web-kernel context (ctx) — every field your handler receives: user, store, AI, HTTP, secrets and more, user-scoped and federally injected once auth checks pass.
Chain dispatch
Chain dispatch — how one natural-language message becomes a dependency-ordered multi-step plan across extensions, with typed results flowing between steps.