Skip to main content
A device runs offline, restarts, changes owners, and gets new software. To stay correct through all of that, KineticVM follows one rule: every fact has exactly one canonical home. Everything else is a cache or a derived view, and the canonical source wins on conflict.

The three layers

The chain is canonical for everything about who and how much.The runtime reads these at use time. It does not store an agent id, an owner, or caps of its own and act on them.

Config values that describe the chain

Some [chain] config values look like state but are not:
  • chain.owner, chain.device_name, chain.per_tx_cap, chain.daily_cap are claim inputs. They are used once, to build the claim link. After the claim, the vault and the Identity Registry are the truth. Changing chain.per_tx_cap after claiming does nothing to the vault. Use /limits instead.
  • chain.registry, chain.vault, chain.attestor, chain.rpc_url, chain.chain_id are pointers. They name where the canonical state is.
If you change chain.owner after a claim, the device does not hand itself to that wallet. Ownership only moves by transferring the agent NFT. See Ownership.

The device key

The device key is created the first time the runtime needs it, after Telegram pairing, not during quickstart. It is a secp256k1 key held behind the DeviceSigner interface, which has two operations: return the address, and sign a 32-byte digest. The software implementation:
  • encrypts device.key at rest with the runtime secret store,
  • zeroizes key bytes in memory after use,
  • never logs, prints, or serializes the key.
A hardware secure element (NXP SE050) will implement the same interface, so contracts and tooling do not change when the key moves into silicon.
Deleting device.key does not free the agent. The registry still holds the old address as the active device. Use rotate to replace a key, or revoke to disable it.

Plugin state

Plugins run as WebAssembly components and have no filesystem. When a plugin needs to remember something across calls and restarts, such as a counter, the last processed order id, or a calibration value, it uses the host state interface.
Every value is scoped to the exact plugin package, capability, and binding that wrote it. One plugin cannot read another’s keys. The SQLite file stores only opaque locators and encrypted envelopes, and is created owner-only (0600).
Each value has a revision. put with none only succeeds if the key does not exist. put with some(revision) only succeeds if the current revision matches. A mismatch returns conflict. Revisions are never reused, even after a key is deleted and recreated, so a stale writer can never overwrite a newer value.
Keys are 1 to 128 bytes of ASCII letters, digits, _, -, or .. Keys are not paths. Each plugin may hold up to 1,024 entries, 64 KiB per value, and 8 MiB in total. Going over returns quota-exceeded.
Reading requires the state_read permission and writing requires state_write, both declared in the plugin’s manifest.toml. Without them, calls return access-denied.
Plugin state is private working memory. It is never attested and never listed. For data that should be provable and sellable, use operation records.