WHEN IT FAILS

What will not work

A constant mapping key, an inherited write with no guard, OpenZeppelin's Ownable, a contract you cannot redeploy. Then the quotas a shipped app runs into, and what happens past each.

The loop in First hour is real. Clicker, the contract the starter gives you (contracts/src/Clicker.sol in a create-interlude-app project, with its tests), keeps an ordinary mapping(address => uint256) and is delegated as written: an ordinary mapping survives the loop. The cases below are the ones that path does not forgive.

A constant mapping key
balances[HOUSE] where HOUSE is address constant lets the optimizer fold keccak256(HOUSE, slot) into a literal. No hash runs, the node cannot see the entry, the write is refused. Make the key immutable.
An inherited OpenZeppelin write
_transfer has no whenNotDelegated. If it writes a slot the node also holds, the next commit fails on oldValue and the session stalls until forceClose. Every write path is yours to audit.
OpenZeppelin Ownable or Ownable2Step
Delegatable already has an owner: owner(), pendingOwner(), the onlyOwner modifier, a two-step transferOwnership / acceptOwnership, and the OwnershipTransferred and OwnershipTransferStarted events. Inherit OZ's as well and those are defined twice: the contract does not compile, and no override fixes it, because Delegatable's are not virtual. Drop the OZ import and its Ownable(initialOwner) constructor call. Your onlyOwner lines then mean Delegatable's owner, the one ship --owner hands you. There is no renounceOwnership.
Storage you inherit
The @custom:interlude annotation goes on the declaration. A variable declared in a library you do not edit, such as OpenZeppelin ERC20's private _balances, cannot carry it, so it cannot be delegated. Keep that state on Monad, or declare it in your own contract. A parent of your own can carry it, but cli 0.2.3's gen reads annotations only from the file of the contract you name: keep the parent in that file, and check that gen lists every variable you marked.
A contract that cannot be redeployed
Delegated storage is declared at construction. An immutable live contract cannot be adopted. That is a property of the EVM, not of this implementation.
A delegated value as an oracle
A getter called on Monad while the partition is live returns the last committed value, with no revert. Delegated.isStale exists; nothing forces a caller to check it. If another protocol must read the live value, that slot belongs on the base chain.
msg.sender inside a session
withSession calls your contract on itself, so during a session call msg.sender is the app, not the user. Anyone can self-sign a grant, so code that trusts msg.sender runs as the contract. Use _actor(). Token-moving and ownership selectors are refused in a session by default (the fixed Delegatable adds multicall, ERC-1363, ERC-4626, ERC-777 and burn). Only the outer selector is checked: override _isSessionBlocked and return super to add yours, including any function that forwards calldata or calls the contract itself.
Two partitions in one call
pass that debits A and credits B needs both entries in the same partition. That is @custom:interlude global on the mapping, not per-key.

Quotas and failure modes

What a shipped app runs into on the hosted path, and what happens past each. The SDK retries what can be retried; the rest is a sentence you can put on screen.

ship
5 deploys an hour per IP and 20 an hour for everyone; 10 failed ones an hour per IP. The deployment may use 12M gas; interlude.toml may name at most 4 [[setup]] calls, 4M gas together. Past a limit control answers with a sentence naming it, and ship prints it and stops.
A refusal after the deploy
Control still names the app: it answers with the error, the address it deployed and a retry command. ValidatorAtCapacity means the validator has no free delegation, so no node starts; ship again later.
Commits
A node ship starts commits every 10 s (Interlude Exchange's books every 2 s). A batch carries at most 233 changed slots. A call that alone writes more is refused with NodeBusyError, retryable: false: split it.
One call
16 KiB signed at most. The SDK signs each call for 5M gas unless you pass gas to createInterludeClient. A call must carry no value: the node moves no MON.
Per caller and per signer
6,000 calls in 10 s per IP (HTTP 429, or -32005 over the WebSocket), and 100 transactions a second per signer (-32005). A full batch answers "retry after the next commit". send retries each with the same signed transaction, unless the node asks for a wait over 5 s, then throws NodeBusyError.
Who pays for commits
Control relays every hosted commit and pays its gas, from hourly budgets: one per app, one shared by every partner app. A spent budget answers 429, and the node waits and sends the commit again.
When commits keep failing
A spent budget that stays spent, or Monad's RPC down: after a run of failed commits, or 30 minutes of them, the node refuses writes rather than promise state it cannot settle. GET /ready answers 503, reads keep working, and writes come back once a commit lands.
When the node goes silent
With no commit for maxBatchInterval (1 hour on the live terms) plus a five-minute grace, anyone can forceClose the session. The app then stays locked through the challenge window, until releaseStake (see Delegation).

Every number, with the setting behind it and the exact refusal, is in LIMITS.md; every address, and the validator’s live terms, in DEPLOYMENTS.md. Both are in the public interlude-sdk repository.

The bond is live

The challenge window and slashing are on-chain. A watcher can replay a batch, and the node answers a challenge by playing the bisection itself. The commit posts the log that hashes to txRoot and the signed bytes behind it, so nothing can be withheld. A confirmed slash walks the applied diffs backwards. A dismissed challenge burns the challenger’s bond; it never pays the validator.

The first-hour commands as a single page are on First hour.