configuration
GitHub提供基于JSON Schema验证的响应式配置注册中心,支持多Worker间共享、实时监听变更及自动清理过期条目。
Trigger Scenarios
Install
npx skills add iii-hq/iii --skill configuration -g -y
SKILL.md
Frontmatter
{
"name": "configuration",
"description": "Schema-validated, reactive registry for named configuration entries — the runtime configuration surface used by engine and Compose workers."
}
configuration
The configuration worker is a server-side registry of named entries. Every entry has an id (e.g. iii-stream, billing-service), a human-readable name and description, a JSON Schema describing the value shape, and a JSON value validated against that schema. Workers call configuration::register once at startup to declare their schema and configuration::set to publish values; consumers call configuration::get / configuration::list to read and bind a configuration trigger to react to changes without polling.
The default fs adapter persists one YAML file per id under ./config and watches the directory for external edits, so manual edits surface as configuration:updated events. The bridge adapter delegates to a remote engine. The worker is engine-owned and may be configured at engine.workers.configuration.
A per-id TTL (off by default) cleans up entries whose last subscriber trigger has unregistered, scoped to the lifecycle of ephemeral workers that come and go without an explicit teardown step.
When to Use
- A worker needs a typed, observable configuration surface other workers can read and validate against.
- Two workers need to agree on the same configuration values without one polling the other or hardcoding a path on disk.
- An operator should be able to edit a single YAML file (or the remote control plane) and have the change propagate to every subscriber without a worker restart.
- A worker comes and goes (sandboxes, ephemeral consumers) and its configuration should be cleaned up automatically when no one is left subscribing.
Boundaries
- Not a general-purpose key/value store — every entry must have a registered JSON Schema. Use the standalone
stateworker for free-form values. - No partial-update surface;
setalways replaces the whole value. Build the new value client-side and ship it in one call. - The
bridgeadapter cannot delete entries on the remote engine; cleanup over the bridge happens via TTL or directly on the source engine. - Over the
bridgeadapter,configuration::ensureis decided by the remote engine (the original candidate is forwarded there), so it requires a remote engine that exposesconfiguration::ensure; against an older remote it fails closed withADAPTER_ERRORrather than falling back to an unsaferegister. - Schemas are not version-checked across re-registrations — re-registering with an incompatible schema simply replaces it. Coordinate schema migrations out-of-band.
Migration and runtime overrides
Compose defaults to <namespace>-<container-key> without a hash (for example,
default-harness). Invalid or over-64-character generated names require explicit config_name.
Compose migrates only the exact previous namespace/key hash before starting a stopped worker.
After hashed migration, only the default namespace adopts the exact bare container key,
unless another container explicitly owns it. The bare source wins over an existing destination;
configuration::migrate is the single operation, delegated through bridges to the authority.
It returns { action, entry } with migrated, preserved (existing destination with absent
source, or matching IDs with an existing entry), or missing (both source and destination absent).
Before migration, Compose and every bridge hop query the read-only
configuration::migration-capabilities and require source_priority_archive_revision: 1.
Unknown or unavailable capabilities fail closed before invoking migration; upgrade the authority.
It replaces the destination with the raw source entry, then archives the original source as
<source>.yaml.bak. The previous destination is not backed up. .yaml.bak, .bak.yaml, and
.bkup.yaml files are ignored during loading, watching and legacy directory migration.
An existing identical backup permits cleanup recovery; a conflicting backup is never overwritten.
Missing source is a no-write no-op. Unsupported adapters fail closed.
Stop source consumers before migrating; do not share an fs directory between engine processes.
config_override is execution-only: Compose writes it only to the private III_CONFIG snapshot,
never to persistent config/<id>.yaml. III_CONFIG_NAME identifies the stored base, not the
override. Workers must not register the merged execution snapshot back into persistent storage.
Functions
configuration::register— declare an id with name, description, JSON Schema, and an optionalinitial_value; idempotent re-registration replaces the schema and metadata.configuration::ensure— atomically seed a default: create or refresh the id but writeinitial_valueonly when no non-null value is stored yet; an existing value (includingfalse/0/"") is preserved verbatim and the seed is ignored. The race-free replacement for read-then-registerwhen seeding from one or many workers. Firesconfiguration:registeredon creation orconfiguration:updatedon refresh.configuration::migrate— move an exact source id with source priority, preserving raw values and metadata and archiving the source. Successful moves emit source:deletedand destination:registered; no-ops emit nothing.configuration::set— replace the value for a registered id; validates against the registered schema and emitsconfiguration:updated.configuration::get— read one entry by id; expands${VAR:default}against live env unlessraw: true.configuration::list— enumerate every registered id with name, description, and schema; never returns the value.configuration::schema— read schema/name/description for one id without exposing the value.
register, ensure, migrate, and set are the mutators; the read-side functions are cache-backed and cheap. Every mutator (plus delete) is linearized per store (the local fs store, one engine process), so a seed can never clobber a concurrent set and two racing seeds resolve to a single winner. Over the bridge adapter the authoritative store is the remote engine and its own linearization applies (the local cache is a best-effort mirror), so ensure is decided remotely, never against the local cache. Reads expand ${VAR:default} placeholders against the live process env on every call, so env changes propagate without restarts — pass raw: true to configuration::get when you need the stored template form.
Reactive triggers
Bind a configuration trigger when a function should run automatically on every register / set / delete — including external fs file edits and bridge-forwarded events from a remote engine. The engine invokes matching handlers asynchronously after each successful mutation and after TTL-driven cleanup, so a worker stays in sync with its configuration without polling.
Reach for it when:
- A worker needs to reload in-memory state when its configuration is rewritten by another component or by an operator editing the YAML directly.
- The same handler should run regardless of who edited the configuration (local SDK call, remote engine via the bridge adapter, or a file edit).
If you only need the new value inside the same function that wrote it, configuration::set already returns old_value / new_value — register a trigger only when a different worker should react.
How to bind
- Register a handler:
iii.registerFunction('stream::on-config-change', handler). - Register the trigger:
iii.registerTrigger({
type: 'configuration',
function_id: 'stream::on-config-change',
config: {
configuration_id: 'iii-stream', // optional. Omit to receive every id.
event_types: ['configuration:updated'], // optional. Subset of configuration:registered|configuration:updated|configuration:deleted.
// condition_function_id is also supported — see get function info.
},
})
Mutations that fire triggers: configuration::register (:registered on first call, :updated on re-registration), configuration::ensure (:registered on creation, :updated on refresh), configuration::set (:updated), TTL cleanup (:deleted), and external fs create/edit/delete events. Reads do not fire triggers.
The worker also respects per-id TTL: when ttl_seconds > 0 is configured and the last trigger bound to a configuration_id is unregistered, the entry is deleted after the TTL elapses. A new trigger registration before the countdown fires aborts the cleanup.
For the event payload shape, call iii get function info on the trigger type or handler function id.
Version History
-
703756d
Current 2026-09-23 08:34
修复Compose中遗留配置优先策略;优化可读配置名称迁移及运行时覆盖逻辑;新增原子性seed-if-empty确保功能。
-
7196103
2026-08-29 04:52
重构了Worker生命周期管理至Worker Compose架构
- c6f6fde 2026-08-20 17:17


