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 statusshows 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
python3to run Python checks andgrepto 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:
| OS | Default | Alternatives |
|---|---|---|
| Linux | Install script | .deb or AppImage, Cargo binaries, build from source |
| macOS | Homebrew | Install script, disk image, Cargo, build from source |
| Windows | Microsoft Store | PowerShell install script, Cargo, build from source; winget is in preparation, not available yet |
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:
| File | Install |
|---|---|
artifactize-desktop-vVERSION-universal-apple-darwin.dmg | Open 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.deb | sudo 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.AppImage | Make 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/MacOSdirectory to/etc/paths.d/artifactizethrough the system administrator prompt. Open a new terminal afterwards and runartifactize --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/binand 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/binthat the app did not create are never replaced. If your shell does not already search~/.local/bin, addexport 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 with | Update |
|---|---|
install.sh or install.ps1 | Run the same command again; it updates the desktop app too |
| Homebrew | brew upgrade artifactize; the desktop app with brew upgrade --cask artifactize |
| Microsoft Store | Automatic; or Library → Get updates in the Store app |
Disk image, .deb or AppImage | Install the newer file from the release the same way |
| winget (not available yet) | After publication: winget upgrade lhj6102.Artifactize |
cargo binstall | cargo binstall artifactize artifactize-tools |
cargo install | cargo 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.
| Backend | Setup | List models |
|---|---|---|
openai | export OPENAI_API_KEY=... | artifactize models openai |
anthropic | export ANTHROPIC_API_KEY=... | artifactize models anthropic |
codex | artifactize login codex | artifactize 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, withstatusexplaining what changed. - Agent tools: an Agent eval using the built-in
read,grep,view_imageandsectiontools and a declaredjsontool, pass/fail schemas, backend and model selection, and a Human sign-off with the built-inopenandreadtools. - 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.
| Field | Rule |
|---|---|
name | Required Artifact name |
tags | Optional list of at most 64 unique nonblank strings, without ASCII controls |
basis | Optional boolean; true declares a no-eval input, waiving its own review obligations but not its dependencies |
fingerprint | Omission uses artifactsum; false disables reuse; otherwise an artifactsum or script table |
views.agent_tools, views.human_tools | Optional maps of declared tools |
views.agent_sessions.<name>.start, .stop | Tool-session commands: required command and args, optional execution_paths; no other fields |
views.agent_tools.<name>.session | Optional name of a tool session declared in the same Artifact; command Agent tools only |
evals.<id>.title | Required nonblank title |
evals.<id>.gate | Optional "blocking"; keeps a Human sign-off strict under --defer-human. Omission follows the request mode; no other values |
evals.<id>.mutex | Optional list of at most 64 unique hierarchical mutex tags; runtime and Agent evals only; no default |
evals.<id>.profile | Required runtime, agent or human profile |
evals.<id>.payload.instruction | Required nonblank instruction for runtime, Agent and Human evals |
evals.<id>.pass_schema, fail_schema | Optional owner-field JSON Schemas for executable evals; a Human eval’s take only form shapes |
evals.<id>.profile_variants | Up 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 befalse; 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. NeitherpatternPropertiesnor$refcan admit an undeclared top-level field. - Top-level
allOf,anyOf,oneOfandnotare 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,durationMsandexitCode. 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 schema | How the review shows it |
|---|---|
{ type = "string" }, optionally with minLength, maxLength | A text box |
{ type = "integer" } or "number", optionally with minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf | A number box |
{ type = "boolean" } | true / false (and – when optional) |
{ enum = ["a", "b"] }, only strings, type = "string" optional | A choice |
{ const = … } | Nothing: the value is filled in |
{ type = "array", items = { type = "string" } }, items optionally with minLength, maxLength | A list of text boxes, with + Add and ✕ per item |
{ type = "array", items = { type = "object", properties = … } } whose properties are the scalars above | Repeated 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:
| Kind | Reviewer | Verdict |
|---|---|---|
| Agent | An exact model through a chosen backend, using declared read-only tools | GREEN or RED with schema-checked owner fields |
| Human | A person who claims the waiting request and submits a sign-off | GREEN or RED with schema-checked owner fields |
| Runtime | A declared program and argument vector | Exit 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:
errorCode | Cause | Retried |
|---|---|---|
AUTHENTICATION | Missing, expired or rejected credentials: HTTP 401/403, no API key, no Codex sign-in or an expired auth file | no; stops the backend |
QUOTA | A billing, credit or plan usage limit (insufficient_quota, a Codex usage limit) | no; stops the backend |
RATE_LIMIT | HTTP 429 without a quota cause, after the retries or when Retry-After reaches the deadline | yes |
TRANSIENT | A connection failure, HTTP 5xx or overloaded, or an interrupted stream; an excessive requested retry delay keeps this classification | yes, before output |
TIMEOUT | The review’s timeout_ms deadline | no |
CANCELLED | Explicit Run cancellation (run cancel or MCP verify_cancel) | no |
PROVIDER_BUDGET_EXCEEDED | max_tokens or max_tool_calls was exceeded | no |
INVALID_RESULT | No valid verdict after the one format repair | no |
PROVIDER_ERROR | Any other provider failure: a rejected request, an unknown or different model, an incomplete response, a malformed tool call | no |
AGENT_ERROR | Anything else, such as an invalid test endpoint or an unusable tool | no |
BACKEND_STOPPED | Not 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 codexuses 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 startinghttp://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-dirforlogin,modelsandverify. Tokens are never stored inside a repository: when$STATE/authlies inside a git work tree, an artifactize workspace or--repo,login codexrefuses and names the folder, anddoctorwarns (State). Use a state directory outside it, orARTIFACTIZE_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_FILEset, for example to~/.codex/auth.json, artifactize reads that file’stokens.access_token(andtokens.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 withcodex login.logout codexleaves 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 asinstructions. Whenreasoningis set it sendsreasoning: {"effort": <reasoning>, "summary": "auto"}, withmaxpassed as is. The headers name the bearer token,ChatGPT-Account-Id,originator: artifactize,OpenAI-Beta: responses=experimentaland the review’ssession-id(prompt caching). Retries, budgets, tool results and usage work as foropenai. - Errors. A usage limit is
QUOTA: it names the plan and when it resets, and stopscodexfor the Run. A plain HTTP 429 is aRATE_LIMIT, retried. HTTP 401 and 403 areAUTHENTICATIONand say how to sign in again. A token endpoint that is unreachable or failing during a refresh isTRANSIENT. - Reuse. Like any backend,
codexrecords its backend, model, reasoning and limits on each result, outside the reuse key: acodexresult and anopenaioranthropicresult for the same eval and fingerprints reuse each other. - Models.
models codexasksGET <root>/models?client_version=0.160.0, the catalog that the Codex release whose protocol artifactize follows receives. It lists the models whosevisibilityislist, 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:
| Backend | Variable | Example | The fake answers |
|---|---|---|---|
openai | ARTIFACTIZE_OPENAI_BASE_URL | http://127.0.0.1:8080/v1 | POST <root>/responses, GET <root>/models |
anthropic | ARTIFACTIZE_ANTHROPIC_BASE_URL | http://127.0.0.1:8080 | POST <root>/v1/messages, GET <root>/v1/models |
codex | ARTIFACTIZE_CODEX_BASE_URL | http://127.0.0.1:8080/backend-api/codex | POST <root>/responses, GET <root>/models |
| Codex sign-in | ARTIFACTIZE_CODEX_AUTH_URL | http://127.0.0.1:8080 | POST <root>/oauth/token and <root>/oauth/revoke; login codex sends the browser to <root>/oauth/authorize |
- The root must be
http://orhttps://onlocalhost,127.0.0.0/8or[::1], without credentials, a query or a fragment. Any other value fails the review,modelsanddoctorwith a message naming the variable, before any connection. An empty value counts as unset. Foranthropic, a trailing/v1is accepted. - The API-key variable is still required and is sent to the fake unchanged, so set
a dummy key. For
codex, pointARTIFACTIZE_CODEX_AUTH_FILEat 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-dirorARTIFACTIZE_STATE_HOME). While a test endpoint is set,verifyrefuses to use a review store, so setARTIFACTIZE_REMOTE=off. Neverremote pusha state that holds fake results. doctorwarns while a test endpoint is set and reports it astestEndpoint.
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 = falsedisables reuse.fingerprint = trueis 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*.artfdeclaration. Explicit.artffile inputs failconfig check; they are not silently skipped. A directory whose name ends in.artfis 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/andnode_modules/..gitignorefiles 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-declarationfilesare 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_schemaandfail_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: artifactsumfilesandignore; scriptcommand,argsandfiles; orfingerprint = 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
gateand evaluation criteria: default profile kind, the fullpayloadincluding instruction and owner context,pass_schema,fail_schema, and runtimecommand/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
| Tool | Agent, model input | Agent, fixed args | Human args | Prints |
|---|---|---|---|---|
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:
readprints 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.listprints one page of the folder’s sorted entries as JSON, like the model’slist.sectionprints one section of a Markdown file. It finds the first heading whose text equalsheadingexactly, 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.helprunsprogram subcommand... --helpand prints its output. The program is a literal program or path. Bare names usePATH(withPATHEXTon 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 everyARTIFACTIZE_*variable (matched ignoring case). For a Human, it runs in the reviewer’s whole environment,ARTIFACTIZE_*variables included, such as anARTIFACTIZE_REMOTE_TOKENcredential. 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 orhttp:///https://URL in the desktop’s default application:xdg-openon Linux and WSL,openon macOS, the Windows shell (ShellExecute) on Windows. The target is passed as one argument, never through a shell. Other URL schemes, such asfile:ormailto:, 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". Startsartifactize-desktop graph --repo PATHusing the normal detached application handoff.artifactize-desktopmust 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 andconfig checkvalidate 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}/pathand{name}or{name}/path, wherenameis an Artifact, as in Human tools. A--flag=prefix is not accepted. Discovery andconfig checkvalidate reference names and target existence against the workspace, not the narrower review scope. An existing{other}/filecan therefore pass configuration validation without being a child. Preflight and calls require thatotheris 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-in | Arguments | Result |
|---|---|---|
graph | Fixed 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 500 | artifactId, 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 200 | artifactId, path, sorted entries: [{name, path, kind}], totalEntries, truncated, nullable nextOffset |
glob | {pattern, path?}; path must name a directory | Searched path, up to 200 sorted logical files, truncated |
grep | {pattern, path?, glob?, caseInsensitive?, maxResults?}; case-sensitive by default, maxResults defaults to/max 200 | Searched 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_DIRnames 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.
launchstarts 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 toolandtool checkkeep 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_msdoes not limit the handed-off program. A launch is neither a verdict nor proof of observation.outputwaits 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 astderr:label. Nonzero/signal exit setsisError: 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(USERNAMEis 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 torun(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 matchingpass_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:
-
Leave
artifactize server runrunning on that host, as above. -
On the teammate’s computer, open a terminal and replace
you@reviews-hostwith their SSH account and the host’s name:ssh -N -L 127.0.0.1:18417:127.0.0.1:8417 you@reviews-hostKeep this terminal open.
-Nstarts no remote command;-Lforwards 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. -
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:
verifyreuses results from the store and publishes its own GREEN/RED results;statuspredicts remote reuse;request submitpublishes Human sign-offs;remote pushsends 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/graphkind (artifactKindsin the record); - the profile, its execution options (backend, model, reasoning, limits, variant),
usage counters and an Agent result’s workspace-relative
executionPathspins; - 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. Theproducerand the Humanreviewerare what the publishing machine claims. - Any
publishtoken can assert any verdict for any key. Give untrusted CI (fork pull requests)readonly, andhumanonly to people who sign off. If a token leaks or is misused,server token revoke NAME --purgealso deletes the entries it published. verify --forcereads nothing from the store, which bypasses a suspect record, and publishes its fresh result, which becomes the store’s latest.cache rmdrops a local key’s records, andserver rmremoves a key’s records from the store.- Only
$STATE/remote.jsonand the environment configure the store, never.artfdeclarations, 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:
verifywarns 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=offdisables 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):
| Route | Scope | Result |
|---|---|---|
GET /v1/whoami | any | {"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 record | publish (+ 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 doctorchecks local readiness without calling a provider or starting a Run;modelslists available model IDs andpruneremoves 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:
| Table | One row per | Columns besides the saved JSON in data |
|---|---|---|
runs | Run | id, repo, status, nullable request_key |
requests | eval of a Run | id, 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 |
executions | execution | id, 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_meta | named value of the state | name, 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}}
| Field | Default | Meaning |
|---|---|---|
enabled | true | false saves no new conversation; saved ones stay readable |
maxBytes | 1 GiB | The store’s size that starts a collection; at least 1 |
targetBytes | 768 MiB when maxBytes is greater than 768 MiB; otherwise (maxBytes / 4) * 3, dividing first with integer division | The 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:
kind | Holds |
|---|---|
review | First 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) |
message | One 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 |
delivery | Display-only streaming data: turn, attempt, nonempty block identifier, contentKind (text or summary), text, and state (delta, complete or interrupted) |
attempt | One 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 |
end | The review’s result, or its errorCode and error |
send | A follow-up starts: its send number, the person’s text, the framing sent before it, filesChanged and the tools offered |
answer | The 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 onPATH. Relative and emptyPATHentries resolve against the command’s working directory. On Unix, an unset or emptyPATHtherefore searches that directory. On Windows, a name without an extension tries each extension inPATHEXT, in order, sonpxfindsnpx.cmd. WithoutPATHEXT, 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:
| OS | Spelling on disk |
|---|---|
| macOS, Windows | Artifactize 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. |
| Linux | Artifactize 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:
| OS | Opener |
|---|---|
| Linux | xdg-open |
| WSL | xdg-open, inside the Linux distribution |
| macOS | open |
| Windows | the 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.
- Command reference
- Runtime CLI
- State
- Declarations (.artf)
- Artifactsum
- Fingerprint scripts
- Completed result reuse
- Cache limits
- File Artifacts
- Eval dependencies
- Scoped input library
- Runtime execution library
- Agent backends
- Codex
- Test against a fake provider
- Agent tools
- Tool diagnostics
- Human tools
- Human reviews
- Built-in tools
artifactize-toolsCLI- Local diagnostics and maintenance
- Review store server
- Remote review store client
- Operating systems
- Privacy policy
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 fullhttp://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
statusbefore and after. - “Why did this run again?” or “verify is slow”: it reads
statusand 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:
- 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.
- 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 = trueonly when your team agrees nobody reviews it, never to get around unfinished reviews. - Writes small declarations in today’s format,
validates them with
config_check, and shows withstatuswhat would run and what would be reused. It then tells you what changed. - 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_logsorsession_showand 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
| Tool | Arguments | Core operation / answer |
|---|---|---|
verify_start | scope, optional request_key, jobs, speculate, defer_human | Submit via runs::start; compact Run observation, not approval |
verify_status | run | runs::status: saved state, counts, elapsed seconds and next_check |
verify_wait | run, optional seconds | runs::wait: Run end or bound; default 30, integer 0–40, whole call under 45 seconds |
verify_watch | run; optional standard _meta.progressToken on the tool call | Change-driven runs::watch, up to 30 seconds; progress messages contain JSON Run events; compact observation at return |
verify_result | run, optional cursor | runs::result: verdict and up to 20 concise findings, next_cursor; ready:false while active |
verify_cancel | run | runs::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.
| Tool | Arguments (optional unless marked required) | CLI counterpart |
|---|---|---|
status | scope (defaults all) | status --all or selected status |
config_check | none | config check |
config_graph | scope (defaults all), impact (false) | config graph [ARTIFACT] [--impact]; MCP also accepts eval/multi-selection |
request_logs | id, stream (stdout/stderr/both), tail, start, end, search | request logs ID with matching flags |
run_list | limit (0–20, default 20), offset | run list --limit 20 --offset N for this repository |
run_show | run | run show RUN |
request_list | run | request list [--run RUN], restricted to this repository |
request_show | id | request show ID |
session_show | reference, summary (default false) | session show REF [--summary] |
tools_check | eval, or artifact/audience/tool | Static tools check; audience is agent or human |
doctor | none | doctor |
models | provider (openai, anthropic, codex) | models PROVIDER |
remote_status | none | remote status |
cache_show | key, 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, usingverify_result’s schema. Follow itsnext_cursorthroughverify_resultfor 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.
| Command | Flags | Output | Exit |
|---|---|---|---|
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-human | Run ID or {run,state}; final text/JSON with --wait; JSON-line events with --watch | 0 accepted; outcome with --wait or --watch |
status [SELECTOR] | --profile NAME, --recursive, --force, --fingerprint-jobs N (CPUs); default --all | text or JSON | 0 satisfied, 1 not |
mcp | MCP stdio protocol; diagnostics on stderr | 0 on clean disconnect | |
config check | text or JSON | 0 | |
config graph [ARTIFACT] | --impact | text or JSON | 0 |
run list | --repo-only | --all, --limit N (50), --offset N (0) | text or JSON | 0 |
run watch RUN_ID | JSON-line updates, eval results, final validation | Run outcome | |
run cancel RUN_ID | cancellation acknowledgement JSON | 0 | |
run show RUN_ID | --wait, --timeout-ms MS (600000, needs --wait) | JSON | 0; outcome with --wait |
request list | --run RUN_ID | text or JSON | 0 |
request show ID | JSON | 0 | |
request logs ID | --stream stdout|stderr|both, --tail N, --start N, --end N, --search TEXT | bounded evidence JSON | 0 |
request claim ID | --reviewer NAME ($USER) | JSON | 0 |
request unclaim ID | --reviewer NAME ($USER) | JSON | 0 |
request tool ID TOOL | --reviewer NAME | text or JSON | 0; 2 tool error |
request submit ID | --verdict GREEN|RED, --fields JSON | --fields-file PATH, --reviewer NAME | JSON | 0, also for RED |
session show REF | --summary | text, or the stored events (or the summary) as JSON | 0 |
session send REF MESSAGE | text or JSON | 0 | |
cache list | --history | text or JSON | 0 |
cache show KEY | --history | JSON | 0; 4 missing |
cache rm KEY | JSON | 0 | |
tools check [EVAL] | --eval ID, --artifact ID, --audience agent|human, --tool NAME, --execute, --args JSON | JSON | 0 ready, 1 not |
tools read PATH | — | UTF-8 text | 0 success, 1 tool error, 2 usage |
tools list [PATH] | — | JSON | 0 success, 1 tool error, 2 usage |
tools glob PATTERN [PATH] | — | JSON | 0 success, 1 tool error, 2 usage |
tools grep PATTERN [PATH] | --glob GLOB, --case-insensitive | JSON | 0 success, 1 tool error, 2 usage |
tools section PATH HEADING | — | Markdown text | 0 success, 1 tool error, 2 usage |
tools help PROGRAM [SUBCOMMAND...] | trailing arguments passed to PROGRAM before --help | text | 0 success, 1 tool error, 2 usage |
tools open TARGET | — | opens default application | 0 success, 1 tool error, 2 usage |
login codex | sign-in URL on stderr; may read a pasted redirect URL from terminal stdin | Signed in with Codex.; JSON {"provider":"codex","signed_in":true} | 0 |
logout codex | removes local tokens and attempts revocation; leaves read-only auth files alone | Signed 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 stdin | text or JSON | 0 |
remote logout | text or JSON | 0 | |
remote status | text or JSON | 0 signed in, 1 not | |
remote push | --dry-run | text or JSON | 0 |
models openai|anthropic|codex | text or JSON | 0 | |
doctor | text or JSON | 0 ready, 1 hard error | |
prune | --older-than DURATION, --dry-run | text or JSON | 0 |
monitor [PATH] | starts on ALL; PATH or --repo PATH selects only the initial repository; --all conflicts with a path | terminal UI | 0 |
review [REQUEST_ID] | --repo PATH (a repo: search term), --all (the default; not with --repo), --reviewer NAME ($USER) | terminal UI | 0 |
server run | --listen ADDR (127.0.0.1:8417) | Listening on http://ADDR/; JSON {"listen":ADDR} | 0 |
server token add NAME | --scopes read,publish,human | the token, once, and a reminder to store it; JSON {"name":NAME,"scopes":[...],"token":TOKEN} | 0 |
server token list | text or JSON | 0 | |
server token revoke NAME | --purge | JSON (also without --json) | 0 |
server rm KEY | JSON | 0 |
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.
| Modes | Dispatch and settlement |
|---|---|
| Neither | Wait for dependency GREEN evidence; the owned Run waits for people |
--defer-human | Wait for machine gates; run past deferrable sign-offs provisionally |
--speculate | Start all requested kinds before unresolved gates; wait for people |
| Both | Start 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"):
| Term | Matches |
|---|---|
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:mine | claimed by you ($USER or --reviewer) |
is:claimed | claimed by anyone |
is:unclaimed | a 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.
| Key | Human Detail action |
|---|---|
c | Explicitly claim a waiting request; active only with Detail focused |
g / r | Choose GREEN / RED and open its form, after claim |
u | Release your claim without a verdict |
| Ctrl-G / Ctrl-R | Choose or switch verdict while editing |
| Ctrl-S | Submit the chosen form |
| PgUp / PgDn | Scroll the focused fields or completed-result viewport, or the Tool log with Tools focused; Tab/arrows still select fields |
| Ctrl-U | Release while editing |
| Tab / Shift-Tab | Switch Tools and Fields; in edited Fields, Tab moves between fields and Shift-Tab switches to Tools |
i / t | Expand the instruction / toggle Technical, while not editing |
| Enter | With Tools focused, run the selected tool at once (after claim) |
| Esc | Cancel work, then stop editing, then leave Detail |
Enter on + Add / Ctrl-D | In 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
$USERunless 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/kalso 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. Alaunchtool reports “launched” and the UI continues. Anoutputtool 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(orlaunched,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 findingsrow: 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 standalonereviewas 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 standalonereviewstarts 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 width | Each side pane |
|---|---|
| Fewer than 100 columns | 16 columns |
| 100–139 columns | 28 columns |
| 140 columns or more | 36 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.
ALLselects 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 optionalcommonDir,worktreePathandbranchalongside theirrepoPathworkspace. - 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/jpast the last Run loads older Runs, and Up/kat 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 pendingwhile 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 state | Meaning |
|---|---|
✓ done | GREEN, executed or reused |
✗ / ! failed | RED / ERROR |
◐ / ? / · in progress | Running, waiting for a Human (with claimant), awaiting admission, waiting for a backend slot or an explicit --jobs cap, or joining an execution |
… waits for X | A running Run waits for X’s required evals to be GREEN |
⊘ blocked by X | An upstream RED or blocked eval prevents execution |
○ / $ / - not run | A 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
| Key | Action |
|---|---|
| Enter / → | Next level; on a tree row, open Detail |
o | Open the selected tree row’s Detail |
| ← / Esc | Step back; Esc never quits |
| Tab / Shift-Tab | Cycle Scope, Runs and the tree; inside Detail, switch sub-areas |
↑/↓ or j/k | Select 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 |
r | Refresh 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-C | q quits outside editing/work; Ctrl-C requests cancellation of any busy Human lifecycle job; otherwise it invokes quitting |
| F2 | Toggle 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.
dopens 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. Usesession show --jsonfor 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,
qquits,?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.