Imperal Docs
Core Concepts

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:

FieldExample
IDI-AH-1
Name"No fabricated IDs in tool args"
SurfaceFederal Anti-Hallucination
Statement"Tool calls whose params model contains an id-shaped field MUST not contain values not seen in conversation history"
EnforcementRuntime check in SDK chat handler — ALERT at >0 violations/hour
BypassNone (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.

V14V22, V24, V31 — Manifest validators

ERROR-severity. Block publishing at Developer Portal time. The 9 you'll meet most:

IDChecks
V14Manifest schema v3 conformance
V15Display name + description not empty/placeholder
V16Icon resolves
V17Pydantic params model is module-scope
V18Pydantic params model has no forward references
V19All @chat.function descriptions ≥ 10 chars and non-boilerplate
V20action_type ∈ {'read', 'write', 'destructive'}
V21icon.svg declared + valid (XML-validated, viewBox required, ≤100KB, no embedded raster)
V22No print() in handlers
V24Handlers 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:

  1. The invariant has a real subset that fits your case → use the documented pattern
  2. 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 symptomInvariantWhat it means
SkeletonAccessForbidden raised in handlerI-SKELETON-LLM-ONLYctx.skeleton.get() was called outside an @ext.skeleton handler; skeletons are classifier-only
Skeleton section never appears in classifier responsesI-SKELETON-SMALL-LIST-INLINESection returned a list of >5 items and the kernel collapsed it to list[N] before injection
Skeleton numbers are stale despite recent activityI-SKELETON-STALENESS-ENVELOPEClassifier sees (cached ~Xs ago) and was told to fetch fresh for specific metrics
Tool call rejected with placeholder argumentI-PARAMS-NO-PLACEHOLDER-VALUESLLM emitted <UNKNOWN> / <TODO> / <EMAIL> as a value; the SDK rejected the call
Tool call rejected with fabricated idI-AH-1LLM emitted an id-shaped value that was never in conversation history
PydanticValidationError after retries exhaustedI-PYDANTIC-RETRY-BUDGETLLM failed validation 2+ times; standard VALIDATION_MISSING_FIELD returned
Confirmation accept rejected with X-Acting-User requiredI-AA-FU-7-CONFIRMATIONS-STRICT-DEPActing-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> valueI-CHAIN-REF-UNRESOLVED-WRITE-HARD-FAILA 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 resultI-NARRATOR-NO-FABRICATED-CONTENT-CLAIMSFederal anti-fabrication regression; report to platform team
imperal build fails with V14V31 errorValidators referenceStatic 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:

DocSurface
SkeletonsI-SKELETON-* lifecycle, compression, staleness invariants
Fact-ledgerI-FACT-LEDGER-* cap and PII invariants, I-DISPATCH-RESULT-DATA-PLUMBED
Context channelsHow the three classifier-visible channels coordinate
Chain dispatchI-CHAIN-* orchestration, I-PARAMS-NO-PLACEHOLDER-VALUES
Pydantic feedback loopI-PYDANTIC-* (5 invariants)
Confirmations guideI-CONFIRMATION-* + Pre-Authorized Action Execution
Validators referenceV1 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.

What's next

On this page