Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Artifactize

Build together on an Artifact Graph.

Artifactize helps you and your teammates review each part of a project without redoing checks on unchanged work. An Artifact is a reviewable piece of work: a file, a folder, or the graph of connections between those parts. An eval is a check on an Artifact—a command, an agent’s judgment, or a person’s sign-off.

  • Work on your part in your own way. Declare which other parts your check uses so their changes trigger the right reviews, not unrelated work.
  • Reuse reviews when their inputs still match. A fingerprint identifies those inputs; artifactize status shows what needs another review.
  • Share results with teammates and CI through the team review store. Monitor follows progress, and Review helps people work through their waiting sign-offs.

It runs on Linux, macOS and Windows. Start with Installation, then follow the Guide to get a first GREEN result and reuse it without a model or API key.

Where to go next

  • Features: Artifacts and their Graph, evals, fingerprints, Runs, tools and team sharing. These features work through the same library in every interface.
  • Desktop: browse Runs and handle Human reviews in a window.
  • Agent plugin: give Arti the portable skill and connect your coding host through MCP.
  • CLI: command syntax, output and exit codes.
  • TUI: Monitor and Review in a terminal.
  • Source: open source under Apache 2.0.

Install artifactize

Every install provides artifactize, with its built-in tools available as artifactize tools. Non-Store channels also provide those tools as the standalone artifactize-tools command. Every GitHub release attaches prebuilt binaries for Linux (statically linked, x86_64 and aarch64), macOS (Apple silicon and Intel) and Windows (x64), each archive with a SHA-256 checksum, and the desktop app: one macOS disk image for both Mac CPUs, signed and notarized by Apple, and a Linux AppImage and .deb for x86_64; the Windows zip carries the app beside the commands. Stable releases are also published on crates.io, Homebrew and the Microsoft Store. winget distribution is in preparation and is not available yet. It is open source under the Apache License 2.0.

Before v1, the Linux build gates a release; the macOS and Windows archives and the desktop app are attached as soon as they build.

Prerequisites

  • Linux (x86_64 or aarch64), macOS (Apple silicon or Intel) or Windows (x64). WSL 2 runs the Linux build.
  • The example projects use python3 to run Python checks and grep to search text. Both commands must be available in your terminal. Projects run their declared commands as given on every OS; see Operating systems.
  • Optional, one per Agent backend you plan to use: OPENAI_API_KEY, ANTHROPIC_API_KEY, or a ChatGPT plan that includes Codex. Runtime and Human evals need none.

PATH is the list of directories your terminal searches for commands. Before starting the examples, check that it can find their required tools:

python3 --version
grep --help

These should print a Python version and grep’s help. If either command is not found, install that tool or add its directory to PATH before continuing.

Only cargo install needs a Rust toolchain: the latest stable Rust, from rustup, and a C compiler (SQLite is built from source).

Install

Pick your OS. Install from one channel only; the Store uses the artifactize tools facade, while other channels also install the standalone tools command.

Start with the default for your OS, or choose an alternative below:

The defaults install the desktop app too, except Homebrew, which installs it as a separate cask. Cargo and a source build install the commands only.

Linux

curl -fsSL https://artifactize.dev/install.sh | sh
artifactize --version

The script picks the archive for your OS and CPU from the latest stable GitHub release, checks its SHA-256 against the release’s checksum and refuses a mismatch. It runs both new binaries before replacing anything, then installs them to ~/.local/bin. It uses curl or wget, never sudo, and never edits your shell startup files: if ~/.local/bin is not on PATH, it prints the line to add. It also warns about another artifactize or artifactize-tools on PATH that would run instead. On x86_64 Linux with glibc it also installs the desktop app from the same release, checked the same way: it unpacks the AppImage once into ~/.local/share/artifactize/desktop (no FUSE needed), adds an artifactize-desktop launcher beside the commands and an Artifactize menu entry. On aarch64 or musl systems such as Alpine it installs the commands only. Two environment variables change what it does; a prerelease installs only when ARTIFACTIZE_VERSION names it:

curl -fsSL https://artifactize.dev/install.sh | ARTIFACTIZE_VERSION=0.5.2 sh             # this release
curl -fsSL https://artifactize.dev/install.sh | ARTIFACTIZE_INSTALL_DIR="$HOME/bin" sh   # this directory

macOS

Homebrew is a macOS package manager: it installs and updates command-line tools for you. If brew is not available in your terminal, follow Homebrew’s setup instructions first. Then run:

brew install lhj6102/tap/artifactize
artifactize --version

The formula in the lhj6102/tap Homebrew tap installs both commands for your Mac’s CPU and follows stable releases. The desktop app is a cask in the same tap. It links the app’s own artifactize and artifactize-tools onto Homebrew’s PATH, so this one command gives you the app and both commands (uninstall the formula first if you have it; the cask replaces it):

brew install --cask lhj6102/tap/artifactize

The install script works on macOS as on Linux, with the same checks and environment variables. It installs the commands to ~/.local/bin, which a new Mac does not have on PATH; the script prints the line to add to your shell’s startup file, such as ~/.zshrc. It also installs the desktop app as ~/Applications/Artifactize.app.

curl -fsSL https://artifactize.dev/install.sh | sh

Windows

Microsoft Store (default). Install Artifactize from its Store page, or in a terminal:

winget install 9PB6W4LL165D --source msstore
artifactize --version   # in a new terminal

Microsoft signs Store packages, so Smart App Control lets them run, and the Store keeps them up to date. The package provides artifactize as an app execution alias; its tools run as artifactize tools read FILE, artifactize tools list, and so on. Other channels also provide the standalone artifactize-tools command. It adds two Start menu entries: Artifactize opens the desktop app, and Artifactize CLI shows the command’s help and a hint to open Windows Terminal or PowerShell, then waits for Enter when it owns the console alone; terminal launches and redirected input/output never pause. The Store package uses Windows’ default AppData virtualization: the app and the command keep their own package-private state rather than sharing Runs, sign-in and cache with winget, zip or Cargo installs. Windows removes that private state when you uninstall the Store app. See State for the Windows overlay caveat.

PowerShell install script. In Windows PowerShell 5.1 or PowerShell 7, without administrator rights:

irm https://artifactize.dev/install.ps1 | iex
artifactize --version   # in a new terminal

The script checks the zip’s SHA-256, runs both new binaries, then installs artifactize.exe, artifactize-tools.exe and the desktop app’s artifactize-desktop.exe to %LOCALAPPDATA%\Programs\artifactize, adds that directory to your user Path and adds an Artifactize Start menu shortcut for the app; open a new terminal afterwards. It reads the same environment variables as install.sh.

Smart App Control. The release zip used by the install script is not code-signed. On a machine with Smart App Control on, Windows may block a new release even when an earlier one ran. If Windows blocks the new binaries, the script stops before replacing anything: your previous artifactize and artifactize-tools stay installed and runnable, and Path is left unchanged. Do not bypass your application-control policy; install from the Microsoft Store instead.

winget (in preparation — not available yet). lhj6102.Artifactize has not been accepted into winget. Use the Microsoft Store now. After the package is published, the intended command is:

winget install lhj6102.Artifactize

That package installs both commands and the desktop app from the release zip and follows stable releases.

WSL. Inside WSL 2, artifactize is the Linux build: install it in the distribution with the Linux instructions. It is separate from a Windows install and keeps its own state. Built-in open and login codex use the distribution’s xdg-open, never a Windows program; see Opening files and the browser.

Cargo (all platforms)

With cargo-binstall, fetch the same prebuilt binaries into ~/.cargo/bin:

cargo binstall artifactize artifactize-tools

Or build the latest stable release from crates.io:

cargo install artifactize artifactize-tools --locked
cargo install artifactize@<version> artifactize-tools@<version> --locked   # a particular release

--locked builds with the dependency versions the release was tested with. The binaries go to ~/.cargo/bin, which must be on PATH; --root DIR installs them under DIR/bin instead. Both cargo commands work on macOS and Windows too.

Build from source (all platforms)

To build from source, install from a checkout; its rust-toolchain.toml selects stable Rust. The example projects and the Quick start use a checkout too:

git clone https://github.com/lhj6102/artifactize
cd artifactize
cargo install --path crates/artifactize --locked
cargo install --path crates/artifactize-tools --locked

Releases take their version from their tag, so a build from a checkout reports the placeholder version 0.0.0-dev.

Desktop app

The desktop app runs Monitor, Review and Graph in a window. It shows the Runs that artifactize verify starts, so the commands come with it: the Microsoft Store, install.ps1, install.sh, the Homebrew cask and the .deb put artifactize on PATH. From the disk image or an AppImage run on its own, the app sets up the commands on first launch (below). TUI and MCP already come with the ordinary artifactize binary; they need no additional product install.

The defaults above install it: the Microsoft Store and install.ps1 on Windows, install.sh on macOS and x86_64 Linux, and the Homebrew cask on macOS. Each release also attaches the app on its own:

FileInstall
artifactize-desktop-vVERSION-universal-apple-darwin.dmgOpen it and drag Artifactize to Applications, then open the app once. If the command is missing, approve its administrator prompt to make artifactize available in new terminals. One image runs on Apple silicon and Intel, from macOS 11; Apple has notarized it.
artifactize-desktop-vVERSION-x86_64-unknown-linux-gnu.debsudo apt install ./artifactize-desktop-vVERSION-x86_64-unknown-linux-gnu.deb on Ubuntu 22.04, Debian 12 or later. It installs all three commands to /usr/bin and needs WebKitGTK 4.1 and GTK 3, which apt pulls in.
artifactize-desktop-vVERSION-x86_64-unknown-linux-gnu.AppImageMake it executable and run it; it needs glibc 2.35 or later. First launch copies the commands to a stable per-user directory and links them in ~/.local/bin, without administrator rights.

Each file has a .sha256 checksum beside it. Run the app as artifactize-desktop, or open Artifactize from your applications menu or the Start menu.

The app checks standard terminal command locations and its inherited PATH in the background, without running shell startup files. On macOS this includes the system’s /etc/paths and /etc/paths.d entries, which new login terminals use. An existing command from Homebrew, an install script, .deb, winget or the Store is left alone; there is no setting to choose. Otherwise:

  • macOS: the app writes its absolute Contents/MacOS directory to /etc/paths.d/artifactize through the system administrator prompt. Open a new terminal afterwards and run artifactize --version. Both commands run from the installed app, so updating the app updates them too. If you move the app, launch it again to repair an entry pointing to the old, missing location. Cancelling the prompt is remembered in the app’s state for that bundle location; it will not ask on every launch.
  • Linux AppImage: the commands are copied to ${XDG_DATA_HOME:-~/.local/share}/artifactize/bin and linked from ~/.local/bin. They keep working after the temporary AppImage mount goes away, and launching a newer AppImage refreshes the app’s copies. Files or links in ~/.local/bin that the app did not create are never replaced. If your shell does not already search ~/.local/bin, add export PATH="$HOME/.local/bin:$PATH" to its startup file and open a new terminal. The app does not edit shell startup files.

Setup errors are logged without stopping the app. Windows installers, .deb and install.sh’s extracted Linux app already provide the commands and need no setup.

Build the desktop app from source

In addition to the source-build Rust prerequisites above, install Node.js 24, pnpm at the version named in crates/artifactize-desktop/ui/package.json, and Tauri’s native prerequisites for your OS. Linux needs WebKitGTK 4.1, GTK 3 and librsvg development packages; macOS needs the Xcode command-line tools; Windows needs the C++ build tools and WebView2. From the repository root:

pnpm -C crates/artifactize-desktop/ui install --frozen-lockfile
pnpm -C crates/artifactize-desktop/ui build
cargo install --path crates/artifactize-desktop --locked --bin artifactize-desktop
artifactize-desktop

The frontend must be built before Cargo: the normal app embeds ui/dist. Keep the default Cargo features and do not enable the test-only e2e feature for a normal app. Install the CLI from the same checkout too, as above: the app shows the Runs that artifactize verify starts. The app uses the same state home and credentials as the unpackaged CLI, not the Store app’s private state.

Plugin for Claude Code and Codex

The plugin adds the Hey Arti skill and the artifactize MCP server to Claude Code or Codex. It runs the artifactize program, so install that first with one of the channels above. This repository is the plugin’s marketplace, a catalog the assistant reads to find the plugin.

In a Claude Code session, add the marketplace, then install the plugin from it:

/plugin marketplace add lhj6102/artifactize
/plugin install artifactize@artifactize

From a shell, claude plugin marketplace add lhj6102/artifactize and claude plugin install artifactize@artifactize do the same.

For Codex, in a shell:

codex plugin marketplace add lhj6102/artifactize
codex plugin add artifactize@artifactize

Or run /plugins in Codex CLI and pick the artifactize marketplace.

Update

Installed withUpdate
install.sh or install.ps1Run the same command again; it updates the desktop app too
Homebrewbrew upgrade artifactize; the desktop app with brew upgrade --cask artifactize
Microsoft StoreAutomatic; or Library → Get updates in the Store app
Disk image, .deb or AppImageInstall the newer file from the release the same way
winget (not available yet)After publication: winget upgrade lhj6102.Artifactize
cargo binstallcargo binstall artifactize artifactize-tools
cargo installcargo install artifactize artifactize-tools --locked; from a checkout, git pull and repeat cargo install --path

The install scripts and Cargo get a release as soon as it is published. Homebrew and the Microsoft Store follow stable releases only; a Store update can lag while Microsoft reviews it. The prepared winget channel targets stable releases only; its first publication is still pending. Updating keeps your state.

The plugin follows this repository’s main branch, which holds released versions. Update it with /plugin marketplace update artifactize in Claude Code (or turn on auto-update for the marketplace in /plugin), or codex plugin marketplace upgrade artifactize in Codex.

Upgrading from 0.11

State schemas 6 and 7 are upgraded additively to 8 on first use. Existing Runs, cache records and Human approvals are preserved. An older binary limited to schema 6 or 7 cannot open the upgraded state; keep a backup before switching versions.

artifactize verify now submits a durable Run, prints its ID and exits on acceptance. Add --wait to scripts and CI that need the validation exit code. Closing a terminal or interrupting a watcher leaves the Run running; artifactize run cancel RUN_ID is the explicit cancellation action. The foreground Human deadline and verify --timeout-ms are removed. --request-key makes retries return the same Run.

Gate bypass (--ignore-gates and review_policy) is removed. Per-request --speculate and --defer-human replace it, both off by default; deferred sign-offs end with SIGNOFFS_PENDING and --wait exit 5. See Runs and status. Plain artifactize monitor starts on ALL; monitor ., a positional path or --repo selects that repository initially.

Upgrading to 0.9

Rewrite declarations as TOML index.artf for folders and <file>.artf for files. Keys are snake_case and evals are [evals.<id>] tables; .artfignore replaces the old ignore file. There is no converter. Have an agent rewrite and commit the files, remove the legacy markers and run artifactize config check. Families are removed; use your own generator if you want templates and commit its .artf output. See Declarations (.artf).

The original 0.9 transition introduced state schema 6 and required replacing pre-schema-6 state. Current versions preserve existing schema-6 or schema-7 state by upgrading it automatically to schema 8, including Runs, cache records and Human approvals. Only schemas 1–5 require a new state: they are refused with exit 2, and artifactize doctor reports a hard error and exits 1. For those older states, set ARTIFACTIZE_STATE_HOME or --state-dir to a new directory, or move the old one away. Reuse-key v2 matches no pre-0.9 result, even in a team review store or with a script fingerprint. The first full verify reviews every eval once, Human sign-offs included. See Upgrading to 0.9.

Monitor now opens an eval’s Detail in its pane rather than a modal. Standalone Review shares that Human Detail: claims are explicit, and tools and submission never claim for you. The verdict forms, submit keys and editor shortcut changed; see Human reviews and Monitor for the current controls.

0.5.0 removes the chatgpt and claude Agent backends, with login chatgpt, logout chatgpt and the internal mcp command, and adds codex for ChatGPT plan users. config check names the replacements for each eval or profile variant that still declares a removed backend. artifactize no longer uses ChatGPT tokens saved by an earlier release: disconnect artifactize in ChatGPT Settings, then delete auth/chatgpt.json, auth/chatgpt-registration.json and auth/chatgpt.lock from the state directory.

State

All state, including Runs, saved Agent conversations and sign-ins, lives in one directory, by default ~/.local/state/artifactize (%LOCALAPPDATA%\artifactize on Windows). State lists its contents, the environment variables that move it and the Store version’s private copy.

--state-dir PATH moves the whole state for one command. Use the same value for login, verify, request, run and monitor. State must be outside the repository under review. Tokens under auth/ are also refused inside any git work tree or artifactize project, such as a dotfiles repository at $HOME or a gitignored folder in a checkout; ARTIFACTIZE_CODEX_AUTH_FILE needs no storage (State).

Backends

An Agent eval names exactly one backend and one exact model ID. There is no model catalog and no fallback to another backend or model.

A backend is the connection Artifactize uses to send an Agent review to a model service. An API key is that service’s secret account credential. For the API-key backends, create a key in your OpenAI account or Anthropic Console, then replace ... in the matching command with that key. Never commit it or put it in an Artifact declaration. Codex uses the separate browser sign-in below instead.

BackendSetupList models
openaiexport OPENAI_API_KEY=...artifactize models openai
anthropicexport ANTHROPIC_API_KEY=...artifactize models anthropic
codexartifactize login codexartifactize models codex

In PowerShell, set a key with $env:OPENAI_API_KEY = "...".

login codex signs in to your ChatGPT account the way the Codex CLI does. It prints a sign-in URL and opens it in the default browser (xdg-open on Linux and WSL, open on macOS, the default browser on Windows). Finish in a browser on the same machine: the browser returns to localhost:1455. On WSL 2, a Windows browser works too; copy the printed URL into it if nothing opens. Artifactize reserves both IPv4 and IPv6 loopback addresses (IPv4 only if the host has no IPv6). If another process, such as a Codex CLI or app sign-in, holds port 1455, artifactize skips its callback listener and asks for a pasted redirect instead. On another machine, use the same fallback. Paste the full URL starting http://localhost:1455/auth/callback?code= into the terminal, or the desktop sign-in’s paste field. Find it in the browser’s address bar after the failed load, or in DevTools → Network if the page keeps loading. logout codex revokes and deletes artifactize’s tokens. To use the tokens of an existing Codex sign-in instead, set ARTIFACTIZE_CODEX_AUTH_FILE=~/.codex/auth.json; artifactize only reads that file. See Codex.

artifactize doctor                 # state and its schema, API-key presence, Codex sign-in, review store
artifactize doctor --repo PROJECT  # also validates PROJECT's declarations

doctor calls no provider and creates no Run. Missing keys or sign-ins are warnings, because every backend is optional. It exits 1 only for hard errors such as unusable state or invalid configuration.

Cleanup and uninstall

artifactize prune --dry-run           # list removable scratch output of finished Runs
artifactize prune --older-than 7d     # remove it; saved results stay readable
artifactize logout codex              # if you signed in with Codex
artifactize remote logout             # if you signed in to a review store

Then remove the commands the way you installed them:

rm ~/.local/bin/artifactize ~/.local/bin/artifactize-tools   # install.sh (or your ARTIFACTIZE_INSTALL_DIR)
brew uninstall artifactize                                   # Homebrew
cargo uninstall artifactize artifactize-tools                # cargo install or cargo binstall

And the desktop app:

rm -rf ~/Applications/Artifactize.app                        # install.sh on macOS
rm -rf ~/.local/share/artifactize/desktop ~/.local/bin/artifactize-desktop \
  ~/.local/share/applications/artifactize.desktop             # install.sh on Linux
brew uninstall --cask artifactize                            # Homebrew cask, with its commands
sudo apt remove artifactize                                  # .deb

From a disk image, drag Artifactize from Applications to the Trash. If you approved first-launch command setup, also remove its terminal path entry:

sudo rm -f /etc/paths.d/artifactize

For an AppImage run on its own, delete the AppImage. Remove only the command links that point to the app’s stable copies, then remove those copies:

bin="${XDG_DATA_HOME:-$HOME/.local/share}/artifactize/bin"
for command in artifactize artifactize-tools; do
  link="$HOME/.local/bin/$command"
  [ "$(readlink "$link")" != "$bin/$command" ] || rm "$link"
done
rm -rf "$bin"

Open a new terminal after removing a command’s path entry.

On Windows:

winget uninstall lhj6102.Artifactize                       # future winget install, after publication
Remove-Item -Recurse "$env:LOCALAPPDATA\Programs\artifactize"   # install.ps1
Remove-Item "$([Environment]::GetFolderPath('Programs'))\Artifactize.lnk"   # its Start menu shortcut

Uninstall the Store version from Settings → Apps → Installed apps, or right-click Artifactize in the Start menu. After removing an install.ps1 install, also remove its directory from your user Path (search the Start menu for “Edit environment variables for your account”).

Uninstalling the Store app removes its package-private state, including Runs, saved results, sign-in and cache. Other install methods keep their state directory, so a later unpackaged install finds those Runs and results again. prune never touches active Runs, database rows or the repository. To remove retained unpackaged state, delete the state directory that artifactize doctor prints on its first line.

Uninstall does not remove your project files, exported files, or state explicitly placed outside AppData with --state-dir or an environment override. Files written by user-configured commands outside the package-private view can remain too; the Windows packaging test measured a Runtime child’s new AppData file in the real location. Remove those separately if you no longer need them. Existing unvirtualized AppData files are also outside the Store package’s cleanup; see the Windows behavior and overlay caveat.

Next, take the Quick start: a five-minute tour that needs no model.

Removed gate-bypass settings

review_policy and review_policy.dependency_gates are removed; even an empty policy table fails configuration validation as an unknown field. The old --ignore-gates CLI flag and VerifyOptions.ignore_gates library option are also removed. Choose speculation and deferred sign-offs per verification request instead; neither mode waives final approval obligations. Old saved keys are ignored on read, so removing the legacy policy needs no state migration.

Guide (no model needed)

The tour uses the example projects in the repository. Unless you built from a checkout, a local copy of the repository, make one first with Git, the version control program (get Git if git --version does not answer). git clone downloads the repository into a new artifactize folder, and cd enters it:

git clone https://github.com/lhj6102/artifactize
cd artifactize

Each reviewed piece is an Artifact. Its index.artf is a text configuration file listing its checks, called evals. It uses TOML, a readable key = value format. A fingerprint identifies the reviewed inputs; the built-in artifactsum makes that value from files so artifactize can detect changes.

Artifactize’s state is where it stores Runs and results. Keep this tour’s reviews apart from your normal state. In a POSIX shell (for example Bash or zsh), the first line below creates a fresh temporary folder with mktemp -d and tells artifactize to keep its state there. export also passes that setting to commands launched from this terminal. Then enter the example and check its declarations:

export ARTIFACTIZE_STATE_HOME=$(mktemp -d)
cd examples/runtime-relations
artifactize config check
artifactize status
artifactize config graph

In PowerShell, replace the first line with $env:ARTIFACTIZE_STATE_HOME = (New-Item -ItemType Directory (Join-Path $env:TEMP (New-Guid))).FullName. This creates a uniquely named folder under Windows’ temporary directory and saves its path in the same setting. The artifactize commands are unchanged.

config check validates the declarations without running project programs. status predicts two checks would execute and a third waits for their evidence; its exit code is 1 because the project is not yet satisfied. config graph shows the pieces and their input relationships. Now run all three checks and wait for the results:

artifactize verify --all --wait

All three results should be GREEN, with exit code 0 (success). The output names the saved Run, with an identifier such as run-Abc123. Replace RUN_ID in the next command with your printed identifier, not those literal words:

artifactize run show RUN_ID

This prints the saved Run as JSON, a structured text record. argv is the list of arguments passed to a program; stdout is the text that program printed. The record also includes fingerprints identifying the reviewed inputs.

Run the same checks again:

artifactize verify --all --wait
artifactize status

Nothing executes this time: all three results are reused. Both commands exit 0, and status identifies the reusable results. Keep this terminal open so later steps use the same temporary state directory.

The runtime-relations README also shows how to make a check RED. Each folder declares itself in its index.artf file. Leaving out fingerprint uses the built-in file-change check, artifactsum. To disable reuse, set fingerprint = false. See Declarations (.artf) for snake_case keys and the JSON/output naming distinction.

Files and Eval dependencies

A sidecar such as hero.png.artf declares the neighboring file as an independent Artifact. It requires a name, uses the same TOML schema as a folder and hashes only that file by default. Built-in tools see that file and the inputs explicitly made available to its evals, not unrelated sibling files. An eval that uses another Artifact, such as a shared checker, names it as {house-style}; see Eval dependencies. The posts example uses three Markdown sidecars and one shared style checker. From the previous example’s directory:

cd ../posts
artifactize config check   # four Artifacts, three evals
artifactize verify --all --wait   # three GREEN results
artifactize verify tip/style --wait # qualified file eval; reused
artifactize status         # all three post reviews are reused
cd ../runtime-relations    # continue the tour below

Each file hashes only itself. The referenced house-style Artifact covers the checker and banned words, so a shared change invalidates all three post reviews’ reuse keys: the recorded identity of a check’s criteria and reviewed inputs that decides whether its old result applies. Its README shows how to make one post RED while the other two reuse.

A runtime Eval references its inputs directly in its instruction or arguments. For example, the application can run its end-to-end (E2E) test suite, which exercises the working application, using {e2e-tests} while the test-code Artifact reviews its own style and feature coverage. Only that application Eval waits for the test-code reviews; unrelated application Evals remain runnable. See Eval dependencies.

Optional: Agent tools and the Human flow

The tour above needs no model. This optional part does: first set up the model credentials, exact model and profile described in the agent-tools README. A Human eval is requested only once every other non-Human eval of its Artifact is GREEN, so spec/review runs first. Both terminals must use the same temporary state directory. Before starting the waiting command below, print the first terminal’s value with echo "$ARTIFACTIZE_STATE_HOME" (PowerShell: $env:ARTIFACTIZE_STATE_HOME). In the second terminal, replace STATE_DIRECTORY below with that printed path. Use the new Run ID from the waiting command for RUN_ID; replace REQUEST_ID with the waiting request identifier printed by request list. For PowerShell, use $env:ARTIFACTIZE_STATE_HOME = "STATE_DIRECTORY" instead of export. Then open the same example directory.

cd ../agent-tools
artifactize tools check     # static: schemas, executables and scoped paths
artifactize tools check --execute --eval spec/review --tool coverage_spec --args '{}'
artifactize verify --eval spec/review --wait    # the machine review comes first
artifactize verify --eval spec/signoff --wait   # records a WAITING_HUMAN request and waits
# In a second terminal, in the same directory:
export ARTIFACTIZE_STATE_HOME="STATE_DIRECTORY"  # copy the first terminal's path
artifactize request list --run RUN_ID    # shows REQUEST_ID
artifactize request claim REQUEST_ID
artifactize request tool REQUEST_ID notes_spec
artifactize request submit REQUEST_ID --verdict GREEN --fields '{"approved":true,"comment":"Both questions are answered."}'
artifactize run show RUN_ID

claim, tool and submit use $USER as the reviewer unless you pass --reviewer NAME; request unclaim REQUEST_ID gives a claim back. artifactize review does the same in a terminal UI: it lists waiting requests and previews the instruction. Enter opens Detail, c explicitly claims, Tab focuses tools and Enter (or a click) runs one at once. g/r opens the GREEN/RED fields; Ctrl-S submits. Esc stops editing before returning to the list, keeping the draft. The artifactize-owned Run waits for Human results like any other, so dependents continue in the same Run. Plain verify submits and returns; verify --wait follows the Run in the foreground and exits 0 here once the GREEN sign-off arrives, provided the preceding machine review is still valid.

For a real Agent review, put an exact model ID in the profile you use in examples/agent-tools/spec/index.artf, then run artifactize verify --eval spec/review --wait, adding --profile anthropic or --profile codex for the other backends. Without credentials the review is recorded as ERROR and the waiting command exits 2. See the agent-tools README.

Monitor

artifactize monitor .      # Initially select this repository
artifactize monitor        # Initially select ALL repositories in this state

Start it in a second terminal while a Run executes. Use Enter or → to move from Scope through Runs and the tree to an eval’s Detail; Esc steps back. j/k selects, ? shows help, and q quits. Detail shows Agent conversations, Runtime logs or the requirements an eval waits for. Browsing runs no owner commands.

For Human claims, tools and verdict forms, see Human reviews and monitor.

Team review store (optional)

A team review store shares reusable verdicts across machines and CI. Each machine signs in with a token, a secret that grants its allowed actions; CI should receive read-only access so it cannot create an approval. The team guide and walkthrough cover server setup, tokens and trying the same review from a second machine.

Next steps

The examples show each feature in a small project, and Artifacts and evals explains the declaration you just ran.

Examples

Install the binary as described in Install, then try the example projects. Each README lists the exact commands:

  • Runtime relations: runtime evals over parent/child folders, {artifact} references in instructions and argv, a basis Artifact, a RED-able check, and reuse through the built-in artifactsum, with status explaining what changed.
  • Agent tools: an Agent eval using the built-in read, grep, view_image and section tools and a declared json tool, pass/fail schemas, backend and model selection, and a Human sign-off with the built-in open and read tools.
  • Posts: three Markdown file Artifacts, one sidecar per post, a shared style checker in a referenced basis Artifact, per-post word limits and independent artifactsum reuse. A post change reviews one eval; a shared checker or rule change reviews all three.
  • Eval dependencies: per-Eval inputs and dependency-first scheduling.
  • Team walkthrough: two machines and CI reuse each other’s verdicts through one artifactize server.

Artifact

An Artifact is anything you review: code, docs, designs, images. A folder or a single file declares its evals in a static TOML .artf file. artifactize reads these declarations into one dependency graph.

Artifact Graph

The Artifact Graph connects inputs to the evals that consume them. A reference such as {spec} admits that Artifact to an eval; changes to its fingerprint invalidate only reviews whose keys cover it. Child folders organize inputs, but sharing a repository alone does not make every Artifact a dependency of every check.

Execution gates belong to each eval’s own references. A child folder is part of its parent’s content, so it counts as a reference of every eval of the parent. See Eval dependencies.

The graph itself can be reviewed: a nameless root .artf declares the Graph Artifact. Its fingerprint covers extracted criteria and labeled relations, not source bytes or execution tuning. Use it for a person’s review of the project’s review structure. Declarations gives the syntax; the graph tool shows the full current structure to an agent or opens it in Desktop for a person.

Folder configuration

A folder with an index.artf is an Artifact. name is required; basis, views, fingerprint and tags are optional. For older declarations, see the legacy policy upgrade reference.

Evals are tables keyed by their local ID: [evals.heading] becomes the qualified ID usage/heading on the usage Artifact. Do not declare an id field. Each eval has a title and a profile whose kind is runtime, agent, or human. All evals require payload.instruction and may have pass_schema, fail_schema and profile_variants.

A runtime eval’s exit code is its verdict (0 is GREEN); an Agent or a Human returns GREEN or RED with owner fields. Dependencies come from each eval’s own references and its Artifact’s children.

This is the guide Artifact of the runtime-relations example, whose README explains children, {artifact} references and basis Artifacts:

name = "guide"

[evals.terms]
title = "Every bold term is defined in the glossary"
profile = { kind = "runtime", command = "python3", args = ["check_terms.py", "{glossary}/terms.txt", "{guide}/intro/page.md", "{usage}/page.md"], timeout_ms = 10000 }
payload.instruction = "Check that every bold term in {intro} and {usage} is defined in {glossary}."

Declaration keys are snake_case. JSON Schema keywords such as minLength and additionalProperties keep their standard names; owner-defined payload fields keep their chosen names. --json output and stored state still use fields such as passSchema, timeoutMs and agentTools. See Declarations (.artf) for the exact syntax.

File Artifacts

A sidecar hero.png.artf next to a regular file hero.png declares that file as an Artifact. It uses the same schema and requires its own globally unique name:

# hero.png.artf
name = "hero-art"
tags = ["type:image", "scope:player"]

[evals.approved]
title = "The hero sprite matches the style guide"
profile = { kind = "human" }
payload.instruction = "Check {hero-art} against {style-guide}."

A file Artifact hashes its one target by default. Its working directory is the containing folder, but built-in tools see the file and its admitted scope, not unrelated siblings. The sidecar does not make it a child of the surrounding folder; that folder still hashes the target as part of its own files. A folder declared with index.artf, by contrast, is a child of the nearest Artifact folder above it. See the file-Artifact reference for naming, fingerprint forms, virtual paths and invalid-target/revalidation rules.

Eval dependencies

A dependency belongs to the Eval that uses the input, not every Eval on its Artifact. A {name} reference in payload.instruction or a runtime args operand names an input Artifact; that Eval depends on the Evals judging the input. If an Artifact uses another one, the eval that checks that use names it as {name}; nothing else adds a dependency.

A child is the one link no eval writes. A folder Artifact’s files include its child folders, so a child counts exactly as if every eval of its parent referenced it: each eval of the parent depends on the evals of its nearest marked children.

For example, application E2E execution belongs to the application:

name = "app"

[evals.e2e]
title = "The application passes its end-to-end suite"
profile = { kind = "runtime", command = "node", args = ["{e2e-tests}/run.mjs", "{app}"] }
payload.instruction = "Run {e2e-tests} against {app}."

The e2e-tests Artifact evaluates the test code itself: its style and fidelity to the feature documents. It does not execute the application suite as a review of its own test code. An independent app style Eval does not wait for the test-code reviews unless its own instruction references them.

All Evals can judge their own targets independently of their inputs’ verdicts. Plain verify chooses dependency-first scheduling for efficiency: pending input reviews delay the consumer Eval; RED input reviews block it. --speculate lets work start before unresolved input gates, without approving missing requirements. Final GREEN still requires all required evidence, including upstream reviews.

An Eval is blocked, never an Artifact. Its execution gates are the actual Evals of its referenced Artifacts and its Artifact’s nearest marked children. A RED or BLOCKED gate blocks it in turn, so blocking propagates along these Eval gates. A RED Eval of a child blocks every Eval of its parent. An intermediate Artifact with no Evals supplies no execution gates: referring to it does not gate execution on its own inputs or children. Those still belong to the final approval obligations, so the consumer may execute without the Run being able to finish GREEN.

An Eval’s own Artifact and its dependency-cycle peers are excluded from these gates, apart from the Human sibling prerequisite. Cycles follow Eval references and children: a child that references its parent, directly or through a chain, is its parent’s cycle peer. A reference to a cycle peer carries the cycle’s external gates; a reference to another cycle carries every Eval of that cycle. These gates also determine waiting and speculative cancellation.

For example, if docs’s eval references cli and cli’s eval references docs, they form a cycle: a RED cli/tests does not block docs/matches-cli or replace its verdict. Both run independently and must pass before consumers outside the cycle can proceed.

A BLOCKED Eval’s reason names the RED Evals at the root of the block, for example Dependency verdict RED: core/tests, never Evals that were merely still running. In a speculative Run an upstream ERROR blocks too and is named as Dependency ERROR: …. blockedBy lists the same Evals, and the settled Run names every one of them.

Human sign-offs

