state

The state worker is a server-side key/value store. Values are addressed by a scope (namespace) and a key, shared across every worker connected to the engine, and persisted through a pluggable adapter (kv or redis). Callers reach the store through six state::* functions invoked with iii.trigger({…

iii-hq/workers3 installsApache-2.0Synced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI

Agent Skills format with YAML frontmatter. Claude Code reads it as-is.

---
name: "state"
description: "The state worker is a server-side key/value store. Values are addressed by a scope (namespace) and a key, shared across every worker connected to the engine, and persisted through a pluggable adapter (kv or redis). Callers reach the store through six state::* functions invoked with iii.trigger({…"
license: "Apache-2.0"
---

# state

The `state` worker is a server-side key/value store. Values are addressed by a `scope` (namespace) and a `key`, shared across every worker connected to the engine, and persisted through a pluggable adapter (`kv` or `redis`). Callers reach the store through six `state::*` functions invoked with `iii.trigger({ function_id: 'state::...', payload })`. Install it with `iii worker add state`; it replaces the engine's built-in `iii-state` worker, which must be removed from the engine config first.

State does not push updates to SDK clients. Reactivity is delivered by a `state` trigger type that fires `state:created`, `state:updated`, or `state:deleted` after every successful mutation, so downstream functions can react to data changes without polling. The `kv` adapter (default) supports `in_memory` or `file_based` persistence; `redis` proxies to a Redis backend. The function surface is identical across adapters.

## When to Use

- Two or more functions need shared state without standing up a separate database.
- A counter or per-entity document needs atomic partial updates instead of read-modify-write.
- A write in one function should trigger side effects elsewhere (cache invalidation, audit logs, notifications, projections).
- You want a specific `scope`/`key` watched and reacted to only when that slot changes.

## Boundaries

- Schema-free by design — every value is opaque JSON. Use the `configuration` worker when entries need a registered JSON Schema and validation.
- Reads (`state::get`, `state::list`, `state::list_groups`) never fire triggers; only `set`/`update`/`delete` do.
- Trigger delivery is asynchronous and does not roll back the write on handler failure; a delete of a missing key still emits `state:deleted` with a null old value.
- Stream-shaped, broadcast-to-subscribers data belongs in the engine's stream surface, not here.
- The store lives in the worker process: the `kv` `in_memory` backend is lost when the worker stops. Use `file_based` or `redis` for data that must survive a restart.

## Functions

- `state::set` — write or replace the value at a `scope`/`key`; fires `state:created` or `state:updated`.
- `state::get` — read one value by `scope` and `key`.
- `state::delete` — remove a key and fire `state:deleted` with the prior value.
- `state::update` — apply ordered atomic ops (`set`, `merge`, `increment`, `decrement`, `append`, `remove`) to the stored value.
- `state::list` — enumerate every value stored in a scope.
- `state::list_groups` — enumerate which scopes currently contain data.

## Reactive triggers

Bind a `state` trigger when a function should run automatically after a value changes — without polling `state::get`. The worker evaluates every registered `state` trigger after a successful `state::set`, `state::update`, or `state::delete` and invokes matching handlers asynchronously.

Reach for it when:

- A write in one worker should drive side effects in another (audit logs, cache invalidation, notifications, projections).
- You want optional gating via `condition_function_id` so the handler only runs when a predicate on the event is truthy (only an explicit `false` blocks).

If you only need the new value inside the same function that wrote it, use the mutator's returned `old_value` / `new_value` instead of binding a trigger.

### How to bind

1. Register a handler: `iii.registerFunction('orders::on-status-change', handler)`.
2. Register the trigger:

```typescript
iii.registerTrigger({
  type: 'state',
  function_id: 'orders::on-status-change',
  config: {
    scope: 'orders',  // optional. Omit to match every scope.
    key: 'status',    // optional. Omit to match every key in the scope.
    // condition_function_id is also supported.
  },
})
```

All `config` fields are optional; tighter filters reduce how often the handler runs. To watch several specific keys, register one trigger per `scope`/`key` pair. Mutations that fire the trigger: `state::set`, `state::update`, `state::delete`.

The handler receives the event payload `{ type: "state", event_type, scope, key, old_value, new_value }`, where `event_type` is one of `state:created`, `state:updated`, or `state:deleted`.

More General & Other skills

← All General & Other skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY