Distributed key-value state storage with scope-based organization and reactive triggers that fire on any state change.Documentation Index
Fetch the complete documentation index at: https://motiadev-docs-verdict-review-plan.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Architecture
State is server-side key-value storage with trigger-based reactivity. Unlike streams, state does not push updates to WebSocket clients — it fires triggers that workers handle server-side.Sample Configuration
Configuration
The adapter to use for state persistence and distribution. Defaults to
kv when not specified.Adapters
kv
Built-in key-value store. Supports both in-memory and file-based persistence.Configuration
Storage method. Options:
in_memory (lost on restart) or file_based (persisted to disk).Directory path for file-based storage. Each scope is stored as a separate file.
Interval in milliseconds between automatic disk saves. Defaults to
5000.redis
Uses Redis as the state backend.Configuration
The URL of the Redis instance to use.
bridge
Forwards state operations to a remote III Engine instance via the Bridge Client.Functions
Set a value in state. Fires a
state:created trigger if the key did not exist, or state:updated if it did.Parameters
Parameters
Atomically update a value using one or more operations. Fires
state:created or state:updated depending on whether the key existed.Parameters
Parameters
The scope to update within.
The key to update.
Array of update operations applied in order. Each operation is a tagged object with a
For Each array element is a literal key.
type field and a path. Use path: "" (or omit path) to target the root value.| Operation | Shape | Description |
|---|---|---|
set | { "type": "set", "path": "status", "value": "active" } | Set a field or replace the root value. |
merge | { "type": "merge", "path": ["sessions", "abc"], "value": { "ts": "chunk" } } | Shallow-merge an object at the root or at any nested path. |
increment | { "type": "increment", "path": "count", "by": 1 } | Add by to a numeric field. |
decrement | { "type": "decrement", "path": "count", "by": 1 } | Subtract by from a numeric field. |
append | { "type": "append", "path": "events", "value": { "kind": "chunk" } } | Push one element to an array or concatenate a string value. |
remove | { "type": "remove", "path": "status" } | Remove a field from the current object. |
set, increment, decrement, append, and remove, paths are first-level field names. For example, user.name updates the field named user.name; it does not traverse into { "user": { "name": ... } }.For merge, path accepts either a single string (legacy / first-level field) or an array of literal segments for nested merge:["a.b"] writes a single key named "a.b", not a → b.Validation: invalid update inputs are rejected with a structured error in the response’s errors array. Reasons include path depth > 32 segments, segment > 256 bytes, value depth > 16, > 1024 top-level keys, type mismatches, non-object targets, or any segment / top-level key matching __proto__ / constructor / prototype. Successfully applied ops still reflect in new_value.Error codes
Eachstate::update op may add an entry to the response errors array. Operations are best-effort: successfully applied ops still reflect in new_value, and failed ops are skipped.
| Code | Triggered when | Fix |
|---|---|---|
set.target_not_object | set tried to write a field while the current value is not an object | Set the root to an object first, or use path: "" to replace the root. |
append.target_not_object | append used a field path while the current value is not an object | Set the root to an object first, or append at path: "". |
append.type_mismatch | append targeted an incompatible existing value, such as appending to a number or appending a non-string to a string | Match the appended value to the existing field type, or initialize the field to an array, string, or null. |
increment.target_not_object | increment used a field path while the current value is not an object | Set the root to an object first. |
increment.not_number | increment targeted an existing field that is not a number | Initialize the field as a number first, for example with set to 0. |
decrement.target_not_object | decrement used a field path while the current value is not an object | Set the root to an object first. |
decrement.not_number | decrement targeted an existing field that is not a number | Initialize the field as a number first, for example with set to 0. |
remove.target_not_object | remove used a field path while the current value is not an object | Set the root to an object first. Removing a missing field from an object remains silent. |
<op>.path.proto_polluted | A path segment is __proto__, constructor, or prototype | Use a different field name. |
<op>.path.segment_too_long | A path segment is longer than 256 bytes | Shorten the field name or merge path segment. |
merge.path.too_deep | A nested merge path has more than 32 segments | Reduce the nested path depth. |
merge.path.empty_segment | A nested merge path array contains an empty segment | Remove the empty segment. |
merge.value.not_an_object | merge value is not a JSON object | Pass an object as the merge value. |
merge.value.too_deep | merge value has JSON nesting deeper than 16 levels | Flatten the value. |
merge.value.too_many_keys | merge value has more than 1024 top-level keys | Split the write into smaller updates. |
merge.value.proto_polluted | A top-level key in the merge value is __proto__, constructor, or prototype | Use a different key name. |
op_index, code, and message; doc_url is optional.
List all values within a scope.
Parameters
Parameters
The scope to list entries from.
Returns
Returns
A flat JSON array of all stored values within the scope:
any[].List all scopes that contain state data.
Returns
Returns
An object with a single
groups field:A sorted, deduplicated array of all scope names that contain at least one key.
Trigger Type
This worker adds a new Trigger Type:state.
When a state value is created, updated, or deleted, all registered state triggers are evaluated and fired if they match.
State Event Payload
When the trigger fires, the handler receives a state event object:Always
"state".The kind of change:
"state:created", "state:updated", or "state:deleted".The scope where the change occurred.
The key that changed.
The previous value before the change, or
null for newly created keys.The new value after the change.
null for deleted keys.Sample Code
- Node / TypeScript
- Python
- Rust
Usage Example: User Profile with Reactive Sync
Store user profiles in state and react when they change:- Node / TypeScript
- Python
- Rust
Usage Example: Conditional Trigger
Only process profile updates when the email field changed:- Node / TypeScript
- Python
- Rust