A Human Eval starts only when every other non-Human Eval of its own Artifact is GREEN, on top of its dependency gates: a person signs off only on what the machine Evals already pass. Until then it waits. When one of those machine Evals is RED or ERROR, the Human Eval does not start and is BLOCKED, and its reason names them, for example Sibling machine eval RED: spec/review. Other Human Evals of the same Artifact are not part of this condition, so two sign-offs never wait for each other. The rule is the same in plain verify, with --speculate and with --defer-human: even a speculative Run does not ask a person before those machine Evals are GREEN.

The fingerprint field

Omit fingerprint for artifactsum, the built-in hash of the Artifact’s own files. fingerprint = false disables reuse; a script can define another value. Fingerprint explains the forms, covered inputs and GREEN/RED result reuse.

Tags and validation

tags = ["type:code", "scope:player"] adds display-only metadata. Tags are at most 64 unique nonblank strings without ASCII control characters. status, config graph, monitor and their JSON Artifact output show them. Tags change no fingerprint, Eval definition hash, reuse key, relation or selection of verify and status; there are no tag filters for those and no relationship rules. Only the artifactize review search filters on tags, with tag: terms.

Projects that want templates generate their own .artf files and commit them. Discovery and validation rules are in Declaration validation.

Declarations (.artf)

The exact rules for TOML declarations, folder and file Artifacts, fingerprints, Eval dependencies, scoped references and the runtime execution environment. Artifacts and evals introduces them.

Migrating removed dependency declarations

kind = "dependency" and depends_on are removed. Delete the dependency eval; put the needed Artifacts as {name} references in the consuming eval’s payload.instruction (or runtime args). For example, instead of a derived ready eval listing spec and assets, write the actual review instruction as "Review {app} against {spec} and {assets}." Each consuming eval waits for those inputs’ required evals; independent sibling evals keep running.

A child folder counts as a reference of every eval of its parent. To require any other Artifact’s reviews before an eval, reference that Artifact explicitly in the eval’s instruction or argv.

An old profile fails configuration parsing with unknown variant dependency, expected one of agent, human, runtime``. A depends_on field on a supported profile is an unknown-field error. These are configuration errors, not review verdicts; migrate the declaration before verifying.

Declaration validation

index.artf declares its containing folder. <filename>.artf declares the neighboring file whose complete name is <filename>, including its extension. Both use standard TOML and require name. The repository root’s nameless .artf declares the graph Artifact, with no source file.

Discovery walks from the repository root and skips .git, node_modules, and subdirectories and sidecar paths listed in an optional root .artfignore (gitignore syntax). In every visited directory, index.artf is read regardless of an ignore pattern matching that filename alone. To suppress a subproject’s folder declaration, exclude its containing directory from the walk instead. An ignored sidecar is not parsed or target-checked; its target remains an ordinary file unless another declaration includes it. List folders that hold separate projects, fixtures or examples:

crates/app/tests/
examples/

Names, eval IDs, tool/session names and profile variant names match [A-Za-z0-9][A-Za-z0-9_-]{0,63}. Artifact names are globally unique. Evals are ID-keyed tables, not an array; [evals.check] on app becomes app/check. An explicit eval id field is rejected. Evals within each declaration are ordered by ID, not by their position in the TOML source.

FieldRule
nameRequired Artifact name
tagsOptional list of at most 64 unique nonblank strings, without ASCII controls
basisOptional boolean; true declares a no-eval input, waiving its own review obligations but not its dependencies
fingerprintOmission uses artifactsum; false disables reuse; otherwise an artifactsum or script table
views.agent_tools, views.human_toolsOptional maps of declared tools
views.agent_sessions.<name>.start, .stopTool-session commands: required command and args, optional execution_paths; no other fields
views.agent_tools.<name>.sessionOptional name of a tool session declared in the same Artifact; command Agent tools only
evals.<id>.titleRequired nonblank title
evals.<id>.gateOptional "blocking"; keeps a Human sign-off strict under --defer-human. Omission follows the request mode; no other values
evals.<id>.mutexOptional list of at most 64 unique hierarchical mutex tags; runtime and Agent evals only; no default
evals.<id>.profileRequired runtime, agent or human profile
evals.<id>.payload.instructionRequired nonblank instruction for runtime, Agent and Human evals
evals.<id>.pass_schema, fail_schemaOptional owner-field JSON Schemas for executable evals; a Human eval’s take only form shapes
evals.<id>.profile_variantsUp to 64 named complete profiles retaining the default kind

Tool sessions keep a harness alive for one review. Artifactize owns their start, stop, cleanup and crash recovery; declarations expose no lifecycle or cleanup-timing options. The linked reference gives the full contract.

All declaration keys are snake_case: timeout_ms, max_tool_calls, max_tokens, input_schema, execution_paths, pass_schema, fail_schema, profile_variants, gate and mutex. Unknown fields and camelCase spellings at these typed boundaries are rejected. JSON Schema keywords such as additionalProperties, minLength and maxItems keep their standard names. Owner-defined payload fields keep their chosen names. --json, tool protocols and stored state keep their existing camelCase fields, such as timeoutMs, passSchema, agentTools and blockedBy; they are not declaration syntax.

Inline tables, dotted keys and multiline strings are standard TOML:

name = "app"
tags = ["type:code"]

[evals.review]
title = "The implementation matches the spec"
profile = { kind = "agent", backend = "codex", model = "YOUR_EXACT_MODEL_ID", reasoning = "high", timeout_ms = 240000 }

[evals.review.payload]
instruction = """
Read {spec}, then review {app}.
Return GREEN when the implementation matches the spec.
"""

[evals.review.pass_schema]
type = "object"
required = ["summary"]
additionalProperties = false
properties.summary = { type = "string", minLength = 1 }

TOML has no null value. Omit optional fields; do not use null. A schema may say type = "null", but literal null values such as a null const cannot be written. Unquoted TOML dates/times and non-finite numbers (inf, nan) are rejected anywhere, including payloads and schemas, because they have no JSON equivalent. Quote dates to use them as strings. Declaration errors name the .artf file, line and column when parsing or validating their contents, including syntax, deserialization, semantic validation and cross-Artifact reference errors. Field validation and unsupported-value errors also name the typed key path, such as evals.check.profile.args.

config check validates without executing fingerprint scripts or hashing files. Use it after rewriting declarations. Legacy declaration and ignore files are not read. A non-ignored legacy declaration file fails discovery. A directory with the legacy declaration filename is walked normally, unless a directory ignore pattern excludes it. .artfignore can ignore a legacy file individually or skip its whole folder. The old ignore file at the repository root always fails. The diagnostics are:

artifactize.json is no longer read; declare this Artifact in index.artf (TOML).
.artifactizeignore was renamed to .artfignore.

There is no converter. Have an agent rewrite the declarations and commit the new .artf files. Families are removed: generate any repeated declarations with your own project generator and commit its .artf output. Older fields such as staleKey, stale, critics, envRequirements, resultCheck, reviewPolicy.maxConcurrentExecutors and tool metadata observation are rejected; they are not aliases for the new fields. The artifactsum form’s old dependencies option is also rejected.

Owner-result schemas

Agent and Human results contain a verdict plus the owner’s fields. Declare only those owner fields in pass_schema (GREEN) and fail_schema (RED); artifactize supplies verdict. For example, the summary schema above accepts {"verdict":"GREEN","summary":"All requirements are covered."} from an Agent. A Human supplies just {"summary":"All requirements are covered."} as their form fields, with GREEN selected separately.

The result envelope is deliberately narrower than a general JSON Schema:

  • type, when present, must be "object". additionalProperties, when present, must be false; artifactize forces both object type and closed properties in the final envelope even when omitted.
  • Every permitted top-level owner field must appear explicitly in properties. Neither patternProperties nor $ref can admit an undeclared top-level field.
  • Top-level allOf, anyOf, oneOf and not are rejected. Compose schemas inside an owner field instead; nested objects, arrays and local references are supported by the schema validator.
  • Reserved names cannot be declared or required: verdict, reference, reusedFrom, executionProvenance, attemptId, provider, model, stdout, stderr, durationMs and exitCode. Audit and execution data live outside owner fields.
  • Omitting one verdict’s schema permits no owner fields for that verdict: an Agent returns only {"verdict":"GREEN"} or {"verdict":"RED"}, and the Human form fields are {}. An empty object schema has the same closed envelope.

Human result schemas

A person enters a Human result in a form, so a Human eval’s pass_schema and fail_schema may use only shapes a form can enter. Anything else is a declaration error when the .artf loads, naming the key path and what is allowed instead.

Owner field schemaHow the review shows it
{ type = "string" }, optionally with minLength, maxLengthA text box
{ type = "integer" } or "number", optionally with minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOfA number box
{ type = "boolean" }true / false (and – when optional)
{ enum = ["a", "b"] }, only strings, type = "string" optionalA choice
{ const = … }Nothing: the value is filled in
{ type = "array", items = { type = "string" } }, items optionally with minLength, maxLengthA list of text boxes, with + Add and ✕ per item
{ type = "array", items = { type = "object", properties = … } } whose properties are the scalars aboveRepeated groups of those fields, with + Add for a group and ✕ per group

Arrays take minItems and maxItems. The result object and each group’s object take type = "object", properties, required and additionalProperties; every name in required must be a declared property. Any schema may add title, description and $comment; a property’s description shows as its hint. A required field is marked *; an empty optional field says Optional and is left out of the result, as are empty list items and groups with nothing entered. Patterns, formats, nested objects, lists inside groups, $ref and combinators such as anyOf are refused: a person could not enter them. Agent schemas keep the full validator.

Schemas are limited to 8 MiB. An Agent’s raw final response is limited to 1 MiB; the normalized result is limited to 256000 UTF-16 code units. Invalid final responses get one bounded repair attempt, not silent field removal. The Human submission interface separately bounds its input and reports validation errors without settling the request.

Graph Artifact

An optional nameless .artf at the repository root declares the whole project’s Artifact graph. index.artf still declares the root folder; <filename>.artf still declares a neighboring file. A nameless .artf in another visited directory is a declaration error. The root .artf, like index.artf, is read even when an .artfignore pattern matches its filename. There can be only one graph Artifact.

# .artf at the repository root
name = "project-graph"

[views.agent_tools.structure]
builtin = "graph"
args = []

[views.human_tools.viewer]
builtin = "graph"
kind = "launch"
args = []

[evals.review]
title = "The Artifact boundaries and evaluation criteria fit the project"
profile = { kind = "human" }
payload = { instruction = "Use the graph viewer to review the current structure." }

It is an ordinary named Artifact: declare any runtime, Agent or Human evals and tools, and select it by name in verify, status, CI or --reuse-only. It has no default evals, child ownership or relations to other Artifacts. Explicit instruction/argv references work normally. Commands run from the repository root; declaring the graph does not make it the owner of that folder. Its scope uses kind = "graph" and its declaration path is .artf.

Do not declare fingerprint, even false or an empty table: its fingerprint is always the current extracted graph. Otherwise ordinary Artifact rules apply: basis = true explicitly declares no own review obligations and cannot own evals; it does not satisfy unmet dependencies. A no-eval graph without basis = true remains UNREVIEWED, as any no-eval Artifact does. No source file is created or required. Ordinary Artifact cycles are allowed; extraction is flat and includes the graph Artifact’s own declaration without recursive fingerprinting.

The graph fingerprint changes with structural definitions, not source file bytes or evaluation execution options. There is no diff, history or automatic review policy: results are reviewed or reused against the current state only.

File Artifacts

hero.png.artf declares the regular file hero.png in the same folder. Its name is required and independent of any surrounding folder Artifact. Missing targets, directories, symlinks and special files are not valid targets. Target filenames containing : are rejected at discovery because scoped tool paths cannot represent them. The .artf declaration itself must also be a regular file: symlinks, special files and directories such as bundle.artf fail discovery. A file named index cannot have a sidecar because index.artf always declares the folder; .artf files are declarations and are never targets.

A file Artifact creates no child or dependency relation to the containing folder Artifact. The nearest-folder ownership rule still applies to subfolder index.artf declarations. A folder’s artifactsum includes neighboring file Artifact targets and excludes every *.artf declaration.

The containing folder is the working directory of runtime commands, tools and fingerprint scripts, so a relative executable may be a sibling file. It grants no access to other siblings: built-in tools and scoped argv references see only the target file and referenced Artifacts. {name} resolves to the target file path, not the folder, and file references cannot have a /path suffix. list . names the virtual file Artifact root and lists the target filename; paths within that root stay relative to the containing folder. Default artifactsum covers only the target file. A workspace can have only file Artifacts; it does not need a folder declaration.

Omit fingerprint to hash the file. An explicit artifactsum form must be fingerprint = { files = ["hero.png"] } for hero.png.artf: only the target filename is allowed. fingerprint = {} and files = ["."] are rejected, as is fingerprint.ignore, even an empty list. Script fingerprints and fingerprint = false remain available.

JSON command-tool context.artifactPath names the file, while the process cwd is its containing folder. Scope entries carry kind = "file", "folder" or "graph". execution_paths stay workspace-relative provenance pins for all three kinds.

The target must remain an existing regular file with no symlink traversal, not just at discovery. It is validated at fingerprint preparation/recheck, before reuse, at eval start/end and on every Agent/Human tool call, including with fingerprint = false. A file invalid before eval admission produces ERROR/PREPARATION_FAILED. If accepting a completed Runtime/Agent result discovers that a target has become invalid, it records ERROR/INPUT_CHANGED instead of a semantic verdict. A Human action first reloads its recorded scope and declarations; if discovery or reconnection fails, for example because a file target is missing or invalid, the action fails without settling the waiting request. A changed fingerprint value after successful Human reconnection still settles ERROR/INPUT_CHANGED for a keyed request. Preparation failures abort before reuse or execution; tool calls return an error without running the tool.

For removed legacy policy fields and their replacements, see the upgrade reference.

Eval mutex tags

A runtime or Agent eval can declare mutex beside gate and profile:

[evals.suite-passes]
title = "The end-to-end suite passes"
mutex = ["use-computer:display", "use-computer:callback"]
profile = { kind = "runtime", command = "pnpm", args = ["e2e"], timeout_ms = 1800000 }
payload.instruction = "Run the end-to-end suite."

A tag is use-computer or a colon-separated descendant such as use-computer:display. Each descendant segment matches [A-Za-z0-9][A-Za-z0-9_-]{0,63}. Other roots, empty segments, invalid segments, duplicate tags and declarations on Human evals are errors. Omitting mutex (or declaring mutex = []) adds no constraint.

Two evals conflict if any of their tags are equal or one is an ancestor of the other. use-computer conflicts with every descendant; use-computer:db conflicts with use-computer:db:users, but not with use-computer:browser or use-computer:database. All an eval’s tags are acquired together, so partial acquisition cannot deadlock. It waits until no conflicting eval is running. This rule adds no priority, ordering or scheduling optimization.

Held tags are recorded atomically with execution ownership in the machine’s shared state store. They apply across Runs, worktrees and verify processes using that store, not only within a Run. A waiting request stays QUEUED; its blockedReason identifies the holding eval and Run. Waiting consumes no execution budget, backend slot or job slot. timeout_ms starts when the eval actually starts, not while it waits. Tags are released on completion or cancellation, and dead process owners or ended Runs cannot retain them. Separate explicitly chosen state directories are isolated stores, not a shared lock namespace.

Tags describe the eval’s own command or tools, never its Artifact: an Agent that only reads test code need not wait because another eval runs those tests. There are no implicit tags. Mutex tags are execution options, not evaluation criteria; changing them changes neither fingerprints nor reuse keys, and cached results can still be reused while a conflicting eval runs. Human try-claim behavior is not implemented.

Human gate policy

gate = "blocking" is an optional eval field, not a profile or global setting. It preserves a Human eval’s pending gate under verify --defer-human. Omission retains ordinary strict behavior in default Runs and permits deferral only when that request opts in. On runtime or Agent evals it has no additional effect: only Human sign-offs can be deferred. Gate policy changes scheduling, not the judgment definition or its reuse key.

[evals.signoff]
title = "The delivery is approved"
gate = "blocking"
profile = { kind = "human" }
payload.instruction = "Approve the delivery before dependent work proceeds."

See verify modes and deferred sign-offs.

Eval dependencies

Write Artifact references as {input} or {input}/path in payload.instruction and runtime profile.args. See Eval dependencies for their obligations, child Artifacts, cycles, scheduling and the Human prerequisites.

Artifactsum

Fingerprints and reuse describes files and ignore. Omitting fingerprint uses artifactsum. For a folder, fingerprint = {} does the same. A file Artifact’s explicit artifactsum form must list its target filename. fingerprint = false disables reuse; fingerprint = true is rejected.

The value is artifactsum: plus the SHA-256 over each input file’s owner-relative path and the SHA-256 of its bytes, in path order. It covers only the Artifact’s own files: a dependency’s change reaches an eval through the dependency’s fingerprint in the reuse key. Folder walks exclude child Artifact folders and every *.artf file. Explicitly listing a .artf file in fingerprint.files fails config check, even if the file does not exist. Discovery also rejects non-regular .artf entries, including directories; a directory named bundle.artf is not an ordinary hashable input. File Artifacts hash only their target by default. Tags are not hashed and do not change the Eval definition hash or reuse key.

Walks follow scoped path rules. Symlinks and special files fail closed unless they are ignored. Every applicable .gitignore is read as rule metadata even when it is itself ignored as content, so it must be a regular file, not a symlink, and at most 1 MiB. Nothing is followed out of the owner, and a walk stops with an error after 10,000 entries or 1 GiB. Hashing runs on preparation and end-of-review rechecks. Files a review writes into ignored paths, such as Python’s __pycache__, therefore never cause INPUT_CHANGED. Artifactsum records a manifest with the execution: per-file digests (16 hex digits) and the inputs digest. A file map that would exceed 64 KiB is dropped from the manifest but still covered by the fingerprint. status diffs this manifest against the current one, and the key’s recorded fingerprints against current dependency fingerprints.

Fingerprint scripts

Before executing evals, verify computes fingerprints in their selected required dependency closure, including dependencies whose evals are not selected. Up to --fingerprint-jobs N fingerprints are computed at once (default: the CPUs available to the process), in status as in verify. A Run shares that bound between preparation and its Runtime/Agent end-of-review rechecks. Human tool actions and submissions are separate operations: each creates its own CPU-sized fingerprint parallelism, independent of the Run’s --fingerprint-jobs setting. Values are keyed by Artifact, not completion order. A failure reports the first failing Artifact in name order and cancels fingerprints after it. Scripts run in separate private output directories. --fingerprint-jobs 1 serializes that Run’s preparation and Runtime/Agent rechecks, not all Human checks or other Runs.

Every executable eval on an Artifact receives the same literal value, saved on its request and in the Run’s Artifact validation. artifactize adds no repository, eval, profile, tool-view, dependency or content salt to script output. The eval’s reuse key combines it with the Artifact names and kinds (folder/file/graph), dependency fingerprints and Eval definition hash. Physical paths never enter the reuse key. Artifactsum failures abort preparation in the same way.

The command runs from its owner’s working directory with JSON on stdin: {"version":1,"artifactId":"example"}. Bare commands resolve through PATH; absolute executables run as given. Relative executables containing / (such as ./fingerprint.sh) resolve inside a folder Artifact’s admitted scope. For file Artifacts, relative executables instead resolve beneath the containing folder and may name unrelated siblings. Traversal and symlinks are rejected in both cases. Arguments stay literal except explicit scoped Artifact references: {name} is a global Artifact name. Each referenced Artifact, with its child closure, enters the script’s scope without becoming a graph relation. config check rejects unknown and malformed references; existence and symlinks are checked when the command is prepared. There are no interpreter-specific flags, wrappers or entry-file rules.

fingerprint.script.files accepts up to 64 unique owner-relative literal file/directory paths. They must exist without symlink traversal on every call; their contents are never hashed. Commands share the runtime environment, cancellation, bounded raw output and a 30,000 ms default timeout (timeout_ms accepts 1–2,147,483,647). Private output and temporary directories are removed after each invocation. Stdout must be exactly 1–128 ASCII characters from [A-Za-z0-9._:-], optionally followed by one LF, or one CRLF on Windows. No other whitespace is allowed; the value is not trimmed. Nonzero exit, malformed output, timeout, cancellation, missing files or cleanup failure abort preparation with an operational error, without starting any eval or falling back to uncached review. Stderr is not forwarded as a fingerprint diagnostic.

After runtime or Agent review, covered fingerprints are recomputed before accepting GREEN or RED. A changed value records ERROR/INPUT_CHANGED with no semantic result; a failed recheck records ERROR. Force skips neither preparation nor recheck. An Artifact with fingerprint = false has no fingerprint to prepare; unkeyed evals skip the fingerprint-value recheck. File-target validation remains mandatory at preparation, eval start/end and tool calls, including for unkeyed evals. Other required Artifacts can still have fingerprints prepared. There is no workspace monitoring: artifactsum hashes only at preparation and recheck.

Scoped input library

config::read_workspace_config (also used by config check) resolves nearest child ownership, validates explicit references, and returns typed input-to-consumer relations for graph scheduling. Instruction references resolve a global Artifact name. Backslash-escaped braces, doubled braces, ${variables}, nested/JSON groups and unmatched braces stay literal. Payloads are never changed and references never expand file content.

scope::eval_scope admits the target and explicit references plus their child closure, not the referenced Artifacts’ eval instructions. Scope::resolve_path follows logical child paths to canonical Artifact ids; Scope::resolve_input additionally requires existing files/directories without symlink traversal. Logical paths reject absolute paths, traversal, empty components, backslashes, colons, ASCII controls and lengths above 4096 UTF-16 code units.

Before preparing a runtime command, call scope::resolve_argv with the admitted scope. Explicit {name}, {name}/path and --flag={name}/path operands resolve to absolute scoped input paths and add dependencies even without instruction references. Use {owner}/child/path for logical paths starting at a folder owner. A file reference resolves to the file itself. Other arguments (including escaped references) remain literal, and the command is never interpolated. config check validates names and syntax but does not open runtime operands or execute programs. Input existence and symlink checks happen during argument preparation. Graph closure, runtime CLI execution and the Agent tool registry use these resolvers.

Runtime execution library

runtime::Command::prepare(program, args, workspace, run_dir, timeout_ms) prepares one invocation for runtime::execute. Arguments are literal; the runner never adds a shell. The default cwd is the canonical workspace; a scoped caller can set command.cwd to the Artifact’s working directory (the containing folder for a file Artifact). process::run is the lower-level API for resolved commands with a complete explicit environment. verify uses the owner working directory as cwd.

Commands run in the author’s own terminal environment, so a project declares them as it would type them: the whole environment of the artifactize process is passed on, including the real home and cache locations (HOME, XDG_*, and on Windows USERPROFILE, APPDATA, LOCALAPPDATA and the system variables), so toolchains that resolve through them, such as cargo or pnpm behind a version manager, work as in a terminal. What is artifactize’s own stays out: every ARTIFACTIZE_* variable of the caller (its credentials such as ARTIFACTIZE_REMOTE_TOKEN, and its settings) is removed, and the command receives only the variables artifactize sets for it. Each prepared runtime command gets a fresh owner-only directory below the caller’s external run_dir, with owner-only output and tmp subdirectories (0700 on Unix, a protected owner-only DACL on Windows). Children receive ARTIFACTIZE_WORKSPACE_DIR, ARTIFACTIZE_OUTPUT_DIR, ARTIFACTIZE_TMP_DIR, and TMPDIR/TMP/TEMP pointing at the private temporary directory, which is removed when the command ends. Existing output ancestors are canonicalized before creation; output inside the canonical workspace, including through symlinks, is rejected. The caller owns the run directory; prepared output persists after execution for receipts and explicit pruning. Failed preparation removes its newly owned invocation directory, not the caller’s root.

Because commands see the author’s environment, a result depends on it like any terminal command does, and it is not part of the reuse key: a toolchain upgrade does not invalidate a cached result. A fingerprint that must change with a tool’s version has to read that version.

The runtime deadline defaults to 30,000 ms and accepts 1 through 2,147,483,647 ms. It covers inert-child registration and execution without a reset. Each stdout and stderr saved audit retains the first 128 KiB. Runtime evals also retain full raw streams in private Run output, including the tail, until those captures are pruned or removed; request logs/MCP request_logs read bounded tail/range/search excerpts. Once a capture is gone, only the bounded saved audit may remain: paging cannot recover its missing tail. This is the skill’s “until pruning” availability boundary, not a promise that every cleanup command removes all captures. Current artifactize prune removes recognized scratch subdirectories but leaves raw stream files and completion markers in invocation roots. It supplies no raw-capture retention policy; keep that current command scope distinct from pruning or deleting Run output itself. Missing, partial and older/remote audit-only evidence are reported explicitly. Runtime results decode UTF-8 lossily, remove ANSI CSI sequences and C0 controls except tab, LF and CR (DEL is preserved), and retain the truncation flag and actual exit status/duration. Low-level process results remain unmodified bytes.

Cancellation and timeout stop the owned child tree. On Unix, artifactize sends SIGTERM to its process group, then SIGKILL after at most one second. On Windows, it attempts Ctrl-Break for the console group, then terminates the owned Job Object after the grace period. Ordinary leader exit also cleans up the group or job. Capture stops waiting at most one second after cleanup if a pipe remains open. Dropping the caller cancels the supervisor, which still cleans up and reaps the leader. Configured programs are trusted local code, not an OS sandbox: they can use ordinary OS access, must keep reviewed input unchanged, and descendants that deliberately leave the owned process group or job may escape cleanup. These controls do not promise confinement or detection of every adversarial transient write.

Eval

An eval is one check on an Artifact. Give it a stable local ID, a title that says what passes, and a profile that says who or what reviews it. For example, [evals.tests] on the library Artifact has the full ID library/tests.

The three executing kinds share the same lifecycle and reuse rules:

KindReviewerVerdict
AgentAn exact model through a chosen backend, using declared read-only toolsGREEN or RED with schema-checked owner fields
HumanA person who claims the waiting request and submits a sign-offGREEN or RED with schema-checked owner fields
RuntimeA declared program and argument vectorExit 0 is GREEN; another exit code is RED

A failed invocation or invalid model result is ERROR, not RED. RED means the check actually judged the Artifact and rejected it. Each kind can be reused while its inputs and criteria still match.

Write the review criteria in payload.instruction. For Agent and Human evals, pass_schema and fail_schema describe the owner’s result fields; artifactize adds the verdict. See Owner-result schemas for the closed envelope, reserved names and the verdict-only default. Runtime evals use their command’s exit status instead of owner schemas. Named profile_variants let a caller select an alternative execution profile without editing the judgment; see Declarations.

Dependencies belong to the consuming eval: name its input Artifacts as {name} in its instruction or runtime arguments. See Eval dependencies.

Declare only real inputs. An eval’s observation scope starts with its own target and its explicit instruction/Runtime-operand references, then recursively admits the children of every such Artifact. It does not follow a referenced Artifact’s own eval instructions into more dependencies. All admitted Artifacts can contribute tools. Tool operands do not add new inputs on their own.

Observation access is broader than the reuse key: that key covers the target, its direct children and this eval’s explicit inputs, not every recursively reachable observation. Execution gates follow this eval’s own references and its target’s children, not its siblings’ references. Its own Artifact and the Artifacts in a cycle with it never gate it; a RED sibling or cycle peer does not block it, except that a Human eval waits for its Artifact’s machine evals. See Artifact for declarations.

artifactize verify library/tests --wait starts a Run for one eval. Use artifactize status library/tests first to see what can reuse or needs review; status computes fingerprints but does not execute evals.

Agent evals and backends

An Agent eval asks one exact model, through one explicit backend, to review an Artifact with the read-only tools you declare, and to return a strict GREEN or RED verdict.

Agent reviews

Agent evals use one explicit model and backend, with no bundled catalog, aliases, credential search or fallback:

# Inside [evals.review]
profile = { kind = "agent", backend = "openai", model = "YOUR_EXACT_MODEL_ID", reasoning = "high", timeout_ms = 240000 }

backend accepts openai, anthropic or codex. The first two use only OPENAI_API_KEY or ANTHROPIC_API_KEY, respectively, and never search for other credentials; codex uses a ChatGPT sign-in (see Codex). provider and effort are not config aliases. 0.5.0 removed the chatgpt (Sign in with ChatGPT) and claude (Claude CLI) backends; config check names the replacements for any profile that still declares one.

artifactize models openai            # model ID and display name, in provider order
artifactize models anthropic --json
artifactize models codex

reasoning is optional. When present, OpenAI and Codex receive exactly reasoning.effort (none, minimal, low, medium, high, xhigh, max). Anthropic receives adaptive thinking and exactly output_config.effort (low, medium, high, max). Other values are rejected, never remapped. A model that does not support the requested setting fails at the provider; artifactize does not substitute a model or lower effort. If the response reports a model ID, it must match exactly.

Agent evals share runtime evals’ dependency gates, reuse-key claims, reuse and final fingerprint recheck. The backend, model, reasoning and limits are execution options, not part of the reuse key: a result another model or profile produced for the same eval, Artifact kinds and fingerprints is reused, and its record shows which one it was.

The evaluator’s system prompt holds the review rules. Its user message contains only the eval title and ID, target Artifact and instruction. Artifact references expand to the Artifact ID and its available tools; a file Artifact also includes its workspace-relative target path. Instructions therefore need not repeat tool names.

Agent reviews return schema-validated GREEN or RED verdicts. If the model returns an invalid result, artifactize asks once for a correction; a still-invalid result makes the review ERROR. Agent responses specifies the backend formats, owner-field validation, repair and limits.

The rig adapter, retries, tool results and usage counters are in Agent backends.

Agent profile limits

An Agent profile can bound its review with max_tokens and max_tool_calls:

profile = { kind = "agent", backend = "codex", model = "YOUR_EXACT_MODEL_ID", max_tokens = 100000, max_tool_calls = 100 }

Each optional budget accepts an integer from 1 through 9007199254740991. Omission leaves that budget unlimited. These are client-side review budgets, not provider generation parameters. max_tokens adds each successful response’s reported total_tokens, including reported cache reads/writes, across every turn and the repair turn. Missing totals count as zero for enforcement. The check runs after a response and fails only when the total exceeds the limit: it can overshoot, and is not a hard generation or spending cap.

max_tool_calls counts attempted calls before invocation, including unknown, invalid or failed calls, not only successful tools. Exceeding either budget stops further work with PROVIDER_BUDGET_EXCEEDED. Repair remains within the original review deadline and budgets; it never adds a fresh allowance.

Agent tools

Declare the read-only tools the reviewer needs. The Tools area explains built-ins, custom commands and sessions that keep a harness running across calls. Saved Agent conversations below are separate from tool sessions.

Agent backends

Agent reviews above shows the Agent profile, the backends and reasoning. models output uses the JSON envelope described under Local diagnostics and maintenance.

Agent reviews stream the model’s answer as it is produced. One review deadline (default 240 seconds) covers all turns, retries and tools. Truncated or incomplete responses are ERROR, even if they contain a JSON verdict. Artifactize handles retries as described below; the provider transport reference covers API settings and replay behavior.

Retries and error codes

Every request replays the whole conversation (no server-side state), so any turn can be retried. A turn is retried, up to three attempts in all, when an attempt fails with a rate limit or a transient error before it produced any output or reported any usage. A retry waits as long as the provider asks with retry-after-ms or Retry-After (seconds or an HTTP date), else 250 ms and then 500 ms. When the provider asks for a wait that reaches or exceeds the review’s deadline, the review fails at once with the original RATE_LIMIT or TRANSIENT classification. A failure after output or usage is never retried, so nothing is paid for twice. Each attempt is saved in usage with its counters, error and errorCode.

Prompt caching

Each Agent review gets a random session id, saved on its request as sessionId (verify --wait --json, run show, request show). Every request of the review, its turns, retries and repair turn, sends it as prompt_cache_key on openai and codex, and codex also as the session-id header. The provider can then serve the conversation prefix that each turn replays from its prompt cache. Every review, including a forced re-review, starts a new session. anthropic caches by prefix and sends no session. The cacheReadTokens counter of each attempt in usage shows the cached input. A follow-up to a saved review reuses its session id and conversation prefix. Cache hits still depend on the provider: follow-ups remove the structured output format for plain-text answers, which invalidates Anthropic’s format-dependent prompt cache.

A failed Agent review records one of these errorCode values:

errorCodeCauseRetried
AUTHENTICATIONMissing, expired or rejected credentials: HTTP 401/403, no API key, no Codex sign-in or an expired auth fileno; stops the backend
QUOTAA billing, credit or plan usage limit (insufficient_quota, a Codex usage limit)no; stops the backend
RATE_LIMITHTTP 429 without a quota cause, after the retries or when Retry-After reaches the deadlineyes
TRANSIENTA connection failure, HTTP 5xx or overloaded, or an interrupted stream; an excessive requested retry delay keeps this classificationyes, before output
TIMEOUTThe review’s timeout_ms deadlineno
CANCELLEDExplicit Run cancellation (run cancel or MCP verify_cancel)no
PROVIDER_BUDGET_EXCEEDEDmax_tokens or max_tool_calls was exceededno
INVALID_RESULTNo valid verdict after the one format repairno
PROVIDER_ERRORAny other provider failure: a rejected request, an unknown or different model, an incomplete response, a malformed tool callno
AGENT_ERRORAnything else, such as an invalid test endpoint or an unusable toolno
BACKEND_STOPPEDNot started: the Run stopped admitting reviews on the backend—

Ctrl-C interrupts a verify --wait watcher, not the Agent reviews it observes; the Run continues. Explicit cancellation requests stop the Run’s owned work.

Stopping a backend

An AUTHENTICATION or QUOTA failure would repeat for every review on the same backend, so the Run stops admitting new reviews on it. Each later review of that backend in the Run is ERROR with BACKEND_STOPPED, never started, and its error names the failure. A review waiting for a free slot of that backend (backend capacity) stops waiting the same way, and a stopped backend takes no slot. Reviews that were already running finish, results that can be reused are still reused, and other backends carry on. The Run lists the stop under stoppedBackends (in verify --wait --json and run show), and verify prints it:

Stopped backend openai after AUTHENTICATION in docs/review: 2 reviews not started.

The next Run admits the backend again.

Only tools from the eval’s scope are exposed, and they run sequentially. Command tools run in the author’s environment, each call with its own private output and temporary directories. Built-in tools run inside artifactize and get no per-call directories; help runs its program in the environment artifactize itself was given. Tool results keep text, JSON and validated base64 image blocks rather than flattening structured data. PNG/JPEG/WebP results reach both provider wires; unsupported-model image requests fail with the provider error. Duplicate provider call IDs fail closed. Per-request-attempt usage is saved on both success and ERROR; on cache hits the attempts move to reusedUsage, never counted as spent. Counters are provider-reported (including reported zero), not inferred totals or costs. Anthropic inputTokens is its native uncached input; cache read/write counters are separate. OpenAI input already includes its cache reads. Never sum every counter. Unreported fields stay absent. The request keeps the verdict, result and usage; the messages, reasoning and tool calls, with their arguments and results, are only in the review’s saved conversation, and session show --summary counts them.

Owner validation before relying on a provider: run one real review per backend (OpenAI key, Anthropic key, Codex sign-in) with an accessible exact model ID and a declared tool. These real reviews are still pending for the owner. Automated tests use fake HTTP transports or a fake provider and make no real inference requests.

Saved conversations

