Skip to content
Specification 1.0 — normative. One section (§17, standing approvals) is explicitly provisional and so marked.

Part IV — Runtime contracts and the app chat surface

An installed app’s LLM turn is ONE self-contained request. A runtime contract is the compact, host-held description from which that turn’s system instructions are assembled — what the app is, which settings shape an answer, what state arrives, what shape to reply in — replacing kilobytes of authoring instructions a runtime move cannot act on. The effect is protocol-level: every conforming app becomes cheap enough to run on a small local model.

A contract is a strict JSON object — an unknown key is a rejection, because an unknown field here would be unreviewed text reaching the model’s system instructions:

FieldBoundMeaning
overview (required)1–600 charswhat the app is; the model’s role
personaNote?≤400voice/tone
stateGuidance?≤500what the app sends each turn (state, never history)
responseGuidance?≤500the minimal reply shape
settings?≤16 entries; keys [a-z0-9_]{1,40}; scalar values (strings ≤120)the settings slice that shapes answers
maxOutputTokens?int 256–8192opt-in, narrowing-only output ceiling; absent means “behave exactly as a contract-less app”

The serialized contract MUST be ≤ 2560 as a whole (RUNTIME_CONTRACT_MAX_BYTES; the reference implementation measures the serialized JSON’s UTF-16 code-unit length, which equals bytes for ASCII content); per-field bounds deliberately sum to more, so a contract may spend its budget on any field.

  • Host-assigned, never app-claimed. No frame carries a contract; an app frame attempting to is ignored by the tolerant parser.
  • Version-linked. The contract lives on the app VERSION row (snug_app_versions.runtime_contract_json). Writing a new version copies it forward; revert and factory-reset copy it from the version being restored — reverted code runs under the contract that shipped with it.
  • Imported contracts are untrusted. On whole-DB import, a hub MUST drop every contract it cannot match — byte-for-byte after canonical (key-sorted) serialization — against a contract it already holds. A contract speaks with system authority; accepting one from an untrusted file would let that file dictate the model’s instructions. The affected app runs contract-less: degraded, never compromised.
  • Graceful degradation. An absent, malformed, or over-bound stored contract reads as “no contract” and the turn proceeds on generic instructions.

A conforming hub MAY offer a chat surface beside an installed app. Where it does:

Classification precedes execution. A message is classified into one of eight intents, and one exhaustive, compile-checked map assigns each intent a lane — a fall-through that silently routes unknown intents somewhere is exactly the defect this rule exists to prevent:

IntentLane
data_read, data_writedata
schema_change, app_changefeature
provider_read, provider_writeprovider (§16)
app_question, otheranswer

Classification MUST fail closed: an unusable classification produces a clarifying reply (≤300 chars), never a default lane — in particular, never the lane that writes code. clarify is deliberately not a lane; it is the router’s failure posture.

The data lane. Reads are isolated by construction: generated SQL executes against a throwaway copy of the app’s own materialized database — never live storage, never behind a “read-only” flag. Isolation is what the copy CONTAINS: other apps’ tables and every hub-namespace table are physically absent. Results are bounded before re-entering the model’s context (reference: 200 rows / 32 KiB), and truncation says so in-band. Writes are proposed, previewed, approved, then re-validated: verbatim statements plus an affected-row preview computed on the throwaway copy; execution only on explicit approval; at execution the dry run re-runs against live data and halts if the affected-row counts drifted from what was approved. Declining executes nothing. (Count comparison, not row comparison, is a disclosed limit of this revision.)

The feature lane writes app versions on model authority — there is deliberately no pre-write confirm. The wall is versioning: a visible in-place reload, a revertable version write, and the newest pinned factory version. Hosts MUST disclose this asymmetry rather than imply the data lane’s gate covers it.

Stored data is untrusted input. Rows may contain text crafted to read as instructions. Any prompt carrying stored data or query results MUST delimit it as untrusted, and delimiter spellings MUST be defanged in the data.