Agent workflow
How coding agents deliver a work package (WP) from docs/ROADMAP.md. The orchestrator assigns
WPs, assembles stacked PRs with gh stack, and merges. Agents never run gh stack, never merge,
and never push to main.
Per-WP protocol
Section titled “Per-WP protocol”-
Read first:
CLAUDE.md,docs/go-standards.md, the WP row indocs/ROADMAP.md, and the sections ofdocs/ARCHITECTURE.md/docs/URL-DIALECTS.md/docs/FORMATS.md/docs/CONFIG.mdthe row cites. Reference source lives in.refs/of the main checkout (gitignored, so not in worktrees: find it withgit rev-parse --path-format=absolute --git-common-dir, then../.refs;scripts/clone-refs.shpopulates it).rgit before implementing anything that mirrors imgproxy, imagor, unpic or libvips. -
Branch: work in your assigned worktree on branch
m1/wp-XX-<slug>(orm2/,m3/), created from the base branch the orchestrator names (usuallymainor the WP you depend on). Never rename the branch. -
Implement exactly the WP scope. Interfaces come from
ARCHITECTURE.md; if you must deviate, write the reason in the PR body section “Deviations” and update the doc in the same branch. Dependencies on other WPs that are not merged yet: code against the interface file on the base branch; do not stub other packages in your branch. -
Verify before claiming done (all must pass; paste the output summary in your report):
Terminal window golangci-lint fmt --diff && golangci-lint run ./...go vet ./... && go fix -diff ./... && go mod tidy -diffgo test -race -shuffle=on ./...Run these inside the base image container, not on the host:
task docker:lint,task docker:test(they mount the worktree into thebuilderstage ofdocker/base, imagextandard-base:devlocally orghcr.io/xantiagoma/xtandard-image-base:slim). The Docker image is the product; a host-only pass (Homebrew libvips) proves nothing and is not accepted as verification. Pure-Go packages may additionally run on the host for speed. -
Commit in small, focused commits with imperative subjects, and end each message with the attribution trailer
Co-Authored-By: Claude Opus 5.5 <[email protected]>(the model doing the work; the orchestrator states the current one in each brief). Use the commit author the brief gives you. -
Push
git push -u origin <branch>. Do not open a PR; the orchestrator does. -
Report (final message, under 300 words): branch name, files added, how the done criteria in the ROADMAP row were verified, benchmarks if the WP asked for them, deviations, and anything the next WP needs to know.
Rules that get PRs rejected
Section titled “Rules that get PRs rejected”- Any
import "C"outsideinternal/vips(andinternal/video/ffmpegunder theffmpegbuild tag, ADR 0010). os.Getenvoutsideinternal/config; globals;init().- Tests that
time.Sleep, use testify, or compare images byte-for-byte. - A new third-party module without a line in the relevant ADR and a licence in MIT/BSD/Apache/ISC.
- GPL/AGPL code or binaries anywhere, including test fixtures. The only exception is the opt-in
gplbase image flavor (x264, x265, GPL FFmpeg, and the Ghostscriptgsprogram run as a subprocess;docker/base/versions-gpl.sh, ADR 0006 amendments 2026-09-27, ADR 0011); Go code and slim/full stay GPL-free. - Touching files owned by another WP without saying so in “Deviations”.
Decisions already taken (do not reopen)
Section titled “Decisions already taken (do not reopen)”- GIF→video (2026-09-27, FORMATS 5.1):
webm= VP9 (libvpx) everywhere;mp4= H.264 (libx264) ingplimages and AV1-in-MP4 (libaom) infull;hevc(libx265) only ingpl. The Go side detects encoders at startup withavcodec_find_encoder_by_name; no openh264. - No GPL code in
slimorfull: their FFmpeg is LGPL-only (--disable-gpl --disable-nonfree), no poppler, exiv2, x264/x265 or GPLdssim. The opt-ingplflavor (WP-70b) adds exactly x264, x265 and FFmpeg with--enable-gpl, and (WP-99) the Ghostscriptgsprogram (AGPL-3.0-or-later, run only as a sandboxed subprocess whenXI_FORMATS_EPS_ENABLED=true, ADR 0011), labelledAGPL-3.0-or-later AND GPL-2.0-or-later AND MIT; patent review is the deployer’s. - HEVC: decode stays (libde265, FFmpeg
hevc); HEIC encode (kvazaar) off by default; HEVC video encode only ingpl(libx265). - Detection model files: YuNet
face_detection_yunet_2023mar.onnx(MIT) and YOLOX-syolox_s.onnx(Apache-2.0), shipped in thefullimage under/usr/share/xtandard/models/with their licences (WP-70). - HEIC: decode on, encode off by default (
XI_FORMATS_HEIC_ENCODE=false). - Invalid URL → 400;
XI_COMPAT_IMGPROXY_STATUS=trueswitches to 404 like imgproxy. - Disallowed source → 403 (imgproxy uses 404; same compat switch).
- POST upload processing: deferred until after M3.
- OpenStack Swift: not a source; use its S3 middleware.
- Unsigned client-generated URLs default to
restrictedwith a startup warning listing allowed widths. - Detection models: YuNet (faces) + YOLOX-s (objects), user-mountable ONNX.
- Playground (WP-80): inside the binary, off by default (
XI_PLAYGROUND_ENABLED=false), served on the admin listener at/_playground/(XI_PLAYGROUND_PATH), and on the public listener only withXI_PLAYGROUND_PUBLIC=true(default false). No build step: plain HTML/CSS/JS embedded withgo:embed, no bundler, no npm, no CDN scripts (works air-gapped; CSPdefault-src 'self'). URLs are signed server-side; keys never reach the browser.