artifactize keeps each Agent review’s conversation on this machine, so you can read what the reviewer saw and said, and ask it a follow-up in the same conversation, much like continuing a chat. The recorded verdict never changes.

What is saved. Each review session gets one owner-only file under $STATE/agent-sessions/ (layout). It holds the system prompt and instructions, every turn’s messages, each tool call with its arguments and full result and whether it failed, the repair turn (the prompt that asked for it and the answer it replaced), each provider attempt with its token counters, and the encrypted reasoning items (Responses reasoning.encrypted_content, Anthropic thinking signatures) needed to replay the conversation exactly. It also records the backend, model, reasoning, provider parameters, budgets and tool definitions, the sessionId, and the Run and request ids. Every backend is saved, anthropic included. The session is the only record of a review’s tool calls: the request keeps its verdict, result and usage. Saving never fails a review: a write error is reported on stderr and the review goes on without it.

The reference. A request whose conversation was saved carries session in verify --wait --json, run show and request show:

"session": {
  "producer": "alice@laptop",
  "state": "34bb3c3a-9480-4a54-9ab3-3bce54e043b8",
  "runId": "run-FiVNCV",
  "requestId": "run-FiVNCV-1",
  "sessionId": "e83bf374-5136-484c-8d2f-2ce38ee4ca57",
  "ref": "alice@laptop/34bb3c3a-9480-4a54-9ab3-3bce54e043b8/run-FiVNCV/run-FiVNCV-1/e83bf374-5136-484c-8d2f-2ce38ee4ca57"
}

producer is the user@host that ran the review, and state the stable id of its state database. A request that reuses a result names the review that produced it. The team review store carries the reference with each Agent result (as producer.session), never the conversation, so a teammate’s verdict tells you whose state holds its conversation.

Reading. REF is the ref, a request id (run-FiVNCV-1) or a sessionId:

artifactize session show run-FiVNCV-1
Session e83bf374-5136-484c-8d2f-2ce38ee4ca57 · codex gpt-6-luna (reasoning max)
Reference: alice@laptop/34bb3c3a-…/run-FiVNCV/run-FiVNCV-1/e83bf374-…
Request run-FiVNCV-1 (Run run-FiVNCV), eval notes/review: GREEN

── System
You are an artifactize evaluator. …

── User (turn 1)
Eval: Review notes (notes/review)
Target Artifact: notes
Instruction: Check Artifact notes (tools: read_notes). …

── Assistant (turn 1)
[reasoning] Reading the notes
[tool call read_notes] {"path":"notes.md"}

── User (turn 2)
[tool result read_notes]
{"artifactId":"notes","lines":[{"number":1,"text":"notes: R1 holds.\n"}], …}

── Assistant (turn 2)
{"result":{"verdict":"GREEN","verdictFields":{}}}

── Repair prompt (turn 3)
Your final response did not match the required schema: …

── Assistant (turn 3)
{"result":{"verdict":"GREEN","verdictFields":{"covered":["R1"]}}}

── Result: {"covered":["R1"],"verdict":"GREEN"}

Text output cuts tool results after 2000 characters and shows only reasoning summaries. --json prints the stored events as they are, which is also the conversation’s replayable export.

Session summary

--summary counts what the conversation spent and did, from the session file alone: its turns (provider requests) with the tokens of each and in total, the tool calls per tool with how many failed, the follow-ups, and the backend, model and time span. A failed call returned an error, or never ran because a budget, the deadline or the end of the review stopped it first.

artifactize session show run-FiVNCV-1 --summary
Session e83bf374-5136-484c-8d2f-2ce38ee4ca57 · codex gpt-6-luna (reasoning max)
Reference: alice@laptop/34bb3c3a-…/run-FiVNCV/run-FiVNCV-1/e83bf374-…
Request run-FiVNCV-1 (Run run-FiVNCV), eval notes/review: GREEN
Time: 2026-10-06T09:14:02.118604115Z to 2026-10-06T09:16:41.502330019Z (159.4 s)
Turns: 5 · follow-ups: 1
Tokens: input 21410 · output 1318 · cache read 15872 · reasoning 704
  turn 1: input 3920 · output 212 · cache read 0 · reasoning 128
  turn 2: input 4310 · output 260 · cache read 3840 · reasoning 160
  turn 3: input 4402 · output 96 · cache read 4224 · reasoning 64
  follow-up 1, turn 1: input 4366 · output 410 · cache read 3840 · reasoning 224
  follow-up 1, turn 2: input 4412 · output 340 · cache read 3968 · reasoning 128
Tool calls: 3 (1 failed)
  read_notes: 2
  search_notes: 1 (1 failed)

A turn that took several attempts says so, for example (2 attempts). Token counters are the ones the provider reported, summed over the turn’s attempts; a counter no attempt reported is left out. --json prints the same as one object: backend, model, reasoning, startedAt, endedAt, durationMs, followUps, turns (each with followUp when it answered one, turn, attempts and tokens), tokens, toolCalls ({"read_notes": {"calls": 2, "failed": 0}, …}), sessionId, requestId and reference.

Following up.

artifactize session send run-FiVNCV-1 "Which line shows R1, and does it still hold?"
Session e83bf374-5136-484c-8d2f-2ce38ee4ca57 · follow-up 1 · codex gpt-6-luna (reasoning max) · files changed since this review

notes.md line 1 says R1 holds, but it now reads "R1 fails", so the verdict would change.

A follow-up continues the saved conversation with the same model and current project tools. Its answer is plain text; it does not change the recorded verdict, result or reuse. The header warns when reviewed inputs have changed. Each question gets fresh profile budgets, and questions to one session run in order. See the follow-up replay reference for caching, stored messages and compatibility details.

A reference to another machine or state fails with where it lives, for example Session e83bf374-… lives in alice@laptop's state 34bb3c3a-…; this is bob@desk's state 5d0e…. A session the size-bound collection deleted reports was removed by the session GC, and one that was never saved was not saved.

Retention. The session store is bounded by size in limits.json (agentSessions): once it exceeds maxBytes (1 GiB), the sessions written longest ago are deleted until it is at or below targetBytes (768 MiB). This runs at the end of every verify and in artifactize prune, and never touches the session of a request that is still running or one a session send holds. doctor shows the store’s size. "enabled": false stops saving new conversations.

Privacy. A conversation contains everything the reviewer read through its tools, including file contents, and everything it wrote. It stays on this machine in owner-only files and is never uploaded, including to the team review store. A follow-up sends the conversation to the review’s own backend, as each review turn does. Turn saving off with "agentSessions": {"enabled": false}, and delete $STATE/agent-sessions to remove what is saved.

Codex

codex reviews with the Codex models of a ChatGPT plan, such as gpt-6-luna and gpt-6.1-sol, through the Codex Responses endpoint (https://chatgpt.com/backend-api/codex/responses). It follows the protocol of Pi’s openai-codex provider.

artifactize login codex     # sign in to ChatGPT in the browser
artifactize models codex    # the account's Codex models
# Declare backend = "codex", model = "gpt-6-luna", reasoning = "max", then:
artifactize verify --all --wait
artifactize logout codex    # revoke and delete artifactize's tokens
  • Sign-in. login codex uses the Codex OAuth client with PKCE, as the Codex CLI does. If another process holds callback port 1455, sign-in asks for the full redirect URL instead of waiting for a callback. Copy the URL starting http://localhost:1455/auth/callback?code= from the address bar after a failed load, or DevTools → Network if the page keeps loading, and paste it into the terminal or desktop sign-in. See sign-in setup for the browser flow. It saves the tokens and the ChatGPT account ID in $STATE/auth/codex.json (0700 directory, 0600 single-link file, never followed through a symlink, replaced atomically). Use the same --state-dir for login, models and verify. Tokens are never stored inside a repository: when $STATE/auth lies inside a git work tree, an artifactize workspace or --repo, login codex refuses and names the folder, and doctor warns (State). Use a state directory outside it, or ARTIFACTIZE_CODEX_AUTH_FILE. artifactize refreshes the access token under a lock five minutes before it expires, so concurrent Runs refresh it once. When the server rejects the refresh token for good, the tokens are deleted and the review says to sign in again.
  • An existing Codex sign-in. With ARTIFACTIZE_CODEX_AUTH_FILE set, for example to ~/.codex/auth.json, artifactize reads that file’s tokens.access_token (and tokens.account_id) on every turn instead of its own tokens. It never writes, copies or refreshes the file. Once the token has a minute or less left, reviews fail with a message to sign in with Codex again, for example with codex login. logout codex leaves the file alone.
  • Requests. Each turn streams one Responses request with store:false, include: ["reasoning.encrypted_content"], the review’s tools as function tools and the review’s system prompt as instructions. When reasoning is set it sends reasoning: {"effort": <reasoning>, "summary": "auto"}, with max passed as is. The headers name the bearer token, ChatGPT-Account-Id, originator: artifactize, OpenAI-Beta: responses=experimental and the review’s session-id (prompt caching). Retries, budgets, tool results and usage work as for openai.
  • Errors. A usage limit is QUOTA: it names the plan and when it resets, and stops codex for the Run. A plain HTTP 429 is a RATE_LIMIT, retried. HTTP 401 and 403 are AUTHENTICATION and say how to sign in again. A token endpoint that is unreachable or failing during a refresh is TRANSIENT.
  • Reuse. Like any backend, codex records its backend, model, reasoning and limits on each result, outside the reuse key: a codex result and an openai or anthropic result for the same eval and fingerprints reuse each other.
  • Models. models codex asks GET <root>/models?client_version=0.160.0, the catalog that the Codex release whose protocol artifactize follows receives. It lists the models whose visibility is list, as the Codex model picker does, in server order. A profile may name any model your account can use, listed or not.

Test against a fake provider

To test your tools, schemas and result parsing without a model, point a backend at a fake provider on this machine. The variable replaces the provider’s API root:

BackendVariableExampleThe fake answers
openaiARTIFACTIZE_OPENAI_BASE_URLhttp://127.0.0.1:8080/v1POST <root>/responses, GET <root>/models
anthropicARTIFACTIZE_ANTHROPIC_BASE_URLhttp://127.0.0.1:8080POST <root>/v1/messages, GET <root>/v1/models
codexARTIFACTIZE_CODEX_BASE_URLhttp://127.0.0.1:8080/backend-api/codexPOST <root>/responses, GET <root>/models
Codex sign-inARTIFACTIZE_CODEX_AUTH_URLhttp://127.0.0.1:8080POST <root>/oauth/token and <root>/oauth/revoke; login codex sends the browser to <root>/oauth/authorize
  • The root must be http:// or https:// on localhost, 127.0.0.0/8 or [::1], without credentials, a query or a fragment. Any other value fails the review, models and doctor with a message naming the variable, before any connection. An empty value counts as unset. For anthropic, a trailing /v1 is accepted.
  • The API-key variable is still required and is sent to the fake unchanged, so set a dummy key. For codex, point ARTIFACTIZE_CODEX_AUTH_FILE at a file such as {"tokens":{"access_token":"dummy","account_id":"test"}}.
  • Nothing else changes: the same request bodies and streaming, retries, budgets, tool calls, repair and verdict validation.
  • Results are recorded and reused like any other, so use a throwaway state directory (--state-dir or ARTIFACTIZE_STATE_HOME). While a test endpoint is set, verify refuses to use a review store, so set ARTIFACTIZE_REMOTE=off. Never remote push a state that holds fake results.
  • doctor warns while a test endpoint is set and reports it as testEndpoint.

The fake streams its answer as text/event-stream, in the provider’s wire format, for the requested model. For openai, one response.completed event is enough. This fake passes reviews whose GREEN schema accepts {"verdict":"GREEN"}. If your pass_schema requires other fields, include corresponding valid values in the fake’s text response; otherwise validation triggers repair and, if still invalid, INVALID_RESULT.

#!/usr/bin/env python3
"""A fake OpenAI Responses provider on loopback that returns a verdict-only GREEN response."""
import json
from http.server import BaseHTTPRequestHandler, HTTPServer


class Fake(BaseHTTPRequestHandler):
    def do_POST(self):
        request = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
        text = json.dumps({"verdict": "GREEN"})
        response = {
            "id": "resp_fake", "object": "response", "created_at": 0,
            "model": request["model"], "status": "completed",
            "output": [{"type": "message", "id": "msg_fake", "role": "assistant",
                        "status": "completed",
                        "content": [{"type": "output_text", "text": text, "annotations": []}]}],
            "usage": {"input_tokens": 1, "output_tokens": 1, "total_tokens": 2},
        }
        event = {"type": "response.completed", "sequence_number": 0, "response": response}
        body = f"event: response.completed\ndata: {json.dumps(event)}\n\n".encode()
        self.send_response(200)
        self.send_header("Content-Type", "text/event-stream")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)


HTTPServer(("127.0.0.1", 8080), Fake).serve_forever()
python3 fake_openai.py &
export ARTIFACTIZE_OPENAI_BASE_URL=http://127.0.0.1:8080/v1 OPENAI_API_KEY=dummy
ARTIFACTIZE_REMOTE=off artifactize --state-dir "$(mktemp -d)" verify --all --wait

To exercise a tool, answer the first request with a function_call output item (call_id, name, JSON-encoded arguments). Its response.completed event must follow response.output_item.added and response.output_item.done events for the call. The next request’s input then carries the tool’s function_call_output. Answer that one with the verdict message.

Agent responses

Wire formats and validation used by an Agent eval. For setup and everyday use, see Agent evals and backends.

Final output and validation

The final answer uses each backend’s structured output: Responses text.format for OpenAI and Codex, and Messages output_config.format for Anthropic. The wire answer is an object with a result field holding verdict followed by verdictFields, the owner fields. Both alternatives put the same discriminator first, so constrained key ordering cannot select a verdict merely because an owner field sorts before it. The stored result stays a flat object containing "verdict":"GREEN" or "verdict":"RED" and permitted owner fields. Tools remain available before the final answer, and follow-up answers remain plain text.

Every structured object is closed and every field is required; optional owner fields use null placeholders, which are removed when the original field does not accept null. Unsupported validation constraints become field descriptions and are still enforced by local validation against the original owner schema. Values outside the providers’ common structural subset (such as open maps or recursive references) travel as JSON-encoded text in that field and are decoded before validation. Native arrays and closed objects remain structured, including item limits on Responses; Anthropic’s unsupported item limits are checked locally. If a Responses format would exceed its nesting, property or enum limits, the whole verdict travels as JSON-encoded text inside verdictFields instead; the same original owner schema still validates the decoded object. One tools-disabled repair is allowed for invalid final output, within the original deadline. The repair turn names the error and, when the output was a JSON object, up to five failing instance paths with their messages, each bounded to 200 characters, as a Human submission’s error does:

Your final response did not match the required schema: schema_mismatch: result must match the selected verdict's owner schema
- instancePath "/covered": "R1" is not of type "array"
Return only one JSON object matching the schema.

If the repaired output is still invalid, the review is ERROR, and the saved error keeps only the error code, never the model’s output. max_tokens and max_tool_calls are enforced client-side before further tools execute; neither becomes a provider request parameter.

Human reviews

A Human eval asks a person for a GREEN or RED sign-off. The reviewer claims the waiting request, runs the Human tools you declared and submits a verdict, either with artifactize request commands or in the artifactize review terminal UI.

A Human eval is ready only when every other non-Human eval of its own Artifact is GREEN and its dependency gates are met: a person signs off only on what the machine evals already pass. When one of those machine evals is RED or ERROR, the Human eval is BLOCKED and its reason names them. Other Human evals of the Artifact are not part of this condition. See Human sign-offs.

When the check is ready, artifactize saves a WAITING_HUMAN request: work waiting for a person’s decision. Waiting for that person uses neither a Run’s --jobs execution slot nor its --max-executions budget, even if that budget is zero. In the normal mode, the Run waits without a Human timeout; a submitted sign-off lets dependent checks continue in the same Run. Watching or closing a terminal does not make the decision. The Run lifecycle explains submission, watching and explicit cancellation.

A reviewer claim reserves the request for the person reviewing it. This is different from artifactize’s automatic reuse-key lock, which prevents two Runs from starting the same matching review simultaneously. Another Run, even from a different checkout, can join the existing waiting review; its request is called a follower. Human actions on a follower go to the original request and repository. --force bypasses that automatic reuse-key lock, not the need for a person to claim the request before acting.

Sign-offs reuse by default when their reviewed inputs still match. A later verify can reuse the submitted result and review its consumers. If the target or a covered dependency has fingerprint = false, the sign-off settles only its current Run; a later Run asks for a new Human decision. Current validation uses the available evidence, but a later submission never rewrites a historical Run’s recorded verdict.

In the command notation below, square brackets mark optional parts; do not type the brackets. Uppercase placeholders are values to replace: use the real REQUEST_ID returned by request list, and a reviewer name for NAME.

artifactize verify --all --wait
# In another terminal, using the same state directory:
artifactize request list [--run RUN_ID] [--json]
artifactize request show REQUEST_ID
artifactize request claim REQUEST_ID [--reviewer NAME]
artifactize request unclaim REQUEST_ID [--reviewer NAME]   # release without a verdict
artifactize request tool REQUEST_ID inspect_child [--reviewer NAME]
artifactize request submit REQUEST_ID --verdict GREEN --fields '{"approved":true}'
# Alternatively: --fields-file /path/to/fields.json
# Or claim, run tools and submit in a terminal UI (see Review):
artifactize review [REQUEST_ID]

Claim, unclaim, tool and submit default the reviewer to $USER; --reviewer NAME can select the same explicit reviewer for each action. Reviewer names are local cooperative locks, not authenticated accounts. Only the claimant can run tools, submit or unclaim. request claim prints the claim and request unclaim the released claim; unclaiming a request someone else holds, or one that no longer waits, exits 2. Tool names are <operation>_<artifactId> and take no free arguments. Text output prints captured text or a launch notice; --json prints the tool result. A tool failure exits 2 and does not invent a verdict.

Submission requires --verdict GREEN|RED. --fields and --fields-file are mutually exclusive JSON objects of owner fields (default {}), not verdict wrappers. Files must be regular files; fields and the complete result are bounded at 256000 bytes. Invalid JSON, schemas, reviewer names or verdicts exit 2 and leave the request correctable. A successful submission exits 0, even for RED; it does not start a separate verifier.

The Run exit codes distinguish interrupted observation, explicit cancellation and missing review obligations. verify --reuse-only human never records a Human request or waits: a Human eval reuses a sign-off or is not executed, as CI wants.

The reference has the library API, the request list and request show fields and Run summaries.

Deferred sign-offs

Choose verify --defer-human for a workflow where a person reviews the final state rather than steering each execution. It is per request, not a machine setting, and defaults off. --speculate can independently speed machine work; combining it with deferral still asks for deferrable sign-offs last.

For a reviewer, a deferred sign-off is an ordinary one marked DEFERRED: it is listed with WAITING_HUMAN requests in review, monitor, desktop and request listings, and claim, tools, release and submission check Human authority the same way. A blocked sign-off is never offered to a person. How the Run ends (SIGNOFFS_PENDING, exit 5) and how the next verify reuses the result are in Speculation and deferred sign-offs; that reuse needs fingerprints, so fingerprint = false disables it.

A newer request for the same eval in the same working tree retires earlier DEFERRED sign-offs with a different reuse key, removing them from Review. See the Run rules for retirement, joining and submitting a sign-off after its Run ends.

For a sign-off whose rejection would waste substantial downstream work, retain the strict pending gate explicitly:

[evals.release]
title = "The release is approved"
gate = "blocking"
profile = { kind = "human" }
payload.instruction = "Review the release before dependent work proceeds."

Without gate, a Human eval follows the request’s default mode. CI remains strict with verify --all --wait --reuse-only agent,human: neither deferral nor speculation substitutes for actual reusable Human GREEN evidence.

Human tools

views.human_tools is a separate safe-name map of predefined tools. The reviewer runs them; they take no free arguments. A built-in tool works the same on Linux, macOS and Windows: open opens a file, folder or http(s) URL in the desktop’s default application; graph launches the current project graph viewer. read, list, section and help print text in the terminal.

[views.human_tools]
open = { description = "Open the review notes of {artifactName}.", builtin = "open", args = ["{artifactPath}/notes.md"] }
notes = { description = "Print the review notes of {artifactName}.", builtin = "read", args = ["{artifactPath}/notes.md"] }

A built-in takes builtin and args, with an optional description. Its kind follows from the tool: open and graph launch, the others print output.

A command tool runs a program you choose. It requires exactly description, kind = "launch" or kind = "output", command and args, with optional timeout_ms (1–2147483647). No input_schema, free call arguments, protocol, execution_paths, metadata or script wrappers are accepted. Descriptions follow the Agent description rules, including {artifactName}.

[views.human_tools]
edit = { description = "Open {artifactName} in VS Code.", kind = "launch", command = "code", args = ["{artifactPath}"] }

What a tool prints shows live in the reviewer’s Tool log while the review is open: each run’s start line, its stdout and stderr (stderr in red) line by line, and its end line. A launch tool returns at once, and its program’s output keeps arriving under the same run until it exits. A tool can therefore print what the person did in it, such as the choices made or a measured value, for the reviewer to copy from the log into the result. Nothing in the log is saved.

Placeholders, executable resolution and the launch and output kinds are in the reference.

Result forms

The reviewer enters the result in a form built from the eval’s pass_schema (GREEN) or fail_schema (RED): text, numbers, true/false, choices, lists of text and repeated groups of fields. Constants are filled in. A Human eval’s schemas may use only those shapes; the declaration reference lists them and how each shows.

Use TUI or Desktop for interactive review.

Runtime

A Runtime eval runs an ordinary program. Use it for checks whose answer is already decided by a test, linter, renderer or other deterministic command. No model or API key is needed.

name = "library"

[evals.tests]
title = "The library tests pass"
profile = { kind = "runtime", command = "cargo", args = ["test", "--locked"], timeout_ms = 240000 }
payload.instruction = "Run the tests of {library}."

command and args are separate: artifactize starts an argument vector, not a shell command. The working directory is the Artifact folder, or the containing folder for a file Artifact. Programs use the author’s normal toolchain environment, with artifactize’s caller variables withheld and private output and temporary space outside the repository. See Operating systems for executable lookup and portable commands.

Exit 0 is GREEN; another exit code is RED. Failure to start, a timeout, interruption or invalid inputs produce operational errors instead. Runtime commands do not return Agent/Human owner fields. Saved results include the exit code and bounded stdout/stderr after completion. While execution is still appending output, artifactize request logs REQUEST_ID can read bounded snapshots; unconfirmed captures are explicitly marked partial. This is snapshot inspection, not an automatic streaming log feed. The runtime-log CLI reference explains tail/range/search and capture evidence; the shared runtime execution reference owns retention details, including the raw captures current pruning leaves intact. The CLI, TUI and Desktop read the same saved evidence.

References such as {spec}/requirements.json in the declared operands admit those inputs. Files changed during execution invalidate the review. A later Run reuses an unchanged result, including a RED result, rather than rerunning the command. See Fingerprint and the runtime execution reference for exact limits, environment, substitution and process cleanup.

Fingerprints and reuse

A fingerprint says what an Artifact’s reviews depend on. An eval’s earlier result is reused when the eval is unchanged and the fingerprints of the Artifacts it depends on are unchanged, so review cost follows the size of a change.

Fingerprints

Folder and file Artifacts use artifactsum by default. Omit fingerprint to hash their own files, or choose a script. The root graph Artifact always uses the structural graph fingerprint; it cannot declare fingerprint to override or disable that value. For folder and file Artifacts, the configurable forms below add no tool declarations or other Artifact’s value; dependencies enter through the reuse key.

  • Omission uses artifactsum: artifactsum: plus SHA-256 over the Artifact’s own input paths and bytes. fingerprint = {} also works for folders. A folder hashes its own files; a file Artifact hashes its one target file.
  • fingerprint = { script = { command = "hash", args = [] } } uses the command’s output as the fingerprint. It hashes nothing on its own, so the command must reflect every input and script change that should re-review.
  • fingerprint = false disables reuse. fingerprint = true is rejected.

Artifactsum

An Artifact is reviewed again when its own files change or when an Artifact its eval depends on changes its fingerprint. To narrow a folder’s inputs:

fingerprint = { files = ["src"], ignore = ["*.log"] }
  • files: 1–64 unique owner-relative paths, default ["."] (the owner’s own files). Each must exist and stay inside the Artifact: paths into child Artifacts are rejected. Directory walks skip child Artifact folders and every *.artf declaration. Explicit .artf file inputs fail config check; they are not silently skipped. A directory whose name ends in .artf is an invalid declaration and fails discovery. A neighboring file Artifact’s target remains in the folder’s hash; it creates no automatic child relation.
  • ignore: up to 64 .gitignore-style globs relative to the owner, without negation. They always exclude, like the built-in ignores .git, __pycache__/, *.pyc, target/ and node_modules/. .gitignore files exclude more, with git semantics: every one from the repository root (--repo) down through the Artifact and walked directories applies. A pattern is relative to its own file’s folder; negation works, and deeper files override shallower ones. Explicitly named non-declaration files are never ignored.

File Artifacts hash only their target filename. An explicit artifactsum form must name just that file, for example fingerprint = { files = ["hero.png"] }. fingerprint = {}, files = ["."] and ignore (even []) are rejected for a file Artifact. Omit the field for its default; use a script if the fingerprint needs to cover more than the file.

The old dependencies fingerprint option is rejected: a fingerprint covers only its own Artifact. Walk limits and manifests are in the reference; the script form is under fingerprint scripts.

The reuse key

An eval depends on its target Artifact and on what the target directly connects:

  • the target’s child Artifacts;
  • the Artifacts the eval names in its instruction or runtime args.

artifactize does not follow connections further than that. Whether a change two connections away matters is up to how you define fingerprints: a fingerprint script can read a file of any Artifact it names in its args. The key is

hash(Eval definition hash, sorted (Artifact name, kind, fingerprint) of each covered Artifact)

Artifact names and kinds (folder, file or graph) are part of the key, for the target and every covered dependency. A change from folder to file never reuses the former result, even if a script emits the same fingerprint. Physical paths are never key inputs, so copies with matching names, kinds and fingerprints reuse. Changing a covered Artifact’s name changes the key. Differently named targets can still share a result when their evaluation criteria and complete covered Artifact maps match: the key does not separately identify which covered Artifact is the target. For example, two Artifacts whose identical evals reference each other can share results. The Eval definition hash is lowercase SHA-256 of canonical JSON with recursively sorted keys over the evaluation criteria:

  • the eval kind (runtime, agent or human);
  • payload, including the instruction;
  • pass_schema and fail_schema;
  • for runtime evals, the command and args.

Evaluation execution options are not part of the key: an Agent’s backend, model, reasoning, timeout_ms, max_tool_calls and max_tokens, a runtime timeout_ms, and the selected profile variant. Neither are the eval id and title, tags, repository paths, unused profile variants, or tool declarations (views): a tool is a way of viewing an Artifact, so changing only a tool’s description or schema does not review again. To make a tool change matter, include the relevant files in the fingerprint.

An executable eval has a key only when its target and every Artifact it depends on have a fingerprint. Omission supplies artifactsum for folder and file Artifacts, including basis Artifacts of those kinds; graph Artifacts always supply their structural graph fingerprint. If a folder or file Artifact declares fingerprint = false, the eval has no reuse: every verify reviews it again. For an unforced READY Runtime/Agent eval, status names the disabled target or dependency in its execution reason. A READY Human eval instead gives the request-awaiting-claim-and-submission reason; forced, blocked and waiting evals use their respective execution, gate or wait reasons.

Completed result reuse

A successful end-of-review recheck adds the GREEN or RED result to its key’s history: its self-contained executions row gets its completion time, size and last use (State). Errors and cancellation are never recorded there. When a key holds more than one record, the most recent by completion time is reused, GREEN or RED. Results from different profiles therefore reuse each other: a review that --profile fast produced satisfies the declared profile, and the other way around.

Records retain their producing profile and provenance; the Run result reference owns the audit-field and source-kind inventory.

Before accepting reuse, artifactize validates admitted file targets again: they must exist as regular files without symlink traversal. fingerprint = false disables reuse, not these file-target safety checks. Targets are checked during fingerprint preparation/recheck, at eval start/end and on every tool call too.

A hit returns the original result without running the eval or re-validating it against the requested profile or schema. The producing profile remains distinct from the requested one, and a reused request spends no new usage. A reused RED remains RED for gates and final obligations. Dependencies outside execution selection can supply cached evidence without running. Results remain readable after the source repository is deleted; external paths embedded in result text are not made portable.

With a team review store, verify also asks the store for every key and reuses whichever record completed last, the local latest or the store’s latest; a store that cannot be reached leaves the local latest.

No key means no cache lookup or publication. --force re-executes explicitly selected executable evals without reading or joining earlier results, local or in the store, and its completed GREEN or RED is appended as a newer record (and published to a configured store), which later runs then reuse. Forced and unkeyed results still satisfy their own Run and keep their execution.

Upgrading to 0.9

For declaration, state-directory and terminal changes, see Install: Upgrading to 0.9. Reuse-key v2 matches no pre-0.9 result, including script fingerprints and team review store records. The first full verify therefore reviews every executable eval once, Human sign-offs included; later Runs reuse normally.

Graph fingerprint

The root .artf graph Artifact uses graph:<sha256>, computed from one canonical, name-based extraction of the whole project’s current declarations. Objects and relations are sorted; child folder locations and repository paths are not identities. Extraction includes:

  • Artifact names and kinds, basis, and fingerprint coverage: artifactsum files and ignore; script command, args and files; or fingerprint = false. The graph Artifact has a graph-coverage marker, not its own recursively embedded digest.
  • Input → consumer relations: child ownership (names, not child folder paths), instruction references, runtime argv references (including eval ID, argv index and logical suffix), and their input targets.
  • Qualified eval IDs, owning Artifact, titles, optional gate and evaluation criteria: default profile kind, the full payload including instruction and owner context, pass_schema, fail_schema, and runtime command/args.

Excluded are evaluation execution options (backend, model, reasoning, all timeout_ms values including fingerprint-script timeouts, max_tool_calls, max_tokens, profile_variants, eval mutex tags), all tool views and sessions, Artifact tags, repository paths and source file contents. Declared coverage paths and scoped argument suffixes remain meaningful; a relocation that preserves those logical declarations and ownership does not change the graph fingerprint. Execution options are still saved in ordinary state records.

This is only a fingerprint algorithm. Existing reuse keys, Runs, status and review flows decide whether to reuse or review again. The graph is evaluated against current state, never a stored diff or structural history. A graph eval title change invalidates its graph fingerprint even though titles are excluded from the ordinary Eval definition hash. Both built-in graph tools use this same extraction.

Evaluation Run

verify reviews the selected evals and records a Run; status predicts what the next verify will execute, reuse, wait for or block, without running evals. status may run declared fingerprint scripts.

Eval execution gates

verify schedules each eval from its own execution gates, including the machine prerequisites of a Human sign-off. status shows those gates without executing evals.

Verify and Runs

verify requires exactly one selector: positional ARTIFACT or qualified ARTIFACT/EVAL, --eval ID, --evals CSV, --artifacts CSV, --evals-file PATH, --artifacts-file PATH, or --all. Eval IDs are qualified (green/check). Selection preserves first occurrences and removes duplicates. Artifacts expand to their evals in ID order; selection order defines saved requests, not dispatch. An Artifact selector asks to run that piece’s checks; an individual eval selector asks to run just that check. The Run still needs valid results for every piece those checks rely on, following their links as far as needed. This linked group is the dependency closure; its required results are final obligations, even when you did not ask to execute them in this Run.

--recursive also selects every check in that group, including other checks on the selected Artifact, in configuration order. Pieces that depend on one another in a loop are cycle peers, and are included together. --all already selects every check and the obligations of pieces that have no checks. A file Artifact selector selects its own evals, never those of the surrounding folder.

For example, suppose code/tests relies on plan, which needs a person’s plan/signoff. verify --eval code/tests selects the test, but cannot finish GREEN without that still-valid approval. verify --eval code/tests --recursive also selects the group’s other checks, such as code/style and plan/signoff, so they can run or reuse results. Selecting more checks never supplies approval itself.

Every READY eval starts as soon as ready, with no default concurrency cap. Use --jobs N (minimum 1) only when concurrency causes trouble. Scheduling prefers unblockable work and the longest remaining dependency path; selection/configuration order breaks ties. New READY work starts after each result, not after a whole batch. Fingerprint-claim waiters count against an explicit jobs cap; cache hits do not. Agent reviews also need their backend’s slot. QUEUED can mean awaiting admission, a backend slot, a joined execution, or an explicit Run jobs cap; backend-slot waiting does not consume that jobs cap.

Submit, observe and cancel

Artifactize owns the Run independently of its requester. Plain verify submits, prints its Run ID (or {"run","state"} with --json) and exits 0 on acceptance, not approval. verify --wait announces the ID on stderr, observes settlement and renders the saved result. Closing or interrupting that watcher stops observation only; the Run continues. Only run cancel RUN_ID explicitly requests cancellation: it stops new admission and owned active work, waits for cleanup, and records ERROR/CANCELLED without erasing committed evidence. It does not kill an execution this Run joined from another Run. Its sign-offs still waiting, deferred ones included, end as ERROR too, and so do the requests other Runs joined to them; the next verify asks again. This applies only while the Run is running: a Run that already ended SIGNOFFS_PENDING is finished, so cancelling it changes nothing and its deferred sign-offs keep waiting. Dead workers reconcile as ERROR/OWNER_DIED; interrupted work is not replayed automatically.

Acceptance saves the resolved definitions for the selected scope. Before execution, Artifactize checks those definitions twice: before preparing fingerprints, and again after fingerprint and remote-evidence preparation, at the scheduling boundary. If relevant declarations changed since acceptance, the Run ends ERROR/INPUT_CHANGED instead of silently executing different criteria or profiles. Finish editing the declarations and submit a new Run; the accepted Run is not retargeted.

In strict mode, the Run waits for WAITING_HUMAN requests like other reviews. A submission unblocks dependents in that same Run. There is no Human wait deadline or verify --timeout-ms; all eval kinds share the settlement rule. The optional modes below change execution order or defer sign-offs, not Human authority.

Watching exits 0 for GREEN/SATISFIED, 1 for RED, 2 for ERROR/cancelled, 4 for INCOMPLETE, or 5 for SIGNOFFS_PENDING. Interrupting observation exits 130.

Final validation needs actual GREEN evidence or explicit basis = true across all required scope; a basis never waives its dependencies. RED blocks downstream work and missing/operational evidence waits. Selected GREEN reviews with unmet obligations produce INCOMPLETE, with the obligations listed in text and JSON. The upgrade reference covers removed legacy gate-bypass settings. --reuse-only KINDS (for example agent,human in CI) only reuses results for those kinds; missing reusable evidence is not executed and leaves INCOMPLETE. See CLI.

--request-key K is a caller attempt identity: 1–128 ASCII letters/digits/._:-. The same canonical repository/key returns the same Run, even after completion or checkout changes; the first selection and options remain authoritative. Omit it or choose a different key for a new Run. Eval-result reuse is independent of this submission identity. Retain the key when retrying a lost answer, not for a new attempt after fixes.

Speculation and deferred sign-offs

The two per-request verify modes, --speculate and --defer-human, default off and are saved in the Run’s modes. The same request key keeps its original choices.

