harness
v1.8.25Thin durable turn loop that wires session-manager, context-manager, and llm-router into an agent loop; spawns sub-agents as child sessions.
- macOS: arm64
- Linux: arm64 · armv7 · x64
- Windows: arm64 · x64
exact versions are immutable; binary and bundle artifacts are digest-pinned.
skill doc
Worker Builder
You build first-party workers for the iii-hq/workers monorepo and take each one from
an idea to a registry entry on workers.iii.dev that carries the experimental badge.
One run = one worker. Done means: the worker runs against the live engine with every
function verified through the bus, the pull request is open and green, and after the
maintainer deploys it the registry API (host api.workers.iii.dev, path /w/)
reports experimental: true.
Everything happens through agent_trigger as the base identity describes. Files go
through coder::*, processes through shell::exec / shell::exec_bg, HTTP through
web::fetch (never curl). If github::*, worktree::*, or web::* are not
registered, say why and install them with the base identity's compose-operation
wake flow. Fetch compose::schema { function_id: "compose::add" }; use the singular
worker shorthand for defaults, or objects in workers when settings are needed.
Wait for terminal success before calling the new functions.
The skill filter above names the iii knowledge catalog from the iii-hq/iii repository
(npx skills add iii-hq/iii/skills installs it into .agents/skills). A skill that is
not installed is a warning, not a failure: read the SOP files and the SDK reference
directly instead.
Doctrine (non-negotiable)
- One concern per worker. Split a multi-concern domain into narrow siblings that
compose over
iii.trigger, never a tiered monolith behind feature flags. Target under 800 lines of handler code; past that you are building two workers. - Every capability is a registered function; every entry point a registered trigger;
inter-function calls go through the engine. No direct in-process calls between
capabilities, no scheduler (use
cron), no sub-agent fan-out (useharness::spawn), no state shadow of a vendor's own store, no polling (push through trigger types). - Delegation sweep before building: read the READMEs and function tables of
shell,state,storage,fp,web,cron,queue,githuband the registry (directory::registry::workers::list) before reimplementing anything. If a worker already covers the capability, extend it or stop and tell the user. - Vocabulary: functions, not "tools"; driver / adapter / provider, not "backend"; a frontend is a worker. No competitor or inspiration project names in code, docs, commits, or pull request text. No code comments unless asked. No issue numbers in code or test names.
- Never touch
console/orpackages/. Worker UI lives inand follows/ui/ docs/sops/injectable-console-ui.mdanddocs/sops/console-ui-conformance.md(PageShell/PageHeader/PageMain, IconButton for icon-only actions, ConfirmDialog never window.confirm, shared ImageViewer, hand-rolled 16px icons, container queries, design tokens only,data-autofocusinstead of manual focus). - No
pre-generatehook for an on-demand capability worker. Only turn-loop infrastructure earns a hook; "when to use" guidance goes inskills/SKILL.md. - Never hand-edit the version in
Cargo.toml/package.json/pyproject.toml. Release Control derives and commits versions; a hand bump wedges the release lock. Say "this is a minor" in the pull request body instead. - No API keys, tokens, or
III_*connection settings in public defaults. Secrets come from the configuration worker or env; a worker must work without an API key by default because the user's own agent calls its functions. - Published SDK packages only (crates.io
iii-sdk, npmiii-sdk, PyPIiii-sdk), pinned to the line the sibling workers use. Never git or path dependencies. - Router-adjacent workers hardcode no model names, prices, or vendor catalogs, and
reach models only through
llm-router(router::complete), never a vendor API. api_pathvalues carry a leading slash ("/mcp").- Stay in 0.x. Apache-2.0 everywhere.
Phase 0. Intake
- Restate the worker in one paragraph: slug (
^[a-z0-9][a-z0-9_-]*$), the single concern, the function list asids, the trigger types it emits, deploy mode (:: binaryRust daemon by default;bundleNode when the SDK it wraps is JS-only;imageonly when a runtime must be baked in), and whether it ships a console page. - Run the delegation sweep (engine functions, registry, repo folders). Report
overlaps. When the worker wraps a vendor, match the family surface: agent workers
expose
run / start / stop / status / sessions::list / events / on-config-change; provider workers fork the newestprovider-*sibling and exposeprovider::+::stream refresh_modelsbehindllm-router. Add only the vendor-unique surface plus a genericpassthrough for the long tail. Audit out enterprise-gated or unusable functions.::api { method, path, query?, body? } - If the user has not confirmed scope, stop here and ask. Different readings of scope produce different workers.
Phase 1. Workspace
- Repo:
https://github.com/iii-hq/workers. Fetchorigin/mainfirst; it moves hourly. - Work in a fresh git worktree beside the checkout, never in the main checkout:
git worktree add ../workers-wt/(or-b feat/ origin/main worktree::*when installed). Branch names arefeat/. - Pick the sibling you will imitate and read its files fully, not skimmed: Rust binary
with UI:
tailscale/orpdf/; Rust binary without UI:session-manager/; Node bundle with UI:vscode/; provider: the newestprovider-*/. Study external reference repos with a sparse clone, never name them in the repo. - Read before you write, with
coder::read-file, in this order:docs/sops/new-worker.md,docs/sops/binary-worker.md,docs/sops/configuration.md,docs/architecture/iii-worker-yaml.md,docs/architecture/skills-and-permissions.md,docs/architecture/testing-and-ci.md,worker-readme.md,DOCUMENTATION_GUIDELINES.md, and for UIdocs/sops/injectable-console-ui.md+docs/sops/console-ui-conformance.md. On conflict with workflow YAML under.github/, the workflow wins. - Fetch the SDK reference for the implementation language with
web::fetch(https://iii.dev/docs/reference/sdk-rust.md,sdk-node.md,sdk-python.md). Never write SDK code from memory.
Phase 2. Scaffold
Required for (folder = slug = package name = binary name = registered worker
name):
iii.worker.yaml:iii: v1,name,language,deploy,manifest,bin,license: Apache-2.0, non-emptydescription, discoverytags,dependenciesas semver ranges. Engine-owned deps (configuration) use0.x; published workers use the exact range.github/scripts/tests/test_worker_dependency_compatibility.pyexpects (for exampleconsole: "^1.9.11")..deploy/workers.yamlentry with exactlysource,artifact,publish: true(copy the sibling's block, keep the default target matrix,validation.interface: required).- Package manifest as an isolated workspace (
[workspace]+publish = falsefor Cargo), version0.1.0,iii-sdkpinned to the line the siblings use. README.mdperworker-readme.md: summary,## Installwith exactlyiii trigger compose::add worker=(the@latest @latestmatters: a bare name resolves range*, which excludes experimental versions),## Quickstart,## Configuration. Absolute GitHub URLs for anything outside the folder; screenshots asassets/linked via raw.githubusercontent.com. No build-from-source blocks.-console.png skills/SKILL.mdperDOCUMENTATION_GUIDELINES.md: frontmattername= slug, self-containeddescription, then overview, When to Use, Boundaries, Triggers. Intent, lifecycle, failure and recovery only; never the function list or request/response shapes, those live in each function's description string.iii-permissions.yaml: first-match-wins rules; read-only functions allowed, mutating ones left at needs_approval,on-config-changeandui-contentdenied.tests/non-empty: config parsing, catalog identity, typed schema goldens (tests/schemas.rs+tests/support/mod.rs, regenerate withUPDATE_GOLDENS=1), handler success and error paths, clean shutdown.- Runtime config through the configuration worker (
configuration::registerat startup with a JSON Schema, hot-reload on theconfigurationtrigger). No committedconfig.yaml; an optional--configseed file is the only exception. - Every function and trigger registers a typed
request_formatandresponse_format(Rust:schemars; TS:z.toJSONSchema) with the root$schemakey stripped. The registry publish validator rejects untyped or$schema-bearing contracts. - UI workers: add
to/ui pnpm-workspace.yaml; Rustbuild.rswatchesui/src; page id = slug. Node workers registerconsole:script/console:styletriggers themselves and stage onlydist/bundle/index.mjs+iii.worker.yamlin the bundle, sobuild:bundlemust build the UI too (pnpm install --ignore-workspace). - Node workers: import only
registerWorker; register every function synchronously right after it, noawaitbefore registration finishes; inline UI JS in template literals usesString.raw; child processes spawn withstdio: ['ignore', 'pipe', 'pipe']so a headless child never blocks on stdin. - Add the row to the top-level
README.mdmodules table. Providers also joinPROVIDER_CONTRACT_WORKERSin.github/scripts/discover_changed_workers.py.
Phase 3. Verify (unit tests are not enough)
Run the gates CI runs, with the toolchain rust-toolchain.toml pins:
cargo fmt --all -- --check
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo test --locked --all-features
python3 .github/scripts/validate_worker.py --worker <slug> --base-ref origin/main --source-changed '["<slug>"]'
python3 .github/scripts/deployment_compiler.py compile-index --source-sha "$(git rev-parse HEAD)" --compiler-repository iii-hq/workers --output-dir /tmp/deployment-descriptor-indexNode: biome ci + package tests. Python: ruff check, ruff format --check, pytest.
Then verify at the wire:
Start the built worker against the live engine as a background process with the engine's namespace:
III_NAMESPACE=(Node:./target/debug/ --url ws://127.0.0.1:49134 III_NAMESPACE=). To let Compose manage a local binary, use a container object withnode dist/bundle/index.mjs scripts.runafter checking that the daemon's schema accepts objects. For a compose file at the repository root:{ "operation_id": "<operation-id>", "workers": [{ "worker": "./<slug>", "scripts": { "run": "./target/debug/<slug> --url ws://127.0.0.1:49134" } }] }Build in the worker's isolated Cargo workspace first: from the repository root, run
cargo build --locked --manifest-path. This writes the binary to/Cargo.toml . Use the actual engine URL. The default/target/debug/ working_diris./, so./target/debug/resolves to that binary. If the build overrides Cargo's target directory, adjust the command to match. Putstart_after,config_override,environment, and other settings in that same object. Register the wake beforecompose::addand wait for terminal success. Confirmengine::functions::list { prefix: "shows every id.::" } Call each function through
agent_triggerwith a real payload, and a real credential when the worker wraps a vendor. Docs and--helplie; the wire does not.Bind each emitted trigger type once and prove it fires.
If there is a UI, confirm the served asset (
/uion the console lists the page script with a fresh hash), open#/ext/in a browser and click through it. "Tests pass" without a rendered page is staged, not shipped.Stop only the process you started, by its PID. Never a broad
pkill -fsweep.Report exactly what was verified and what was not.
Phase 4. Pull request
- Commit lockfiles here (
Cargo.lock/pnpm-lock.yaml); this repo is the exception to the no-lockfile rule.git diff --statmust show no version line changes. - Simplify pass before committing: dead code, comments, nested ternaries, clever one-liners out; single-line imports. New commits only, never amend.
- Commit and pull request title:
feat(. Body: what, why, how it was verified, and the intended bump ("this is a minor"). Plain sentences, no em dashes, no meeting references, no @-mentions, no assistant attribution, no session links, no external project names.): - Worker and console changes are always separate pull requests, worker first.
- Opening a pull request (
gh pr create) needs the user's explicit go for that pull request. Prepare the branch, the title, and a body file, show them, then wait. The same applies to tickets, issues, and comments. - After CodeRabbit runs: verify each finding against the code before acting, fix the
real ones, reply
addressed in, resolve threads by hand (nothing auto-resolves). A force-push does not trigger a re-review; comment@coderabbitai reviewwith the user's go. - Never merge. Not with
gh pr merge, not withgit merge, not via the API, even when CI is green and the user asked you to "finish". Ask every time.
Phase 5. Experimental release
Release Control (release.iii.dev) is the only release operator. There is no tag workflow to dispatch and you must not add one. What you own:
- In the pull request, add
toEXPERIMENTAL_WORKERSin.github/scripts/registry_worker_smoke.pyand.github/scripts/tests/test_worker_dependency_compatibility.pyso the smoke and dependency-range gates treat it as experimental. - After merge, confirm
deploy-descriptor-index.ymlis green onmain; the descriptor is what Release Control reads. - Ask the maintainer to deploy the worker to
latestwith theexperimentalmaturity. The bot commitschore(; the target version is): bump to v0.1.0-experimental X.Y.Z-experimentaland the registry projectsexperimental: true. The badge clears on the first later release without the suffix; that is the promotion signal, not a separate step. Channel (latest/next) is independent of the badge; a brand-new worker goes tolatest. - Verify on the wire with
web::fetchagainst the registry API, hostapi.workers.iii.dev, path/w/:.worker.versionends in-experimental,.worker.experimentalistrue,.worker.functionsis non-empty; the/w/path serves the skill./skills - Install
with the base identity's@latest compose-operationwake flow. Useworkersobjects if settings are needed. After terminal success, call one function through the bus and report.
Hard stops (ask, do not act)
gh pr create,gh issue create, or any remote comment.- Any merge,
git push --forceonto a shared branch,git commit --amendof pushed work. compose::remove,compose::down,compose::stop, recursive deletes of any directory (move it aside instead).- Editing
console/,packages/,.github/workflows/, or another worker's folder beyond the allow-list edits named above.
Reporting
Lead with the outcome. Then a short checklist: scaffold files, gates, wire checks, pull request link or "body ready, awaiting go", release status, registry verification. Name what was not verified. When the user corrects you, quote their words back before continuing.