Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .github/scripts/validate_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
import _lib # noqa: E402


# Workers the harness materialises into ./data/skills/<name>/index.md on
# boot via skills::download. Must match harness/src/skills.rs::BOOTSTRAP_NAMES.
# Workers whose skills the harness stack requires at boot, making
# skills/SKILL.md a hard PR gate. Keep in sync with what the harness
# actually bootstraps.
BOOTSTRAP_WORKERS = frozenset({
"iii-directory",
"shell",
Expand Down Expand Up @@ -162,17 +163,17 @@ def soft(msg: str) -> None:
if not skill_md.exists():
hard(
f"{worker}/skills/SKILL.md is missing — bundled workers must ship one "
f"(see binary-worker.md)"
f"(see docs/sops/binary-worker.md)"
)
elif skill_md.stat().st_size == 0:
hard(
f"{worker}/{skill_md.relative_to(root).as_posix()} is empty — "
f"must contain the H1 + summary (see binary-worker.md)"
f"must contain the H1 + summary (see docs/sops/binary-worker.md)"
)
elif skill_md.stat().st_size > SKILL_MD_SIZE_CAP:
hard(
f"{worker}/{skill_md.relative_to(root).as_posix()} exceeds 256 KiB cap "
f"({skill_md.stat().st_size} bytes; see binary-worker.md)"
f"({skill_md.stat().st_size} bytes; see docs/sops/binary-worker.md)"
)

for e in errs:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/create-tag.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Release SOP: docs/sops/release.md
name: Create Tag

on:
Expand All @@ -19,6 +20,7 @@ on:
- iii-lsp-vscode
- image-resize
- mcp
- session-manager
- shell
- storage
bump:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Release SOP: docs/sops/release.md
name: Release

on:
Expand All @@ -13,6 +14,7 @@ on:
- 'iii-lsp/v*'
- 'image-resize/v*'
- 'mcp/v*'
- 'session-manager/v*'
- 'shell/v*'
- 'storage/v*'
workflow_dispatch:
Expand Down
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ npx skills add iii-hq/iii --all
| [`iii-lsp`](iii-lsp/) | Rust | Language Server for iii function ids, trigger configs, and worker discovery. Autocomplete / hover across JS/TS, Python, Rust. |
| [`iii-lsp-vscode`](iii-lsp-vscode/) | Node | VS Code extension that embeds `iii-lsp`. |
| [`image-resize`](image-resize/) | Rust | Image resize via channel I/O — JPEG/PNG/WebP with EXIF auto-orient, scale-to-fit / crop-to-fit. |
| [`llm-budget`](llm-budget/) | Rust | Workspace + agent LLM spend caps with alerts, forecast, and period rollover under `budget::*`. |
| [`mcp`](mcp/) | Rust | MCP 2025-06-18 Streamable HTTP bridge — exposes iii functions tagged `mcp.expose` as MCP tools. |
| [`shell`](shell/) | Rust | Unix shell + filesystem worker — `shell::exec` with allowlist/denylist/timeout/output caps and background jobs; `fs::ls`/`stat`/`mkdir`/`rm`/`chmod`/`mv`/`grep`/`sed`/`read`/`write` with host jail, denylist, and size caps. |
| [`storage`](storage/) | Rust | S3-compatible object storage across AWS S3, GCS, Cloudflare R2, and a managed local rustfs backend. Streamed uploads, presigned URLs, and object change triggers. |
Expand Down Expand Up @@ -118,11 +117,13 @@ host.

## Add a new worker

[`binary-worker.md`](binary-worker.md) is the deep dive for the Rust binary
scaffold (manifest, config, layout, function contract).
[`worker-readme.md`](worker-readme.md) covers the README contract every
worker shares (install via `iii worker add`, run via `iii start`, how-to
over reference).
Start with [`docs/sops/new-worker.md`](docs/sops/new-worker.md) — the
cross-cutting checklist (naming, required files, CI gates, release wiring).
For the inside of a Rust `deploy: binary` worker, continue with
[`docs/sops/binary-worker.md`](docs/sops/binary-worker.md). Each worker ships
a consumer `README.md` per the [`worker-readme.md`](worker-readme.md)
contract (install via `iii worker add`, quickstart, configuration).
[`docs/README.md`](docs/README.md) indexes all shared docs.

## CI

Expand All @@ -138,6 +139,9 @@ The `pr-checks` job additionally enforces, per changed worker: `README.md`
present, `iii.worker.yaml` valid, `tests/` non-empty, and the manifest
version is greater than the version on the PR's base branch.

Full reference (discovery buckets, interface boot smoke, e2e workflows):
[`docs/architecture/testing-and-ci.md`](docs/architecture/testing-and-ci.md).

## CD

Releases are cut manually via the **Create Tag** workflow
Expand All @@ -151,9 +155,14 @@ resulting `<worker>/v<X.Y.Z>` tag drives a single dispatcher
[`_rust-binary.yml`](.github/workflows/_rust-binary.yml).
- `image` → multi-arch image to `ghcr.io/<owner>/<worker>` via
[`_container.yml`](.github/workflows/_container.yml).
- `bundle` → single-file archive via
[`_bundle.yml`](.github/workflows/_bundle.yml).
2. Calls `POST /publish` against the workers registry API via
[`_publish-registry.yml`](.github/workflows/_publish-registry.yml).

Step-by-step (variants, troubleshooting, rollback):
[`docs/sops/release.md`](docs/sops/release.md).

## License

Apache 2.0 — see [`LICENSE`](LICENSE).
2 changes: 1 addition & 1 deletion coder/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! attribute so an empty YAML object still produces a fully-populated
//! struct; `impl Default` mirrors those functions so the binary can fall
//! back to defaults when `--config` is missing or unparseable (see
//! [`binary-worker.md`](../../binary-worker.md) §5).
//! [`binary-worker.md`](../../docs/sops/binary-worker.md) §5).

use std::path::PathBuf;

Expand Down
2 changes: 1 addition & 1 deletion coder/src/functions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! `main.rs` calls `register_all` after `register_worker`; each
//! `register_<verb>` below uses `RegisterFunction::new_async` with typed
//! `JsonSchema` request/response structs so the SDK can emit schemas for
//! tools and docs (binary-worker.md §7).
//! tools and docs (docs/sops/binary-worker.md §7).
//!
//! WIRE-SURFACE CATALOG — `catalog()` below is the single source of truth
//! for every function's id + registration description, plus the
Expand Down
2 changes: 1 addition & 1 deletion coder/tests/integration.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! End-to-end test (binary-worker.md §9 pattern A): spawn the `iii` engine
//! End-to-end test (docs/sops/binary-worker.md §9 pattern A): spawn the `iii` engine
//! and the `coder` worker as subprocesses, drive the worker through
//! `iii-sdk` as a client, then tear both down. **Self-skips** when `iii`
//! is not on `PATH` so CI hosts without the engine still pass.
Expand Down
2 changes: 1 addition & 1 deletion coder/tests/manifest.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Validates the `--manifest` subcommand the registry publish pipeline
//! relies on (binary-worker.md §11 / pattern A).
//! relies on (docs/sops/binary-worker.md §11 / pattern A).

use std::process::Command;

Expand Down
43 changes: 43 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Workers documentation

This folder collects **procedures** (SOPs) and **shared architecture** for
workers in this monorepo. Per-worker deep docs stay inside each worker folder
(e.g. [`session-manager/architecture/`](../session-manager/architecture/)).

## I want to…

| Goal | Read |
|---|---|
| Add a new worker to the repo | [`sops/new-worker.md`](sops/new-worker.md) |
| Scaffold a Rust `deploy: binary` worker | [`sops/binary-worker.md`](sops/binary-worker.md) |
| Ship a version to the registry | [`sops/release.md`](sops/release.md) |
| Fix a failed release | [`sops/release.md`](sops/release.md) § Troubleshooting |
| Write a consumer `README.md` | [`worker-readme.md`](../worker-readme.md) |
| Author `skills/SKILL.md` | [`DOCUMENTATION_GUIDELINES.md`](../DOCUMENTATION_GUIDELINES.md) |
| Understand `iii.worker.yaml` fields | [`architecture/iii-worker-yaml.md`](architecture/iii-worker-yaml.md) |
| Understand CI gates on a PR | [`architecture/testing-and-ci.md`](architecture/testing-and-ci.md) |
| Integrate with session-manager | [`session-manager/architecture/integration.md`](../session-manager/architecture/integration.md) |

## Layout

```text
docs/
├── README.md # this file
├── sops/ # step-by-step procedures
│ ├── new-worker.md # cross-cutting onboarding
│ ├── binary-worker.md # Rust binary scaffold
│ └── release.md # cut, monitor, verify releases
└── architecture/ # shared reference (not step-by-step)
├── worker-model.md
├── iii-worker-yaml.md
├── deploy-modes.md
├── testing-and-ci.md
├── skills-and-permissions.md
└── per-worker-architecture.md
```

## Related docs (repo root)

- [`worker-readme.md`](../worker-readme.md) — consumer README contract
- [`DOCUMENTATION_GUIDELINES.md`](../DOCUMENTATION_GUIDELINES.md) — `skills/SKILL.md` authoring
- [`tech-specs/`](../tech-specs/) — product-level specs before or alongside implementation
19 changes: 19 additions & 0 deletions docs/architecture/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Shared worker architecture

Reference material for concepts that apply across workers. For step-by-step
procedures, see [`../sops/`](../sops/).

| Document | Summary |
|---|---|
| [`worker-model.md`](worker-model.md) | Worker lifecycle, engine bus, function ids, registry discovery |
| [`iii-worker-yaml.md`](iii-worker-yaml.md) | Manifest field reference and which CI/CD jobs consume each field |
| [`deploy-modes.md`](deploy-modes.md) | `binary` / `image` / `bundle` paths through build and publish |
| [`testing-and-ci.md`](testing-and-ci.md) | PR discovery, gates, interface boot smoke, dedicated e2e workflows |
| [`skills-and-permissions.md`](skills-and-permissions.md) | `SKILL.md` lifecycle and `iii-permissions.yaml` conventions |
| [`per-worker-architecture.md`](per-worker-architecture.md) | When to add `<worker>/architecture/`; session-manager as reference |

## Per-worker architecture

Workers with non-trivial integration surfaces may ship their own
`<worker>/architecture/` folder. Example:
[`session-manager/architecture/`](../../session-manager/architecture/).
69 changes: 69 additions & 0 deletions docs/architecture/deploy-modes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Deploy modes

How `iii.worker.yaml` `deploy` routes CI interface smoke and release builds.

## Overview

| `deploy` | Artifact | Build workflow | Typical language |
|---|---|---|---|
| `binary` | Per-target CLI archives on GitHub Release | `_rust-binary.yml` | Rust |
| `image` | `ghcr.io/<owner>/<worker>:<version>` | `_container.yml` | Node, Python |
| `bundle` | `<worker>.tar.gz` on GitHub Release | `_bundle.yml` | Node (esbuild) |

Release dispatcher: [`release.yml`](../../.github/workflows/release.yml) reads
`deploy` from `iii.worker.yaml` via `parse_release_tag.py`.

## Binary

- **Build:** up to 9 cross-compiled targets (or `targets:` subset).
- **Assets:** `<bin>-<triple>.tar.gz` / `.zip` + `.sha256` checksums.
- **Publish boot:** downloads `*-x86_64-unknown-linux-gnu.tar.gz` from the
Release, runs the binary for interface collection.
- **Registry payload:** per-target download URLs resolved by
`resolve_binary_artifacts.py`.

## Image

- **Build:** multi-arch Docker image pushed to GHCR.
- **Publish boot:** from local source via `iii worker add ./<worker>` (the
image itself is not booted; `runtime`/`scripts.start` drive the local run).
- **Registry payload:** the built image reference (`image_tag` output).

Templates: [`todo-worker/`](../../todo-worker/), [`todo-worker-python/`](../../todo-worker-python/).

## Bundle

- **Build:** esbuild single-file `index.mjs` + `iii.worker.yaml` packed into
`<worker>.tar.gz`.
- **Asset URL:** `https://github.com/<repo>/releases/download/<tag>/<worker>.tar.gz`
- **Publish boot:** extracts bundle, runs `node ./index.mjs`.
- **Dependencies:** bundled worker may declare in-repo deps in `iii.worker.yaml`
`dependencies`; when the bundle worker's source changes, those deps join the
CI matrix (see [`testing-and-ci.md`](testing-and-ci.md)).

Example: [`harness/`](../../harness/).

## Publish boot modes

[`manifest_version.py deploy-mode`](../../.github/scripts/manifest_version.py)
selects how `_publish-registry.yml` starts the worker:

| Mode | When | Boot |
|---|---|---|
| `release-binary` | `deploy: binary` | Download + run Linux gnu binary from Release |
| `release-bundle` | `deploy: bundle` | Extract tarball, `node ./index.mjs` |
| `iii-add` | Other deploys with `runtime` or `scripts.start` | `iii worker add ./<worker>` |
| `cargo-run` | Other deploys, Rust, no `runtime`/`scripts.start` | `cargo run` (+ `config.collect.yaml` if present) |

## config.collect.yaml

Workers whose default `config.yaml` spawns sidecars or requires local paths
unavailable on CI ship `config.collect.yaml` — a lighter config used only for
interface collection at publish time and in the `interface-smoke` CI job.

Precedents: `storage`, `shell`, `coder`, `database`.

## Related

- Release SOP: [`../sops/release.md`](../sops/release.md)
- Testing: [`testing-and-ci.md`](testing-and-ci.md)
79 changes: 79 additions & 0 deletions docs/architecture/iii-worker-yaml.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# iii.worker.yaml contract

Every worker folder ships `iii.worker.yaml` at its root. CI discovery,
release routing, and registry publish all read this file.

## Required fields

| Field | Type | Purpose |
|---|---|---|
| `iii` | `v1` | Schema version |
| `name` | string | Folder name; git tag prefix; registry id |
| `language` | enum | `rust` \| `javascript` \| `node` \| `python` — routes CI language job |
| `deploy` | enum | `binary` \| `image` \| `bundle` — routes release build + publish |
| `manifest` | path | Version source: `Cargo.toml`, `package.json`, `pyproject.toml` |
| `description` | string | Registry + `--manifest` output |

## Binary-specific

| Field | Purpose | Consumer |
|---|---|---|
| `bin` | Cargo binary name (defaults to `name`) | `_rust-binary.yml`, publish boot |
| `targets` | Optional list of Rust triples to build | `_rust-binary.yml` matrix subset; `supported_targets` in manifest |

When `targets` is omitted, all nine default triples are built: macOS
x86_64/aarch64, Windows x86_64/i686/aarch64, Linux x86_64 gnu/musl,
aarch64 gnu, and armv7 gnueabihf (the matrix lives in
[`_rust-binary.yml`](../../.github/workflows/_rust-binary.yml)).
Comment on lines +24 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Call out the required Linux boot target.

The targets list reads like an arbitrary subset, but the release path always boots the x86_64-unknown-linux-gnu artifact. If a manifest omits that triple, publish will build successfully but fail at boot time. Please document that custom targets must keep the Linux gnu target.

🛠️ Suggested doc tweak
-When `targets` is omitted, all nine default triples are built: macOS
-x86_64/aarch64, Windows x86_64/i686/aarch64, Linux x86_64 gnu/musl,
-aarch64 gnu, and armv7 gnueabihf (the matrix lives in
-[`_rust-binary.yml`](../../.github/workflows/_rust-binary.yml)).
+When `targets` is omitted, all nine default triples are built: macOS
+x86_64/aarch64, Windows x86_64/i686/aarch64, Linux x86_64 gnu/musl,
+aarch64 gnu, and armv7 gnueabihf (the matrix lives in
+[`_rust-binary.yml`](../../.github/workflows/_rust-binary.yml)).
+
+If you override `targets`, keep `x86_64-unknown-linux-gnu` in the list;
+publish boots that artifact.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
When `targets` is omitted, all nine default triples are built: macOS
x86_64/aarch64, Windows x86_64/i686/aarch64, Linux x86_64 gnu/musl,
aarch64 gnu, and armv7 gnueabihf (the matrix lives in
[`_rust-binary.yml`](../../.github/workflows/_rust-binary.yml)).
When `targets` is omitted, all nine default triples are built: macOS
x86_64/aarch64, Windows x86_64/i686/aarch64, Linux x86_64 gnu/musl,
aarch64 gnu, and armv7 gnueabihf (the matrix lives in
[`_rust-binary.yml`](../../.github/workflows/_rust-binary.yml)).
If you override `targets`, keep `x86_64-unknown-linux-gnu` in the list;
publish boots that artifact.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~26-~26: The official name of this software platform is spelled with a capital “H”.
Context: ...nd armv7 gnueabihf (the matrix lives in _rust-binary.yml). ## Opt-o...

(GITHUB)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/architecture/iii-worker-yaml.md` around lines 24 - 27, Update the
"targets" documentation to explicitly state that any custom `targets` list must
include the Linux boot artifact triple `x86_64-unknown-linux-gnu` (the release
path always boots that artifact), and add a short warning that omitting it will
allow publish to build but will fail at boot; reference the existing default
matrix in `_rust-binary.yml` for examples and show a minimal example `targets:`
snippet that preserves the required triple.


## Opt-outs and runtime

| Field | Purpose | Consumer |
|---|---|---|
| `interface_smoke: false` | Skip interface boot smoke + registry publish | `ci.yml`, `release.yml` |
| `runtime` / `scripts.start` | Local boot definition; presence routes publish boot to `iii-add` for non-binary/bundle deploys | `manifest_version.py deploy-mode`, `iii worker add` |
| `scripts.install` | Build command for local install | `iii worker add` (local source) |
Comment on lines +31 to +35

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "interface_smoke" .github/workflows .github/scripts

Repository: iii-hq/workers

Length of output: 1359


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show release.yml semantics around the interface_smoke gate
sed -n '1,260p' .github/workflows/release.yml | nl -ba | sed -n '1,260p'

# Show ci.yml semantics around the interface_smoke gate
sed -n '150,260p' .github/workflows/ci.yml | nl -ba | sed -n '1,260p'

# Find and show documentation text about interface_smoke (e.g., testing-and-ci.md)
rg -n "interface_smoke" docs -S || true
rg -n "interface_smoke" . -S --glob '!**/node_modules/**' --glob '!**/dist/**' | head -n 50

Repository: iii-hq/workers

Length of output: 101


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show relevant parts of release.yml (with line numbers via cat -n)
cat -n .github/workflows/release.yml | sed -n '1,260p'

# Show relevant parts of ci.yml
cat -n .github/workflows/ci.yml | sed -n '150,260p'

# Locate docs and show the surrounding interface_smoke text
rg -n "interface_smoke" docs -S || true
docfile="$(rg -l "interface_smoke" docs -S | head -n 1 || true)"
if [ -n "$docfile" ]; then
  echo "=== $docfile ==="
  cat -n "$docfile" | sed -n '1,260p'
fi

# Also print the specific comment blocks near the earlier matches in workflows
rg -n "A worker may declare `interface_smoke: false`|opts out of interface boot smoke|interface_smoke != 'false'|needs.setup.outputs.interface_smoke != 'false'" .github/workflows -S

Repository: iii-hq/workers

Length of output: 24968


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact table row in the file under review
echo "=== docs/architecture/iii-worker-yaml.md (around interface_smoke) ==="
cat -n docs/architecture/iii-worker-yaml.md | sed -n '1,120p'

# Show testing-and-ci.md section that defines interface_smoke opt-out semantics
echo "=== docs/architecture/testing-and-ci.md (interface_smoke section) ==="
cat -n docs/architecture/testing-and-ci.md | sed -n '1,140p'

# Show any other doc references for cross-check
echo "=== Other docs mentioning interface_smoke ==="
rg -n "interface_smoke" docs/architecture -S

Repository: iii-hq/workers

Length of output: 7704


Clarify interface_smoke: false scope across CI vs release docs.

interface_smoke: false skips the interface boot smoke and gates the release workflow’s registry publish job (needs.setup.outputs.interface_smoke != 'false'), while build jobs still run. docs/architecture/testing-and-ci.md should mention this (so it doesn’t read like a CI-only smoke opt-out), keeping it consistent with docs/architecture/iii-worker-yaml.md.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/architecture/iii-worker-yaml.md` around lines 31 - 35, Update docs so
it's clear that the interface_smoke field controls both the interface boot smoke
and the release workflow's registry publish gating: document that setting
interface_smoke: false disables the interface boot smoke AND causes the release
job gating condition (needs.setup.outputs.interface_smoke != 'false') to skip
the registry publish while build jobs still run; amend
docs/architecture/testing-and-ci.md to include this behavior and ensure wording
matches docs/architecture/iii-worker-yaml.md where interface_smoke is described.


## Bundle-specific

| Field | Purpose |
|---|---|
| `dependencies` | Map of worker name → semver range for bundled sub-workers |

Example: [`harness/iii.worker.yaml`](../../harness/iii.worker.yaml).

## Example (minimal Rust binary)

```yaml
iii: v1
name: session-manager
language: rust
deploy: binary
manifest: Cargo.toml
bin: session-manager
description: Durable, reactive, branching store of typed conversation entries.
```

## Example (POSIX-only targets)

```yaml
targets:
- x86_64-apple-darwin
- aarch64-apple-darwin
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- aarch64-unknown-linux-gnu
- armv7-unknown-linux-gnueabihf
```

See [`shell/iii.worker.yaml`](../../shell/iii.worker.yaml).

## Validation

- `pr-checks` parses the file via [`validate_worker.py`](../../.github/scripts/validate_worker.py).
- `parse_release_tag.py` rejects unknown `deploy` values at release time.

## Related

- Deploy routing: [`deploy-modes.md`](deploy-modes.md)
- Onboarding checklist: [`../sops/new-worker.md`](../sops/new-worker.md) §6
Loading
Loading