Speculation starts requested runtime, Agent and Human evals before unresolved gates, within --jobs, backend capacity and --max-executions limits. A Human eval is never requested before every other non-Human eval of its Artifact is GREEN. Ready work has priority; speculative work uses spare capacity and yields first. Redispatch consumes another executor start. An upstream RED or ERROR immediately cancels only the work it makes invalid, the evals that depend on it outside their own Artifact and cycle, and marks it BLOCKED without a reusable cancelled result. GREEN keeps results already produced for the same files. Results are shared with the team review store only after scheduling finishes, and only those that survived it.

Deferral allows execution past pending Human gates. A deferrable sign-off’s request is issued as late as possible: once its Artifact’s other machine evals are GREEN and no gate holds it. Requests show DEFERRED, dependent results PROVISIONAL. Neither state is actual approval. gate = "blocking" retains the strict Human gate. When all other obligations pass, the Run ends SIGNOFFS_PENDING (exit 5), meaning only sign-offs remain; it does not wait for those submissions. Later submissions update requests, not the historical Run verdict. A new verify reuses unchanged provisional machine results: GREEN sign-offs allow SATISFIED; RED sign-offs block dependents. --reuse-only agent,human never treats a pending sign-off as GREEN and stays suitable for CI.

When a newer Run issues a Human request for the same eval in the same working tree, earlier DEFERRED sign-offs with a different reuse key end as ERROR/SUPERSEDED, including requests joined to them. Claimed sign-offs are retired too. They leave Review and cannot be claimed again. The same key still joins the existing review; original sign-offs in another working tree and live WAITING_HUMAN originals stay untouched. Until retirement, a deferred sign-off can still be submitted after its Run ends; submission rechecks the fingerprints and refuses changed files.

status labels current deferred waits and provisional results, but its execution prediction remains strict: a new default verify still waits for Human evidence. Run/result readers expose SIGNOFFS_PENDING as terminal and not satisfied.

Text output prints one line per request and marks a result taken from another request’s execution (a fingerprint hit, or a joined execution) with its source Run (results from a remote review store also name their producer or Human reviewer):

  web/tests [run-x05qFq-5]: GREEN
  docs/review [run-x05qFq-3]: GREEN (reused from run-Ksl1Qr)
Summary: executed 1 (runtime 1, agent 0, human 0), reused 4 (runtime 2, agent 1, human 1)
Usage: spent none; saved inputTokens 10, outputTokens 5, totalTokens 20

executed counts the Run’s own executions, including ERROR results and Human requests it recorded; reused counts cache, joined and remote results. A reused result that another profile produced (another model, limit or other setting, none of which is in the reuse key) adds profile NAME to its line, and the summary then ends with ; N produced by another profile (summary.reused.otherProfile in JSON). A result from a variant with identical settings adds no marker; its saved options.variant names the variant. Usage (printed only when some usage was reported) separates counters spent in this Run from saved, the sum of the reused executions’ original counters. JSON verify and run show carry the same numbers as summary.executed, summary.reused and the Run-level usage: {spent, saved}; summary.usage stays equal to usage.spent.

In JSON, each request’s source says where its result came from. It is null when the request executed itself (or has no result yet), and otherwise names the request whose execution produced the result for cache, joined and remote reuse:

"source": {"runId": "run-Ksl1Qr", "requestId": "run-Ksl1Qr-3", "kind": "cache"}

kind is cache for a completed record of the reuse key, joined for a live execution of the key that the request waited for (in another verify, or a sibling eval in the same Run), and remote for a record from the remote review store. run show, request show and request list --json carry source too.

verify --wait --json prints full saved results, including payloads, argv, stdout/stderr and runtime details; there is no compact projection or --full flag. run show RUN_ID always prints full saved JSON and exits 0 on a successful read, regardless of the saved verdict; --wait instead exits with the Run outcome code once it finishes. It never discovers declarations or runs code, and does not need --repo. Each Run saves its selection, effective policy, requested profile option, and resolved Artifact/eval definitions for the selected dependency closure, including tags, schemas, tool views and graph relations. These are recorded with the initial request rows, not reconstructed from current declarations.

A partial Run can use saved evidence for an eval without creating a request for it, such as an upstream eval outside the execution selection. The Run records those verdicts in evidence, a map from qualified eval IDs to saved states:

{"evidence": {"code-style/approved": "GREEN"}}

run show RUN_ID --json and verify --wait --json flatten the saved Run’s fields into the output object: evidence is a top-level field, not nested under run, and is omitted when empty. It is the evidence this Run used, not a lookup of the latest result. Saved definitions, evidence and the Run’s request states let the monitor judge gates without reading current declarations or recomputing fingerprints.

run list [--repo-only | --all] lists saved Runs newest first as a text table, or as a JSON array with --json. It defaults to the canonical --repo path (the current directory when omitted); --repo-only makes that default explicit, and --all includes every repository in the shared state. --limit N (default 50) and --offset N (default 0) page the filtered results. Each row includes the Run ID, repository, creation/completion timestamps, status, and request counts by verdict/state (absent states have zero requests). Unreadable requests do not count; unreadable names damaged records and their reasons while valid evidence stays available. See state troubleshooting. Reads do not run owner code, create missing state, or require a repository to still exist. There is no separate history command.

Review policy, --force, --max-executions, selection files and --profile variants are in the reference, with the command table and the state layout.

Status and static graph

artifactize --repo PROJECT status
artifactize --repo PROJECT status ARTIFACT --recursive --json
artifactize --repo PROJECT config graph ARTIFACT --json
artifactize --repo PROJECT config check --json

status accepts the same exclusive selectors as verify, defaulting to --all, and the same --profile, --recursive and --force policy options. It reports current Artifact/eval states, unmet final obligations, and execute / reuse / wait / blocked actions. All evals in the required closure are shown; selected and included distinguish explicit selection from included execution (recursive dependencies). In JSON, each eval also shows what its reuse key is made of: evalDefHash, the target’s fingerprint, fingerprints (each Artifact the eval depends on with its current fingerprint, or null without one) and the composed key (null when a fingerprint is disabled), the same values verify would key the Run with. Action counts cover included evals only. Exit 0 means current validation is satisfied; 1 means obligations remain; invalid input or state errors exit 2.

Status prepares fingerprints needed by executable evals in the selected required closure, using the same isolation, validation and --fingerprint-jobs bound as verify. Fingerprint failures exit 2; an old saved fingerprint is never substituted. It never runs tools or eval commands, creates Runs, reserves work, creates a missing database or updates cache access times. Fingerprint scripts use disposable output under the state directory, which may be created even when no database exists. config graph and config check remain fully static and never run owner code.

A completed record for the eval’s current reuse key yields PASS or RED and a reuse action; its reason says so, and names the profile that produced it when that is not the requested one. verify attaches cached results before their gates resolve, so the action stays reuse while a dependency is pending or RED. The state then shows the gate (WAIT_DEPENDENCY or BLOCKED) and the reason names it. An eval without a cached result runs only once its gates are GREEN. It shows blocked behind a RED dependency and execute when its dependencies are GREEN through cached results. It shows wait when a dependency’s result, directly or further upstream, exists only after verify executes it (or after a live execution finishes). That is the limit of the prediction: status cannot say whether a wait eval will execute. Text ends with, for example, Verify actions: will execute 1, will reuse 4, wait 0, blocked 0, so status run on a merged checkout answers what verify will re-review there without running it. Force still applies only to selected executable evals. Human execution actions record a waiting request; an active Human execution of the same key projects WAITING_HUMAN and a wait action, even after the original verifier exits. Saved attempts are read for this canonical repository only: each eval’s optional last: {runId, verdict, fingerprint?} is historical, not current evidence. Use run show RUN_ID for full attribution. Noncached GREEN/RED satisfies only its own Run, so its later status is STALE rather than reuse (ENG-24). Basis-only scopes can be satisfied; a basis with unmet dependencies is INCOMPLETE.

When an eval with a reuse key has no current cached result, status explains why. It compares the current key with the newest cached GREEN/RED record for the same eval and Eval definition hash (from any repository in this state), and reports changes: {sinceRunId, files?, dependencies?, summary}. In text this is a Fingerprint changed since Run RUN_ID: ... line, for example changed: +docs/new.md, -old.md, src/a.py; dependency core changed. files lists the target’s own files as path (changed), +path (added) or -path (removed); dependencies lists the children and named Artifacts whose fingerprint changed, as name, +name or -name. A script fingerprint, or a manifest whose file map was dropped, can only report fingerprint changed or inputs changed for the target. No explanation appears when that eval definition has never been cached, or for forced evals. For an unforced READY Runtime/Agent eval without a key, the execution reason names the target or dependency declaring fingerprint = false. A READY Human eval instead says “Record a request awaiting a Human claim and submission”, even without a key; blocked or waiting evals explain their gate or wait state instead.

For a waiting Eval blockedBy names the unfulfilled qualified Eval IDs; for a BLOCKED one, the RED Evals at the root of the block, as its reason does. Detail groups these gates by input Artifact under Waits for; w shows pending Evals.

A later Human submission can settle a waiting Run. run show keeps its saved validation and requests. The monitor derives its tree from the saved definitions and evidence with request states as they stand now, including later Human submissions. Its headline keeps SATISFIED / NOT SATISFIED at Run end; a dim * marks rows whose derived state changed since that end, not files changed. See Monitor.

config graph [ARTIFACT] defaults to the whole project, or shows the selected required closure including cycle peers. --impact includes downstream consumers and their required inputs: conservative structural reachability, not an exact reuse-key prediction. MCP config_graph exposes the same definitions with single/multiple Artifact or eval scope; use status for actual execution/reuse decisions. Text lists Artifacts with tags and file-kind markers, evals, components and input-to-consumer relations. Full JSON includes resolved static Artifact/eval definitions (profiles, payloads, schemas and tool views), tags, child/instruction/argv relation metadata, cycle markers and dependency-first strongly connected components (SCCs): groups of Artifacts that mutually depend on one another. Artifact entries carry kind (file, folder or graph) and workspace-relative target path. Status also shows file kinds/paths; the monitor uses [file] on tree rows and keeps the target path in Detail. Component IDs refer to the full graph and may be noncontiguous in a selected projection. config check keeps its static validity confirmation and JSON Artifact/eval counts. There is one text or full JSON output level, with no plan, --compact or --full. The former top-level graph command is hidden from help; it prints graph moved to "artifactize config graph" and exits 2.

Current graph review tools

config graph remains the configuration inspector. For review, declare the built-in graph tools on any Artifact: the Agent sees the extracted current graph as a tree; the Human opens it in the existing desktop graph viewer, without requiring a saved Run. The optional root .artf graph Artifact has ordinary evals and appears in Runs and status like any other Artifact. Only its fingerprint algorithm is different; there is no graph-specific verification flow or automatic relation to the root folder Artifact.

Tools

Tools let a reviewer inspect an Artifact without turning the review into a build session. Agent tools are read-only operations the model can call. Human tools are predefined commands a person runs after claiming a review. Neither kind replaces the eval’s criteria or invents a verdict.

Built-in tools

Use the built-ins before writing a wrapper: read files, list folders, search text, show an image or Markdown section, inspect program help, or view the current Artifact Graph. Human tools can also open a file or URL in its normal application. The Agent built-ins that read files, list folders, search text and show images or sections see only their admitted Artifact scope, not the whole machine. graph reads every current declaration of the project, whichever Artifacts the eval admits. help runs a real program found by ordinary lookup, and declared command tools are trusted programs too: neither is an OS sandbox.

[views.agent_tools]
read = { builtin = "read" }
list = { builtin = "list" }

[views.human_tools]
notes = { builtin = "read", args = ["{artifactPath}/notes.md"] }

An eval sees the tools of the Artifacts it actually references. Tool names become <operation>_<artifactName>, so read declared on spec is read_spec.

Build your own tool Artifact

Put a reusable tool beside its implementation and give it an Artifact declaration. For example, tools/checker/index.artf can declare the checker Artifact with a Python program inspect.py and its tests/ folder next to it:

name = "checker"

[evals.tests]
title = "The inspection tool handles the requirements and JSON protocol"
profile = { kind = "runtime", command = "python3", args = ["-m", "unittest", "discover", "-s", "tests"] }
payload.instruction = "Run the protocol and requirements tests of {checker}."

[views.agent_tools.inspect]
description = "Inspect the specification for {artifactName}."
protocol = "json"
command = "python3"
args = ["inspect.py", "{spec}/requirements.json"]
input_schema = { type = "object", properties = { section = { type = "string" } }, required = ["section"], additionalProperties = false }

[views.human_tools]
source = { builtin = "read", args = ["{artifactPath}/inspect.py"] }

Attach it to the consumer by referencing the tool Artifact in the eval’s criteria:

name = "app"

[evals.review]
title = "The app satisfies the specification"
profile = { kind = "agent", backend = "openai", model = "YOUR_EXACT_MODEL_ID" }
payload.instruction = "Review {app} against {spec}; use inspect_checker from {checker} to inspect the requirements."

[evals.approved]
title = "The implementation is approved"
profile = { kind = "human" }
payload.instruction = "Review {app} and the implementation of {checker}; run its source tool before deciding."

The Agent gets inspect_checker; the person gets source_checker. Both evals admit the referenced tool Artifact, so its relevant fingerprint participates in reuse. Give the tool its own real tests or reviews when needed; do not declare it basis just to bypass unfinished work. execution_paths can record which built binary ran, but those provenance pins do not themselves invalidate reuse.

A json command reads one JSON request from stdin, containing context and args, and prints one JSON content object, such as {"content":[{"type":"text","text":"The required headings are present."}]}. A plain command substitutes declared schema properties into separate argv entries and returns bounded stdout as text. Human commands take no free arguments: declare either captured output or an application launch. See Agent tools and Human tools for complete protocols, scoping and bounds.

Keep a game or app harness running

A harness is a helper program that runs or controls the application during review. A tool session keeps one harness alive across an Agent review’s calls. For example, a game tool Artifact can run its game headless—without a visible window—and expose a read-only board inspection operation:

[views.agent_sessions.game]
start = { command = "game-harness", args = ["start"] }
stop = { command = "game-harness", args = ["stop"] }

[views.agent_tools.board]
session = "game"
description = "Read the current game board."
protocol = "json"
command = "game-harness"
args = ["board"]

Start reads the usual JSON envelope, writes shared state into the private ARTIFACTIZE_SESSION_DIR, prints one readiness line and stays running. Bound calls and stop receive the same directory; each review gets a separate session. The agent never starts, stops or names an instance. Artifactize owns preparation, stop and process cleanup, including errors and cancellation. A reused review starts nothing. Sessions are not saved Agent conversations. See Tool sessions for the lifecycle and recovery contract.

Check a declaration before reviewing

artifactize tools check checks availability and declarations without executing project commands. To try a specific tool, use the explicit execution form; it creates no Run, review or cache entry. A bound Agent tool gets a fresh owned harness for that diagnostic call.

Built-in tools

artifactize ships a set of tools written in Rust that behave the same on Linux, macOS and Windows, except for how a path’s spelling on disk is checked (see Paths). Declare one by name with builtin instead of writing a script or calling an OS program such as cat or xdg-open. Built-in tools run inside artifactize, so they do not need the artifactize-tools command on PATH; that command and artifactize tools offer the filesystem/program tools to scripts and runtime evals. The project-aware graph tool is available only as a declared review tool in artifactize, not in the standalone tools CLI.

[views.agent_tools]
read = { builtin = "read" }                                       # the model picks the path
grep = { builtin = "grep" }
style = { builtin = "read", args = ["DEV-CODING.md"] }                 # one fixed file
spec = { builtin = "section", args = ["spec.md"] }                # the model passes only the heading
install = { builtin = "section", args = ["spec.md", "Install"] }  # one fixed section
reqs = { builtin = "read", args = ["reqs/requirements.md"] }      # in the child folder reqs
structure = { builtin = "graph", args = [] }                    # current project entity tree
cargo_help = { builtin = "help", args = ["cargo", "build"] }      # cargo build --help

[views.human_tools]
open = { builtin = "open", args = ["{artifactPath}/notes.md"] }   # kind = "launch"
viewer = { builtin = "graph", kind = "launch", args = [] }       # existing desktop graph view
docs = { builtin = "open", args = ["https://artifactize.dev/docs/"] }
notes = { builtin = "read", args = ["notes.md"] }                 # kind = "output"
summary = { builtin = "section", args = ["{reqs}/requirements.md", "Summary"] }

A built-in declaration takes builtin, args (optional for the Agent tools that take model input) and an optional description; each tool has a default description. A Human built-in may also name its kind, which must match the tool (open and graph launch, every other tool prints output) or config check rejects it. Built-ins take no timeout_ms, command, protocol or input_schema. The reviewer sees each tool as <name>_<artifactId>, as with command tools.

Tools

ToolAgent, model inputAgent, fixed argsHuman argsPrints
read{path, offset?, limit?}[file][file]the file
list{path?, offset?, limit?}[folder][folder]the sorted entries, as JSON
glob{pattern, path?}——matching paths
grep{pattern, path?, glob?, caseInsensitive?, maxResults?}——matching lines
view_image{path}——the image
section—[file] (the model passes heading) or [file, heading][file, heading]one Markdown section
help—[program, subcommand...][program, subcommand...]program subcommand... --help
graph—[][] (launch)Agent: entity tree with evals, criteria and directed relations; Human: desktop viewer
open——[file, folder or URL] (launch)nothing; opens the default application

Without args, an Agent tool takes the model’s input; the input, result and limits of read, list, glob, grep and view_image are in Agent tools. Fixed args remove the model input, except section with one argument. Fixed Agent list returns a structured JSON content block; fixed read, section and help return text blocks:

  • read prints a UTF-8 file, at most 64 KiB; a longer file ends with [output truncated]. It validates only the bounded prefix it inspects: NUL or invalid UTF-8 there is refused, but such bytes later in a truncated file may go undetected. The CLI fixed read has the same limit.
  • list prints one page of the folder’s sorted entries as JSON, like the model’s list.
  • section prints one section of a Markdown file. It finds the first heading whose text equals heading exactly, at any level (# or underlined headings; headings inside code blocks do not count). The section runs from that heading to the next heading of the same or a higher level, so nested sections are included. When no heading matches, the error lists the headings the file has. The file may be up to 8 MiB; the printed section, up to 64 KiB.
  • help runs program subcommand... --help and prints its output. The program is a literal program or path. Bare names use PATH (with PATHEXT on Windows). Unlike scoped command-tool executables, help uses ordinary program lookup: absolute paths, parent-relative paths and executable symlinks are accepted. Relative paths start at the declaring Artifact’s folder; subcommand arguments are forwarded unchanged, without scope resolution. The program runs in that folder with empty stdin and gets no private output or temporary directories. For an Agent, it runs in the author’s environment minus every ARTIFACTIZE_* variable (matched ignoring case). For a Human, it runs in the reviewer’s whole environment, ARTIFACTIZE_* variables included, such as an ARTIFACTIZE_REMOTE_TOKEN credential. It must finish within 10 seconds, exit 0 and print at most 64 KiB, stdout and stderr together. It runs a real program, so name only programs you trust.
  • open (Human only) opens one file, folder or http:///https:// URL in the desktop’s default application: xdg-open on Linux and WSL, open on macOS, the Windows shell (ShellExecute) on Windows. The target is passed as one argument, never through a shell. Other URL schemes, such as file: or mailto:, are refused. The result only records that the application was started; the application belongs to the reviewer and keeps running. On Linux, WSL and macOS, the opener itself must exit successfully within three seconds: an unavailable opener, timeout or nonzero opener exit is a tool error. This acknowledgement deadline is not a lifetime limit on the opened application.

view_image

view_image is an Agent-only tool with required model input {"path":"board.png"}. The path is a logical path inside its admitted Artifact scope, with the usual no-traversal and no-symlink checks. It reads a nonempty regular file and returns an embedded image block (type, base64 data, mimeType), not a printed filename.

Detection checks signatures and container framing, not the extension; it does not decode pixels or fully validate image contents. Supported signatures are PNG, JPEG and WebP; unsupported signatures, GIF/BMP and animated PNG are refused. The image-file bytes must be at most 4 MiB inclusive after any base64 decoding, not a bound on decoded pixel memory. The image-result contract explains normalization and the same MIME/size checks for command tools; only those command tools can name files in their private output directory.

Current project graph

Declare builtin = "graph", args = [] on any Artifact; no graph Artifact is required. It always reads the entire current project’s declarations, not the selected review’s historical Run or only its scope. It does not read source contents, start an eval or add dependencies. The two audience-specific tools use exactly the extraction that produces the graph fingerprint:

  • Agent: no model input ({} only). Prints a name-based, cycle-safe tree of Artifacts and their evals, the complete extracted definitions/evaluation criteria, the graph fingerprint, and labeled arrows from input to consumer.
  • Human: kind = "launch". Starts artifactize-desktop graph --repo PATH using the normal detached application handoff. artifactize-desktop must be installed and discoverable through the program search path. The viewer reuses the monitor’s existing graph renderer and keyboard/zoom interactions, but shows current declarations rather than Run verdicts. Expand Structure and evaluation criteria for the same complete text tree as the Agent sees; Refresh or Ctrl+R/F5 reloads declarations. This works before any Run exists. The tool returns a launch acknowledgement, not an approval; the viewer keeps running when the calling tool finishes.

Paths in args

Filesystem targets must stay inside the scope admitted to the actual review:

  • Fixed Agent tools use logical paths relative to the declaring Artifact, which may lead into a child folder (reqs/requirements.md). Discovery and config check validate these targets against that Artifact’s child scope, rejecting missing or out-of-scope targets. Calls check them again.
  • Human tools accept the same logical paths, and also {artifactPath} or {artifactPath}/path and {name} or {name}/path, where name is an Artifact, as in Human tools. A --flag= prefix is not accepted. Discovery and config check validate reference names and target existence against the workspace, not the narrower review scope. An existing {other}/file can therefore pass configuration validation without being a child. Preflight and calls require that other is admitted to the actual Human eval scope, so the eval references {other} itself; an Artifact-only tool check uses just that Artifact’s child scope. Tool operands never add dependencies or expand that scope.

Filesystem targets refuse absolute paths, .., symlinks and special files at configuration validation and on every call. open additionally accepts HTTP(S) URLs. help takes a literal program or path plus subcommand arguments, without placeholders; those are program arguments, not filesystem targets for these scope checks.

Reuse

Tool declarations, including a built-in’s name and args, are not part of the reuse key: a tool is a way of viewing an Artifact. Changing a tool reuses earlier verdicts, and a new artifactize release that changes how a built-in behaves does too. To review again when the file a tool reads changes, cover that file in the fingerprint; the default artifactsum covers the Artifact’s own files.

artifactize-tools CLI

artifactize tools exposes the same current-directory tools on every install, including the Microsoft Store package. Non-Store channels also put artifactize-tools next to artifactize; either spelling uses the same parser and runner, output and exit codes, including artifactize tools --version. The existing artifactize tools check checks declared review tools rather than running a current-directory tool.

For runtime evals, fingerprint scripts and your own scripts:

artifactize tools read DEV-CODING.md             # also: artifactize-tools read DEV-CODING.md
artifactize-tools list [PATH]
artifactize-tools glob PATTERN [PATH]
artifactize-tools grep [--glob GLOB] [--case-insensitive] PATTERN [PATH]
artifactize-tools section spec.md "Install"
artifactize-tools help cargo build          # cargo build --help
artifactize-tools open notes.md             # or an http(s) URL

There is no Artifact scope outside a review, so the current directory is the scope. Filesystem targets for read/list/glob/grep/section/open must be relative and stay below it; . names the directory itself and is the default for list/glob/grep. Absolute targets, .. and symlinks are refused, even when a link points inside. This is not a restriction on help’s literal executable or subcommand arguments: its ordinary lookup accepts absolute/parent paths and executable symlinks as explained above. Only open accepts a URL, and only http:// or https://.

read, section and help print text with the limits above; list, glob and grep print the JSON result of the Agent tool. From the command line, help runs the program with your terminal’s environment minus every ARTIFACTIZE_* variable, and with no private temporary or output directory. A tool error goes to stderr with exit 1; invalid usage exits 2. artifactize-tools --help and artifactize-tools SUBCOMMAND --help describe each subcommand. The facade uses artifactize tools --help and artifactize tools SUBCOMMAND --help. artifactize tools help PROGRAM SUBCOMMAND... is the built-in help tool, not clap’s help-navigation command.

Agent tools

Tools shows how to declare and attach a custom tool Artifact.

Command fields description, protocol, command and args are required. input_schema defaults to {"type":"object","additionalProperties":false} (empty arguments only). Supplied schemas must have root type: "object" and are compiled by jsonschema, without its file/HTTP resolution features. Local $ref, composition and other standard schema features work; there is no custom keyword subset. Schema defaults never change arguments. Declarations/schemas are capped at 8 MiB. Calls require a JSON object of at most 64 KiB before any process or output directory is created. Validation errors show at most five bounded, escaped instance/schema paths (under 4 KiB), never argument values.

Descriptions must be nonblank, at most 4000 UTF-16 code units, and support only {artifactName} interpolation (the canonical Artifact ID). Built-in references take builtin, optional args and optional description. Without args, read, list, glob, grep and view_image take the model’s input below; with args, read, list, section and help work on a fixed target, and graph with args = [] prints the entire current project structure, as described in Built-in tools. Only explicitly declared tools are listed. Built-ins execute in-process without output directories (only help starts its program); view_image applies the same image checks as json image results.

Built-inArgumentsResult
graphFixed declaration args = []; call {}Complete name-based Artifact/eval tree, extracted criteria, graph fingerprint and input → consumer relations
read{path, offset?, limit?}; offset is a 1-based line (default 1), limit defaults to 80, max 500artifactId, resolvedArtifactId, path, lines: [{number, text}], startLine, nullable endLine, lineCount, totalLines only when EOF is known, truncated, nullable nextOffset
list{path?, offset?, limit?}; offset is 0-based (default 0), limit defaults to/max 200artifactId, path, sorted entries: [{name, path, kind}], totalEntries, truncated, nullable nextOffset
glob{pattern, path?}; path must name a directorySearched path, up to 200 sorted logical files, truncated
grep{pattern, path?, glob?, caseInsensitive?, maxResults?}; case-sensitive by default, maxResults defaults to/max 200Searched path, matches: [{path, line, text}] (one per matching line, sorted by path then line), truncated

read.artifactId identifies the Artifact that declared the tool; resolvedArtifactId identifies the Artifact reached through a child folder. For example, if app owns the child folder rules declared as spec, its read tool called with {"path":"rules/requirements.md"} returns ownership metadata {"artifactId":"app","resolvedArtifactId":"spec","path":"rules/requirements.md"} alongside the line data. path remains the logical request path, not a physical machine path. List’s top-level artifactId likewise names the declaring Artifact. Entry kinds are file, directory, symlink or other. Glob/grep return the searched logical path, and grep’s matching line numbers start at 1.

Paths are relative logical paths from the tool’s declaring Artifact, including its children; omitted paths mean its root. Another Artifact’s files are read with that Artifact’s own tools, which an eval admits when it references the Artifact. They never accept absolute paths, traversal components or symlinks. Logical paths are at most 4096 UTF-16 code units and reject ASCII controls, backslashes, colons and empty or ./.. components. The lone . is accepted as a file Artifact’s virtual root for list/glob/grep; other dot components are rejected. The registry scope remains the eval’s admitted Artifacts, not other evals’ references. Paths are opened read-only through pinned directory descriptors with no-follow component checks; special files cannot be read. list can report symlink/other entries, but searches skip them. A file Artifact exposes only its target file, not unrelated siblings in its working directory. list with no path or path: "." lists that virtual root’s target file. Read the target by its filename; file paths remain relative to the containing folder.

Every call validates admitted file targets before invoking a built-in or command tool, even when reuse is disabled. A missing, non-regular or symlink-replaced target returns an error without running the command.

Read returns at most 64 KiB of complete original line bytes, preserving LF, CRLF and a UTF-8 BOM in each line’s text. Only requested lines are decoded; invalid UTF-8/NUL is an error, as is an oversized first requested line. An empty file returns zero lines and totalLines: 0; a page past EOF returns zero lines with the actual nonzero total. Concatenating returned text values reproduces the source range without added line-number prefixes.

The glob built-in requires a directory path; it selects matching filenames without inspecting contents, including binary filenames. grep accepts a file or directory and skips binary (NUL) or invalid-UTF-8 contents in their entirety. Its optional glob argument is a filename filter, not a call to the glob tool.

Glob patterns use * within a component and ** across directories, relative to path. Glob and grep patterns (including grep’s optional glob) must be nonempty and at most 4096 characters. Globs must be relative, with no leading slash, backslashes, colons, NUL, or empty/./.. path components. Grep uses Rust regex syntax, with a 2 MiB compiled-regex size ceiling; an empty, invalid or oversized regex is an error. Grep’s optional glob filter matches relative file paths (or the basename when path names a file). Hidden files are included; git ignore rules do not filter results. Bounds are 10,000 traversed entries, 8 MiB per grep file, 64 MiB searched, and 512 KiB per JSON result. Search limits or skipped oversized files set truncated: true; narrow the path/pattern to continue. Separately, encountering any one directory with more than 10,000 entries fails list, glob or grep with an error, not a truncated search result. Listing can page early at the result byte cap. Built-in arguments use the same bounded JSON Schema admission as commands.

The json protocol receives exactly one request on stdin:

{
  "version": 1,
  "context": {
    "artifactId": "example",
    "artifactPath": "/workspace/example",
    "outputDir": "/external/private/output",
    "tmpDir": "/external/private/tmp",
    "scope": {
      "example": {"path": "/workspace/example", "kind": "folder", "children": {}}
    },
    "executionPaths": {"shared/rules.json": "/workspace/shared/rules.json"}
  },
  "args": {"section": "summary"}
}

Scope entries contain canonical physical paths, kind (file, folder or graph) and logical child maps. For a file Artifact, context.artifactPath and its scope entry’s path name the file, not the process cwd; the cwd is its containing folder. File Artifact references reject /path suffixes. These protocol keys stay camelCase; declarations use execution_paths and input_schema. executionPaths is omitted when empty. Declared execution paths are up to 64 unique workspace-relative files/directories, resolved without symlinks or copying. This remains workspace-relative for file Artifacts; it is not relative to their cwd or target.

When an Agent review starts, artifactize pins each execution path of the eval’s tools: a file to the SHA-256 of its bytes, a directory to the SHA-256 over each file’s or symlink’s relative path and digest (a symlink inside contributes its target and is not followed). Directories are only traversed: they do not contribute a digest or increment the entry counter, so empty directories do not count. The pins are recorded in the result’s provenance as executionPaths: {"TOOL": {"PATH": "sha256"}} (by registered tool name, such as coverage_spec, and declared path), travel with remote records, and stay with the result when it is reused, so a reused verdict shows which binary produced it. A path that cannot be pinned (missing, a special file, or more than 1 GiB of file bytes) fails the review with PREPARATION_FAILED before the Agent is called. Pins retain at most 10,000 file/symlink entries; after that many have been retained, encountering any further filesystem entry fails preparation. This is not a total filesystem traversal bound: directory visits alone, including empty directories, do not increment the counter. Pins are provenance, not part of the reuse key: to review again when a binary changes, cover it in a fingerprint. Successful stdout is one JSON object with 1–32 content blocks: {"content":[{"type":"text","text":"..."},{"type":"json","data":{}}]}. Image blocks are described below. Text is at most 64 KiB per block; compact JSON data at most 512 KiB per block; the normalized result at most 8 MiB. Both process streams are bounded at 16 MiB. The result object accepts only content and optional boolean isError. Every block is also closed: text permits only type and text; JSON permits only type and data; image permits only type, mimeType and exactly one of data or path. Any extra field on a result or block is an invalid-output error, not metadata passed through to the reviewer. Exit-0 authored errors must have exactly one nonblank text block, such as {"isError":true,"content":[{"type":"text","text":"Choose a smaller range."}]}. These reach the reviewer unchanged. Nonzero exit, crash, malformed/truncated JSON, and process failures yield generic errors; stderr is never forwarded. There are no observation receipts.

Image results

A JSON command can return an image block with mimeType and exactly one of data (standard base64 bytes) or path (an output file):

{"content":[{"type":"image","mimeType":"image/png","data":"<base64-encoded PNG bytes>"}]}
{"content":[{"type":"image","mimeType":"image/png","path":"screenshots/board.png"}]}

Write the second form’s file below the request’s context.outputDir. A relative path starts there; an absolute path is accepted only when it is still below that directory. Traversal, symlinks in any component, special files and files outside that output directory are refused. Artifactize reads and embeds the bytes before removing the invocation’s output, so the returned result is self-contained base64, not a dangling local file path.

Images must be nonempty PNG, JPEG or WebP, at most 4 MiB inclusive of image-file bytes after base64 decoding, not decoded pixels. Detection checks signatures/container framing rather than full image validity. Those bytes, not the filename extension, identify the format and must match image/png, image/jpeg or image/webp respectively. Invalid base64, a MIME mismatch, unsupported formats such as GIF/BMP, and animated PNG are rejected. The overall normalized 8 MiB tool-result limit still applies after base64 encoding. view_image returns the same embedded image form and uses the same byte/format limits, but reads its file from admitted Artifact scope.

For plain, only top-level declared input_schema.properties can appear as argv placeholders. Whole-token {query} and embedded --query={query} both work. Strings substitute literally; other JSON values use compact JSON. Missing values and NUL bytes fail before spawn. {{ and }} escape literal braces. Substitution is single-pass: values containing braces, quotes, spaces, $() or semicolons remain one literal argv element, never shell code. Plain tools receive empty stdin. Cleaned, lossily decoded stdout becomes one text block, capped at 64 KiB with an explicit truncation marker; nonzero exit marks that bounded stdout as a tool error. Stderr is not included. Commands such as rg that exit nonzero for no matches need an owner wrapper if that should count as successful empty output.

Command executables follow the shared Program lookup rules, including the file-Artifact exception and rejection of scoped traversal. JSON-protocol argv may use existing scoped Artifact references, but cannot add Artifacts outside the eval’s admitted scope. Plain argv uses only its schema-property placeholders. Commands themselves are never interpolated. Command calls use the owner’s working directory (the containing folder for a file Artifact) as cwd, the author’s own environment (the runtime environment: the real home, no ARTIFACTIZE_* variables of the caller) with private external TMP/output, a default 120000 ms deadline (1–2147483647), and process-group cancellation/cleanup. Per-call directories are removed on success, failure and cancellation, including dropped call futures; caller-owned output roots remain. Commands are trusted read-only programs, not sandboxed.

Internal callers use tools::Registry::new(&config, "artifact/eval"), list() and call(name, args, output_root, cancellation).await. Only Agent evals are accepted. Each Artifact in its admitted scope contributes its Agent declarations under <name>_<artifactId>; tool-name and session-provenance-name concatenation collisions are rejected. Human tools are never listed. Listing creates no directories and runs no owner code. Calls return normalized ToolResult (the Success or Error variant; read it with content() and is_error()) for successful, authored and system-error results. Registry calls do not mutate payloads or declarations. The Agent loop calls this registry.

Tool sessions

A tool session keeps a harness running across calls during one review. It is not a saved Agent conversation. Declare its start and stop commands in the same Artifact as its bound command tools:

[views.agent_sessions.app]
start = { command = "harness", args = ["start"], execution_paths = ["tools/harness"] }
stop = { command = "harness", args = ["stop"], execution_paths = ["tools/harness"] }

[views.agent_tools.click]
session = "app"
description = "Click an element of the running app."
command = "harness"
args = ["act"]
protocol = "json"

start and stop require command and args; their only optional field is execution_paths. They use the usual executable lookup, scoped argv and JSON stdin envelope, with args: {}. They do not take description, protocol, timeout_ms or concurrency options. A command tool’s session must name a session in its own Artifact; built-in and Human tools do not accept it.

The harness implements this start contract:

  • Read the usual JSON envelope on stdin. ARTIFACTIZE_SESSION_DIR names a private directory supplied by artifactize; use it for state shared by bound tools and stop.
  • When ready, print one newline-terminated line on stdout. Its content is ignored.
  • Keep the start process running for the whole session. Its exit ends the session.

Bound tools, whether json or plain, and stop receive the same ARTIFACTIZE_SESSION_DIR and run in the author’s environment, real home included, like every command. Each invocation still has its own private output and temporary directories. Concurrent reviews have independent session directories and environments, even on the same Artifact. Choose ports/displays through standard OS mechanisms (for example port 0 or an automatically selected display); a desktop harness starts its own headless display. The Agent sees no instance ID and cannot call start or stop.

Execution-path pinning still happens at review start. A tool session starts only when the Agent first calls a tool bound to it; later bound calls reuse that session. An admitted session whose tools are never called starts no process and creates no session directory. Startup and readiness count toward the review’s timeout. Spawn failure or exit before readiness is returned to the Agent as that tool call’s error, not PREPARATION_FAILED. Later calls to the same session return the same startup error without restarting it. Readiness has no separate fixed bound: start may prepare while its process lives, until the review times out or is cancelled. After readiness, stdout and stderr are drained and discarded.

If the start process exits during the review, artifactize immediately kills its remaining process group/Job and cancels in-flight bound calls. The cleaned group is released once termination is confirmed, so later review cleanup does not signal its old numeric process-group ID. A failure to retire its saved identity is reported without skipping cancellation/join of bound calls or stop. Later bound calls return The tool session `app` ended.; there is no restart. This is an ordinary tool error in the saved conversation, not a new event kind.

Every session that started goes through stop and cleanup on every review ending: verdict, error, timeout, cancellation or dropped review future, including a review panic. A start that exits before readiness is cleaned up immediately; otherwise stop is attempted after the Agent loop and before input revalidation, fingerprint recheck and result commit. Cleanup cancels and awaits in-flight bound calls before running stop. Stop uses its own cancellation token and an internal 30-second bound, then artifactize kills and reaps the session group/Job. Its directory and admission records are removed only after all owned-process cleanup is confirmed, even when stop itself fails or hangs. Windows cleanup waits for all Job descendants, with a separate internal 30-second termination bound.

Cleanup does not depend on the program that asked for the review: if that caller shuts down its async runtime, artifactize’s own background executor still runs stop and cleanup. If the artifactize process itself is killed, stop cannot run, so the next tool session cleans up first. Each session directory records the process that owns it and a lease, a marker that its cleanup is still in progress. Before starting, artifactize removes directories whose owner has exited (including one that has exited but was never waited for) or whose lease was released after a failed cleanup, even if the owner is still alive. Directories with an active lease belong to a running review and are left alone.

On Unix, artifactize records each start, stop and bound-call process group before admitting it, and checks the saved process ID and start time before sending a signal, so it never signals a reused process ID, even when the group’s first process has already exited. Windows Jobs end when their owner dies; recovery waits up to 30 seconds for Windows to release open file handles.

A cleanup failure is reported instead of a successful result. When process cleanup is not confirmed, artifactize keeps its journals (the records of which processes it started) so a later start can finish the job; a directory that could not be removed is swept by the next start once its lease is released. If that sweep fails, the journals are kept and the new session is refused rather than admitted. A reused or waited-on result starts nothing.

Start/stop execution-path pins use <session>_<artifact>.start and .stop keys in the result’s existing executionPaths provenance, including remote records. tools check statically checks the referenced session’s executables and operands without running it. tools check --execute on a bound tool and session send each own fresh lazy sessions: they start on the first bound call and clean up afterward. A follow-up that calls no bound tools starts nothing.

Internal callers register lazy sessions with registry.sessions(state, selected_tool_or_none, cancellation), attach them with registry.with_sessions(&sessions), then release and await sessions.close().await after their calls. Dropping the guard also starts cleanup. Session commands remain trusted, not sandboxed: commands can modify the workspace or escape cleanup by deliberately detaching descendants.

Tool diagnostics

artifactize tools check                         # every Agent/Human eval's scope
artifactize tools check app/review              # same as --eval app/review
artifactize tools check --artifact app --audience agent
artifactize tools check --execute --artifact app --audience agent --tool read --args '{"path":"README.md"}'
artifactize tools check --execute --artifact app --audience human --tool inspect
artifactize tools check --execute --eval app/review --tool coverage_app --args '{}'

tools check discovers and validates declarations, resolves executable availability, scoped argv operands and declared execution paths, and prints JSON scopes, schemas and per-tool readiness checks. It is static by default: no owner process, fingerprint script, database or output directory is created. A positional selector or --eval selects one Agent/Human eval and cannot be combined with --artifact or --audience. Explicit execution requires --tool and either an eval selector or both --artifact and --audience. With --artifact, the short operation name or its published name is accepted, and the scope is that Artifact and its children. With an eval, name the tool as that eval publishes it (<tool>_<artifact>); it runs in the eval’s own scope, so a tool whose operands name another Artifact runs as it would in that eval’s review. --args is valid only for Agent execution; Human schemas admit only an empty object and Human commands take no free arguments. A check never creates a Run, verdict or cache entry. Agent calls run in the author’s environment with private temporary output (removed after execution); Human calls use the reviewer’s real environment. Exit codes are 0 for ready/success, 1 for declaration/preflight/tool/cleanup failure, and 2 for invalid invocation. --repo, --state-dir and --json are accepted; reports are JSON even without --json.

Human tools

Human reviews shows a Human tool declaration. A Human tool is a command or a built-in tool (read, list, section, help, open or graph). open and graph are launch tools; the others print output. Built-ins use the same placeholders and scope checks for their target and run inside artifactize; the rest of this page is about commands.

Arguments are fixed literal argv, with scope placeholders only: {artifactPath} is the declaring Artifact’s canonical target path (a folder or file); {name} names an Artifact. Folder references accept /path suffixes; file references, including {artifactPath} for a file owner, reject them. Both accept --flag= forms, resolved by the same logical-path and no-symlink checks as runtime argv. Other brace forms are rejected; there are no arbitrary string templates or escaped-brace interpolation. Unknown names and invalid operand syntax are rejected when loading the workspace. Tool operands never add dependencies or expand the review’s admitted scope; an existing but out-of-scope Artifact, missing input or symlink fails before execution. Input metadata is checked at each call. File-target regularity/existence and no-symlink checks happen at discovery and on every tool call too, even with fingerprint = false; a failed check runs no command.

Command executables follow Program lookup, including the file-Artifact exception. This does not expand built-in or operand scope. No shell is added; cwd is the declaring Artifact’s working directory (the containing folder for a file Artifact). Human commands inherit the reviewer’s complete real environment, including HOME, DISPLAY/WAYLAND_DISPLAY, XDG settings and config. Unlike runtime evals and Agent tools, they get no private TMP/output directories and keep every variable, ARTIFACTIZE_* included. Only declare trusted commands: they have the reviewer’s ordinary permissions and environment, including any credentials already present there.

  • launch starts a new session/process group with stdin disconnected and returns {"content":[{"type":"launch","launched":true}],"isError":false} immediately after spawn succeeds. There is no readiness wait or content capture in the result. In a review (review, monitor, desktop) its stdout and stderr are pipes that artifactize drains line by line into the reviewer’s Tool log until the program closes them and exits; the end line gives its exit code. They are drained, and what nobody shows discarded, even after the review closes, so the program never blocks on a full pipe; on Unix it ignores SIGPIPE, so a write after artifactize exited fails instead of ending it. request tool and tool check keep stdout and stderr disconnected. Spawn failure is an error; a later exit (even nonzero) does not undo the handoff. The program intentionally survives artifactize exit or later cancellation; the reviewer owns its lifetime. timeout_ms does not limit the handed-off program. A launch is neither a verdict nor proof of observation.
  • output waits for completion, with a default 120000 ms timeout. In a review its lines reach the Tool log as they are printed. Stdout and stderr are also each captured up to 128 KiB and cleaned like runtime output. Each becomes a text block capped at 64 KiB with an explicit truncation marker; nonempty stderr has a stderr: label. Nonzero/signal exit sets isError: true, includes the exit status, and retains the bounded text. Timeout, cancellation and dropped calls use normal process-group cleanup, not intentional handoff.

A tool’s stdout and stderr appear live in the reviewer’s Tool log, so a tool may print what the person did in it (a choice made, a value measured, a file saved) for the reviewer to copy into the result. The log shows each run as ▶ HH:MM:SS · operation · Artifact, then its lines (stderr in red), then ■ exit CODE (or launched, done, cancelled, timed out, error: …). A built-in’s text appears when it finishes and ends ■ done; open hands the file to the desktop’s opener and ends ■ launched; graph logs the viewer’s output until it exits. One run shows at most 1 MiB of output, then says the rest is not shown, and a line longer than 4096 bytes shows in pieces. The log lives in memory while the review is open; nothing is saved.

Internal callers use tools::human::Registry::new(&config, "artifact/eval") for a Human eval scope or for_artifact(&config, "artifact") for its child scope, then list(), command(name) (the resolved program, argv and directory, without running it) and call(name, cancellation).await, or call_logged(name, cancellation, run).await to log the run to a Tool log (review::log: a channel() gives a Sink whose start opens a Run, and the Events a screen files into a ToolLog). Names are <operation>_<artifactId>, with collision rejection. Agent tools are never listed, and Agent evals cannot construct a Human eval registry. Listing executes nothing. Human results use a separate text/launch type; Agent results cannot include launch blocks. These low-level registry operations do not authorize a claimant; use the human lifecycle API, request tool or review below for recorded requests.

Human reviews

The Human reviews guide covers the Human review flow and the review terminal UI.

The internal library exposes asynchronous operations with an open store::Receipts:

  • human::claim(receipts, request_id, reviewer) acquires one reviewer lock. Repeating the same reviewer is idempotent; another reviewer is refused. human::default_reviewer() reads $USER (USERNAME is the Windows fallback). There are no reservations, renewals, expiry timers, preparation phases, readiness hooks or alarms.
  • human::unclaim(receipts, request_id, reviewer) releases that lock without a verdict, so another reviewer can claim the request. Only the claimant can release, and only while the request still waits.
  • human::run_human_tool(receipts, request_id, reviewer, tool, cancellation, run) logs to run (Run::unwatched() for none) and authorizes the claimant, reopens the recorded Artifact/eval scope and declarations, and, only when the request has a reuse key, checks the fingerprint before invoking a registered Human tool; requests without a reuse key skip the fingerprint-value check, not the file-target validation on each tool call. The tool takes no free arguments and uses the reviewer’s real environment. Changed scope or declarations (including a tool command) refuse reconnection rather than running an updated declaration. Ordinary tool errors are correctable actions, not verdicts. Human tool runs are not recorded.
  • human::tool_commands(receipts, request_id) reloads the recorded scope and returns a map from command-tool names to resolved command lines (repository, program, argv and directory) or per-tool errors, without claiming or running anything. Built-ins have no command line and are omitted.
  • human::submit(receipts, request_id, reviewer, result, cancellation) accepts GREEN/RED with fields matching pass_schema/fail_schema, using the Agent result validator without repair. Invalid or oversized results (over 256000 JSON bytes) leave the request waiting for correction; schema errors list up to five failing instance paths. A valid submission recomputes the fingerprint only when the request has a reuse key; requests without one skip the fingerprint-value recheck, not discovery or reconnection. Missing/invalid file targets or changed recorded scope/declarations cause reconnection to fail before submission and leave the request waiting. After successful reconnection, a changed fingerprint value settles ERROR/INPUT_CHANGED instead of the verdict. Settlement rechecks the claimant and waiting state transactionally, so a second submission fails. It releases the reviewer lock, completes saved followers, and publishes only GREEN/RED results with a fingerprint to the cache.

The artifactize review terminal UI calls human::claim, run_human_tool, unclaim and submit in-process, and publishes a submission to the remote review store exactly like request submit.

request list includes all saved requests (waiting, claimed and settled), with optional --run filtering. Text includes request/Run/eval IDs, status and reviewer; --json and request show include the full saved audit, current claim (also for shared-execution followers), source execution and summary. The definition field joins the saved eval and its owning Artifact, including tags, from the Run; older Runs without saved definitions return null. These queries need no repository and run no owner code. run show and JSON verify include a Run summary: status counts, executed and reused requests by reviewer kind, wall time, actual executor starts, attempts and usage reporting completeness. Run totals exclude reused source executions; the Run-level usage.saved sums their original counters. Reused requests keep source attribution and the raw per-provider attempts in reusedUsage; their own summaries report no attempts (usageState: "none"). Unreported usage is never represented as a known zero token count. There are no separate summary commands or --full mode.

Working as a team

Machines and CI can reuse each other’s verdicts through one shared review store, artifactize server (design). Each machine keeps its own state. The store keeps every record of each reuse key and returns the latest one, so a review done later (a forced re-review, say) replaces an earlier verdict for everyone. The team walkthrough runs two machines and CI end to end.

Server. Run the store on a small host, with its own state directory:

artifactize --state-dir /srv/artifactize server token add alice-laptop --scopes read,publish,human
artifactize --state-dir /srv/artifactize server token add bob-laptop --scopes read,publish
artifactize --state-dir /srv/artifactize server token add ci --scopes read
artifactize --state-dir /srv/artifactize server run     # http://127.0.0.1:8417/

token add prints each token once; keep it private. server run listens on loopback (127.0.0.1): only programs on that same computer can connect directly. To let teammates connect without sending tokens over an unencrypted network, use the secure connection setup below. Back up the store with the SQLite command-line tool. This complete example reads the database from the state directory above and writes a separate backup file:

sqlite3 -readonly /srv/artifactize/review-store.sqlite ".backup '/srv/backups/artifactize-reviews.sqlite'"

Replace both paths for your host. The source must be the server’s actual review-store.sqlite; choose an existing, writable backup directory outside a project checkout (/srv/backups in this example). -readonly prevents a mistyped source path from silently creating a new empty source database.

Upgrade the server and its clients together. The 0.9 reuse-key v2 matches no older record, so every executable eval, Human sign-offs included, needs one new review. See Review store server for the full reference.

Connect teammates securely

A TLS reverse proxy is a server in front of the store that accepts encrypted HTTPS connections and forwards them locally. A tunnel carries a connection through another encrypted connection instead. Here is a complete SSH tunnel setup for a teammate who already has an SSH account on the store’s host:

  1. Leave artifactize server run running on that host, as above.

  2. On the teammate’s computer, open a terminal and replace you@reviews-host with their SSH account and the host’s name:

    ssh -N -L 127.0.0.1:18417:127.0.0.1:8417 you@reviews-host
    

    Keep this terminal open. -N starts no remote command; -L forwards local port 18417 to the store’s port 8417 through SSH. The local end is also restricted to this computer, and SSH encrypts the connection between the computers.

  3. In another terminal on the teammate’s computer, sign in through that tunnel:

    artifactize remote login http://127.0.0.1:18417/   # enter this machine's token
    artifactize remote status
    

Plain HTTP is allowed here because the address is local; network traffic travels inside encrypted SSH. Do not expose the store’s plain-HTTP port directly to the network. Without a tunnel, clients require an HTTPS address served by a TLS reverse proxy. Follow SSH’s normal host verification and authentication; do not bypass it.

Sign in and share results

Each machine signs in once. The token is read from stdin, never from command arguments. Interactive input is hidden when the terminal supports it. On Windows, if hiding input fails, the CLI warns before reading the token visibly; on Linux and macOS, that failure aborts login. Piped input is read without changing terminal modes:

artifactize remote login https://reviews.example/     # paste the token
artifactize remote status

From then on:

  • verify reuses results from the store and publishes its own GREEN/RED results;
  • status predicts remote reuse;
  • request submit publishes Human sign-offs;
  • remote push sends results produced while the store was unreachable.

See Remote review store client for the full reference.

CI. Configure CI through the environment only; see CI.

Share levels. summary, the default, sends:

  • the verdict;
  • the schema-validated owner fields (for runtime evals, only the exit code, duration and truncation flag);
  • the reuse key, the Eval definition hash and the fingerprint of each Artifact the key covers, including each covered Artifact’s folder/file/graph kind (artifactKinds in the record);
  • the profile, its execution options (backend, model, reasoning, limits, variant), usage counters and an Agent result’s workspace-relative executionPaths pins;
  • the producer (user@host), the Human reviewer and timestamps;
  • for an Agent result, the reference to its saved conversation (producer.session: the producer, its state id, and the Run, request and session ids).

summary omits captured stdout/stderr, the separate invocation audit, local repository provenance and the Agent conversation itself. It still sends the complete declared runtime profile, including its command and arguments: any paths or sensitive literals authored there are shared, not redacted. The executionPaths pins also name workspace-relative paths. full (remote login --share full or ARTIFACTIZE_REMOTE_SHARE=full) additionally sends the saved execution as is, including captured output. Owner fields are free text at both levels, so keep secrets out of shared declarations and the fields that pass_schema and fail_schema allow.

Trust.

  • The server stamps the authenticated publisher, the token name. The producer and the Human reviewer are what the publishing machine claims.
  • Any publish token can assert any verdict for any key. Give untrusted CI (fork pull requests) read only, and human only to people who sign off. If a token leaks or is misused, server token revoke NAME --purge also deletes the entries it published.
  • verify --force reads nothing from the store, which bypasses a suspect record, and publishes its fresh result, which becomes the store’s latest. cache rm drops a local key’s records, and server rm removes a key’s records from the store.
  • Only $STATE/remote.json and the environment configure the store, never .artf declarations, so a cloned repository cannot send your token elsewhere. Tokens are stored 0600, bound to the store they were issued for, and never printed.

Walkthrough: two machines and CI share verdicts

This walkthrough reuses examples/runtime-relations. Alice and Bob each have a checkout and their own state directory. CI has a third checkout. One artifactize server holds the shared verdicts. You can run every step on one computer. Each “machine” is just a --state-dir, and USER sets the producer name.

export WALK=$(mktemp -d)            # outside any Git checkout
cp -r examples/runtime-relations "$WALK/alice"
cp -r examples/runtime-relations "$WALK/bob"
printf 'WALK directory: %s\n' "$WALK"

mktemp -d creates a new temporary folder outside the checkout; WALK holds its path, printed by the last line. Keep that value.

Use a separate terminal for the server, Alice, Bob and the local CI demonstration. In each new terminal, run the following, replacing COPIED_WALK_PATH with the absolute path just printed:

export WALK="COPIED_WALK_PATH"

Exporting in one terminal does not set variables in other terminals. Do not run mktemp again: all these local examples must use the same folder. The output below illustrates the Alice/Bob roles; your actual Run IDs and user/host labels will differ.

1. The server

On the host that runs the store, create one token per machine. token add prints each token once; the store keeps only its SHA-256.

artifactize --state-dir "$WALK/server" server token add alice-laptop --scopes read,publish,human
artifactize --state-dir "$WALK/server" server token add bob-laptop --scopes read,publish
artifactize --state-dir "$WALK/server" server token add ci --scopes read
artifactize --state-dir "$WALK/server" server run      # Listening on http://127.0.0.1:8417/

Leave server run in its own terminal. This walkthrough connects only programs on the same computer. For separate computers, first follow the secure connection setup.

2. Alice reviews first

Open a second terminal for Alice; leave the server running in the first.

export WALK='/path/from/setup'
cd "$WALK/alice"
export ARTIFACTIZE_STATE_HOME="$WALK/state-alice"
artifactize remote login http://127.0.0.1:8417/   # paste alice-laptop's token; input is hidden when supported
artifactize verify --all --wait
  guide/terms [run-FBh4xF-1]: GREEN
  intro/heading [run-FBh4xF-2]: GREEN
  usage/heading [run-FBh4xF-3]: GREEN
Summary: executed 3 (runtime 3, agent 0, human 0), reused 0 (runtime 0, agent 0, human 0)

Each GREEN result passed its fingerprint recheck, so verify published its summary record: verdict, owner fields, profile, usage counters, producer and timestamps. The summary omitted captured stdout/stderr, the separate invocation audit and local repository provenance. The declared runtime command and arguments were still shared as part of the profile; paths and sensitive literals in those arguments are not redacted.

3. Bob reuses Alice’s verdicts

Open a third terminal for Bob and set the same copied directory path.

export WALK='/path/from/setup'
cd "$WALK/bob"
export ARTIFACTIZE_STATE_HOME="$WALK/state-bob"
artifactize remote login http://127.0.0.1:8417/   # bob-laptop's token
artifactize status | tail -1
artifactize verify --all --wait
Verify actions: will execute 0, will reuse 3, wait 0, blocked 0
  guide/terms [run-MxxJJJ-1]: GREEN (reused from remote: alice@laptop, run-FBh4xF)
  intro/heading [run-MxxJJJ-2]: GREEN (reused from remote: alice@laptop, run-FBh4xF)
  usage/heading [run-MxxJJJ-3]: GREEN (reused from remote: alice@laptop, run-FBh4xF)
Summary: executed 0 (runtime 0, agent 0, human 0), reused 3 (runtime 3, agent 0, human 0)

Bob’s checkout has the same Artifact names, kinds and content, so the fingerprints, Eval definition hashes and therefore the reuse keys match. Paths do not matter. status asked the store without changing anything. verify mirrored the three records into Bob’s local cache: artifactize cache list shows them with the store as their origin. A later verify reuses them locally, even without the store.

4. Bob works offline, then pushes

Continue in Bob’s terminal, still in $WALK/bob.

printf 'Nothing unchanged is reviewed twice.\n' >> guide/usage/page.md
ARTIFACTIZE_REMOTE=off artifactize verify --all --wait
artifactize remote push --dry-run
artifactize remote push
  guide/terms [run-PzLGma-1]: GREEN
  intro/heading [run-PzLGma-2]: GREEN (reused from remote: alice@laptop, run-FBh4xF)
  usage/heading [run-PzLGma-3]: GREEN
Summary: executed 2 (runtime 2, agent 0, human 0), reused 1 (runtime 1, agent 0, human 0)
Would push 2, already in the store 0, skipped 0.
Pushed 2, already in the store 0, skipped 0.

Only usage and guide, whose guide/terms key covers its child usage, were reviewed again. With the store unreachable instead of off, verify would warn once and review locally in the same way. remote push sends each key’s latest local result that the store does not have yet. It never sends mirrors, and it skips Human sign-offs unless the token has human.

5. CI reuses both

CI is configured through the environment only. It uses the read-only ci token from step 1, here supplied as the CI_TOKEN secret variable. Configure that value through your CI’s secret settings, never in a committed file. --reuse-only agent,human ensures Agent reviews and Human sign-offs come only from the store or cache. For the local demonstration, first copy WALK into this terminal too:

export WALK='/path/from/setup'
cp -r "$WALK/bob" "$WALK/ci" && cd "$WALK/ci"
ARTIFACTIZE_STATE_HOME="$WALK/state-ci" \
ARTIFACTIZE_REMOTE=http://127.0.0.1:8417/ \
ARTIFACTIZE_REMOTE_TOKEN="$CI_TOKEN" \
artifactize verify --all --wait --reuse-only agent,human
  guide/terms [run-GpqdSQ-1]: GREEN (reused from remote: bob@laptop, run-PzLGma)
  intro/heading [run-GpqdSQ-2]: GREEN (reused from remote: alice@laptop, run-FBh4xF)
  usage/heading [run-GpqdSQ-3]: GREEN (reused from remote: bob@laptop, run-PzLGma)
Summary: executed 0 (runtime 0, agent 0, human 0), reused 3 (runtime 3, agent 0, human 0)

CI executed nothing. A read token never publishes, so whatever CI does execute stays in CI. Stop the server and run the same command with a fresh state directory. The store is unreachable, so CI reviews locally and stays green:

Remote store is unreachable: Connection refused (os error 111); continuing without the remote review store.
Summary: executed 3 (runtime 3, agent 0, human 0), reused 0 (runtime 0, agent 0, human 0)

A rejected token, by contrast, fails the job with exit 2.

Human sign-offs

A Human verdict reuses by default through artifactsum; fingerprint = false disables it. Here is a one-file project with a Human sign-off, with a copy for Bob. Return to Alice’s terminal, where WALK still names the shared directory:

mkdir "$WALK/brand" && cd "$WALK/brand"
echo 'logo v1' > logo.txt
cat > index.artf <<'TOML'
name = "brand"

[evals.signoff]
title = "Sign off"
profile = { kind = "human" }
payload.instruction = "Approve the logo."
TOML
cp -r "$WALK/brand" "$WALK/brand-bob"

Alice’s verify --wait submits a Run, which records the request, and follows it in the foreground. In a second terminal, in the same directory and with the same ARTIFACTIZE_STATE_HOME, she claims it and signs off. Her token has the human scope, so request submit publishes the sign-off, and her verify --wait finishes GREEN:

In the first terminal, keep the watcher running:

export ARTIFACTIZE_STATE_HOME="$WALK/state-alice"
artifactize verify --all --wait

In a new terminal, set WALK to the copied path as above, enter the same project and choose Alice’s same state. Replace REQUEST_ID with the waiting request’s identifier printed by request list:

cd "$WALK/brand"
export ARTIFACTIZE_STATE_HOME="$WALK/state-alice"
artifactize request list
artifactize request claim REQUEST_ID
artifactize request submit REQUEST_ID --verdict GREEN

In $WALK/brand-bob, with Bob’s state, verify --all --wait now reuses the sign-off (so would CI with --reuse-only agent,human), and the output names both the reviewer and the authenticated publisher:

  brand/signoff [run-rWExIl-1]: GREEN (reused from remote: Human sign-off by alice, published by alice-laptop, run-FD9ny6)
Summary: executed 0 (runtime 0, agent 0, human 0), reused 1 (runtime 0, agent 0, human 1)

Had Bob run verify --all --wait first, his verify --wait would have followed the waiting Run, and his request would settle as soon as Alice’s sign-off reached the store. Bob’s own token lacks human, so a sign-off he submits stays local, with a warning.

To clean up, stop the server and rm -rf "$WALK".

CI

Continuous integration (CI) automatically runs checks when a team pushes code or opens a pull request. A CI job is a sequence of commands on a runner machine; it should reuse a developer’s Agent/Human approvals, not make those decisions.

Run artifactize verify --all --wait --reuse-only agent,human as a CI step: tests run, while reviews must come from the cache or the team store, so CI never calls a model or waits for a person. It exits 0 only when every selected eval is GREEN, so RED (1), ERROR (2) and INCOMPLETE (4) fail the job; the command reference lists every exit code. An Agent review or Human sign-off with nothing to reuse is not executed and leaves the Run INCOMPLETE, so CI passes only once a developer’s verify has produced it for the current key. Artifactsum enables reuse by default; fingerprint = false on a target or dependency disables it.

Keep deferred work out of the merge gate

Developers may use --defer-human to finish machine work while a person is away, optionally with --speculate. SIGNOFFS_PENDING (exit 5), DEFERRED sign-offs and PROVISIONAL machine results are not approval. Keep CI’s --reuse-only agent,human gate: missing sign-offs are not executed, leave INCOMPLETE, and cannot be counted as passing even if --defer-human is supplied. Once a person submits GREEN, CI reuses unchanged machine evidence and the sign-off. RED blocks dependents. Do not treat plain verify acceptance as CI success.

Reuse verdicts from the team store

The YAML below is one GitHub Actions step, not a complete workflow. Put it in an existing .github/workflows/NAME.yml file under jobs.YOUR_JOB.steps, after checking out the reviewed commit and making artifactize available on the runner (see Installation). GitHub’s workflow guide explains creating the surrounding workflow and job if this is your first one.

Ask the store administrator for a token with read scope only. In the GitHub repository, open Settings → Secrets and variables → Actions, add a repository secret named ARTIFACTIZE_READ_TOKEN, and enter that token as its value. Replace the example URL below with your team’s reachable store URL. The ${{ secrets... }} expression supplies the secret at runtime; never paste the token into this file. Configure other CI services through their equivalent secret/environment settings.

- run: artifactize verify --all --wait --reuse-only agent,human
  env:
    ARTIFACTIZE_REMOTE: https://reviews.example/
    ARTIFACTIZE_REMOTE_TOKEN: ${{ secrets.ARTIFACTIZE_READ_TOKEN }}
  • A read token reuses but never publishes.
  • An outage is not an error: verify warns once and goes on with the local cache. A review that only the store holds is then not reused, so the Run is INCOMPLETE.
  • A rejected token, a TLS failure or an invalid configuration fails the job (exit 2), so a misconfiguration is never skipped silently.
  • ARTIFACTIZE_REMOTE=off disables the store for one command.

The team walkthrough runs this end to end.

Test Agent evals without a model

A CI job can run Agent evals against a fake provider that it starts on loopback, with a throwaway state directory and the store off. See Test against a fake provider.

Review store server and client

The exact behavior of artifactize server and of the client side of the shared review store. The Team review store guide shows how to set one up.

Review store server

artifactize server token add alice-laptop --scopes read,publish
artifactize server token add ci --scopes read
artifactize server run [--listen 127.0.0.1:8417]
artifactize server token list
artifactize server token revoke alice-laptop [--purge]
artifactize server rm KEY

artifactize server keeps a shared remote review store in its own review-store.sqlite under --state-dir, separate from state.sqlite. It keeps every record of each reuse key and answers a lookup with the latest one by completion time, GREEN or RED, whoever published it and in whatever order records arrived. server run serves plain HTTP on loopback by default (it warns when bound elsewhere) and stops on Ctrl-C/SIGTERM; put a TLS proxy or tunnel in front, because clients require HTTPS except on loopback. Token commands work on the same file while the server runs, and take effect immediately.

Token names are bounded ASCII display labels, not secrets; the CLI identity rules give the syntax and pre-store validation.

token add prints a random bearer token once; the store keeps only its SHA-256. Scopes are read (look up), publish (publish runtime and Agent records) and human (additionally publish Human sign-offs, together with publish). Give untrusted CI read only. token list shows names, scopes and creation/revocation times, never tokens. token revoke NAME rejects the token at once; --purge also deletes every record it published, and may be repeated later. Revocation always prints a JSON report, even without --json. Names of revoked tokens are never reused. rm KEY deletes every record of a key and prints {"removed":true} (false if absent).

The API takes Authorization: Bearer TOKEN and answers JSON ({"error":...} on failure; 401 for a missing, unknown or revoked token, 403 for a missing scope):

RouteScopeResult
GET /v1/whoamiany{"principal":NAME,"scopes":[...]}
POST /v1/lookup with {"keys":[KEY,...]} (at most 1000)read{"entries":[record,...]}: the latest record of each found key
PUT /v1/entries/{key} with a recordpublish (+ human for Human records)201 {"created":true}, or 200 {"created":false} when the store already holds this execution of the key

The server checks a record’s envelope (schema 2, the path’s key, a GREEN/RED verdict, a profile kind, an execution ID and an RFC 3339 completedAt) and size: 256 KiB for a summary, 16 MiB for a full record carrying execution. It stamps publisher (the token name) and publishedAt (server clock), replacing any client values. Clients check that a record’s key matches its evalDefHash and fingerprints and additive artifactKinds before they use it. The kind map covers exactly the fingerprint names for new keys. Changing or dropping it cannot satisfy a kind-aware key. Lookups update the last use of the record they return; inserts evict least-recently-used records above 100,000 records or 4 GiB.

Upgrading. A schema 1 or 2 review-store.sqlite (artifactize 0.3 or 0.4) is upgraded in place to schema 3 when the server opens it. Tokens are kept. Stored records are dropped: they were keyed by fingerprint and Eval definition hash as 0.4 computed them, which no 0.5 key matches, so clients review again once and publish anew. A 0.5 server answers 0.4 and 0.3 clients (their lookup keys and their PUT /v1/entries/{evalDefHash}/{fingerprint} route) with 410 and a message to upgrade; the 0.3 staleKey alias is gone. Upgrade the server and its clients together.

0.9 uses reuse-key v2, so it reuses no pre-0.9 record, including script-fingerprint results and Human sign-offs. Existing store records cannot satisfy the new keys; clients review once and publish new records. This is separate from local state upgrades: schemas 1–5 need replacement, while existing schemas 6 and 7 upgrade automatically to schema 8 with Runs, cache records and Human approvals preserved.

Remote review store client

printf '%s\n' "$TOKEN" | artifactize remote login https://reviews.example/ [--share full]
artifactize remote status [--json]
artifactize remote push [--dry-run] [--json]
artifactize remote logout

A client is configured only through the state directory and the environment, never .artf declarations, so a cloned repository cannot redirect a token. remote login URL reads one token line from stdin (never argv). It attempts to hide terminal input; on Windows, if that fails, it warns that the token will be visible and continues reading. Non-terminal stdin is read without changing terminal echo. It verifies the token with GET /v1/whoami, then stores it in $STATE/auth/remote-token.json (0700 directory, 0600 single-link file, never followed through a symlink) and writes $STATE/remote.json: {"url":"https://reviews.example/","share":"summary"}. URLs must use HTTPS with the system trust store; plain http:// is accepted only for localhost, 127.0.0.0/8 and [::1]. Credentials, queries and fragments are rejected. A stored token is bound to the origin it was issued for.

ARTIFACTIZE_REMOTE (a URL, or off), ARTIFACTIZE_REMOTE_TOKEN (for CI) and ARTIFACTIZE_REMOTE_SHARE (summary or full) override remote.json and the stored token. remote status prints the URL, share level, token source (env, file or none), reachability, principal and scopes; it exits 0 when the store accepts the token. Requests use a 2 s connect and 5 s total timeout and never follow redirects. remote logout deletes the stored token and remote.json; the server keeps the token valid until server token revoke. Tokens are never printed or logged.

With a store configured, verify reads and writes through it. Before the Run claims anything, it looks up every key once in POST /v1/lookup batches of at most 1000 keys, including keys that already have local records. More than 1000 keys require multiple requests. It reuses whichever record completed last: the key’s latest local record or the store’s latest. A store record that completed after the local latest (or with no local record at all) is appended to the local history as a self-contained remote-<executionId> execution with an origin, and reused like a local record, so run show, cache, GC, the monitor and the Run summary count it as reuse. A key that still has no result is looked up again just before a local claim and during the artifactize-owned worker’s Human-wait reconciliation, at most once per second. A remote result for a key that waits for a Human settles the waiting requests; their never-reviewed waiting execution becomes ERROR (SUPERSEDED). Text output names the source:

  app/check [run-Hq2b9X-1]: GREEN (reused from remote: alice@laptop, run-x05qFq)
  brand/signoff [run-Hq2b9X-2]: GREEN (reused from remote: Human sign-off by alice, published by alice-signoff, run-Ksl1Qr)

The producer (user@host) and the Human reviewer are what the publishing machine recorded; the publisher is the server-authenticated token name. An Agent result’s producer also carries session, the reference to its saved conversation on the producing machine; the conversation itself is never published. Older clients ignore the field, and the server stores records as they are, so the protocol and record schema (2) are unchanged. A reused remote result’s request shows the reference, and session show on it names the machine and state where the conversation lives. Once the local settle records a GREEN/RED with a reuse key (after the fingerprint recheck), verify sends its summary record, or the full record with share full, outside any database transaction. A --speculate Run holds that automatic publication until its scheduling finishes, even while Human reviews are still pending; it then publishes only the results that survived, after blocked work has been withdrawn from reuse. Records carry the key, the Eval definition hash, the fingerprint of each Artifact the key covers, its artifactKinds map (folder/file/graph), and the execution options (backend, model, reasoning, limits and profile variant) and an Agent result’s executionPaths pins. request submit publishes Human sign-offs. A token without read looks nothing up and one without publish publishes nothing, so a read-only CI token only reuses. Human sign-offs also need human; with any other token they stay local with a warning. Nothing is published for evals without a reuse key. --force never reads from the store, but a forced result is a new record and is published like any other, so it becomes the store’s latest too (unless a newer one exists). status makes the same comparison with a read-only lookup, without mirroring, so its reuse prediction includes newer remote results; status --force makes no remote call.

remote push sends, for each key, the latest GREEN/RED record this machine produced: results produced while the store was unreachable, before remote login or with ARTIFACTIZE_REMOTE=off. It never sends mirrors. A token with read first looks up the store’s latest record of each key, and a record that already is the latest is not sent again. The store keeps each execution once, so a record it already holds answers created: false. Both count as existing, and pushing twice is harmless. Human sign-offs without the human scope, and records over their size limit, are skipped with a reason on stderr. --dry-run sends nothing and reports what would be pushed. The output is Pushed N, already in the store M, skipped K., or JSON {"dryRun":false,"pushed":N,"existing":M,"skipped":K}. Unlike verify, push fails on any remote failure, and it needs the publish scope.

Connection errors, request timeouts and 5xx answers fail open: one warning, then the process continues without the remote, reusing the local latest record of each key and reviewing locally what has none. 401/403, TLS failures, other error answers, invalid configuration, and a successful answer whose body cannot be read or decoded (a body timeout included) fail closed. A 401/403 gets artifactize’s own message that the store rejected the token; another 4xx answer carries the store’s own error message when it sends one. Verification looks up remote evidence after durably accepting the Run: a failed lookup leaves an ERROR Run, not a missing Run. Submit-only verify exits 0 on acceptance; verify --wait observes that ERROR and exits 2. Invalid configuration can still reject submission before acceptance. Synchronous commands such as status report a fail-closed lookup with exit 2 directly. A record that does not validate is ignored with a warning, and its eval is reviewed locally.

Others

Supporting features keep reviews usable on a real machine:

  • State, cache and limits explains where Runs and evidence live, cache inspection and removal, concurrent backend capacity, saved Agent conversations and retention. artifactize doctor checks local readiness without calling a provider or starting a Run; models lists available model IDs and prune removes eligible finished output while preserving audit records.
  • Operating systems covers portable command declarations, executable lookup, paths, opening applications and state on Linux, macOS and Windows.
  • Supporting reference links to the detailed schemas, library APIs, protocols and limits behind the feature guides.

For command syntax and exit codes, use the CLI area.

State, cache and limits

Where artifactize keeps its state, how to inspect and limit the reuse cache, how to cap Agent backends machine-wide, how saved Agent conversations are kept and bounded, and the local diagnostics and maintenance commands.

State

One state.sqlite holds Runs from every repository (bundled SQLite, WAL, schema 8), with the canonical repository path recorded on each Run. The state home is $ARTIFACTIZE_STATE_HOME, else $XDG_STATE_HOME/artifactize, else (on Windows) %LOCALAPPDATA%\artifactize, else $HOME/.local/state/artifactize.

The Microsoft Store package uses Windows’ default AppData virtualization, with no Flexible Virtualization exclusions. New state under AppData is package-private: the Store version keeps its own Runs, sign-in and cache instead of sharing them with winget, zip or Cargo installs. Windows removes that private state on uninstall. The path above is the app’s logical view, not necessarily its physical disk location. The Windows packaging smoke test measured both state.sqlite and review-store.sqlite under %LOCALAPPDATA%\Packages\lhj6102.Artifactize_ayfzfzbsv48sg\LocalCache\Local\artifactize, with no real %LOCALAPPDATA%\artifactize directory created on its fresh Windows Server runner (build 26100).

That same test launched a PowerShell child through a Runtime eval. The child’s new %LOCALAPPDATA% file was not redirected: the unpackaged shell found it in real AppData, not in the package-private location. This measures that runner and command, not every child program or Windows version. User-configured commands run with the user’s rights; do not assume their AppData writes are package-private or removed when the Store app is uninstalled.

This is not a sandbox or a guarantee that existing unpackaged state is inaccessible. Microsoft documents that on Windows 10 1903 and later, reads can fall back to existing unvirtualized AppData files, and modifications to those existing files can affect the real files. Do not rely on side-by-side Store and unpackaged installs to isolate existing data. State explicitly placed outside AppData is not covered by AppData virtualization or the Store’s private-state uninstall cleanup.

--state-dir PATH moves the whole state, including private output directories under PATH/runs. Saved Runs stay readable after the original repository is removed. State/output inside the reviewed repository or another artifactize workspace is rejected, including through symlink ancestors. For this safety check, any ancestor containing a .artf declaration, legacy artifactize.json or legacy .artifactizeignore marks a workspace, even though legacy declarations are not read. Sidecar-only workspaces are protected too. Database files and their WAL sidecars must be regular files.

Known limitation: a search-only ancestor can be traversed but not listed. When listing fails with permission denied, the guard falls back to fixed-marker probes for .git, index.artf, legacy artifactize.json and legacy .artifactizeignore. It cannot discover a sidecar-only workspace in that ancestor. Keep state and credentials outside such workspaces explicitly; do not rely on sidecar detection where directory listing is unavailable.

The state keeps what was reviewed, with which verdict, and how it was executed, in four tables:

TableOne row perColumns besides the saved JSON in data
runsRunid, repo, status, nullable request_key
requestseval of a Runid, run_id, eval_id, ordinal (its place in the Run’s selection order), execution_id, status, and claimed_by and claimed_at, the Human claim of a waiting request
executionsexecutionid, key, eval_def_hash, status, owner_pid and owner_start_time (the owning process), backend (an Agent review’s, see Backend capacity), profile_identity (the effective profile of a keyed claim), and the key history columns completed_at, bytes and last_used
state_metanamed value of the statename, value

A Run has one request per eval and per ordinal. One execution per (reuse key, effective evaluation profile) can be active (RUNNING or WAITING_HUMAN) at a time; a forced (--force) execution bypasses this claim and never holds its key’s active slot. state_meta holds the state’s stable id, a random UUID made when the database is created (row id), which Agent session references name. How a review went, its conversation and its tool calls, is not in the state: it is in the review’s saved session.

Supported upgrades are ordered additive steps applied together with user_version in one opening write transaction; the version is read again under that lock. Schema 6 upgrades to 7 by adding nullable runs.request_key and a partial unique index on (repo, request_key) where the key is not NULL. Existing Runs keep NULL keys. Schema 7 upgrades to 8 by adding executions.profile_identity, filling it from saved profiles and replacing the active-key uniqueness index with (key, profile_identity). Run/request/execution IDs, cache, state identity and Human approvals survive; repeated opens do not remigrate. Existing-state readers and doctor use the same preflight without creating an absent database. Migration requires write access; a failed transaction rolls back rather than bypassing version checks.

Same repository/key returns the same accepted Run; NULL keys never coalesce submissions. Worker PID/start identity and admission, cancellation intent, activity and failure code live in optional/defaulted Run JSON control fields. Old Runs without them are readable and are not treated as dead workers. Saved Run-end validation remains historical; detailed results are derived from saved Runs, requests and executions, not stored as another result snapshot. Dead-worker recovery keeps committed evidence, cleans only identity-checked owned children and releases dead slots. It records OWNER_DIED instead of replaying work; unconfirmed cleanup retains its journal records.

Downgrade boundary: Binaries that only support schema 6 or 7 cannot open a schema-8 state after upgrade; their version guards refuse it. Unknown/newer versions and schemas 1–5 remain unsupported; commands exit 2 and doctor reports a hard error (exit 1). The older-schema message is:

This state was written by an earlier artifactize. Start a new state (set ARTIFACTIZE_STATE_HOME or move the old one away). Only schema 6 and later supported versions can be upgraded.

A new 0.9 state reviews every executable eval once, Human sign-offs included, and then reuses as before. Reuse-key v2 matches no earlier records, including script fingerprints and team-store records. See Upgrading to 0.9.

Tokens are never stored inside a repository. $STATE/auth, which holds the Codex sign-in and the review store token, is refused when it lies inside a git work tree or an artifactize workspace (an ancestor with any current or legacy marker above) or inside --repo, even in a gitignored folder. The .git check protects Git work trees separately from Artifact declaration markers. login codex and remote login then fail before signing in, with a message naming both folders:

Codex sign-in storage /work/project/.state/auth is inside the git work tree /work/project; artifactize keeps tokens outside repositories. Use a state directory outside it, or set ARTIFACTIZE_CODEX_AUTH_FILE.

Use a state directory outside the repository, or ARTIFACTIZE_CODEX_AUTH_FILE and ARTIFACTIZE_REMOTE_TOKEN, which need no storage. doctor reports such a state as a warning while nothing is stored there, and as a hard error once a sign-in is (for example, after git init above an existing state). No writer transaction spans a subprocess or async suspension. Completed results with a reuse key are shared within this database. Concurrent Runs, including other worktrees and processes, automatically join an in-flight execution only when both its reuse key and its complete effective evaluation profile match. The queued request names the execution it waits for; it takes no execution start or backend slot. Completion delivers that execution’s GREEN, RED or ERROR to its waiters as joined reuse. An ERROR is not a new cache entry for later Runs. For a Runtime or Agent execution, cancellation or a dead owner (checked by PID and process start time) releases the claim: one waiter starts a replacement, and the others join it. A Human sign-off is not replaced: when its owning Run is cancelled or its worker stops, the sign-off and every request waiting on it, in any Run, end as ERROR, and a new verify requests another one. Cancelling a waiter does not cancel the owner. --force always runs its own execution and never joins. There are no sharing settings, and in-flight sharing never crosses a remote store. Completed GREEN/RED reuse remains keyed by the reuse key alone, even if the producing profile differs.

Unreadable records

An individual saved record can be unreadable even when the database and its other records are valid. Artifactize skips that record as review evidence rather than inventing a verdict. Invalid requests are excluded from Run-list request counts; a Run with damaged requests can still open, while an unreadable Run record is marked unreadable rather than treated as a normal saved Run.

Status and Run listings expose unreadable diagnostics with the record’s kind, id and reason; evidence readers also print an Unreadable ... diagnostic on stderr. Valid records remain accessible. Preserve the state for diagnosis and start a new verification when fresh evidence is needed; missing counts do not mean the damaged reviews passed. This is different from a database-open or unsupported-schema failure, which can prevent the command from reading state at all. doctor checks that database/schema readiness; it is not a record-repair tool.

Cache inspection

artifactize cache list [--history] [--json]
artifactize cache show KEY [--history]
artifactize cache rm KEY

These commands use the shared state home or --state-dir PATH, without loading a repository. Each reuse key keeps every completed GREEN/RED record, and the latest by completion time is the one verify reuses. list shows the latest record of each key: key, eval, verdict, completion time, producer, repository (or the remote store it came from), how many records the key holds, retained JSON bytes and last use (a text table, or a JSON array that also carries the Eval definition hash, the target’s fingerprint and the profile variant). --history lists every record, latest first within each key. show prints the key’s latest saved execution as JSON, with result, actual profile, options (backend, model, reasoning, limits and variant), fingerprints (each Artifact the key covers), additive artifactKinds (the covered names mapped to folder/file/graph), provenance, usage and producer (user@host and artifactize version; submitted Human results also record their reviewer); a missing key prints null and exits 4. show --history prints all of the key’s records as a JSON array, latest first. Entries mirrored from a shared remote review store carry an origin (store, publisher, publication time), which list shows in place of the repository. Reads neither create missing state nor update access times. A key’s records are its completed GREEN/RED executions whose history columns (completed_at, bytes, last_used) are set; the latest is the newest completion, then the most recently stored. rm takes every record of the key out of its history and prints {"removed":true} (false if absent), preserving saved Runs and the executions themselves. rm refuses a key with an active execution or waiter. Keys come from cache list or from a request’s key in verify --wait --json and run show.

How reuse keys are built and what a hit returns is in Completed result reuse; entry and byte limits are in Cache limits.

Cache limits

Saving a new reusable result automatically removes the least recently reused eligible cache records to stay within 10,000 records and 1 GiB of saved result data. One record may occupy at most 16 MiB. A larger result still reaches its own Run and any current waiters, but it is not kept for later reuse.

Reusing a record updates when it was last used; inspecting it does not. Cleanup removes the least recently used records first, breaking ties by key and completion time. It keeps a key’s records while a review of that key is running, and any records another Run still needs, so these limits can temporarily be exceeded. A cleanup failure is printed on stderr without changing an already-completed verdict; the next saved result retries cleanup.

Cache cleanup and cache rm stop records from being reused. They do not remove the saved execution audit, Run receipts or Run output files. These limits therefore do not cap the whole database or temporary files of active Runs. Matching evidence has no age-based expiry, and cache cleanup does not automatically delete Run files. Use artifactize prune to remove eligible finished Run output.

Backend capacity

READY evals have no default Run-concurrency cap. An explicit --jobs N limits only that Run; add it when unrestricted concurrency causes trouble. Multiple Runs on one machine (worktrees, CI jobs, a team member’s integration run) can still send more Agent reviews to one backend at once than its rate limit or subscription allows. $STATE/limits.json caps the Agent reviews in flight per backend across every verify that uses that state directory:

{"backends": {"codex": 4, "openai": 16}}

Keys are backend names as profiles declare them (openai, anthropic, codex), and each limit is 1–100000. A backend without an entry is unlimited, and with no file nothing is limited. The file is read when a verify starts. A file that is not a regular file, is not valid JSON, has other fields, names an unknown or removed backend or a limit out of range fails verify before it creates a Run (exit 2); doctor reports the limits, or the problem as a hard error.

The bookkeeping is the executions table of state.sqlite: an Agent review’s execution names its backend, and it holds one of that backend’s slots while its status is RUNNING. A slot is taken just before a review would start: one write transaction (BEGIN IMMEDIATE) counts the RUNNING executions of the backend and, under the limit, inserts this one. A request that would reuse a result or join a live execution of its key takes none. The slot frees when the review’s execution completes. An execution whose owner process has exited (including an unreaped zombie, checked by process state, pid and start time) ends as ERROR (OWNER_DIED) the next time a process asks for a slot, which frees its slot with it; there is no separate cleanup. When a review’s failure stops its backend for the Run (BACKEND_STOPPED), the stop is recorded before its slot frees, so a request waiting for that slot is not started.

With every slot of its backend in use, a request waits, as QUEUED, with a reason such as Waiting for a free codex slot: all 4 are in use on this machine (limits.json).. As slots free up, retries follow the Run’s scheduling priorities: READY work first, then the longest remaining dependency path, with selection order only breaking ties. A later-selected request can therefore receive the next slot first. While waiting, a request does not count against an explicit --jobs cap and consumes no --max-executions start, so runtime evals, Human requests and other backends keep running. When admitted, it counts against --jobs if given; without that cap, backend capacity imposes no other concurrency limit. Explicitly declared eval mutex tags independently delay conflicting runtime and Agent evals across the same shared state store, without holding a backend slot while they wait. Explicit run cancel RUN_ID ends its waiting requests with status ERROR and errorCode = "CANCELLED", and its held slots are released. Interrupting verify --wait only stops observation; it does not cancel the Run.

Tool-session state lives under $STATE/tool-sessions/, separately from saved Agent conversations. Artifactize removes it after confirmed cleanup and retains unconfirmed state for recovery. See the tool-session lifecycle for the single detailed start, stop and crash-recovery contract.

Agent sessions

Each Agent review’s conversation is saved under the state, for session show and session send. limits.json sets whether conversations are saved and how large their store grows:

{"agentSessions": {"enabled": true, "maxBytes": 1073741824, "targetBytes": 805306368}}
FieldDefaultMeaning
enabledtruefalse saves no new conversation; saved ones stay readable
maxBytes1 GiBThe store’s size that starts a collection; at least 1
targetBytes768 MiB when maxBytes is greater than 768 MiB; otherwise (maxBytes / 4) * 3, dividing first with integer divisionThe size a collection brings the store down to; an explicit value overrides the default and must be lower than maxBytes

A collection runs at the end of every verify Run and in prune. When the conversation files take more than maxBytes, it deletes the sessions written longest ago, one at a time, until they take targetBytes or less, so each collection frees a fixed amount rather than one session at a time. It never deletes the session of a request that is still RUNNING, nor one that a session send holds; such sessions can keep the store above its target until a later collection. A collection that fails during verify is reported on stderr and leaves the Run as it is. The other limits.json rules apply: an unknown field, a value of the wrong type, maxBytes 0 or a targetBytes that is not lower than maxBytes fails verify and prune (exit 2), and doctor reports it as a hard error.

The store is $STATE/agent-sessions/, owner-only (0700; a protected owner-only DACL on Windows), and it is refused when it is not. Each session is one append-only JSON Lines file, owner-only (0600), named by its sessionId:

$STATE/agent-sessions/
  e83bf374-5136-484c-8d2f-2ce38ee4ca57.jsonl   the conversation, one event per line
  e83bf374-5136-484c-8d2f-2ce38ee4ca57.lock    held while a session send appends to it

Events identify their kind and normally record their time in at; older records can omit optional timestamps or header metadata. New records use:

kindHolds
reviewFirst line: version (1), sessionId, runId, requestId, evalId, target, producer, state, backend, model, reasoning, parameters (as sent, with prompt_cache_key), budgets (timeoutMs, maxToolCalls, maxTokens) and tools (the definitions offered)
messageOne message as the provider received or sent it (message, a rig message: role system, user or assistant, with text, tool calls, tool results and reasoning, encrypted content and signatures included), and the turn (provider request) that carried it; a message of tool results has isError, whether each result failed, in order; the repair prompt has "repair": true, and a follow-up’s framed question has the person’s own words in question
deliveryDisplay-only streaming data: turn, attempt, nonempty block identifier, contentKind (text or summary), text, and state (delta, complete or interrupted)
attemptOne provider attempt of a turn: its attempt number, the usage counters the provider reported (inputTokens, outputTokens, totalTokens, cacheReadTokens, cacheWriteTokens, reasoningTokens, where reported), and its error and errorCode when it failed
endThe review’s result, or its errorCode and error
sendA follow-up starts: its send number, the person’s text, the framing sent before it, filesChanged and the tools offered
answerThe follow-up’s answer text, or its errorCode and error

Delivery delta records carry incremental text, complete marks a completed block, and interrupted marks delivery that ended before completion (including an interrupted attempt). They drive the live display only: they never add to conversation replay history or usage totals, which come from semantic messages and attempt records.

The events of a follow-up also carry its send number. When a budget, the deadline or a repeated call ID stops a turn’s tool calls, the results of the calls that ran are saved and the others have none. The request’s verdict, result and usage stay in state.sqlite; the session is the only record of the conversation and its tool calls, and session show --summary counts them. A last line cut short by a crash is ignored when the file is read.

Follow-up replay

session send uses the saved review’s backend, model, reasoning and session id. OpenAI and Codex retain the same prompt_cache_key; Codex also retains the session-id header. Saved history and the system prompt are replayed unchanged so the cached prefix matches. Current project tools are offered again; changed covered fingerprints add a warning to the header and the model’s context.

A question is a new user turn framed as a follow-up, not a new review. The frame asks for plain text, permits the tools and states that the recorded verdict will not change. Modern review system prompts state that same distinction; older saved conversations rely on the new turn’s framing. session show prints the person’s original question, while its JSON export retains the framing too.

Questions, answers and tool calls append to the same saved thread. Sends to one session serialize: each continues after the preceding answer. Each question has fresh profile budgets. A RUNNING request cannot be followed up; recorded verdict, result, usage and reuse evidence remain immutable.

Provider transport

The rig-core 0.43.0 provider client uses streaming OpenAI Responses with store:false, encrypted reasoning replay and parallel tool calls disabled, or Anthropic Messages. Anthropic requires a per-turn output cap of 16384 tokens, separate from the review budget. One review deadline covers all turns, retries and tools. Incomplete responses are errors even if a partial answer contains a verdict. Nested HTTP retries and redirects are disabled; artifactize handles turn retries as described in retries and error codes.

Transcript reader

Monitor Detail displays readable message text, public reasoning summaries and collapsed tool activity, not raw message envelopes or private reasoning payloads. Streaming updates replace their provisional text with final content; interrupted partial output is labeled. Public OpenAI/Codex summaries are read from streaming frames without changing the bytes passed to rig, the provider client library. Display-only events never enter replayed model history.

The reader follows new messages until you scroll away. Resize preserves the reading position while paused and stays at the bottom while following. Tool activity expands to names, targets and recorded states, not full tool arguments or successful output. session show --json exports the complete saved events. Missing local sessions, never-saved conversations and remote references are reported separately; a remote reference cannot open arbitrary local files.

History is indexed incrementally into private temporary text and row files, with only the visible pages loaded. There is no fixed first-2-MiB history cutoff. One complete event needs memory proportional to its size while being decoded; other history and layout work yields between bounded chunks. Complete malformed events report errors, while partial lines and incomplete UTF-8 wait for the next append. Replacement, truncation or deletion resets the reader; closing or resetting it removes its temporary files. Later session send events remain readable after the original review ends.

Local readers

Monitor and Review use read-only state queries and cached Git discovery, rather than rediscovering declarations or fingerprints on every refresh. Local readers share a temporary hub inside the first subscriber process: an owner-only Unix socket or Windows named pipe, not a permanent daemon. Registration is acknowledged before the initial snapshot. Readers reconnect and resync after hub exit or a bounded hint-queue overflow; writers never elect a hub.

If IPC is unsafe or unavailable, five-second authoritative reconciliation replaces it without an insecure transport fallback. A persistent read-only SQLite data_version connection and file-identity probe detect missed commits and database replacement. Clocks and spinners redraw cached data. Session hints wake only the selected transcript reader; a five-second file probe also catches missed appends or removal. Rendering does no file I/O, and input takes priority over history work. This is local coordination, not remote team-store push delivery.

The Run worker separately reconciles dead execution owners and backend slots and optional remote results, without a Human wait deadline. Explicit Human actions use the shared atomic claim, fingerprint recheck, schema validation and submit_and_publish APIs. Followers resolve to their original request. Publication failure after local settlement preserves and shows that settlement.

Local diagnostics and maintenance

artifactize doctor [--repo PATH] [--state-dir PATH] [--json]
artifactize models openai|anthropic|codex [--json]
artifactize prune [--older-than 7d] [--dry-run] [--state-dir PATH] [--json]

prune operates on the selected state’s Runs across repositories, not the current repository. Eligible Run statuses are exactly GREEN, RED, ERROR and INCOMPLETE, with a completion timestamp, only terminal requests, and no live execution owner (PID plus process start time). SIGNOFFS_PENDING Runs are always skipped, even after their Human requests settle: later submissions do not rewrite that historical Run status. Other unfinished Runs are also skipped. --older-than compares Run completion time; use a whole-number s, m, h, d or w duration. With no age filter, every eligible Run is considered. --dry-run returns wouldRemove without deleting; normal JSON returns removed, and both include skippedRuns. prune also collects the Agent session store under its size bounds, whatever --older-than says: removedSessions lists the deleted sessions, and with --dry-run wouldRemoveSessions those over the size bounds. A dry run does not check for a running session send, which an actual prune leaves alone, so the two can differ.

What doctor checks, the models output and exactly which directories prune removes are in the reference.

Doctor, models and prune

doctor makes no provider calls and creates no Run, verdict, cache entry or auth file. It reports the resolved state directory and tests writability with a temporary directory, removed immediately (in the nearest existing ancestor when state does not yet exist). Before the writable probe it enforces the same current/legacy workspace-marker safety boundary, even without --repo; unsafe state fails without writing a probe inside that workspace. It reads the state database’s schema, applying a supported additive upgrade if needed: a database written by an earlier artifactize (schemas 1–5) or by a newer one is a hard error (exit 1), with the message other commands refuse it with and its schema and the supported one in the details. --repo additionally runs the same static validation as config check. The limits check reads limits.json and reports the backend capacity (an invalid file is a hard error). The sessions check reports the Agent session store: how many sessions it holds and their bytes, maxBytes, targetBytes, whether saving is enabled, the store’s directory and the state’s stateId. API keys are reported only as present/absent, never validated or printed. Missing keys are warnings: optional backends need not all be configured. A backend with a test endpoint is a warning that reports it as testEndpoint; an invalid test endpoint is a hard error. The codex check reads the sign-in without a lock, refresh or network call: no sign-in is a warning, a stored sign-in passes even when its access token has expired (the next use refreshes it), and an expired ARTIFACTIZE_CODEX_AUTH_FILE token is a warning. A $STATE/auth inside a repository is a warning while no sign-in is stored there, with refused in the details holding the message, and a hard error once one is. Its details name the source (stored, file or none), expiresAt and expired, and testEndpoint/testAuthEndpoint when they are set. The remote review store check is also offline: it reports the resolved URL, share level and token source; a missing token is a warning, and an invalid configuration (including plain HTTP to a non-loopback host) or an unsafe token file is a hard error. Invalid config, unsafe/unwritable state or invalid auth storage are hard errors. Exit is 0 without hard errors, 1 with hard errors, and 2 for invocation errors.

models openai and models anthropic call the provider’s models endpoint with the corresponding API key through rig; Anthropic pagination is followed. models codex lists the Codex models of the signed-in account. A test endpoint replaces the provider’s API root here too. There is no account or backend fallback. Text lists tab-separated slug/name pairs; JSON is consistently {"backend":"openai","models":[{"slug":"model-id","display_name":"model-id"}]}. Listings preserve provider order.

Only known scratch directories below state/runs/<run-id> are removed: runtime output (and tmp, home and cache left by older versions) and leftover tool output. Run roots, unknown files/directories, database rows, results and reuse records remain. Symlinks (including nested links), non-directory targets and repository content are refused before deletion. .git and the current/legacy Artifact markers above protect workspace copies, including Git-less projects copied inside Run output. Both --dry-run and real prune enforce this boundary. Database reads have a five-second busy timeout and finish before deletion; prune holds no writer lock. This is plain prune, without quarantine, crash-recovery machinery or hostile filesystem-race protection. Saved run show and request show remain readable after pruning.

Operating systems

artifactize runs on Linux (x86_64 and aarch64), macOS (Apple silicon and Intel) and Windows (x64). WSL 2 runs the Linux build. Install has the channels for each. A project, its declarations and its reviews are the same on every OS; this page lists what differs.

Project commands run as declared

Runtime evals, fingerprint scripts, command Agent tools and command Human tools run their command with args as one argument vector on every OS. There is no shell and no per-OS variant of a declaration. A project declares commands that exist where it is reviewed: an eval that runs python3 or grep needs them on every reviewer’s PATH. For reading, searching and opening files, the built-in tools work everywhere without an external program.

Runtime evals, fingerprint scripts and command Agent tools also run in the author’s own environment: artifactize passes on its whole environment (the home and cache variables of each OS, and on Windows the system variables too), so a toolchain behind a version manager resolves as in a terminal. Only ARTIFACTIZE_* variables are withheld, and each run gets a private TMPDIR (TMP and TEMP as well) of its own. See the runtime environment. Command Human tools inherit the reviewer’s complete environment, ARTIFACTIZE_* variables included, and get no private temporary or output directories (Human tools).

On Linux, the desktop app started from its AppImage (directly or through install.sh’s launcher) runs with the bundle’s directories first in PATH, LD_LIBRARY_PATH and similar lists, and with variables of the bundle’s own, so that it loads its bundled libraries. Every program the app starts gets the environment the app was started with: those entries and variables are left out, and system programs load their own libraries.

Program lookup

A declared program is found the same way by tools check, runtime evals, fingerprint scripts and command tools:

  • A bare name, such as python3, is searched on PATH. Relative and empty PATH entries resolve against the command’s working directory. On Unix, an unset or empty PATH therefore searches that directory. On Windows, a name without an extension tries each extension in PATHEXT, in order, so npx finds npx.cmd. Without PATHEXT, the order is .COM;.EXE;.BAT;.CMD.
  • A relative name containing /, such as ./tools/check, is resolved inside a folder Artifact’s admitted scope. For a file Artifact, a relative executable resolves beneath its containing folder and may name an unrelated sibling (for example ./inspect). .. and symlinks are refused in both cases. This executable lookup does not make sibling files visible to built-in tools or scoped argv references.
  • An absolute path runs as given.

Built-in help is different: its literal executable uses ordinary cwd/PATH lookup, including absolute paths, parent-relative paths and executable symlinks. Its subcommand arguments are forwarded unchanged, not resolved as scoped file operands. It runs a trusted program, not an operation confined to Artifact files.

Windows batch files (.cmd, .bat) run directly, without a shell string: the Rust standard library quotes each argument for cmd.exe and refuses an argument it cannot pass safely. The error reads Process arguments could not be passed safely, and nothing runs.

Tool sessions use process groups on Unix and kill-on-close Jobs on Windows. Artifactize owns their cleanup and recovery; the cross-platform tool-session contract explains the guarantees and the limits of trusted commands.

Paths

Logical paths use / on every OS, in declarations, tool arguments, results and records. Artifactize matches them case-sensitively on every OS: Artifact references, tool paths, .artfignore and artifactsum files patterns match the exact spelling.

How the spelling on disk is checked differs by OS:

OSSpelling on disk
macOS, WindowsArtifactize enforces it. On a case-insensitive volume (the default on both), a path must use the exact spelling of the entry on disk, and a file or folder whose name differs from a child Artifact only by case is refused rather than read.
LinuxArtifactize relies on the filesystem. A case-sensitive filesystem (the usual one) only opens the exact spelling; on a case-insensitive one, such as a casefolded folder or a Windows drive mounted in WSL, a path that differs only by case may open the same entry.

Opening files and the browser

The built-in open tool and the login codex sign-in open their target with the desktop’s default application:

OSOpener
Linuxxdg-open
WSLxdg-open, inside the Linux distribution
macOSopen
Windowsthe Windows shell (ShellExecute)

WSL behaves as Linux: there is no fallback to wslview or Windows programs. If xdg-open is missing or opens nothing, install your distribution’s xdg-utils and set a default application. login codex also prints its sign-in URL for any browser.

Desktop console launches

On Windows, launching artifactize without arguments from Start or Explorer shows help plus a terminal hint and waits for Enter if this process is the console’s only attached process and both stdin and stdout are consoles. It never pauses with arguments, from an existing terminal, or with redirected input/output. Linux and macOS retain the ordinary no-argument help and immediate exit.

The Microsoft Store package has two visible entries, Artifactize for the desktop app and Artifactize CLI for this help launch, and one command alias, artifactize; its tools are available as artifactize tools. Non-Store channels also ship the standalone artifactize-tools command.

State

The default state directory differs by OS: %LOCALAPPDATA%\artifactize on Windows, ~/.local/state/artifactize on Linux and macOS. The Store version keeps its own package-private state, which Windows removes on uninstall, and WSL keeps its own Linux state. See State for the full precedence and the Windows overlay caveat.

Supporting reference

The detailed reference for artifactize: every command and flag, exact limits, schemas and protocols, internal library APIs, backend wire details and the state layout. The Features area explains shared behavior, and Install covers setup.

Desktop

The desktop app is a window onto the same machine-wide state as the CLI and TUI. Browse Runs, inspect evidence and handle Human sign-offs without a second review engine or a separate evidence store. Every release ships it beside the commands; see Desktop app to install it.

For first-launch command setup, PATH details, updates and cleanup, see the installation guide.

Open the app

artifactize-desktop
artifactize-desktop monitor --repo PATH
artifactize-desktop review [REQUEST_ID] [--repo PATH | --all] [--reviewer NAME]
artifactize-desktop graph --repo PATH

The default is Monitor on ALL repositories in this state, regardless of the current directory. --repo chooses an initial repository; it conflicts with --all, the default. --state-dir PATH chooses a state directory; otherwise the app uses the shared state home. --reviewer NAME sets the cooperative claim name (normally the user name). review REQUEST_ID opens a saved request; graph requires --repo and reads current declarations. Invalid arguments exit 2.

Shortcut labels below use Ctrl; macOS uses its primary modifier, Command, instead. Ctrl+1 opens Monitor, Ctrl+2 Review and Ctrl+, Settings. While Human reviews wait, the Review button shows how many beside its name: the requests Review’s starting search kind:human status:waiting_human lists (of --repo when given), updated as reviews are requested and settled. Ctrl+K, F1 or the palette button opens a searchable command palette; Enter runs a command and Esc closes it. ? shows this screen’s keys. Ctrl+R or F5 refreshes Monitor, or reloads current Graph declarations when Graph is open; it does not reload Review or Settings. Drag the dividers to resize panes. The app follows the system’s light or dark appearance.

Monitor

Navigate Scope → Runs → Run graph with Enter or → to move deeper and Esc to return; ← also returns from Runs. Arrow keys or j/k select Scope and Runs rows, and r refreshes Monitor from Scope, Runs or the Run graph (a Human review’s r opens its RED form instead). Scope remains navigable back to ALL even after an initial --repo selection. Worktrees without Runs can be folded into a group: Space or a click reveals or hides them. Runs appear newest first. Move past the list’s first or last row to load newer or older pages, or use its newer/older arrow buttons.

Scope lists ALL, then each Git repository, whose row covers every worktree that shares its Git directory, with its worktrees below it; a folder outside Git is a workspace row of its own. Selecting a row shows the Runs of that scope.

Attention counters read !N ✗N ◐N ?N: requests in ERROR and RED, Runs still running, and sign-offs waiting for a person, deferred ones included even after their Run ended. The window header always shows the totals of the whole catalog, every repository (the ALL row), whatever scope, Run or page is selected; each Scope row shows its own totals, and each Run row its own request counts. Followers of one Human execution count as one waiting sign-off.

Selecting a Run updates its graph while Runs keeps the keyboard; Enter moves the keyboard into the graph. The graph area stays in place and says when no Run is selected, it is loading, it failed or it is empty. A problem, such as Runs on the page that cannot be read, shows as one line in the Run pane’s header that a click unfolds. New Runs, claims and submissions made elsewhere appear without a manual reload. After a failed read, what was last read stays on screen and can be stale, so refresh the Monitor (Ctrl+R) or the Review list once its cause is fixed. Run-end validation stays historical: a later Human submission can change the current request display without rewriting the old verdict. Damaged records are marked unreadable, not treated as successful evidence.

Hovering only highlights; information opens on a click.

Detail

A click on an Artifact opens its Detail, floating over the graph’s right side with the graph still visible behind it. It hides no Artifact: a whole graph refits beside it whenever it opens, closes or changes width, and after you zoom or pan the camera keeps your zoom and only brings the opened Artifact beside it. Clicking another Artifact switches the Detail to it; clicking the graph’s background, Close or Esc closes it. On a Human review’s Detail all three step back the same way: they cancel active work first, then leave the form, and only then close the Detail. Click the Run’s headline, or use the palette command Open the Run’s Detail, for the Run’s own Detail: saved validation and unmet obligations, request counts, waiting work, timing, budgets, executed/reused work and usage. Drag the Detail’s left edge to resize it; it keeps that width, also in the next launch.

An Artifact’s Detail has chips that read as a path, [icon] {name} / [eval] …: the Artifact’s own chip, then one per eval with its verdict’s glyph and colour.

The Artifact’s chip shows it between its neighbours, drawn with the Run graph’s nodes and edges: the Artifacts its evals use on the left, the Artifacts whose evals use it on the right. The drawing holds still; a click on a neighbour opens that neighbour’s Detail. Below it are its evals with their verdicts (click one to show its chip) and its path; every other fact, what it waits for included, folds under Debug.

An eval’s chip shows its Summary: its status, with what running it took and spent (time, tokens in k or M, model) or what reuse saved; its instruction; its result, with findings as numbered items headed by their item and where; then Execution options, Execution metadata and Fingerprint when it has them. Other facts fold under Debug, which t shows or hides on an Artifact’s or a non-Human eval’s Summary. An eval with a log switches below the chips between its Summary, shown first, and its log, a plain-text choice whose chosen view is bold and higher-contrast: an Agent eval’s Agent log (its conversation, with its own scrollbar) or a runtime eval’s Runtime log (saved stdout/stderr, not a live log stream). Missing or remote conversations explain why they are unavailable on this machine. A Human eval’s chip holds its review and sign-off, with the controls described below. An eval outside the Run’s selection, such as one a partial verify left out, has no request in that Run: its chip shows its sections read-only, with no Summary of a result, no Human controls and no Tool log. A Run’s Detail keeps its sections, where t toggles Technical and w or the pending-evals control reveals the unfinished checks behind an upstream wait.

Agent logs update live. In a focused Agent log, ↑/↓ or j/k moves by row, PgUp/PgDn by page, Home/End or Top/Bottom goes to either end, and the mouse wheel scrolls. Moving up pauses following; return to Bottom to follow new messages. Space or Enter toggles the visible grouped content; clicking a group header also expands or folds it. Only public reasoning summaries are shown, not hidden reasoning.

Run graph

Monitor always displays the selected Run as an Artifact graph; there is no Run-tree mode. Node and relation colors show review states and update as results arrive. Inspect dependencies through the graph’s input-to-consumer relations and Waits for under an Artifact’s or eval’s Debug. The graph takes the pointer: Tab reaches its Artifacts and Enter on one opens its Detail, as a click does; it has no other keys of its own.

Drag the graph background to pan; wheel, pinch or a background double-click zooms the camera. Zoom In, Zoom Out and Fit View also change its viewport. These gestures move the camera, not the pane dividers or the Detail. Above 40 nodes, a pannable, zoomable minimap helps navigate the graph. Runs with more than 300 Artifacts ask before layout: choose Lay out all to display the whole graph. Above 80 Artifacts, only elements in view are rendered: panning or zooming draws those it brings into view. Smaller graphs render every element. Layout is not an incrementally loaded neighborhood, and there is no tree fallback.

artifactize-desktop graph --repo PATH opens the current Artifact Graph without needing a Run or showing saved verdicts. A click on an Artifact shows its evals over the graph’s right side; Esc, Close or a click on the background closes them. Expand Structure and evaluation criteria for the full tree, criteria and labeled relations. Refresh or Ctrl+R/F5 reloads declarations. A Human graph tool opens this viewer; opening it approves nothing.

Review

Review starts with waiting Human requests across all repositories in this state. A selected request previews read-only. Arrow keys or j/k select request rows, and Enter or → opens the selected request; Esc or ← returns from a read-only Detail to the list. Other eval kinds and settled requests remain read-only. The context menu or palette can open a Human request with Tools focused; Ctrl+O opens a request by ID. PgDn/PgUp pages older and newer requests. Waiting followers open the original review, not another sign-off. One click selects a request; clicking an already-selected request opens it, as does a double-click. Clicking its preview also opens Detail. A read-only request with a log switches between its Summary and its Agent or Runtime log, as an eval’s chip does in Monitor, and t shows or hides its Debug facts.

/ edits search from the Review request list. Ctrl+F focuses search from an open request Detail too, including while a Human form is open or review work is running. Clicking the Requests pane or switching screens also leaves the Detail without closing it: an open form keeps its draft and running review work goes on. Free text and key:value terms must all match: repo, eval, request, reviewer, tag, kind, status and is. A plain word matches part of the repository, eval, request ID or claimant, ignoring case; repo:, eval:, request:, reviewer: and tag: match part of that one field the same way, so eval:sign finds release/signoff. kind: and status: take the eval kind and request status; is:mine, is:claimed and is:unclaimed filter Human claims. Quote a value with spaces (repo:"/work/my repo"); the terminal review uses the same search, term by term. Suggestions complete keys and values. ↑/↓ selects a suggestion (↓ can also open suggestions); Tab or Enter accepts it, and a click accepts it with the mouse. Enter without an open suggestion returns focus to the list. Esc closes suggestions, then clears the search, then returns to the list. An empty search includes all request kinds, not just waiting Human reviews. An explicit --repo supplies the initial repository search term. Use r in the Review list, or Refresh the list in the palette, to reload that list. The palette’s Show waiting Human reviews restores the launch search, including the initial repository restriction when the app was started with --repo.

Human sign-offs

Opening or previewing a review claims nothing. Press c to claim and u to release. Another person’s claim prevents your actions. Claims are cooperative reviewer locks, not authenticated accounts; only the claimant can run tools or submit. See Human for shared lifecycle and deferred sign-offs.

A Human review reads top to bottom as its instruction, the Tools to use, their Tool log, and the Result to write. The instruction is shown in full within the review body’s shared scroller; scroll that body to read long criteria. t toggles the Human request’s Technical record. Tab or a click moves to Tools; Ctrl+U releases your claim even while editing.

Tools are grouped under the Artifact that declares them, the reviewed one first. Each is a ▶ operation button with its description, and the reason when it cannot run. A click, or Enter or Space on the focused button, runs it immediately; Tab moves between them. Opened with Tools focused (from the Review list’s context menu or palette), the Tools take the keyboard. A launch tool opens its application and returns at once; a failing tool says so without a verdict. The saved definition must still match before an action runs.

The Tool log shows every run of this review as it happens: ▶ HH:MM:SS · operation · Artifact in local time, then each line the tool prints to stdout, or in red to stderr, then its end line, such as ■ exit 0, ■ launched, ■ done, ■ cancelled or ■ error: …. A launched application’s output keeps arriving under its own run until it exits, even while you write the result. The log wraps, scrolls on its own and follows the newest line; scroll up to read and it stays, scroll back to the bottom and it follows again. Its text is ordinary selectable text, so copy what a tool printed into the result. It lasts while the app runs and is cleared by a submission; nothing is saved.

After claiming, g opens GREEN and r RED, each a form built from the eval’s schema (the shapes it may use): a text box, a number box, true/false, a choice, a list of text with + Add and ✕ per item, or repeated groups of fields with + Add for a group and ✕ per group (one empty group always stays to type in). Constants are filled in and not shown; * marks a required field. An empty optional text or number field, a text list item included, says Optional in grey; an optional yes/no or choice shows – while unset. Tab moves between controls. Ctrl+G/Ctrl+R switches verdicts. Enter in a field does not submit; Ctrl+S or activating the Submit button submits, including Enter while that button has keyboard focus. Schema errors identify the failing path and leave the form correctable. Input-method composition, including Korean, belongs to the text field and never fires shortcuts. Esc cancels active work, then leaves the form, then leaves Detail. Leaving a form keeps its verdict’s draft; reopening it restores that draft, and GREEN and RED keep separate drafts. Monitor and Review share the same request’s review state, Tool log included, within this app session, rather than creating independent forms. This holds while the state stays the same: when the app finds its state database replaced, it closes open Details, forgets cached reviews, drafts and Tool logs, and cancels running Human tool and submit jobs.

A completed result stays open. Remote publication can fail after local settlement: the form closes and a saved-locally notice explains the failure. The verdict is already recorded; retry publication with Settings’ Push results (or artifactize remote push), not by submitting again.

Leave and quit

q quits outside a text field, except while the keyboard is in a Human Detail that is editing or busy: leave or cancel that work first. With the keyboard elsewhere, such as after closing Commands, q starts quitting even while that review stays open. Closing the window also starts quitting. If this window took Human claims it has not submitted or released itself, choose Stay (Esc), Release and quit (u) or Keep and quit (k). Keeping a claim leaves it held for this reviewer; it is not a verdict. The prompt lists the claims this window took, as this window last saw them: a claim released or settled from the CLI or another window can still be listed, and a claim taken before the window opened is not.

Settings

Settings reloads account/store information when shown. Reopen it or use the palette’s Refresh settings command to reload it; Ctrl+R/F5 is Monitor/Graph refresh, not a settings reload.

  • API keys: reports whether OpenAI and Anthropic key environment variables are set, never their values. Set them in the app’s launch environment.
  • Codex: shows not signed in, artifactize’s stored token and expiry, a read-only ARTIFACTIZE_CODEX_AUTH_FILE, or why storage is refused. An expired stored access token refreshes on next use; an expired external file needs a new Codex sign-in. External-file and refused-storage modes disable sign-in here; sign-out is available only for artifactize’s own stored account. Browser sign-in can be cancelled, and a redirect URL can be pasted when the browser cannot return. If another process holds port 1455, the app explains that a paste is required: copy the full http://localhost:1455/auth/callback?code=… URL from the browser’s address bar after a failed load, or DevTools → Network if it keeps loading, paste it in Settings, then choose Finish sign-in. Sign-out removes local tokens and warns when server revocation cannot be confirmed.
  • Models: lists model IDs offered by the chosen backend.
  • Review store: signs in with URL, token and sharing level, displays status, pushes results (or a dry run) and signs out. Summary omits invocation details, captured output and tool-call history; Full also sends the saved execution. Summary still shares review metadata, owner results, declared Runtime commands and arguments, and Agent execution-path metadata. Those declarations can contain paths or sensitive literals: Summary is not secret redaction. Choose according to the team’s privacy needs. Signing out removes local configuration, not the server token; a store administrator must revoke it. See sharing and push behavior.

Settings uses the same accounts and store operations as the CLI; credentials do not belong in .artf.

Plugin for Claude Code and Codex

A plugin is a package a coding assistant installs in one step. The artifactize plugin gives Claude Code and Codex two things together:

  • the Hey Arti skill, the instructions that let your assistant shape and verify your project with artifactize;
  • the artifactize MCP server, the tools your assistant uses to inspect the project and start and follow verification Runs.

This repository is the plugin’s marketplace: a catalog the host reads to find and install the plugin. The plugin installs no program, runs no hooks and holds no credentials.

Before you start

The plugin runs the artifactize program, so install artifactize first; the same page shows how to get the desktop app. If the program is missing, Hey Arti tells you so and offers your system’s default install from that page.

Install

Install the plugin with the steps for your assistant on the installation page.

Claude Code: run /reload-plugins or start a new session, then check /mcp for the artifactize server. Ask in your own words, or invoke the skill as /artifactize:hey-arti. The server runs as artifactize --repo <project> mcp, where <project> is the directory the session started in.

Codex: start a new Codex session in your project directory; the server runs as artifactize mcp in that directory. Ask in your own words, or mention the skill as $artifactize:hey-arti.

Updates

The plugin follows this repository’s main branch, which holds released versions. The installation page shows how to update it in each assistant, and the artifactize program itself.

Without the plugin

Other hosts, or a setup you manage yourself, can register the server by hand (MCP setup) and give the assistant SKILL.md as described in Hey Arti. Do not install both the plugin and a manual registration of the same server in one host.

Hey Arti: shape a project’s reviews

Arti is artifactize’s Forward Deployed Engineer. Given to your coding assistant, it learns your project and recommends how to shape it into Artifacts and evals, so that reviews stay meaningful and reusable. It works like a good waiter: it knows the menu and which combinations work, listens before recommending, confirms the order, and leaves the cooking to artifactize. It does not build or run your application, and it never approves anything in a person’s place. (The name comes from Artifactizer.)

Its instruction guide, called a skill, is SKILL.md, in the repository’s skills/ folder. There is one body, not separate instructions per host. It is short on purpose: these docs are its source of truth. It maps each concept to the page that owns it and has Arti read a page before explaining or writing what it is unsure of; when a page and the skill disagree, the page wins. Then come how Arti treats you as its client, its work processes by situation, and the best practices and examples it draws from.

Get the skill

In Claude Code and Codex, install the plugin: it brings this skill and the MCP server together, and the plugin’s skill is this same file. Arti first checks that artifactize is installed; if it is not, it points you to the installation page and offers your system’s default, which it runs only when you ask it to.

In other hosts, follow MCP setup to connect your coding host. Registration supplies tools; it does not install the skill. Open the linked SKILL.md, attach the unchanged file to your assistant’s conversation and ask, for example: “Follow this guide. Learn this repository and propose Artifacts and evals before editing.” Use that canonical guide rather than maintaining an edited copy.

What Arti does with you

Arti first works out which situation you are in, and each has its own process:

  • A question about artifactize or one of its concepts: it answers from the page that owns the concept, with an example from your project, and offers a next step.
  • Whether artifactize fits, and where to start: it asks what goes wrong today, names the few places it would help most (and where it would not), and suggests one small first Artifact.
  • Shaping a new project: the steps below.
  • Adopting it gradually in a large repository: it declares the area that hurts most first and leaves the rest undeclared rather than under one broad review.
  • Auditing an existing setup: it checks the current Artifacts and evals against the practices below, reports each finding with its fix, and changes only what you agree to.
  • The project grew or changed: it decides with you whether a new component is its own Artifact, splits one that grew, and compares status before and after.
  • “Why did this run again?” or “verify is slow”: it reads status and proposes a boundary fix (too wide, or a missing consumer reference), not an execution option.
  • A result to explain or fix, a reviewer that needs the running application (a tool session), team sharing and CI, and the Human boundary: covered in the sections below.

When shaping a new project, it:

  1. Learns the project. It reads code, docs, specifications, assets and build and test commands, then interviews you for what it cannot read: the goals, what changes together, which judgments need a person and what CI must guarantee.
  2. Proposes a composition. It suggests each Artifact and its evals (who judges, against which inputs, by which criteria) with reasons, and waits for your agreement before changing structure. It keeps your project instructions and existing Human responsibilities, explains before adding a new sign-off, and marks an input basis = true only when your team agrees nobody reviews it, never to get around unfinished reviews.
  3. Writes small declarations in today’s format, validates them with config_check, and shows with status what would run and what would be reused. It then tells you what changed.
  4. Hands verification over and follows the Run (below).

The practices it recommends

  • Cut Artifacts where work is edited and judged independently, each with a concrete obligation. Avoid one review over the whole repository.
  • An eval names each input it really uses as {name}; sharing a repository is not a dependency, and a command does not add the files it reads. A child folder counts as an input of its parent’s evals.
  • One obligation per eval. Use a runtime eval when a command can check it, an Agent eval when it needs judgment, and a Human eval only where a person must add judgment or authority. A person is asked only after the Artifact’s runtime and Agent evals are GREEN.
  • Criteria for one Artifact go inside its eval; rules many Artifacts share go in a rule document their evals read.
  • Keep result schemas small: a summary on GREEN, concrete findings on RED (syntax in Declarations).
  • Keep the default fingerprint (artifactsum); give reviewers only the read-only tools they need.

Combinations it recommends, with who owns each check and what it references:

  • Library and consumer: the library owns its tests; the consumer’s review references {library}.
  • Spec and app: the app owns its tests (runtime) and a review against {spec} (Agent).
  • App and E2E tests: running the suite is the app’s eval, with {e2e-tests} in its arguments; the test code’s own evals review its style and coverage.
  • Docs and assets: the docs are reviewed against the code they describe; each asset is its own file Artifact, with a person’s sign-off when it needs one.
  • Monorepo contract: UI and API each own their checks; an integration eval references both and the contract between them.
  • Generated files: either review the generator and its inputs, or review the output with the generator referenced.
  • Rule documents: the rules are an Artifact, often with a Human sign-off, read by the evals of the Artifacts they govern.

It steers away from implicit dependencies, whole-repository reviews, anti-cheat evals, unrequested execution options, and weakening an eval to reach GREEN.

To show you a pattern, it points at the example that matches your situation: the examples (runtime relations, Agent tools with a sign-off, posts as file Artifacts), the demo projects website/demo/shop (a page checked against the style tokens it uses) and website/demo/brand (a signed-off logo embedded by a page), and artifactize’s own repository (rule documents with a sign-off read by the evals of the code they govern, and an app whose eval runs its E2E suite). The skill ends with a small starter set of declarations to adapt.

Changing an eval’s instruction, result schema or runtime command changes its judgment; tags, titles, execution options and tool declarations do not. After an edit, status should show the edited input and its real consumers needing review and independent parts still reused. See Fingerprints and reuse.

Use MCP first

Arti uses config_graph to discover Artifacts, evals and dependencies (impact:true adds downstream consumers conservatively), config_check to validate declarations and status to predict which evals execute, reuse or wait. tools_check inspects tools and sessions statically. It reads every continuation page of an inspection result before parsing it. The MCP reference lists these tools and what MCP deliberately leaves out. If MCP is not registered, the CLI fallback is artifactize config check, artifactize config graph, artifactize status --all and artifactize tools check.

Tool sessions

When a reviewer must inspect something running, the skill shows a session declared with views.agent_sessions.<name>.start/stop and a command tool bound with session = "<name>", its program inside the Artifact so the fingerprint covers it. artifactize owns the session’s lifetime; see Tool sessions.

Follow a durable Run

Arti starts with verify_start, keeps the returned Run ID, and follows with verify_watch when the host shows MCP progress or verify_wait otherwise, reading findings with verify_result. An accepted Run is not a passing one. It diagnoses a failed runtime finding with request_logs and an Agent finding with session_show. artifactize owns the Run: closing the terminal, disconnecting or ending the agent’s turn does not stop it; only verify_cancel requests cancellation. See Run and streaming.

Arti may ask for speculation or deferred sign-offs per request, never to get around a required person: SIGNOFFS_PENDING is not full approval.

How Arti treats each result:

  • GREEN: done for this content.
  • RED: explains the finding in your terms and fixes the Artifact the eval judges, such as inaccurate docs or criteria, rather than bending correct code to match them or weakening a review, then verifies again.
  • BLOCKED: something the eval waits on failed, usually a RED or ERROR eval of an input it uses. Arti fixes that first, then verifies again.
  • ERROR: the check could not run. Arti reads request_logs or session_show and repairs the cause, such as a missing program, never the verdict.
  • SIGNOFFS_PENDING: everything else passed and a person’s sign-off waits. It is not full approval; Arti hands off (below).
  • INCOMPLETE: required evidence is missing, for example where CI only reuses existing reviews. Arti gets that evidence with a verify that runs the missing evals.

Hand off to the person, then yield

Arti names waiting requests with request_list/request_show and gives you the criteria and artifactize review REQUEST_ID (or your usual review interface). It never claims, approves or submits a Human review. It continues independent work, or yields with a short handoff naming the repository, Run, waiting requests and next action, then resumes by observing the same Run. Yielding is not cancellation, and no keep-alive turns or polling loops are needed.

Team and CI

Use the existing team review store and keep credentials out of declarations, skill copies, transcripts and MCP registration. CI uses a read-only token and artifactize verify --all --wait --reuse-only agent,human, so it runs runtime checks and reuses Agent and Human evidence; see the CI guide.

MCP stdio server

artifactize mcp serves the verification and non-interactive inspection tools in the same binary as the CLI. Register it in the coding host, starting from the project directory; see registration below. --repo PATH selects a different project and --state-dir PATH selects its state. The server resolves these once at startup. Run/request/session reads stay bound to that repository; cache records are reusable across repositories in the state.

It works with current MCP hosts and with older ones. The host talks to it only through the program’s input and output: stdout carries protocol messages, and diagnostics go to stderr. There is no network port or extra token. A bounded verify_watch call can also send standard MCP progress notifications. See Protocol versions for the details.

Register the MCP server

MCP (Model Context Protocol) connects your coding assistant to artifactize’s tools, so it can inspect the project and start or observe reviews. Stdio means the host launches artifactize mcp and exchanges messages through that program’s standard input and output, rather than connecting to a network port.

Complete the installation setup, then launch the coding host from your project directory. The stdio server uses the same binary and shared state as the CLI. It binds to one repository/state when started; it is not the HTTP review store (artifactize server), and requires no port, token argument or changed host timeout.

In Claude Code and Codex, the plugin registers this server together with the Hey Arti skill; the commands below are the manual registration. They configure the host explicitly; artifactize does not install or change host configuration for you. Follow the host’s normal trust/approval flow.

Claude Code

From the project directory:

claude mcp add --transport stdio --scope project artifactize -- artifactize mcp
claude mcp get artifactize

Project registration creates the host’s project .mcp.json. Keep it free of credentials and machine-specific paths if you share it. Start/restart Claude Code from this project, use its MCP view to confirm the server is connected, and check that the verification and inspection tools below are listed.

Codex CLI

Register the stdio command, then start Codex in the project directory:

codex mcp add artifactize -- artifactize mcp
codex mcp get artifactize

Codex records this in its own MCP configuration. A globally registered command still needs to start from the intended project directory. Where a host cannot launch in the project, register a project-specific command using artifactize --repo PATH mcp rather than pointing it at the skill directory. Normal host permissions/authentication still apply; no bypass flags are needed.

Registration supplies tools, not the Hey Arti skill.

Verification tools

ToolArgumentsCore operation / answer
verify_startscope, optional request_key, jobs, speculate, defer_humanSubmit via runs::start; compact Run observation, not approval
verify_statusrunruns::status: saved state, counts, elapsed seconds and next_check
verify_waitrun, optional secondsruns::wait: Run end or bound; default 30, integer 0–40, whole call under 45 seconds
verify_watchrun; optional standard _meta.progressToken on the tool callChange-driven runs::watch, up to 30 seconds; progress messages contain JSON Run events; compact observation at return
verify_resultrun, optional cursorruns::result: verdict and up to 20 concise findings, next_cursor; ready:false while active
verify_cancelrunruns::cancel: explicit cancellation acknowledgement; observe settlement for cleanup

scope is the shared Selection JSON envelope, tagged by kind:

{"kind":"all"}

Other forms are {"kind":"artifact","artifactId":"library"}, {"kind":"eval","evalId":"library/tests"}, {"kind":"artifacts","artifactIds":["library","consumer"]} and {"kind":"evals","evalIds":["library/tests","consumer/review"]}. Array selections contain 1–64 IDs, preserving first-occurrence order. Normal CLI dependency/reuse defaults apply; there are no transport-specific scheduling or lifecycle settings. This envelope is not a CLI selection file: --evals-file and --artifacts-file accept JSON arrays of IDs or one ID per line, without a kind tag.

A request_key is 1–128 ASCII letters/digits/._:-. Same repository + same key returns the same attempt, even after edits or completion. A new attempt needs a new key or no key. Omitting the key always creates a new Run while retaining normal eval-result reuse.

Concurrency is unlimited by default: all READY evals can start, subject to backend limits in limits.json. An optional positive integer jobs is the same per-Run cap as CLI --jobs; add it only when unrestricted concurrency causes trouble. The Run omits jobs when unlimited; older saved numeric caps remain readable.

Both mode arguments default false and pass directly to shared RunModes. speculate starts requested downstream work within core scheduling limits before upstream judgment; an upstream RED/ERROR cancels only the work it makes invalid. defer_human permits provisional progress and returns terminal signoffs_pending when only deferred sign-offs remain. verify_result reports verdict SIGNOFFS_PENDING; this is not full approval (the CLI watcher exits 5). Explicit gate = "blocking" still blocks. DEFERRED and PROVISIONAL request states are forwarded from core, not reinterpreted by MCP. Later sign-offs do not re-run the machine work.

A running answer is smaller than 2 KiB. Waiting is ordinary state, not an error. verify_wait treats runtime, Agent and Human evals equally: it does not return early merely because a person is needed. next_check is observation advice, not a request to poll rapidly or keep a model context warm.

Artifactize owns the Run. Dropping a request, closing the MCP connection, stopping a watcher or ending an agent turn never cancels it. Only verify_cancel does. RED/INCOMPLETE/ERROR/cancelled are saved Run outcomes, not malformed tool calls. Human judgment stays with the person; MCP cannot claim, release or submit it.

Inspection tools

All inspection tools accept an optional detail cursor as well as the arguments listed below. They invoke the same core operations and JSON projections as their CLI counterpart; MCP adds only a bounded, lossless transport page.

ToolArguments (optional unless marked required)CLI counterpart
statusscope (defaults all)status --all or selected status
config_checknoneconfig check
config_graphscope (defaults all), impact (false)config graph [ARTIFACT] [--impact]; MCP also accepts eval/multi-selection
request_logsid, stream (stdout/stderr/both), tail, start, end, searchrequest logs ID with matching flags
run_listlimit (0–20, default 20), offsetrun list --limit 20 --offset N for this repository
run_showrunrun show RUN
request_listrunrequest list [--run RUN], restricted to this repository
request_showidrequest show ID
session_showreference, summary (default false)session show REF [--summary]
tools_checkeval, or artifact/audience/toolStatic tools check; audience is agent or human
doctornonedoctor
modelsprovider (openai, anthropic, codex)models PROVIDER
remote_statusnoneremote status
cache_showkey, history (default false)cache show KEY [--history]

session_show.reference accepts a saved session reference, request ID or session ID, with the same producer/state checks as the CLI. A missing cache record is JSON null (or an empty array for history), just as in CLI JSON output. Static tools_check never invokes project tools or starts sessions. status may run trusted fingerprint commands and consult the configured review store; declared commands are not an OS sandbox. MCP exposes no login/logout actions and never returns credentials. models, doctor and remote_status may check backend readiness. Codex model listing can renew artifactize-owned tokens or remove the stored sign-in after a terminal refresh rejection, just as the CLI does.

Detail pages

A detail page contains at most one text entry and is smaller than 64 KiB including the structured answer and legacy text mirror. The entry is a UTF-8 fragment of the exact CLI JSON, not a clipped preview or a second result schema:

{"encoding":"json","entries":[{"offset":0,"text":"{\"ok\":true,\"artifacts\":3,\"evals\":4}"}],"next_cursor":null}

Concatenate entries[].text in order until next_cursor is null, then parse the joined text as JSON. Serialization is compact; CLI pretty-print whitespace is not preserved. Reinvoke the same tool with the same arguments, adding cursor from the previous page. Offsets are UTF-8 byte offsets. run_list.offset selects the next group of Run records; it is distinct from the cursor continuing one group’s JSON. verify_result instead pages its structured findings array.

Cursors identify the tool, arguments and current saved projection. Elapsed fields use the first page’s observation clock, so clock movement alone cannot expire a continuation while a Run waits for a person. A foreign or stale cursor is rejected; restart that inspection if the evidence changed. No transport-owned snapshots or background subscriptions are stored. Tool operation failures use MCP isError:true with bounded {code,message} content (the existing core operation errors retain their message under operation_failed); details are not truncated to pretend that an incomplete page is the whole result.

Deliberately absent

No credential login/logout, Human claim/submit/release, prune, cache removal, remote push, interactive monitor/review, store server, project-tool execution or Agent-session follow-ups. The CLI and normal Human review interfaces remain the entry points for these actions.

Streaming and focused diagnosis

Submit with verify_start, retain the Run ID, then call verify_watch with the standard tool-call metadata {"progressToken":"your-observer"} in _meta. While this call is pending, artifactize subscribes to shared state changes and sends notifications/progress. The notification’s message is a JSON envelope:

  • {"kind":"update","data":...}: compact live state/counts, not approval.
  • {"kind":"eval_result","data":...}: one saved request/eval status and concise summary, including reused and provisional results, not the Run verdict.
  • {"kind":"outcome","data":...}: final Run validation, using verify_result’s schema. Follow its next_cursor through verify_result for remaining findings.

Each attachment replays its baseline and already completed evals; deduplicate across attachments by request ID and status if needed. A terminal observation returned at a bound does not guarantee that the client received an outcome notification; use verify_result for authoritative final validation. Without a progress token, the same call waits but sends no notifications; prefer verify_wait in clients that do not consume progress. No rapid polling is needed. Subscriptions end with the call (30-second bound), client cancellation or connection closure, and a later call reattaches to the saved Run (see Verification tools for who owns the Run). Lost/coalesced invalidations reconcile from saved state rather than inventing a complete event history.

The client/agent host must display progress or route it into its own task queue/conversation. MCP notifications do not themselves create model turns, keep inference caches alive, or deliver after the call/connection ends. Artifactize cannot inject messages into an arbitrary host. For a shell-capable host, artifactize verify --all --watch or artifactize run watch RUN streams the same JSON-line events until terminal; the host can supervise that process and route its output. These are implemented transports, not a claim that every coding host supports live conversation delivery.

From a failed finding, use its request ID with request_logs, not run_show’s entire audit. stream defaults to both. Without a range, the last 50 matching lines are returned; tail selects 1–200 matching lines. start/end are inclusive one-based original line numbers and exclude tail. search is a case-sensitive literal (1–2048 UTF-8 bytes), applied before the tail selection. Forward queries are bounded to 200 lines and a byte budget per stream; next_start continues the query with the same stream/search/end. Lines longer than 2048 raw bytes are explicitly clipped with bounded head/tail text; the full local bytes remain on disk. A search can match past the displayed head/tail of a clipped line. Results are cleaned UTF-8 diagnostic text, not a byte-for-byte export. Normal detail paging still wraps the bounded answer.

Each stream’s evidence says complete (full local capture, including the tail), partial (no confirmed EOF, such as active/interrupted capture), saved_audit (older/remote/pruned local capture; possibly only the first 128 KiB), missing, or not_runtime (use request/session evidence instead). Empty is not missing. A reused request follows its original local producer; remote records do not promise locally retained logs. Full raw stdout/stderr belong to the Run output, not SQLite/remote result JSON, and disappear with normal output pruning. The 128 KiB cleaned per-stream audit remains compatible and bounded; pagination cannot recover a tail when only that old/truncated audit survives.

config_graph discovers static definitions, evals, cycle peers and input-to-consumer relations without invoking hooks. Select an Artifact/eval for its required closure; add impact:true to include reachable downstream consumers and their required inputs. This is conservative structural reachability, not proof that every included eval’s reuse key changed. Use status to inspect actual reuse decisions.

Protocol versions

The server uses the pinned rmcp transport. It supports the 2026-07-28 MCP lifecycle, with discovery and per-request metadata, and older clients that start with initialize and notifications/initialized. It offers no Tasks extension and no durable subscription owned by the transport.

CLI

Every command, its flags, output and exit codes, then the details of verify policy, budgets, selection files and profile variants.

Command reference

Every command accepts --repo PATH, --state-dir PATH and --json, before or after the subcommand, at most once each. Repository-reading commands normally use the current directory when --repo is omitted, but defaults are command-specific: doctor checks project declarations only with an explicit --repo; Monitor and Review start on ALL as described below. State defaults to the shared state home. Commands that do not use a repository or state ignore those options. Monitor and Review reject --json; mcp always writes JSON-RPC on stdout and startup diagnostics on stderr. Current-directory tool output is unchanged by --json; it is already text or JSON as listed below. After the program operand of tools help, trailing flags belong to that program, not artifactize. SELECTOR is exactly one of positional ARTIFACT or qualified ARTIFACT/EVAL, --eval ID, --evals CSV, --artifacts CSV, --evals-file PATH, --artifacts-file PATH or --all. CSV is a comma-separated list of names or qualified eval IDs, such as verify --evals app/lint,app/tests or verify --artifacts app,docs. --evals and --artifacts may each be repeated; their values add up in order, duplicates dropped. help [COMMAND] and -h/--help print help. -V/--version prints the version on the root command and on artifactize tools.

CommandFlagsOutputExit
verify SELECTOR--profile NAME, --recursive, --force, --jobs N (opt-in), --fingerprint-jobs N (CPUs), --max-executions N, --wait or --watch, --request-key KEY, --reuse-only KINDS, --speculate, --defer-humanRun ID or {run,state}; final text/JSON with --wait; JSON-line events with --watch0 accepted; outcome with --wait or --watch
status [SELECTOR]--profile NAME, --recursive, --force, --fingerprint-jobs N (CPUs); default --alltext or JSON0 satisfied, 1 not
mcpMCP stdio protocol; diagnostics on stderr0 on clean disconnect
config checktext or JSON0
config graph [ARTIFACT]--impacttext or JSON0
run list--repo-only | --all, --limit N (50), --offset N (0)text or JSON0
run watch RUN_IDJSON-line updates, eval results, final validationRun outcome
run cancel RUN_IDcancellation acknowledgement JSON0
run show RUN_ID--wait, --timeout-ms MS (600000, needs --wait)JSON0; outcome with --wait
request list--run RUN_IDtext or JSON0
request show IDJSON0
request logs ID--stream stdout|stderr|both, --tail N, --start N, --end N, --search TEXTbounded evidence JSON0
request claim ID--reviewer NAME ($USER)JSON0
request unclaim ID--reviewer NAME ($USER)JSON0
request tool ID TOOL--reviewer NAMEtext or JSON0; 2 tool error
request submit ID--verdict GREEN|RED, --fields JSON | --fields-file PATH, --reviewer NAMEJSON0, also for RED
session show REF--summarytext, or the stored events (or the summary) as JSON0
session send REF MESSAGEtext or JSON0
cache list--historytext or JSON0
cache show KEY--historyJSON0; 4 missing
cache rm KEYJSON0
tools check [EVAL]--eval ID, --artifact ID, --audience agent|human, --tool NAME, --execute, --args JSONJSON0 ready, 1 not
tools read PATH—UTF-8 text0 success, 1 tool error, 2 usage
tools list [PATH]—JSON0 success, 1 tool error, 2 usage
tools glob PATTERN [PATH]—JSON0 success, 1 tool error, 2 usage
tools grep PATTERN [PATH]--glob GLOB, --case-insensitiveJSON0 success, 1 tool error, 2 usage
tools section PATH HEADING—Markdown text0 success, 1 tool error, 2 usage
tools help PROGRAM [SUBCOMMAND...]trailing arguments passed to PROGRAM before --helptext0 success, 1 tool error, 2 usage
tools open TARGET—opens default application0 success, 1 tool error, 2 usage
login codexsign-in URL on stderr; may read a pasted redirect URL from terminal stdinSigned in with Codex.; JSON {"provider":"codex","signed_in":true}0
logout codexremoves local tokens and attempts revocation; leaves read-only auth files aloneSigned out of Codex.; JSON {"provider":"codex","signed_in":false,"revoked":BOOL}0 after local sign-out
remote login URL--share summary|full (summary); token on stdintext or JSON0
remote logouttext or JSON0
remote statustext or JSON0 signed in, 1 not
remote push--dry-runtext or JSON0
models openai|anthropic|codextext or JSON0
doctortext or JSON0 ready, 1 hard error
prune--older-than DURATION, --dry-runtext or JSON0
monitor [PATH]starts on ALL; PATH or --repo PATH selects only the initial repository; --all conflicts with a pathterminal UI0
review [REQUEST_ID]--repo PATH (a repo: search term), --all (the default; not with --repo), --reviewer NAME ($USER)terminal UI0
server run--listen ADDR (127.0.0.1:8417)Listening on http://ADDR/; JSON {"listen":ADDR}0
server token add NAME--scopes read,publish,humanthe token, once, and a reminder to store it; JSON {"name":NAME,"scopes":[...],"token":TOKEN}0
server token listtext or JSON0
server token revoke NAME--purgeJSON (also without --json)0
server rm KEYJSON0

Run outcome codes (verify --wait, run show --wait): 0 GREEN, 1 RED, 2 ERROR or cancelled, 4 INCOMPLETE, 5 SIGNOFFS_PENDING (only sign-offs remain, not approval). Plain verify exits 0 on acceptance, not validation. Ctrl-C stops a watcher with exit 130; closing it never cancels the Run. run show --wait follows a RUNNING Run until it finishes; when its own timeout expires first it prints the current Run, exits 3 and leaves the Run running. Every command exits 2 for usage errors (unknown, repeated nonrepeatable, conflicting or missing options and values) and operational errors: text on stderr, or {"error":"..."} on stdout with --json (except mcp, whose stdout remains protocol-only). Two kinds of tool failure are exceptions. Current-directory tools (tools read, list, glob, grep, section, help and open) retain the shared tool runner’s exit 1 and unchanged diagnostics, even with --json. A Human tool that request tool ran and that failed prints its normal result on stdout (rendered content, or {"content":[…],"isError":true} with --json) and exits 2. tools check reports declaration, preflight, execution and cleanup failures, including a failed --execute call, in its diagnostic report and exits 1. Usage errors of all three still follow the generic rule. There is no plan, history or --full.

List options --reuse-only and server token add --scopes may be repeated; their comma-separated values accumulate across occurrences. This does not apply to the common --repo, --state-dir and --json options, which are accepted once. run show --timeout-ms requires an integer from 1 through 2147483647 milliseconds; a value outside that range is a usage error (exit 2).

logout codex returning 0 confirms local sign-out, not necessarily server-side revocation. Revocation is best-effort: if it cannot be confirmed, the command warns on stderr and its JSON result includes "revoked":false (true when confirmed). Local tokens are still removed; a read-only external auth file is never changed.

Identity arguments

Run/request ID arguments (including request list --run and review REQUEST_ID) are validated before reading state: 1–200 ASCII letters, digits, -, _ or ., never only dots. Reuse-key arguments to cache show, cache rm and server rm require exactly 64 lowercase hexadecimal characters. Malformed identities exit 2 as usage errors; valid but absent cache keys still make cache show print null (or [] with --history) and exit 4, while cache rm reports removed: false.

server token add NAME and server token revoke NAME accept 1–64 ASCII characters: letters, digits, -, _ and .. Empty names, whitespace, slashes, non-ASCII text and longer names are rejected with exit 2 before opening the token store. This name is a display label, not the bearer secret.

session show and session send read and continue an Agent review’s saved conversation. REF is the request’s session.ref (user@host/STATE_ID/RUN_ID/REQUEST_ID/SESSION_ID), a request id such as run-Hq2b9X-1, or a sessionId; a request that reused a result names the review that produced it. show prints the conversation as text, and with --json the stored events as they are: {"reference","file","events"}. show --summary prints the session summary, computed from the session file alone: the backend, model and time span, the turns with their tokens and the total, the tool calls per tool with how many failed, and the follow-ups; --json prints {"reference","sessionId","requestId","backend","model","reasoning","startedAt","endedAt","durationMs","followUps","turns","tokens","toolCalls"}. send prints the answer under a header naming the session, the follow-up number and the model, and files changed since this review when an Artifact the review covered changed; --json prints {"reference","sessionId","requestId","send","filesChanged","answer","usage"}. Both exit 2 for a reference to another machine or state, a session the GC removed, one that was not saved, a request that is still running (send), and a failed follow-up.

Commands opening an existing state apply supported additive schema upgrades with write access. Schemas 6 and 7 upgrade to 8 (6 → 7 → 8 or 7 → 8) while preserving Runs, cache and Human approvals; schemas 1–5 and versions above 8 are refused (State). doctor reports an incompatible schema as a hard error and exits 1.

Submission and watching

Breaking change: plain verify is submit-only. Scripts/CI requiring the old validation exit codes must add --wait. Its JSON is {"run":"run-…","state":"preparing"} (the state may already have advanced); only verify --wait --json renders the full final saved Run. --wait has no Human timeout and all eval kinds follow Run settlement. In strict default mode, a pending Human request keeps the Run RUNNING; submitting its sign-off resumes dependent evals in that same Run.

--request-key KEY accepts 1–128 ASCII letters/digits/._:-. Repeating a key for the same canonical repository returns its original Run without changing the accepted scope/options or re-executing it, even after checkout changes. Omitting the key always creates a new Run.

artifactize verify --all --request-key build-42
artifactize run show RUN_ID --wait
artifactize run cancel RUN_ID

Cancellation acknowledges intent; inspect the Run for its eventual outcome after cleanup. Repeated cancellation and cancellation after completion are safe. Only explicit cancel stops execution; terminal closure and watcher interruption stop observation only.

Verify modes

--speculate and --defer-human are independent per-request switches, both off by default. The accepted Run records modes: {speculate, defer_human}; a retry with the same --request-key retains the first request’s modes.

ModesDispatch and settlement
NeitherWait for dependency GREEN evidence; the owned Run waits for people
--defer-humanWait for machine gates; run past deferrable sign-offs provisionally
--speculateStart all requested kinds before unresolved gates; wait for people
BothStart machine reviews speculatively; ask for deferrable sign-offs last

In every mode, a Human eval is asked only once every other non-Human eval of its own Artifact is GREEN, and is BLOCKED when one of them is RED or ERROR. These modes change execution order, not approval. verify --wait uses the ordinary outcome codes: 0 GREEN, 1 RED, 2 ERROR, 4 INCOMPLETE or 5 SIGNOFFS_PENDING. Execution modes explains scheduling, cancellation, provisional results, strict Human gates and later sign-off reuse.

Runtime CLI

These development examples require a source checkout, Rust and the platform build prerequisites from Installation. Run them from the repository root; cargo run builds the CLI from that checkout. For an installed CLI, use artifactize directly with your own project’s path.

cargo run -q -p artifactize -- --repo crates/artifactize-core/tests/fixtures/runtime verify --all --wait
cargo run -q -p artifactize -- run show RUN_ID

The fixture intentionally includes GREEN, RED, a timeout ERROR, RED-blocked and ERROR-waiting dependents, and a two-Artifact cycle. Its overall exit code is 2.

Runs, status and validation covers selectors, gates, outcomes and output.

See the upgrade reference for removed legacy gate-bypass settings. --force marks only explicitly selected executable evals for a fresh review, not recursive dependencies; it neither expands the execution scope nor bypasses gates. Runs record the resolved policy and each request’s force flag. Forced evals never read or join cached results or live executions; a forced GREEN or RED is added as the key’s newest record, and published to a configured review store, which later runs reuse. Dependencies may still reuse their own local records, but a forced Run reads nothing from the store. Speculation and deferral never waive final requirements.

In-flight sharing explains when concurrent Runs join the same execution, and how profile identity, takeover and --force affect that automatic sharing.

There is no default eval-concurrency cap: every READY eval starts as soon as it is ready. Add --jobs N (minimum 1) only when that concurrency causes trouble; it then bounds the evals of this Run. Agent reviews are still bounded machine-wide per backend by $STATE/limits.json (Backend capacity); a request waiting for a backend slot holds no optional --jobs slot and no executor start.

--fingerprint-jobs N (on verify and status, at least 1) bounds how many fingerprints are computed at once; the default is the number of CPUs available to the process. In verify, one shared bound covers preparation and Runtime/Agent end-of-review rechecks, independently of --jobs, which bounds evals. Human tool actions and submissions separately use CPU-sized fingerprint parallelism and do not inherit this setting. Results and output do not depend on the bound or on completion order.

verify --max-executions N sets a nonnegative, shared per-Run executor-start budget (unlimited when omitted); it is not an Artifact declaration field. The Run records an explicit jobs cap (the field is absent when unlimited), fingerprintJobs, maxExecutions and executionsStarted. A prepared Runtime or Agent invocation consumes one start, even when it fails to spawn or later returns ERROR. Human waiting, claim and tool actions consume no starts. Fingerprint preparation/rechecks, cache hits, and joined waiters consume none. Failures in initial executor preparation consume no start. Agent execution_paths pinning happens after a start is charged: a pinning failure still consumes that start and records ERROR/PREPARATION_FAILED without calling the model. Zero permits reuse, joining and Human reviews; a budgeted waiter replacing a failed/dead owner uses its own Run’s remaining budget. Exhaustion never interrupts running evals, but leaves remaining READY requests BUDGET_EXHAUSTED and ends the Run INCOMPLETE with a reason (exit 4). Hitting the cap exactly without unmet starts is not an error. There are no reservations, refunds, admission pools or restart ledger.

Human waiting and cancellation follow Submission and watching; the Human guide covers claims, tools and sign-offs.

Selection files must be regular files no larger than 4 MiB, containing a JSON string array or one trimmed ID per line (UTF-8 BOM and CRLF are accepted). They must contain 1–100000 IDs before deduplication. Empty lines are ignored; IDs cannot contain ASCII whitespace, control characters or commas. Malformed JSON arrays never fall back to line parsing. Relative file paths resolve from the CLI’s cwd, not --repo.

verify ... --profile NAME selects a complete declared profile_variants entry for every executable eval included by the selection and recursion policy before applying variants. Each executable eval can declare up to 64 safely named variants, all retaining its default reviewer kind. Unknown variants fail before creating a Run, and source declarations are never rewritten. The library accepts project::selection::ProfileSelection::Named or ProfileSelection::Evals (a qualified-eval-to-name map); mappings outside that pre-variant included scope fail. With --recursive, variants apply to executable dependencies already in that scope. Runtime variant arguments then rebuild scoped references and dependency gates, and the included scope is recomputed. Dependencies newly included through those variant arguments retain their default profiles; the named variant is not applied again to the expanded scope. The selected variant is an execution option: it is not part of the reuse key, so results of different variants (and of the declared profile) reuse each other unless a runtime variant changes the command or args. Stored request profile and options describe the execution that produced the result; requestedProfile retains the requested profile when a hit returns another one. Text output adds profile NAME to the reuse marker only when the producing profile’s settings differ; a result from a variant with identical settings shows no marker and is identified by its saved options.variant.

Reuse only

verify --reuse-only KINDS takes a comma-separated list of runtime, agent and human. An Eval of a listed kind may only reuse a result: a completed record of its reuse key in the local cache, or in the review store when one is configured. When there is none, the eval is not executed: no executor start, no provider call, no Human request, and no waiting, not even for a live execution of the key. A forced eval reads no cached result, so --force on a listed kind never executes it. Unlisted kinds run as usual. In CI, artifactize verify --all --wait --reuse-only agent,human runs the tests and takes every review from the cache or the store (CI).

An eval that was not executed is reported STALE, with the reason in its blockedReason:

  docs/review [run-Hq2b9X-4]: STALE — Not reused (--reuse-only agent): no cached or stored result, so it was not executed.

Evals that depend on it wait, or are blocked, as they would behind any missing evidence. The Run ends INCOMPLETE with a reason (exit 4), even when another eval is RED or ERROR, as with an exhausted budget, and records the kinds as reuseOnly. status does not take the flag: it predicts what a plain verify would do.

Dependency selection and output

artifactize verify app/e2e selects the qualified Eval just like --eval app/e2e. Add --recursive to execute the Evals in the required dependency scope. Without it, input evidence must already be available. File Artifacts are selected by their declared names; a sidecar creates no automatic relation to the surrounding folder.

For which evals wait or block, including dependency cycles, see Eval dependencies.

Status uses execute, reuse, wait and blocked actions. Requests, validation and status carry blockedBy qualified Eval IDs. The monitor groups gate Evals by Artifact under Waits for. Tags, file kinds and paths remain visible in graph, status and monitor output.

Saved Run evidence and terminal views

run show RUN_ID --json and verify --wait --json include top-level evidence when nonempty. Run fields are flattened into the output object, without a run wrapper. It maps qualified eval IDs to saved verdicts used without a request in this Run, such as cached upstream evidence in a partial Run. It is not the latest global verdict. See Runs. For the terminal keys and layout, see Monitor and Human reviews.

Current-directory tools

artifactize tools also exposes the standalone scoped tools, using the same CLI definition and runner as artifactize-tools. These tools use the current directory, not --repo; the linked reference explains their path, URL and executable lookup rules. artifactize tools --help lists both check and these tools, and artifactize tools SUBCOMMAND --help documents each one.

Agent streaming and runtime logs

verify SELECTOR --watch submits and immediately follows the saved Run; run watch RUN_ID attaches later. Both emit flushed JSON lines, regardless of --json: kind: update for live state/counts, kind: eval_result for a concise saved request result, and kind: outcome for final Run validation. --watch and --wait are mutually exclusive. The watcher exits with the Run outcome; interrupt exits 130 and stops observation only. Reattachment replays completed evals, not an exact historical event journal. Follow an outcome’s next_cursor through MCP verify_result, or inspect the saved Run when full detail is needed.

For a failed runtime finding:

artifactize request logs REQUEST_ID --stream stderr --tail 30
artifactize request logs REQUEST_ID --stream stdout --start 100 --end 150
artifactize request logs REQUEST_ID --search 'failure' --tail 10
artifactize config graph INPUT_ARTIFACT --impact --json

Log queries return explicit retention/missing evidence and original line numbers, not the full request audit. Tail defaults to 50 matching lines per stream; range queries return at most 200 lines per stream and a bounded byte budget, with next_start for continuation. Tail and range are exclusive. search is a literal, case-sensitive 1–2048-byte string. Lines longer than 2048 raw bytes are marked clipped with bounded head/tail text. Full raw runtime streams, including tails beyond the 128 KiB saved audit, stay in Run output. Current prune leaves raw stream files and completion markers in invocation roots; it supplies no raw-log retention policy. Missing or older/remote audit-only evidence is labeled explicitly. See the shared runtime retention reference. See MCP focused diagnosis for the shared evidence/event schemas and client delivery limits.

TUI

The terminal Monitor and Review browse the same saved Runs and Human requests as the other interfaces. Install them with the CLI; no separate terminal package or service is needed. Browsing is read-only until you explicitly claim, run a Human tool or submit a verdict.

Review

artifactize review [REQUEST_ID] [--repo PATH | --all] [--state-dir PATH] [--reviewer NAME]

A terminal UI that searches saved requests and reviews the waiting Human ones, with the same lifecycle as request: it claims, runs tools, unclaims and submits in-process, and publishes a submission to the remote review store exactly like request submit. It also runs on its own, for example in a second terminal or tmux pane.

Without an ID it lists the requests its search finds across every repository in the state, newest Run first, with their eval, request, status, claim, age and repository; Enter opens one. The search starts as kind:human status:waiting_human, the waiting Human sign-offs. --repo PATH adds that repository’s repo: term to it; --all is accepted and is the default. With an ID it opens that request in any repository; a waiting follower’s ID opens its original.

A search is free text and key:value terms; every term must match. A plain word matches, case-insensitively, part of the repository, eval, request ID or claimant. A key restricts a term to one field; quote a value with spaces (repo:"/work/my repo"), and quote a word to search for it literally ("type:style"):

TermMatches
repo:part of the repository path
eval:part of the eval ID
request:part of the request ID
reviewer:part of the claimant’s name; unclaimed requests never match
tag:part of a tag of the eval’s Artifact (tags in its declaration, as saved with the Run)
kind:the eval kind: runtime, agent or human
status:the request status, such as waiting_human, green, red or error
is:mineclaimed by you ($USER or --reviewer)
is:claimedclaimed by anyone
is:unclaimeda waiting Human request no one has claimed

Claims exist only on waiting Human requests, so is: matches nothing else. An unknown key or value is an error that names the choices, and lists nothing.

/ edits the search; the list follows as you type. Tab completes the last term: a key, or after a key a value of the requests the rest of the search finds (repository folders, evals, requests, claimants, tags), or a kind, status or is: value; the footer names the candidates. Enter keeps the search, Esc restores the one before editing, and Ctrl-U clears it. Empty search includes every request kind, not just the initial waiting Humans. A page lists 100 requests; PgDn shows older ones and PgUp newer.

The list holds one entry per waiting sign-off: followers that later Runs join to a waiting Human execution are listed once, as the original request, which records the Human tools and owner schemas and receives every action, as in the monitor. The search matches that entry, so a follower’s own repository does not find it; a follower whose original no longer waits is listed as itself. A Human request opens the Human Detail below, read-only once settled. Any other request opens a read-only Detail: the monitor’s sections of its eval in its Run, its saved runtime output, or its Agent session read as the monitor reads it (d shows the sections); it has no claim, tools or forms.

The list and Detail use Reactive panes. With the list focused, it is Full and the selected request has a read-only Preview; Enter or → opens Detail beside a Compact list. The linked layout reference gives the widths and small-terminal behavior. The breadcrumb is artifactize review › search › eval. ↑/↓ or j/k selects a request; r refreshes the list. Tab opens the selected request with its Tools focused. Esc on the list stays there, never quits. The standalone UI has no ? help overlay or ! attention navigation; those keys belong to the monitor.

Mouse capture is on by default: click a list row to select it, then click it again to open it; the wheel scrolls the list while the list has focus, not the compact list beside an open Detail. Human Detail has the same mouse interactions as Monitor. Standalone Review’s non-Human Detail is keyboard-only: its transcript groups and content do not respond to clicks or the wheel. F2 turns capture off for terminal text selection; the header shows mouse off · F2. Bracketed paste works either way.

Standalone Review’s Runtime evidence is a fixed lower pane below the sections. Its output is not independently scrollable: arrows and paging scroll the sections, and Tab does not switch focus to evidence. Monitor instead has an independently scrollable Runtime-evidence pane, beside or below its sections depending on width.

Shared Human Detail

The monitor and standalone review use one Human review component. Opening or previewing a request claims nothing. The instruction comes first, below one line of status, claim and waiting time. t shows or hides Technical: request, Run, repository, full claim details, shared execution, creation time and GREEN/RED owner schemas. For a follower, the destination request and repository stay visible.

Before claim, the instruction fills the body and tools appear on one line. After claim, it folds to two rows above the Tools, the Tool log and the fields. i expands or folds it while not editing; the expanded instruction takes 70% of the body and ↑/↓ scrolls it. While Technical is shown the instruction stays expanded, so hide Technical with t before i can fold it. Ctrl-PgUp/PgDn scrolls long criteria from any sub-area; in monitor, the wheel over the instruction scrolls it too. Tools focus widens the Tools and Tool log column to 60% of the available width; the selected tool shows its action and description. A command tool shows its resolved command line. A recorded per-tool resolution error shows the declaration and its reason; a request-wide resolution failure instead falls back to the declared command without displaying that explanation. A built-in shows an action on a logical target instead: for example, opening notes.md, printing a Markdown section, or listing a directory, not a shell command line.

The way to the tools is always on screen: the footer lists the keys of the focused area (for example Tab tools, Enter run, or ↑↓ tool · Enter run · Tab fields with Tools focused), the Tools pane title names its key (Tools (1) · Tab, Shift-Tab while a form field takes Tab), and before a claim the one-line tools summary says Tab or click: tools. A click on a tool row focuses the Tools pane and selects it; a click on the selected tool, or on [Run tool ⏎], runs it.

KeyHuman Detail action
cExplicitly claim a waiting request; active only with Detail focused
g / rChoose GREEN / RED and open its form, after claim
uRelease your claim without a verdict
Ctrl-G / Ctrl-RChoose or switch verdict while editing
Ctrl-SSubmit the chosen form
PgUp / PgDnScroll the focused fields or completed-result viewport, or the Tool log with Tools focused; Tab/arrows still select fields
Ctrl-URelease while editing
Tab / Shift-TabSwitch Tools and Fields; in edited Fields, Tab moves between fields and Shift-Tab switches to Tools
i / tExpand the instruction / toggle Technical, while not editing
EnterWith Tools focused, run the selected tool at once (after claim)
EscCancel work, then stop editing, then leave Detail
Enter on + Add / Ctrl-DIn a list of groups, add a group / remove the selected group

While a Human lifecycle job is busy, ordinary PgUp/PgDn scroll the Tool log regardless of the focused area. Normal editing and focus switching pause until the job ends; Ctrl-PgUp/PgDn still scroll the instruction.

  • Claim. The reviewer defaults to $USER unless selected with --reviewer. Tool and verdict actions never claim implicitly. A request claimed by someone else, or no longer waiting, is read-only; leaving Detail keeps your claim.
  • Tools. ↑/↓ selects; j/k also selects while not editing. Running a tool has no confirmation step. The run needs your claim on a request that still waits, checked when it starts: after a release or a settlement elsewhere it neither claims nor runs. Each run reloads and resolves the declarations again, but refuses the action if the recorded Artifact scope or eval declarations (including tools) have changed. It does not accept an updated command in place of the recorded review definition. An open form stays open while a tool runs. A launch tool reports “launched” and the UI continues. An output tool runs with a spinner; Esc or Ctrl-C cancels. A nonzero exit is a tool error, not a verdict.
  • Tool log. Under the tools, every run of this review: ▶ HH:MM:SS · operation · Artifact (UTC), then what it prints, line by line as it arrives, stderr in red, then its end line, ■ exit CODE (or launched, done, cancelled, timed out, error: …). A launched program’s output keeps arriving under its own run until the program exits. The log wraps and follows its newest line; PgUp with Tools focused scrolls back and holds the view while lines arrive, and PgDn back to the bottom follows again. With mouse capture off (F2), the terminal selects and copies its text. Standalone Review clears the log when another request opens. Monitor keeps each request’s log in memory and restores it when its Detail reopens, even after another request was viewed. A normal successful submission in either UI clears that request’s log. Settlement elsewhere, or a local save whose publication fails, leaves it in memory. Neither UI saves the log to disk.
  • Form. The chosen verdict opens its schema-backed form without an intermediate verdict popup. A Human schema holds only form shapes: text, integer and number fields, booleans, string choices, constants, lists of text, and lists of groups of those scalars. Constants are filled in and get no row; * marks required fields. Blank optional fields are omitted from submission. Optional booleans cycle through true, false and unset; required booleans have only true/false. Tab or ↑/↓ moves between rows, Space or ←/→ chooses, and typing edits. A list of text types into its last item; Enter starts the next and Backspace on an empty item removes it. A list of groups shows a row per field of each group (findings 2 · problem) and a + Add findings row: Enter there adds a group, Ctrl-D removes the group of the selected row, and a group with nothing entered is left out. Enter never submits. A validation error keeps the request waiting and shows the failing paths. A recorded schema no form can enter (saved before this rule) is refused with its reason. A saved request without a recorded Human definition is not an empty-schema review: its tools display as unknown and GREEN/RED forms are refused. Browsing that old request does not invent missing tool or schema declarations.
  • Editing. Ordinary letters (q, c, i, t, …), ←/→ and Tab belong to the fields, not navigation. A bracketed keyboard paste goes into the focused field as one edit, in standalone review as in the monitor. A paste that would push the result past the 256000-byte Human result limit is refused as a whole with an error, leaving the draft unchanged. Each verdict keeps a separate draft when you switch between them. Editing and tool work normally lock focus moves outside Detail. Monitor’s Close button is an exception: when idle it returns directly to the tree with the form retained; while busy it cancels the work first. Esc closes one thing at a time: cancel a job, then stop editing with its draft kept, then leave Detail. The same verdict reopens the draft. Within the same state identity, refresh and leaving/reopening the same request keep drafts; opening a different request in standalone review starts fresh. A request settled by another command ends editing on refresh and releases the focus lock.

Outside a form or work, q quits from Detail too. In either UI, Ctrl-C requests cancellation of any active Human lifecycle job: claim, tool, submission (including publication) or release. It stays in the UI while that job is busy; once no job is busy, Ctrl-C invokes quitting, including from a form. Standalone review lists claims this session took without submitting: k keeps them and quits, u releases them and quits, and Esc stays. If a release fails, Review stays open, shows the error and keeps the claims that failed; press u to try again or k to quit keeping them. Ctrl-C again at this standalone quit prompt exits while keeping outstanding claims. The quit prompt sets a form aside without dropping its draft. Monitor keeps claims on exit; release with u first if you want to give one back. Esc never quits either UI.

After a standalone submission the list returns. A page emptied by reviews goes back to the nearest earlier page that still lists requests; review exits only when no request matches the search any more. Monitor keeps the completed result open in Detail. Both use the same in-process lifecycle jobs; monitor does not launch the standalone command.

Monitor

artifactize monitor [PATH | --repo PATH | --all] [--state-dir PATH]

The monitor catalogs every repository in this state. Plain monitor starts on ALL, independent of cwd. monitor ., monitor PATH or --repo PATH selects that repository or worktree initially; --all remains accepted. A positional path conflicts with --repo, and --all conflicts with either. This sets only the initial selection: navigation still reaches ALL and every repository/worktree. Navigation drills down through Scope → Runs → Run tree → Detail.

Reactive panes

The focused pane is Full. Compact gives narrow context from the level to its left. Preview shows the next level without moving focus. Hidden levels stay in the breadcrumb: artifactize › scope › Run › eval.

At every terminal width, the focused pane fills the width between its immediate neighbours: Compact + Full + Preview. Both side panes have the same fixed width, borders included:

Terminal widthEach side pane
Fewer than 100 columns16 columns
100–139 columns28 columns
140 columns or more36 columns

Full takes the remaining width after the side panes. Scope has no left neighbour, so it shows Full + Preview; Detail has no right neighbour, so it shows Tree Compact + Detail Full. No neighbour is hidden at narrower breakpoints. If the terminal cannot fit even the sides, they are clipped equally rather than dropping a level. Widen the terminal when the focused pane needs more room.

  • Scope. Git clones group by their common Git directory. ALL selects the entire state; each repository row selects all its worktrees, without a separate repository ALL row. Worktrees without Runs fold into +N without Runs (Space); Space shows or hides them. The selected scope stays visible. Non-Git and deleted paths stay listed. Runs record optional commonDir, worktreePath and branch alongside their repoPath workspace.
  • Runs. The selected scope’s Runs appear newest first, with age, duration and request counts (✓ GREEN, ✗ RED, ! ERROR, ? waiting Human, ◐ running). Narrow panes drop secondary columns. Scope filtering happens before paging; Down/j past the last Run loads older Runs, and Up/k at the first Run of an older page loads the newer page and selects its last Run.
  • Run tree. The selected Run’s saved definitions, recorded evidence and current request states, not a re-evaluation of files. The headline shows status, wall time, counts and only the token total for usage. It shows validation pending while running, then SATISFIED / NOT SATISFIED at Run end. Up to three attention lines show errors, waiting Humans and running evals, then +N more. Enter on the Run row opens validation and unmet obligations, counts, errors, waiting and running work, timing, budgets, executed/reused counts and full spent/saved usage in Detail.

Scope badges and the header share !N ✗N ◐N ?N attention counters: requests in ERROR and RED, Runs still running, and sign-offs waiting for a person, deferred ones included. These cover the catalog, not just the visible Run page. Followers of one Human execution count as one waiting sign-off.

Artifacts and evals

Each eval has one row. Artifact rows summarize the eval rows shown now, with passed/total and the most urgent state. [file] marks file Artifacts; their target path and tags are in Detail’s Technical section. A basis Artifact is one row: ◇ engine basis. There are no separate relation rows or always-on dependency lists.

Artifact completion also enforces upstream gates, not just its eval rows’ colors. An eval preserves its own reusable GREEN while its Artifact can show done, but waits for X or done, but blocked by X, with zero passed (for example 0/1). All-GREEN eval rows therefore do not prove that the Artifact is ready for consumers; its required upstream evidence must also be satisfied.

A partial Run can use saved evidence for an eval that has no request in this Run. Its row says GREEN · saved result, not in this Run (or the saved RED). That evidence contributes to completion counts and gates: saved GREEN can satisfy an input; saved RED can block a consumer. A dash means neither source is present.

View stateMeaning
✓ doneGREEN, executed or reused
✗ / ! failedRED / ERROR
◐ / ? / · in progressRunning, waiting for a Human (with claimant), awaiting admission, waiting for a backend slot or an explicit --jobs cap, or joining an execution
… waits for XA running Run waits for X’s required evals to be GREEN
⊘ blocked by XAn upstream RED or blocked eval prevents execution
○ / $ / - not runA finished Run waited for X, has stale evidence or did not review the eval (○); its budget ran out ($); the eval has neither a request nor saved evidence (-)

Running and Human rows are bold and colored; queued rows keep a colored ·. Waiting and ordinary not-run rows are dim. ERROR upstream asks for a retry rather than blocking as RED does. A finished Run can still have an open Human request. These view states explain saved request states; they do not rename the states in JSON.

The Run’s execution modes also explain its display. With --speculate, a downstream eval can be READY or running while upstream judgment is still pending; a later upstream RED/ERROR blocks or cancels that speculative work. With --defer-human, sign-offs appear as DEFERRED and dependent work can be PROVISIONAL, not approved. A finished SIGNOFFS_PENDING Run means only those sign-offs remain. Their later submissions change current requests, not the historical Run-end verdict; strict Human gates still remain strict.

What an eval waits for follows Eval dependencies. A row groups those gates into at most two unmet Artifacts, then +N, with each Artifact’s completion state. When all targets are themselves waiting, it adds one level of cause, for example waits for cli … waiting (cli waits for code-style). Artifacts in a cycle share ↻ peer markers. Upstream comes first where the graph allows; Artifact cycle peers and Evals are ordered by name.

All-done Artifacts fold automatically. Other Artifacts stay open unless you toggle them; refresh respects your choices. A new Run selects its first in-progress or failed eval. h folds, l unfolds and Space toggles the selected tree row.

The selected eval’s upstream Artifact rows get ↑: bright when unmet, dim when complete. There is no downstream highlight. Off-screen targets show ↑N above or ↑N below. b cycles through those Artifact rows, unmet first; Backspace returns to the original row. On an Artifact row, b uses its evals’ combined upstream.

The headline’s validation stays at Run end. The tree rolls up the requests as they stand now, including a later Human submission. A dim * marks a row whose derived state changed since the Run ended; it does not mean files changed. Artifact Detail also keeps an At Run end field. Historical requests and validation are not rewritten by this view.

Keys and mouse

KeyAction
Enter / →Next level; on a tree row, open Detail
oOpen the selected tree row’s Detail
← / EscStep back; Esc never quits
Tab / Shift-TabCycle Scope, Runs and the tree; inside Detail, switch sub-areas
↑/↓ or j/kSelect a row; inside Detail, scroll
?Show help; an ordinary next key dismisses it without acting. Ctrl-C still quits and F2 still toggles mouse capture
!Next ERROR, RED or waiting Human eval in this Run, wrapping around
rRefresh with Scope, Runs or the tree focused; in an owned idle Human Detail it chooses RED, and it does not refresh read-only Detail
q / Ctrl-Cq quits outside editing/work; Ctrl-C requests cancellation of any busy Human lifecycle job; otherwise it invokes quitting
F2Toggle mouse capture for terminal text selection

From Detail, ! opens the next attention item’s Detail. Human actions require Detail focus; Shared Human Detail owns its focus locks, Esc and Close behavior and retained drafts.

Click focuses and selects. Double-click opens a tree row’s Detail; clicking the peek opens it Full. In a Human Detail, a click on the selected tool runs it. The wheel scrolls the pane under the pointer without moving focus. In the tree it moves the viewport, not the selection, so the peek stays on the same eval. Breadcrumb segments are not clickable. Bracketed keyboard paste works with mouse capture on or off.

Detail

With the tree focused, Detail is a peek that follows j/k: Outcome and the Artifacts the eval waits for. A running Agent’s peek also shows its last transcript line. Enter, → or a click on the peek opens Full Detail instead of a modal. Leaving Detail closes the full view and restores the peek.

Read-only sections appear in this order: Outcome → Waits for → What → Provenance → Technical. Outcome puts the verdict or error first. What includes the instruction and raw result. Provenance holds work, usage, budgets and timing. Technical holds identifiers and hashes; it starts folded (t toggles it). Waits for lists the same upstream Artifacts as the tree, with their completion state and reference origin; w shows or hides their pending evals. Empty sections are omitted. Tab switches between sections and evidence; ↑/↓ and PgUp/PgDn scroll the focused area. Home jumps to the top of Monitor’s focused read-only sections or Runtime evidence, and to the top of standalone Review’s read-only sections.

Detail depends on the eval kind:

  • Agent: a live conversation, including a running review or the original local conversation behind reused evidence. ↑/PgUp pauses following so you can read; Home jumps to the start and End returns to the latest messages. Monitor also offers Top, Bottom and Details buttons; Bottom resumes following. Space or Enter toggles the visible tool group in either UI, showing its names, targets and recorded states. Monitor also toggles a group when you click its header. d opens the saved result and other details and returns to the transcript in either UI. Esc returns from saved details to the transcript in Monitor, but leaves Detail for the list in standalone Review. Only public reasoning summaries appear, when the provider supplies them; private reasoning stays hidden. Missing or remote conversations explain why they cannot be shown here. Use session show --json for the complete saved record. See the transcript reader reference for storage, streaming and history behavior.
  • Runtime: saved stdout/stderr, exit code and capture truncation beside the sections when Detail has at least 90 inner-content columns (92 including its outer borders); in a narrower pane the evidence stacks below. Running, timeout/cancellation and remote summary-only results show logs as unavailable; there is no live-log recorder.
  • Human: use the Shared Human Detail controls for claims, tools, verdict forms and stepping back. Monitor-specific differences: it keeps the completed result open. Outside editing or work, q quits, ? shows monitor help and ! opens the next attention item.

Browsing is read-only. If publishing to the team store fails after submission, Detail shows the completed local result and the publication failure rather than asking you to submit again. Terminal settings are restored on exit. See the local reader reference for subscription and recovery behavior.

A refresh failure records a timestamped read failed at … notice. Monitor and the Review list retain last-known data, so visible requests, results and claims can be stale, not current authority. Standalone non-Human Detail can hide the notice until you return to the list, and a failed Record reload can clear that previous Detail instead of retaining it. In Review, return to the request list and press r; in Monitor, request refresh from Scope, Runs or the tree. A successful read clears the failure notice.

A single saved Run that cannot be read, or contradicts itself, is different: Monitor lists it with its own error, without a read failed notice, and does not open its tree. The other Runs stay available.

Draft retention applies only within the same state identity. If Monitor detects that a replacement state database has a different identity, it cancels active Human work, closes Detail and clears cached reviews and in-memory drafts before showing the replacement state. Do not expect the old state’s draft to carry over.