Changelog#
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased#
Changed#
The Linux packages carry the family’s own GStreamer (owner rule 2026-10-17: never the distro’s).
packaging/app.jsonnamesgst-launch-1.0, five plugins (libcamera optional) and the seven elements--backend gstreameruses; the hub’s packager bundles them, proves each element loads from the package alone, and the launchers put the bundled tool first onPATH.get_gstreamer_envpasses everyGST_variable on, sogst-launch-1.0gets that environment.The Linux packages start on Debian 13 and Ubuntu 24.04: the image’s glibc travels with the bundle’s Python, and the
.debno longer asks forlibc6 (>= 2.43).The README logo is the family’s ant (2026-10-17, owner: one icon in every repository), drawn from OmniAccelerANT’s
assets/icons/kataglyphis_app_icon.svg.
0.0.32 - 2026-10-17#
The default model is ModelANT’s DEIM-D-FINE-N (Apache-2.0), fetched by checksum; the tracked
yolov26m.onnxis gone (2026-10-17). It was Ultralytics YOLO26, which is AGPL-3.0: an MIT project cannot ship it, and 78 MiB of weights did not belong in a code repository. Its history stays as it is (owner decision 2026-09-15).resources/models/models.locknames the.onnx, its LICENSE and its NOTICE by SHA-256 and URL, with OxidANT’s format and entries, and the newscripts/fetch_models.pyputs them beside it: stdlib only, one implementation for Linux and Windows, with OxidANT’sfetch-models.shsemantics (--dest,--lock, a cache keyed by checksum in$KATAGLYPHIS_MODEL_CACHE, else the XDG cache or%LOCALAPPDATA%, both copies checked,.partwrites, mode 644).ci_packaging.sh,Build-Windows.ps1andBuild-WindowsArm64.ps1run it before packaging, and the packages ship all three files, since Apache-2.0 asks for the NOTICE to travel with the model. A missing model now names that command, in the self-test’s JSON and in the log.A plain resize for models that ask for one. DEIM-D-FINE-N wants its 640x640 input stretched, not letterboxed, and says so in its ONNX metadata (
kataglyphis.preprocess=stretch). The monitor and the self-test read it (wants_stretch),preprocess(..., stretch=True)resizes each axis on its own, andDecodeConfig.scaletakes an (x, y) pair to undo it. Any other model keeps the letterbox, so a YOLO export passed with--modeldecodes as before; the [1, 300, 6] output is the layout the generic decoder already read. The self-test reports which one it used ("preprocess"). The window title, the banner and the CLI description no longer say YOLOv10.In
:lateston arm64: on OxidANT’stwo_cats.jpgthe monitor’s own pre/postprocess, from source and from the packaged bundle’s compiled modules alike, finds both cats (class 15) at 0.909 and 0.604 over its default--conf0.5.ci_packaging.shfetched the model from GitHub into a fresh container, and the tar.gz, deb and AppImage self-tests passed with it. The seven lint gates and the six static-analysis gates pass.
0.0.31 - 2026-10-16#
Every push to
mainreleasesv<version>, and only when all five lanes are green (2026-10-16,release.yml, the family’s rule in ANTfrastructure’sbranch-and-release-strategy.md). The release carries what the pushedv*tags did, every lane’spackages/(the.deb, AppImage and tarball of both Linux arches, both Windows arches’ MSI, MSIX, zip and test certificate), now from one run:scripts/linux/collect-release-assets.sh, whose suite runs with Linux x64 on every push, gathers them and refuses two files of one name. The tag triggers and the four lanes’releasejobs are gone, and with them thecontents: writethaton-request.ymlgranted. The hub moves to 0910c9ba, whose Windows app bundle leaves the PyPIonnxruntimeout of the lock export: the image’s CPython 3.15 has no PyPI wheel of it, which stopped Windows x64’s packaging (520aca2a). Windows arm64’s packaging stopped at pyyaml on 3.15, which has no cp315 win_arm64 wheel; the hub moves on to a7800f00, whose cross bundle builds it as a pure-Python wheel from uv.lock’s hash-checked sdist.A push runs Linux x64 and the gates; Linux arm64, Linux riscv64, Windows x64 and Windows arm64 run on request (2026-10-16), the family’s CI rule (ANTfrastructure’s
branch-and-release-strategy.md) that OmniAccelerANT, OxidANT, AccelerANTgine and BeschleunigerBallett follow.on-request.ymlstarts the lanes a pushed commit message asks for with the ci token (windows,arm64,riscv64,all); every lane keeps its Run workflow button, and a pushedv*tag still builds and attaches the release packages as before. Each lane’s concurrency group names the lane and its caller, so a push, a request and a run by hand never cancel each other.ruff 0.17.0, ANTfrastructure 3a0ab1c3 -> 0768e5e0, and the 2026-10-13 updates; the ty gate is green again. The hub’s tool batch moves
RUFF_VERSIONto 0.17.0, sopyproject.toml,.pre-commit-config.yamlanduv.lockfollow it in the same commit, as the consumer-pins gate requires. The lock’s maintenance moves nine more packages, mlflow 3.17.1 among them. mlflow still caps pyarrow below 26, so the override stays. onnxruntime-genai stays at 0.17.0, the hub’s version, because 0.17.1 is on PyPI but has no tag on GitHub.benchmarks.ymltakes setup-uv v10.3.0 and ollama 0.40.3, both by digest. ty had flaggedLaneProcesssince the lock moved to ty 0.0.86: it readlistens_hereas always None, because_find_listenerset it as a side effect. That failed static analysis on every lane._find_listenernow returns the pair, and its tests pass unchanged.docs/source/monitoring.mdtakes the formatter’s blank line in a code block, so the pre-commit hooks pass. In:latest: the seven lint gates and the six static-analysis gates pass. The tests pass on 3.14 with the chain ORT (2548 passed, 75 skipped), on 3.14t (2542, 77) and on CPython 3.15.0 with the test extra (2541, 78).OrchestrANT installs on the images’ Python 3.15. The lock is regenerated with uv 0.13.0 (
uv lock --upgrade): numpy 2.5.4, scipy 1.18.1, contourpy 1.4.0, pyarrow 26.0.0 and the other compiled dependencies now carry cp315 wheels. Three changes keep it installable and correct there:[tool.uv] override-dependencies = ["pyarrow>=26"]: mlflow 3.17.0 caps pyarrow below 26, and no pyarrow below 26 has a cp315 wheel. mlflow uses pyarrow only in its HDFS artifact store, whosepyarrow.fsAPI 26 keeps.py-cpuinfo2>=10.1replacespy-cpuinfo: pytest-benchmark 5.3 moved to the fork, and both installcpuinfo/.orchestrant.yolo.monitorimports the samecpuinfo.get_cpu_info.ai-edge-litertapplies below 3.15 only: PyPI has no cp315 wheel or sdist (2.3.0, 2.4.0.dev nightlies), and the images ship one built from source. The Windows scripts name no Python any more:Build-Windows.ps1runs the image’s pinned X.Y and X.Yt, and static analysis, packaging and the arm64 lane take the pin through the hub’sGet-PinnedPythonVersion(ANTfrastructure 97118139 -> 156f79f4, CON91). Proven on the host:uv lock --checkwith uv 0.13.0; the base and test extras on CPython 3.14.8 pass 1725 tests, 45 skipped; a dry-run of the image torch stage’s sync on CPython 3.15.0 resolves 180 packages.
The thread-safety tests on the installed cp314t wheel run through the hub’s step.
scripts/linux/ci_packaging.shcalls the hub’stest-free-threaded-wheel.sh(hub ff91d46d, CON66) with this repo’s knobs, in place of the localci_free_threaded_wheel_tests.sh, which is gone. Same venv, same tests, sameORCHESTRANT_REQUIRE_COMPILED=1andpython -I; the lock’s set now comes fromuv export --locked. A local run of the wrapper in:latestproved 64 compiled modules and passed 8 tests, 1 skipped (no free-threaded OpenCV).import orchestrantno longer loadsmatplotlib.pyplot: 86 ms instead of 800 ms on x64. pyplot was 0.72 s of the 0.80 s, and under riscv64 QEMU that import cost 31-37 s per subprocess (bench_agent.py --listtook 41.9 s against a 60 s timeout).MetricsPlotterimports it on its first plot; the tests that patchmatplotlib.pyplot.*are unchanged, and a new one fails when the package import loads pyplot again (checked against an eager import). Hub CON83’s other two calls stay as they are: bench grading keeps its pytest plugins, sincebenchmarks/testsalready disables their autoload for its nested runs and a native run pays 1 s; and the process ceiling stays probed once, so it cannot drift between launch and assertion.Every compiled module builds with clang-cl on Windows; the first one used MSVC’s
cluntil now.ClangBuildExtset the compiler’scctoclang-clbefore the firstcompile(), which then raninitialize()and putcl.exeback. Soorchestrant/dummy.ccompiled with MSVC’sclin every Windows build, GIL and free-threaded, x64 and the arm64 cross lane (run 38221408508:D9002on its--target).build_extensionnow initializes the compiler first. Thespawnhook that was meant to catch this is gone: setuptools 84, which the isolated build resolves, never calls it. A local run of the Windows x64 lane compiled all of them with clang-cl and none withcl.exe.The Windows arm64 cross lane ships the cp314t wheel too, and the device proves it. The hub (f15de3c9, CON75) builds it on the cross lane with the image’s free-threaded trees, gates it, and packs it with a device kit in
dist/windows-arm64/free-threaded. Thewindows-11-armrun job then loads every compiled module with the GIL off.setup.pyadds--target=aarch64-pc-windows-msvcto the extensions’ compile arguments whenVSCMD_ARG_TGT_ARCHisarm64. Without it,ClangBuildExt’s clang-cl compiled for the host.In
:winarm64,orchestrant-0.0.30-cp314-cp314t-win_arm64.whlbuilt this way and passed the tag, import and ARM64 gates.
The Windows x64 app bundle builds again, and the arm64 test staging asks only the image.
Run 38086634222 failed the bundle’s arch gate on four violations, all in the runtime’s
ensurepippip wheel: distlib’s I386 and ARM64 launcher templates. The hub pin moves to 712ba4e6, whose gate reports such templates instead of counting them. A local run of this lane on that gate passed every step, the app bundle and installers included.scripts/windows/Stage-Arm64Tests.ps1installs the pytest stack only from the:winarm64wheel store and stops when it lacks one, instead of falling back to PyPI (hub CON67). Run 38086634226 already staged from that store.
The compiled modules are audited for free-threading, and CI runs the cp314t wheel from eight threads. The cp314t wheel has been built and proved on both Linux lanes since the hub pin 4f0cb7f7:
64 compiled module(s) of orchestrant loaded on free-threaded 3.14.8; the GIL stayed disabled, run 38086634220. That proof creates each module without running its body. The 3.14t test leg ran the source, so no compiled code ever ran with the GIL off. The audit found five places where module or instance state was shared without a GIL-free guard:gateway._SERVED, the process-wide reply tally, counted with a bareCounter +=. Concurrentpost_jsonreplies lost counts. It now takes a lock, andserved()reads under the same lock.openai_api.gpu_probe(), the one-per-process GPU probe: two first callers each opened a probe and leaked one. It is now double-checked under a lock.The GStreamer reader queued a view of its reusable
readintobuffer, which the next read overwrote while the consumer still held it. It now queues a copy. The reader also raisedqueue.Fullwhenopen()put its probe frame back concurrently, and readself.processthree times whilerelease()set it toNone. Both paths now go through_offer_frameand a single read ofprocess.streaming.FrameCapture.get_framecheckedempty()and then calledget_nowait(). A second stream on the same capture could take the frame in between and kill the first withqueue.Empty. It now catchesEmpty, and the producer usesput_nowait.The monitor read the log deque with
list()while loguru sinks on other threads appended to it, which raisesdeque mutated during iteration. The neworchestrant.pipeline.logging.log_buffer_linescopies it under the deque’s own lock.
tests/unit/test_thread_safety.pydrives each of these from eight threads, on every leg. It also checks that importing every module leaves the GIL off on a free-threaded interpreter.pipeline.capturenow resolves its exports lazily, aspipelineandyoloalready did. That lets the GStreamer backend import without OpenCV, which has no cp314t wheel, so the 3.14t leg runs these tests too.scripts/linux/ci_packaging.shthen runsscripts/linux/ci_free_threaded_wheel_tests.sh. That step runs the same file against the installed cp314t wheel on 3.14t, and fails when a module under test came from source (AGENTS.md § 2).openai_api.pychanged, so the lab’stool_sha256changes with it. Measured locally in:latest:Before the fixes, the file failed 6 of its tests against a cp314t wheel, and 5 on the 3.14t source leg (lost gateway counts, 8 probes instead of 1, an overwritten frame, the reader’s
'NoneType' object has no attribute 'stdout',deque mutated during iteration). It failed 4 on 3.14 with the GIL.After them, the wheel step passes 8 tests with 1 skipped, and the GIL stays off.
A wheel built without
freethreading_compatiblefails at import:The global interpreter lock (GIL) has been enabled to load module 'orchestrant'.
The riscv64 lane’s 20 failures, and the 3.5 h test leg that ran it into the 6 h job limit. Three causes, none of them riscv64 code (run 37520541295):
RLIMIT_NPROC counts every task of the UID, and a container’s
/procshows only its own. In CI the runner agent is uid 1001 as well, so the candidate ceiling (visible tasks + 64) sat below the real count. Under qemu-user every candidate’s start creates a QEMU thread, and 17 tests died withqemu_thread_create: Resource temporarily unavailable.bench_coding._nproc_ceilingnow takes the kernel’s own count, measured in a fresh interpreter by starting a thread under trial ceilings. With 80 tasks of uid 1001 in a second container, the five affected modules went from 126 failures to none under the riscv64 image.qemu-user accepts a guest’s RLIMIT_AS and drops it, so a candidate there had no address-space ceiling (2 tests). The grader notices that the limit does not read back and hands the same ceiling to QEMU as
QEMU_RESERVED_VA; the report’sgrader_selfcheck.rlimits.as_enforced_bysays which held. A new test proves on every arch that twice the ceiling fails and a quarter of it does not.Every nested
python -m pytestinbenchmarks/testsloaded this venv’s eight pytest plugins: 49 of its 60 s under QEMU, pytest-md-report’s chardet import alone 32 s. The 600 s self-test timed out on it (the 20th failure). The suite’s conftest now setsPYTEST_DISABLE_PLUGIN_AUTOLOADfor its subprocesses; the toy repos they grade use no plugin. The two agent-bench modules, 11575 s of the lane’s 12751 s, took 3806 s under the riscv64 image on the dev box (the self-test 340 s), all passing.bench_coding.py’stool_sha256changes with it. The riscv64 wheel’s cross link is the hub’s to fix: it took the hostlibc.soand failed (run 37329297990).
A proved free-threaded wheel beside the GIL one.
pyproject.tomldropsProgramming Language :: Python :: 3.14t, which is no trove classifier (PyPI refuses an upload that names it), for the officialProgramming Language :: Python :: Free Threading :: 2 - Beta: Beta, because the 3.14t test legs gate. The hub’s packaging drivers read that classifier: once the hub pin carries them, they build a second Cython wheel on the image’s 3.14t (cp314-cp314t), then load its 64 compiled modules in a fresh 3.14t venv and fail when the GIL comes back on.setup.pytherefore sets Cython’sfreethreading_compatible=True(cython>=3.1, the first with the directive). Proved locally with the hub’s new drivers::latestshipsorchestrant-0.0.30-cp314-cp314{,t}-manylinux2014_x86_64.manylinux_2_17_x86_64.whl. The hub now runs thepackagingextra’s auditwheel, so the Linux binaries are repaired for the first time; until now they shipped aslinux_x86_64.:winamd64shipsorchestrant-0.0.30-cp314-cp314{,t}-win_amd64.whl. Both app bundles still take the cp314 wheel.The suite against the installed cp314t wheel on 3.14t ends with
sys._is_gil_enabled()False and the same results as the cp314 wheel on 3.14: Linux 2418 passed, Windows 1637. The failures both wheels share (57 and 25, plus 3 errors) come from testing a source-stripped compiled install, such as run-start source hashes and subprocesses on the image’s/opt/venv, not from free-threading.
Dependencies follow the hub’s 2026-10-07 Renovate bumps (hub 07388b85, which this repo now pins): torch 2.14.1 and torchvision 0.29.1 in every extra and the riscv64 git sources, onnxruntime-genai(-cuda) 0.17.0 (the newest version tagged on GitHub; PyPI’s 0.17.1 has no tag the image could build), ruff 0.16.10, and in
uv.lockiree-base-compiler/runtime 3.12.0 and onnxruntime 1.30.0. The image’s torch stage and the consumer-pins gate compare these with the hub’sversions.env, so they move with the hub pin.The arm64 test staging installs pytest and its plugins offline from the image’s wheel store when
PYTHON_WHEELScarries them (hub CON67: 31 cp314 wheels, SHA-pinned in the hub’sversions.env). An image published before that still resolves them from PyPI, and the log says which source it used. Proven on the host: the 33 pinned wheels resolve the whole set with--no-index.benchmarks/testsjoinstestpaths, so every lane runs the LLM lab’s offline suite, not only the path-filtered x64 job inbenchmarks.yml, which is gone. Its ten harness modules skip on Windows with a reason: the candidate sandbox needs POSIX rlimits, process groups and shell fixtures. On Windows the suite runs 795 tests (20 skipped); in:latestboth Linux legs run 2531.Fixed while joining: the suite now runs under the project’s
filterwarnings = ["error"], which turned resource leaks collected after the session into a failed run.client.http_error_detailandopenai_api._get_jsonclose theHTTPErrorthey handle; against a real server, an unclosed one holds its connection until GC.The offline guard closes the socket it refuses, which
socket.create_connectiondoes not do for a non-OSError.benchmarks/tests/conftest.pycloses everyHTTPErrora test made. Its blanket ResourceWarning ignore now marks only its own directory:pytest_collection_modifyitemssees the whole session, so it had applied totests/too.
A riscv64 lane:
.github/workflows/linux-riscv64.ymlruns the riscv64 image under QEMU on an amd64 runner and pytest on it (tests only,test-extras: test). It needs ANTfrastructure 63638861, which the pin moves to.uv.locknow covers riscv64:[tool.uv] environmentsnames it as a real environment, torch/torchvision resolve through the git sources, andpytorch-customis empty there on purpose. Every non-riscv64 row saysplatform_machine != 'riscv64'to keep impossible cross-products out.
0.0.30 - 2026-10-02#
The first release whose tag attaches the arm64 packages to the release itself: the
Windows arm64 lane’s zip, MSI and MSIX and the Linux arm64 lane’s tar.gz, deb and
AppImage, each proven in CI today. Also new under it: the device pytest run now
reports, measures and benches like x64’s (TESTS: passed=921 failed=0 skipped=40
on windows-11-arm), with the reports uploaded from the device.
Added#
MSIX for Windows, and the Linux packages for arm64 too.
The MSIX puts all three commands on
PATHas execution aliases. It is signed with a test certificate, which is shipped next to it as…-test-signing.cer; trust that certificate first, as described in README § Installation.The arm64 CI leg now builds its own tar.gz, deb and AppImage.
CI proves the MSIX unpacked, and by its signature against that
.cer. Server Core cannot install an MSIX.
Windows arm64: the zip, MSI and MSIX (
windows-arm64-cross.yml, “Windows arm64 · cross build + test”; renamed from “… + run” on 2026-10-02, now that the arm64 job runs the pytest suite, not a launch smoke only). They are cross-built in the hub’s:winarm64image from the pure wheel. Awindows-11-armrunner then starts the bundle and the unpacked zip withyolo-monitor --self-test. With:winarm64rebuilt without NVIDIA (owner decision 2026-10-01; the stack belongs in:winarm64-nvidia) the packages are zip 204 MB, MSI 167 MB and MSIX 207 MB (run 36876194988). Before the rebuild, the arm64 OpenCV’s CUDA modules made them about 900 MB.
Changed#
CI follows the fleet’s workflow convention.
ubuntu-26.04-amd64-arm64.ymlis split intolinux-x64.yml(“Linux x64 · build + test”) andlinux-arm64.yml(“Linux arm64 · build + test”), onearchesrow each. Both attach their packages to av*release. Every lane starts frompermissions: contents: read, and the README badges are in the fleet order.The Windows bundle carries one ONNX Runtime: OpenCV’s DNN module now loads the one in
onnxruntime\capiinstead of a second copy (561 → 550 MB unpacked).
Fixed#
tests/conftest.py(thentests/unit/conftest.py) selects matplotlib’s Agg backend throughMPLBACKEND. This stops TkAgg crashing the Windows CI container. The Benchmarks workflow’s frontend suite, which runs without matplotlib, loads again; on the v0.0.29 tag it had failed on that import.Every lane runs the whole configured suite, and 3.14t really tests (2026-10-01).
The suite: CI used to run
tests/unitonly. It now runstestpaths, sotests/integrationruns too: through the hub on Linux, and through a barepytestinBuild-Windows.ps1.3.14t: the free-threaded leg used to sync nothing and pass on a warning. It now syncs only the
testextra and gates. To get there:atheris is gone: it was unused and has no cp314t wheel.
orchestrant.yololoads its re-exports lazily.yolo/core/preprocess.pyimports OpenCV only when it resizes. With those, the pure-numpy tests import without cv2, and only the OpenCV/ORT-bound module skips. Measured on free-threaded 3.14.7 in:latest: the whole unit suite passed.
Windows arm64 runs the pytest suite on windows-11-arm, under the arm64 bundle’s own
python.exe, so it tests the shipped package (scripts/windows/Stage-Arm64Tests.ps1, hub 65d61617). The stager also copiesspeed_summary.pyandanswers.py, whichtest_benchmark_data.pyloads by file path. Without them the first arm64 run stopped there at collection. Since 2026-10-02 the staging also carries x64’s coverage and report plugins (pytest-cov/pytest-md/pytest-md-report/pytest-html, pluscoverage— all available as cp314win_arm64wheels, resolved against a+gilinterpreter because a bare3.14can pick a free-threaded build whose cp314t.pyds would not load) and runsbench/demo_pytest_benchmark.pyas a second manifest entry, so the device pytest run now reports, measures and benches like x64’s. The device’s reports leave the runner through the hub’s newtest-results-pathupload (orchestrant-windows-arm64-test-results, hub 305a1c92). With no__init__.pybeside them they cannot shadow the installed package.The conftest moved to
tests/conftest.py. Withtestpaths, pytest loadstests/unit/conftest.pyonly once it reachestests/unit. By thentests/integration’simport orchestranthas already imported matplotlib, which readsMPLBACKENDonce, at import. Both Windows x64 legs then drew with TkAgg and crashed intest_metrics_plotter.py(0xC0000005). Before the whole suite ran there, the unit conftest loaded first. That crash only came to light once every leg gated.Two tests close their
HTTPErrors. An unclosed one warns when it is collected, and the free-threaded leg collects it after the session, wherefilterwarnings=errorturned it into exit 1 although every test had passed.
Security#
The 44 open Dependabot alerts in
uv.lockare fixed (2 critical, 27 high):gitpython 3.1.51 → 3.2.0;
mlflow 3.14.0 → 3.16.1;
virtualenv 21.6.1 → 21.14.2;
urllib3 2.7.0 → 2.8.0;
cryptography 48.0.1 → 50.0.2;
sqlparse 0.5.5 → 0.6.0;
aiohttp 3.14.1 → 3.14.3;
soupsieve 2.8.4 → 2.10.
Dependabot cannot raise them itself here. Its updater fails on the git source
pytorch/vision(private_source_authentication_failure), so its security-update runs stay red while an alert is open.
0.0.29 - 2026-10-01#
Added#
Installable packages: MSI and zip on Windows; deb, AppImage and tar.gz on Linux.
packaging/app.jsonopts this repo into the hub’s Python app bundles. The CI lanes build a relocatable folder with its own CPython, the locked wheels, the chain ONNX Runtime and the model. They wrap it as those five packages and start each one once withyolo-monitor --self-test. The MSI and the deb are installed, started and removed. Av*tag attaches the packages to its GitHub release. Measured on 2026-10-01: MSI 209 MB, zip 254 MB, deb 213 MB, AppImage 248 MB, tar.gz 266 MB.Windows ships the image’s OpenCV, which does not import Media Foundation, so the Windows CI test venvs use it too. PyPI’s
cv2did not import in the Server Core container at all.The Linux packages need glibc 2.43 (Ubuntu 26.04+), because the bundled chain ONNX Runtime asks for it.
yolo-monitorruns from any directory: the first step toward packaged builds. Until now it read the model fromresources/models/in the working directory and logged tologs/there, so it worked only from a checkout.Model.
orchestrant/paths.pyresolves it from$ORCHESTRANT_MODEL, then the install’sshare/orchestrant/models/, then the checkout.Logs. A packaged install logs to the user’s state dir.
Execution provider. It is chosen from what ONNX Runtime offers: CUDA, DirectML, then CPU. DirectML was never tried before.
--self-test. It runs one inference on a blank frame and prints JSON, so CI can install-test a package without a camera or display.appextra. It holds whatyolo-monitorimports beyond the core: numpy, OpenCV and ONNX Runtime. Until now those came only with anml-ai*extra.It conflicts with every
ml-ai*extra: each brings its own OpenCV and ONNX Runtime, and uv cannot forkopencv-pythonacross both.It asks for
opencv-python>=5. Unversioned, uv’s win32 fork took ml-ai’s riscv64 git build (4.13), which then had to compile.CI still syncs
ml-aiand leavesappout.
The hub’s Windows rocm image builds this repo’s torch. ANTfrastructure
2da6c29acompiles torch 2.14.0 / torchvision 0.29.0 from source against ROCm 10.0 (gfx1200, gfx1201), because AMD’s ROCm 10.0 Windows wheels stop at 2.13.0 and the image refused to pair them with this lock. AGENTS.md § 4’s torch bullet says which hub image takes which wheel line. Nothing here changes. At9427c7a0the first rocm build of it is green: this repo’sdevelop(705e769) passed its wheel smoke on the source-built torch (13/15, no failures), and the image’s smoke gate passed.The gateway’s acceptance: the lab measured through APISIX (roadmap P9.1;
benchmarks/docs/gateway-acceptance-2026-09-25.md, raw files and a derive snapshot inbenchmarks/benchmark_results/2026-09-25-gateway-acceptance/).Transparency: on
raw-npuandraw-gpuno contract answer changed. All 42 tool cases got the direct verdict and 41 byte-identical replies. The NPU paid a median 10 ms to first token, and decode is unchanged.Shaping:
lab-chatscored what the NPU with the tools prompt scored (41 ties). It answered the tool case the NPU refuses and the ~7k-token documents, on the GPU.Explained: the refusal left open in P8.1 is
context_length_exceeded, at 4353 prompt tokens.Found: the size rule sends the ~3.1k-token documents to the GPU too, at about 12× the NPU’s time. P9.2 sets out the owner’s decision.
The procedure now says to run a gateway
bench_toolsorbench_chatwith the direct run’s--label, so bench_compare pairs them.
scripts/linux/serve-stack.sh: the lab’s gateway, the eighth thin wrapper (P1 of the APISIX design). It runs ANTfrastructure’slinux/llm-stack/scripts/serve-stack.sh(keys,up,status,reload,down: APISIX on127.0.0.1:9080in front of the GenieX lanes, from WSL) with the renderer pointed atbenchmarks/prompts/, whose tools prompt the registry pins by sha256, and atLLM_BACKENDSwhen that is set, so the gateway serves the registry the lab reads. AGENTS.md § 4 gains two pitfalls: stop the gateway during a direct-lane campaign, andlab-raw-*is not byte-transparent.A report taken through the gateway says which lane served, and on what. A
lab-*backend’s base URL is the gateway, and such a report recordedruntime: null. Nowruntime_info()answers a gateway alias with its primary lane’s runtime, found exactly as a direct run of that lane finds it (the lane map is the registry’sservingblock), solab-raw-npuandgeniex-npureports compare like for like. The newprovenance.gatewayblock (orchestrant/benchmark/gateway.py;nullon a direct lane) names the lanes the alias reaches, the overflow lane’s own runtime, the running gateway’s/gateway/infoshas and whether it was rendered from the registry the lanes were read from (registry_matches), andserved: the lane every reply named in itsX-Gw-Lane/X-Gw-Reroutedheaders, counted inclient.post_json.compare()names a gateway run against a direct one, another alias, a changed gateway and runs that other lanes served, and it drops its “served models differ” note between a gateway run and a direct one (the gateway’s/v1/modelsis its alias list). Nothing changes for a direct lane: only a base URL equal to the registry’sserving.gateway.listenis treated this way, and only its static/gateway/infois asked anything.benchmarks/README.md(Through the gateway) lists thelab-*backends, what a report records, what the gateway changes on the way through, and the P1 acceptance procedure. Whatgateway.pyreads crosses the gitlink, soTestAgreesWithTheHubholds it to the pinned hub: every alias (andraw-*route) reaches the lanes the renderer routes it to, every registry entry at the gateway names a lane,/gateway/infocarries each key a report keeps, andgeniex-shapesets the two headersservedcounts.orchestrant-bench depth: the decode-at-depth trace as a lab tool (roadmap P8.4, the campaign’s defect 6). The 2026-09-24 depth traces came from a scratch script that was never stored and recorded no provenance.orchestrant/benchmark/depth.pysends the same warm-up, prompt (the contract’s filler at seed 7, pinned by sha256 in the tests) and streamed request, cuts the same windows and writes the same six fields, so the tracked*-depth-8k.jsonfiles compare with new traces. The report uses the shared envelope (runtime, model files,tool_sha256overdepth.pyandanswers.py), reads the host load over the 30 s rest before the measured request, keeps the raw delta times, and gives a burst window a null rate. A server error inside the stream, a timeout, an HTTP error or a reply with no generated token is recorded in the row’serrorand exits 1; the report is written first.Every report’s provenance records its command line (
argv, defect 6). Credentials are redacted (client.redact_argv): a flag named for one loses its value, and a value shaped like one — a provider key prefix (sk-,glpat-), a token prefix (hf_,ghp_,github_pat_), a bearer header, a key-named URL parameter, a URL’s password — is replaced under any flag.provenance.compare()does not read it.The roadmap’s P8 measurements, written up as § P8 of
benchmarks/docs/roadmap-campaign-2026-09-24.md, overbenchmarks/benchmark_results/2026-09-25-p8/(reports, the chain script and aderive.py.snapshotthat recomputes every number). The tool gap narrows to one case, no longer separable, and the recommendation stands for tool calling (P8.1): withprompts/tool-disambiguation.mdthe Qwen3-4B-Instruct NPU bundle passes 40 of the 41 cases it answered and the same model’s GGUF on the CPU lane 42/42 — 1 case to 0, p = 1.0, +2.4 points [−2.3, +7.2] — where without the prompt the GGUF had won 7 to 0; the prompt fixed all eight of the bundle’s misses and cost it one. Not separable is not equal: the point estimate still favours the GGUF, and the bundle refused the suite’s longest request with a 400, as in 10 of the 12 draws of that case tracked since 2026-09-23. The instruct GGUF answers the ~7k-token chat documents the bundle refuses (P8.2: 32/33, not separable from the bundle or the thinking 4B). In one trace per lane, taken on different days, the Adreno GPU lane decodes the 9B distill at 0.52–0.54× the CPU lane’s rate after 7.2k tokens instead of doubling it (P8.3), so the agent stays on the CPU lane. On the default tool set, without opencode’s preamble and schemas, the 9B passes 33 of the 34 cases it passed 27 of under them (P8.5: 7 to 1, p = 0.070, −17.6 points [−33.1, −2.2]). The roadmap marks P8.1–P8.3 and P8.5 measured.benchmarks/docs/roadmap-campaign-2026-09-24.md— the roadmap campaign of 2026-09-24/25, written up over its raw reports (benchmarks/benchmark_results/2026-09-24-roadmap/and2026-09-24-upgrade-check-v070/). Every number is recomputed from them by one script stored beside them,derive.py.snapshot, withspeed_summary’s pooled definitions andstats’ intervals; one section per item measured — P4.1, P7.2, P7.3, P4.2, P4.3, CV-4, P3.2/P3.3, R2/R7/R11, P7.4–P7.7, P1.2, Ollama against GenieX on byte-identical files, and the Adreno GPU lane. The recommendation holds for chat on inputs that fit the bundle’s 4096 tokens, for coding and for agent work (the 9B distill: 11/15 trials, pass^3 60 %); for tool calling it now depends onprompts/tool-disambiguation.md, which no run used — without it the Qwen3-4B-Instruct GGUF on the CPU lane passes 41/42 cases where the NPU bundle passes 33/41 (7 to 0, p = 0.016). P4.1 (Qwen3-8B) and P4.2 (Qwen2.5-Coder-7B) are re-measured on v0.7.0 and no longer conditional; the hub page’s § 1i/§ 1n coding tables are re-derived under the fixed grader. The page lists seven lab defects the campaign found, and corrects the data commits’ decode figures, which were means of per-request rates (the instruct GGUF’s “26.1 tok/s” is 22.2 pooled). The roadmap marks each item with its result and gains Phase 8; the lab README links the page.--prompt-variantsreports the spread, andbench_codinghas paraphrases too (roadmap P1.2). The classic and novel tasks carry two paraphrases each, andstrings_normalize_tag,lists_chunk_with_remainder_policy,stateful_classify_ticketandbash_split_comma_listone each. Each keeps the signature, every rule and every worked example (examples);benchmarks/tests/test_bench_coding_variants.pychecks each paraphrase against its original literal by literal and runs the worked examples against the reference. Bothbench_toolsandbench_codingnow report the spread — the report gainsvariant_spread,variant_spread_rate,variant_case_count,variant_spread_cases,by_variantandvariant_outcomes, and the run prints the cases passed in one phrasing and failed in another, the score per phrasing and the effective sample. With one draw per phrasing a sampling lane shows spread from an unlucky draw alone (a third of two-way cases at 80 % per draw); the run says so, and--repeats 3shrinks that share.bench_codingcountstotalas one attempt per request and votes on determinism per(task, phrasing), asbench_toolsalready did. No lane has been measured with the flag yet.Every lab tool takes a run-start record (roadmap P1.5, review SRC-1). Before its first request, the speed runner,
lanes,contract,bench_tools,bench_coding,bench_agent,bench_embeddingsandbench_chattake the start time,tool_sha256over their files (so a mid-run edit shows up assource_changed_during_run, as it already did for the speed runner) and a 3 s host-load snapshot (hostload.load_snapshot), written into provenance asrun_started_utc,host_loadandtool_files.provenance.compare()— and sobench_compareandcontract --diff— names two runs taken under different load (other cores differ by more than 0.3), and prints a strongerHOST WAS BUSYwhen either run started above one core of other load, even against a baseline older than the record. The thresholds come from the GenieX v0.7.0 CPU lane: about −14.5 tok/s per core of other load; 30 tok/s on a quiet machine, 14.1 at 0.93 other cores. Incontract --diffboth notes are warnings; inbench_comparethey now withhold a verdict (exit 4, below).A WSL2 harness records the Windows host’s load (roadmap P1.5).
bench_codingandbench_agentrun in WSL2 against Windows lanes, and theirhost_loadrecordedother_cores: null, because the VM’s counters are not the host’s.hostload.load_snapshot()now reads the Windows host through interop when it runs in WSL, the lane URL is loopback and nothing in WSL listens on its port. The neworchestrant/benchmark/winhost.pymakes onepowershell.exe -EncodedCommandcall. It samplesWin32_PerfRawData_PerfOS_Processoridle ticks around aStart-Sleepand sums theWin32_ProcessCPU time of the process listening on the port (Get-NetTCPConnection) and its children. It uses CIM classes becausetypeperfcounter paths are localised, and this host runs a German Windows. The arithmetic, rounding and subtraction are the local reading’s;other_coresthen includes the WSL VM itself — the harness and anything else running in WSL. Everyload_snapshot()reading now carriesvia("local"or"wsl-interop"). A failed interop reading keeps the old null and appends the reason tonote; with nothing listening on the port on Windows either,busy_coresis the host’s and the note says so. Measured from WSL2 with the NPU lane serving: 3.3 busy cores of 8, the lane 1.04, other 2.26, and 4.5 s end to end for the 3 s window; with no lane listening, 0.68 busy of 8 and 2.3 s for a 1 s window.bench_comparesays what a “no regression” is worth, and stops treating repeats as independent draws (roadmap P7.1). Three draws of one prompt are not three trials. When some case’s repeats disagree, the score now carries a case-clustered interval (CR1 cluster-robust, after Miller 2024 “Adding Error Bars to Evals”, Wilson on the effective sample): the v0.7.0 NPU tool run reads98/124 = 79% [71-85%] clustered [66-88%, deff 2.6]. Paired reports print the paired estimate, the mean per-case difference, computed exactly: two identical runs readpaired diff +0pt [+0, +0]where the unpaired interval read ±10 pt, and cases that cancel print +0pt, not -0pt. Every “unchanged” and “no regression detected” is followed by the minimum detectable drop at 80 % power for that case count, at the observed back-flip rate, never taken below 5 % — at face value, one back-flip in 42 cases (2.4 %) printed a smaller detectable drop than seeing none did. 42 paired cases cannot see a drop under 26 points, and 31 cases catch a 10-point drop only 8–11 % of the time. After a multi-label comparison the closing line names the weakest pairing: the one with the largest detectable drop, not the one with the fewest cases. On a sampling lane run with--repeatsabove 1 it also prints pass^k (tau-bench’s unbiased estimator), with k the repeats — the paraphrases of--prompt-variantsare not draws: 73 % pass^3 at 79 % per draw. New inorchestrant.benchmark.stats:clustered_rate,paired_difference,pass_hat_k,paired_power,paired_mde,back_flip_estimate.bench_agent.py --repeats N(roadmap P7.4). Each trial gets a fresh scratch repository and a fresh opencode data and state directory.XDG_STATE_HOMEis redirected too now: the recent-model list and prompt history used to leak into~/.local/stateand carry across trials, and opencode 1.18.31 reads it — when the config names no model, its default model is the most recent entry instate/model.json, so a run without--modelran whatever model the host used last. Trials run round-robin over the tasks. Rows carryattempt; the report addsrepeats,trials_run,per_task,pass_hat_k(k = 1..N, throughstats.pass_hat_k) andwilson_95, the interval the score line only printed until now. No lane has been measured with it yet.benchmarks/upgrade_check.py: the post-upgrade protocol as one command (roadmap P7.5). Per lane, in a fixed order and never two lanes at once:orchestrant-bench contract(+--diffagainst--previous),speed --stream --correctness, an answer-sizedspeed --max-tokens 2048,bench_tools --repeats 3andbench_coding(through WSL with--wslon Windows, else skipped with the reason); thenbench_compare --dir. It writes into a fresh directory, withsteps.jsonland aMANIFEST.mdmapping each file → command → exit code and naming each lane’s serving runtime. It exits non-zero on a failed step, a regression, nothing compared, a lane that was unreachable or relaunched mid-check, a speed step whose correctness check answered wrong or that lost a prompt, or a check that ran no step.bench_compare’s exit 1 (regression) and 3 (nothing compared) stay separate, and an exit 1 without a REGRESSION verdict counts as a failure. It has not had its first live run yet.PowerShell coding tasks (roadmap P7.6, first half).
bench_codinghas apowershellrunner: pwsh in the bash tasks’ sandbox, checks run one statement at a time, PSScriptAnalyzer at error severity where the module is installed, and a visible SKIP without pwsh. Six tasks come from this repository’s own traps:#requires -Version 7.0versus 5.1, module-private nested imports, pipeline output in a return value, single-element array unrolling,$null -eq $xoperand order, and-ErrorAction Stopfor try/catch. Each has a reference, the original bug and 1–3 plausible half-fixes — 18 known-wrong answers in all, and the contract tests reject every one. pwsh runs with an 8 GiB address space, a 1 GiBDOTNET_GCHeapHardLimitand W^X off, because .NET cannot start under the 1 GiB RLIMIT_AS or the 8 MiB RLIMIT_FSIZE (measured on pwsh 7.6.6, aarch64 WSL2). No model has been measured on them yet.A medium-size repository for
bench_agent:fix_medium_repo(roadmap P7.6, medium-repo half). A 32-file toy ledger package with one sign bug two imports from its red tests, verified by its own suite and by a CLI run on inputs the agent never sees; that hidden check decodes the program’s output leniently, so bytes that are not text fail the trial instead of ending the run. Three cheats are refused: edited red tests, a root-conftest patch, hardcoded numbers. The data is inbenchmarks/bench_agent_medium_files.py, the task inbenchmarks/bench_agent_medium.py.benchmarks/bench_chat.py: a chat-quality instrument (roadmap P7.7). 33 code-graded cases cover instruction following, JSON-schema replies, multi-turn memory and document QA at ~1k/~3.5k/~8k tokens. OVERFLOW and CUT rows are excluded and counted, repeats are spaced, each category gets a Wilson interval, and results use the shared report envelope and the run-start record;tool_sha256coversbench_chat.pyanddeterminism.py. Measured on the NPU lane 2026-09-24: 27/30 = 90 % [74–97 %], the threedoc_8krows OVERFLOW; the CPU lane not yet.bench_sweeprunsbench_chat(roadmap P7.7).chatis a sweep tool liketools: the candidate’s endpoint,--labeland the sweep’s--repeats, intochat_<slug(label)>.json. It is gated first, skipped onunreachable, and never overwrites a file. It keepsbench_chat’s own--max-tokens(2048) and every category, and it is not in the default--tools.orchestrant-bench contract: three new checks, 21 in all.output_cap: does the server stop short of a 3000-tokenmax_tokens, and at how many tokens.bundle_system_prompt: evidence of a default system prompt, read from usage.response_format_json_schema: is the schema honoured, ignored or refused. The module docstring and the lab README no longer say the output cap and the bundle’s system prompt are unchecked.Reports name the files that served, the drivers under them, and every lane’s runtime (review PROV-1).
provenance.runtimenamed the GenieX build and its serve flags, but not the weights a model id resolved to, the bundle config that decides how they sample, or the drivers. Given the served model id — whichwrite_reportnow passes whenever every row served one model, so single-lane reports carry it too —runtime_info()addsmodel_files:a GGUF (and its mmproj) by size, mtime,
head_sha256(its first MiB) andsampled_sha256(16 windows of 64 KiB spread evenly to the end of the file). The first MiB alone identifies no quant: in this cache it is byte-identical across all four Qwen3-4B quants, because it holdsgeneral.*and the vocabulary, andgeneral.file_typesits at 5.66 MiB;a QAIRT bundle by its
genie_config.jsonhashed in full, with the sampler and context size lifted out, its context binaries (with the same two hashes), the HTP extensions file and the QAIRT build it was compiled with;every file says whether it still matches the size in
geniex.json.
It also adds
drivers: the Hexagon NPU and Adreno GPU driver versions from the Windows registry, which Windows Update moves while the GenieX build stays the same.compare()printsMODEL FILES CHANGEDbehind an unchanged id, and names an editedgenie_config.jsonor HTP extensions file. Alanesreport now puts each lane’s ownruntimeon its row; before, the provenance block covered only the first lane.Lane-runtime files for tools run from WSL2 (review OPS-7). From WSL2 the Windows-side lane process is invisible, so the runtime could only be guessed from the installed binary (
verified: false).orchestrant-bench runtimes --output lanes-runtime.json geniex-npu geniex-cpu, run on the host once the lanes are up, writes the host’s own view; exportLLM_LANE_RUNTIMES=<its /mnt/c path>in WSL2 and every report there takes each lane’s runtime, model files and drivers from it. A snapshot older than 12 h keeps its data but losesverified, and so does one whose lane ran another CLI version than the installed GenieX now reports (snapshot.installed_cli_mismatch): v0.6.1 → v0.7.0 was one session. An entry the host could not attribute ({"error": ...}) is skipped, and the probes after it still run.bench_comparediffs each lane’s runtime in alanesreport (roadmap OPS-7). The provenance block covers one URL (the first lane’s, or the batching endpoint’s under--batching), so a second lane rebuilt, relaunched with other flags or re-pulled behind the same id moved its tok/s unnoted.compare_lanes.pymatches lane rows by name and prints provenance’s own runtime notes per lane as! lane NAME: ...(SERVING RUNTIME CHANGED, serve flags, MODEL FILES CHANGED,genie_config.json/HTP extensions edits). A lane in one report only is named, because the aggregate row then covers another set of lanes. Rows older than the field take the provenance runtime when their URL is the block’s.provenance._runtime_notesis now the publicruntime_notes.Benchmark viewer: Answers, load and energy, Serving runtime and Server contract cards (review OPS-6, viewer half). They show answered k/n, time to first answer, thinking share, lane and other cores, CPU-rail J/token gross and net with the report’s
net_reliableverdict (gross only for a run with no idle baseline),provenance.runtimewith its serve flags (a lanes report’s lane column no longer listsaggregate), and contract reports as one grid of checks against runs, oldest first by each report’s timestamp, with changed answers highlighted. The per-prompt drill-down gains first answer, lane and other cores, and J/token.tests/unit/frontend/test_page_compiles.pybuilds and dry-run compiles the Reflex page and skips without Reflex;.github/workflows/benchmarks.yml’s viewer job now runstests/unit/frontendwithreflex==0.9.6.post1, so CI compiles the page where the test used to skip.tests/unit/benchmark/test_bench_report.py::TestTheViewerReadsTheTrackedRunbuilds the manifest overbenchmarks/benchmark_results/2026-09-23-geniex-upgradeand checks that the viewer’s tables print the GenieX v0.7.0 page’s published figures.The speed runner knows whether an answer arrived.
answers.pyreads every place a server puts thinking (inline<think>,reasoning,reasoning_content) and recordsfinish_reason,answeredandttfa_s(the first token after any thinking); a reply cut atmax_tokenshas nowall_s_to_answer, and the summary printsAnswered k/nand averages time to an answer over the answered rows only. The viewer andorchestrant-bench reportfollow — their answer column says how many rows it covers (“24.8s (8/9 answered)”), since a run that cut more replies would otherwise rank fastest — and read an older report’s never-closed<think>as all thinking.bench_compareis a speed tripwire too.compare_speed.pypairs decode, prefill and TTFT per prompt and flags SLOWER when the median decode ratio falls by more than 5 % or the prompts’ own scatter; a CPU lane measured over 0.3 cores of other load is reported, not judged; CPU-rail J/token is reported; it derives the load for reports older thanother_cores, and a run that load could explain — slower under load, or faster than a loaded baseline — is printedNOT judged. A speed report no longer gets a latency verdict (its wall moves withmax_tokensand includes cut replies). A contract pair, or any pair with nothing in common, now exits 3 (NOTHING COMPARED; 2 stays argparse’s usage error) instead of “no regression detected”.bench_comparerefuses a load-confounded verdict (roadmap P1.5). When a pairing’s run-start load notes fire — a run started with more than 1.0 other cores busy, or both recorded their load and it differs by more than 0.3 — the verdicts load can move (the speed tripwire’s, the per-attempt time, lane throughput) are withheld, named on a closingWITHHELD for loadline, and the run exits 4,CONDITIONS DIFFER. Scores, per-case flips and batching are still judged. The order is REGRESSION 1 > CONDITIONS DIFFER 4 > NOTHING COMPARED 3 > 0, for one pair and over--dir. A baseline that predates the record is not refused for lacking it, and an NPU-lane speed pair (under 4 lane cores on both sides) is still judged when neither run started above 2.0 other cores, the range it was measured unmoved over, with a line saying so.--allow-load-differencerestores the old behaviour: the gated verdicts are judged, aNOT judgedline passes unjudged.upgrade_check.pyrecords exit 4 as the failing statusconditions-differinsteps.jsonlandMANIFEST.md, and has no override of its own.contract --diffprints the same notes and is deliberately not gated. The exit codes, their order and the gate live in the newbenchmarks/compare_verdict.py.orchestrant-bench contract— re-check the server behaviours the lab depends on, and diff them across runtime upgrades. Every GenieX release moved one of them (v0.6: the output cap,max_tokens, tool-call parsing, the prefix cache; v0.7: QAIRT stop sequences,power_mode, the bundle’s system prompt), and each was found by hand after it had distorted a number. Eighteen checks, each answeringyes/no/inconclusive/error/skippedwith evidence;--diffexits 1 when any answer moved. Its first run found that the v0.6.1 QAIRT lane ignores a chatstopwhile reportingfinish_reason: "stop"and caches a conversation extended by a turn but not an identical repeat. Two of its checks,temperature0_is_greedyandidentical_repeat_intact, name the GenieX defects under Fixed below; every determinism check puts an unrelated request before each draw.Measured CPU and energy per request on a Windows host.
hostload.pyfinds the process listening on the lane’s port and reports its CPU-seconds over the request (lane_cpu_s,lane_cores) and everything else the machine did meanwhile (other_cores— a llama.cpp CPU lane lost about 20–55 % of its decode rate to 0.5–1.0 cores of IDE and antivirus work, and no report said so);energy.pyreads the Windows Energy Meter Interface through PDH (English counter names, so a German install reads the same) and interpolates the CPU-cluster rails’ cumulative energy onto each request’s exact bounds (cpu_rail_energy_j,cpu_rail_j_per_token, and*_net_*net of an idle baseline). The Snapdragon X exposesCPU_CLUSTER_0/1and no NPU or GPU rail, and every report’senergy.scopesays so. This replaces the roadmap’s “joules are not measurable here” for the half of the question that separates the lanes.benchmarks/docs/geniex-v0.7.0-cpu-npu-2026-09-24.md— the GenieX v0.6.1 → v0.7.0 upgrade, measured on the CPU and NPU lanes, with every raw report underbenchmarks/benchmark_results/2026-09-23-geniex-upgrade/. The upgrade changed two lane behaviours (QAIRT/v1/completionsstop sequences,power_mode) and no model output; its NPU slowdown is--log info(−13 % decode, +21 % CPU-side J/token gross), not the runtime. It also retires beliefs of the hub’s GenieX page: the QAIRT lane caches a conversation extended by a turn, “T=0 samples” is a server defect rather than the models, since v0.6 the NPU + CPU pair delivers less than the NPU lane alone (0.54–0.78×), and on llama.cpp4ff829eone Unsloth DynamicIQ3_XXSanswered correctly again (a three-question smoke test). Revised the same day after a six-lens, adversarially verified review of the lab — the page’s “What the review corrected” lists every number that moved.Runtime provenance. Reports record
runtime— the serving build (GenieX CLI, QAIRT and llama.cpp versions from the lane’s own binary, or Ollama’s/api/version) and the lane’s serve flags — andinterpreter, the Python build’s own platform (an x64 Python under emulation still reports ARM64).bench_comparenamesSERVING RUNTIME CHANGEDand changed serve flags before any score. Speed reports gained theprovenanceblock the Sphinx page already claimed they had, andbench_comparereads it.The PowerShell lint gate this repo never had.
scripts/windows/Invoke-Lint.ps1(the dev-box command) and thelint-powershelljob of.github/workflows/windows-2025.yml(CI) run ANTfrastructure’swindows/scripts/Invoke-Lint.ps1overscripts/windows/: a mandatory parse pass, an AST-trap pass (comma-attribute quoting, switch shadowing, glued parameter tokens) and PSScriptAnalyzer 1.25.0 against the hub’s ownPSScriptAnalyzerSettings.psd1, consumed by reference so nothing is copied here to drift.-FailOnAnalyzerand-Pathare both passed and neither is optional: without the first the analyzer prints findings and exits 0, without the second the hub script grades the HUB’s trees out of this checkout. Before this, ~400 lines of Windows build driver were gated by nothing but running them. The gate landed as a standalone.github/workflows/powershell-lint.yml; that file is gone again in the same unreleased cycle, for the reason under Changed below.The hub’s
--ratchetsmeasurement gates, with their freeze files seeded and committed.scripts/linux/run-lint-gates.shnow passes--ratchetsunconditionally, adding nine--rootgates: the docs cross-reference gate, code size, complexity, dead functions, comment size, stdout returns, masked declarations, trailing conditionals and the shellcheck warning ratchet. The first run is the seed, sofunction-size.allow(22 rows),file-size.allow(9),code-complexity.allow(28),comment-size.allow(7) anddead-functions.allow(1) land in the same change; four gates need no file because this tree has nothing over their limits. Every row carries a reason, because the contract is four-way — a frozen number that shrinks, or an entry whose subject is gone, fails exactly like new growth.benchmarks/,frontend/,bench/andexamples/are graded by the static-analysis gate. The hub’sSTATIC_ANALYSIS_EXTRA_PATHS(Linux) and-ExtraPaths(Windows) reach first-party code that is not$PACKAGE_NAME; 48 files of it had been outside every analyser because the driver’s target list was the package,tests/,docs/source/conf.pyandsetup.py. codespell, vulture, ruff check and ruff format are clean over all four (ruff measured where its EXE rules cannot fire; 15 of the 22 EXE001 in Fixed); bandit could not reach them at all until the pin below. The Windows lane invokes the driver throughpwsh -Commandrather than-Filefor this:-Filebinds ONE element of an array parameter and silently discards the rest, so the lane would have reported green over two trees nothing had read.Decision, 2026-09-15: the NAS document-AI thread moves here from ANTfrastructure, beside the benchmark lab that owns the question.
benchmarks/nas_census.py(the census: walk a document tree and publish the four numbers — total PDF pages, scanned fraction, German fraction, table density — plus the gate that decides whether a VLM gets budget at all),benchmarks/tests/test_nas_census.py(44 tests, offline, and green without PyMuPDF, which is the environment this repo actually has), andbenchmarks/docs/nas-document-ai.md(the 36-agent review that prescribes both: model shortlist, per-file-type routing, and why the Hexagon NPU cannot read a page). The hub keeps no copy. The page was written in ANTfrastructure and says so, and its links now resolve from here — hub pages throughthird_party/ANTfrastructure/, the census tool as a sibling. The census needs no dependency this repo does not already have: it is stdlib-only, andpip install pymupdfupgrades it from an extension census to per-page classification, reported as SKIPPED rather than as a fabricated zero when it is absent.AMD GPU support across monitoring and the benchmark runner.
GPUProbenow picks a vendor: NVML for NVIDIA as before, and AMD through ADL on Windows (the driver’satiadlxx.dll) or theamdgpusysfs counters on Linux. The Windows ADL path reads the modern PMLog sensors for utilization, temperature and power — the legacy Overdrive5 API thatpyadlwraps returnsADL_ERRon RDNA-era cards, verified on an RX 9070 XT — and the adapter memory APIs for VRAM; no Python package is required. Thegpu-rocmextra addsamdsmi, used only to resolve a Linux card’s marketing name. AMD devices are ordered largest-VRAM-first, so index 0 is the discrete GPU on an APU+dGPU host.orchestrant-benchnow recordshardware.gpu(vendor, name, VRAM, backend) and per-promptgpu_utilization_percent,gpu_memory_used_gbandgpu_power_watts; the viewer renders them as a hardware row, a comparison column and a chart.The benchmark viewer is a Reflex app in
frontend/(thefrontendextra), replacing the React/Vite app that travelled with the lab. It reads the manifest directly (ORCHESTRANT_BENCHMARK_MANIFESToverrides the default), so the build/copy step is gone; the table and Wilson-interval logic is pure Python infrontend/frontend/benchmark_data.py, tested without Reflex.orchestrant.benchmarkandorchestrant-bench: the LLM endpoint runner. Ported from ANTfrastructure’slinux/llm-stack(one request path, backend registry, speed/lane measurements, statistics, provenance) so OrchestrANT owns the measuring identity the capability benchmarks build on. Stdlib plus the already-presentpsutil; named backends come from the serving stack’s registry, found throughLLM_BACKENDS, the hub submodule, or a package copy. Tests live intests/unit/benchmarkand run offline;requestsjoins the test extra for the live-contract modules.
Changed#
Comments are one line, only the why (2026-09-30). The owner rule for every repo of the family, in
third_party/ANTfrastructure/AGENTS.md§ Comments: one line, only the why, and linked from § 2 here. 136 files, 6.5k lines out and 1.4k in; what was a trap moved toAGENTS.md§ 4 (pythonpath,tool_sha256, the two bootstrap copies) or to the hub page it belongs to. Code is unchanged. Becausetool_fingerprinthashes raw source, every lab tool whose comments changed has a newtool_sha256: reports from before and after compare as different tool versions.comment-size.allowholds no rows: nothing is over one line.ANTfrastructure to
497ba711. The comment sweep itself, whosecomment-sizegate now grades every language, plus CON38 to CON41 (clang-tidy’s GCC, atheris’ libFuzzer,3.14vs3.14tvenvs, the arm64/riscv64 Vulkan WSI) and the Windows rocm builders sharing one owner. The two bootstrap copies follow the hub templates.scripts/linux/ci_static_analysis.shno longer clearsVIRTUAL_ENV. The family image exportedVIRTUAL_ENV=/opt/venv(root-ownedbin/) andUV_PYTHON; it has exported neither since hub CON18 (:latestof 2026-09-29), so theexport VIRTUAL_ENV=""that keptuv syncoff/opt/venvwent.UV_PROJECT_ENVIRONMENTandUV_NO_SYNC=1stay: the driver still never activates the venv it creates.ANTfrastructure to
547d9fd0: the hub’s images now build this repo’sdevelop.renovate-local.sh --applytook two hub commits.547d9fd0sets the hub’sAPP_REF=developand resolves it to the commitdeveloppoints at whenever an image build starts, so what is pushed here is what the next image contains, with no release tag in between (AGENTS.md § 4). It also passes the ROCm extra aspytorch-rocm10.3a692ecagivescontainer-ci-windowsa compiler cache across runs; it is reached at@developanyway. Of what this repo calls through the pin, onlyversions.envAPP_REFchanges; the pin gate still passes (7 pins compared). Known gap, on the hub side: the Windows ROCm image cannot build torch 2.14 yet, because AMD’s ROCm 10.0 wheels stop at 2.13.0.ANTfrastructure to
c2385230, byrenovate-local.sh --apply. Three hub commits: the consumer-pins gate now also holds torch, torchvision and onnxruntime-genai (9468eb11; this repo passes, 7 pins compared), the runtime image keeps base’s sccache/ccache caps (a0835db9), and newcompiler-cache-restore/-saveactions (c2385230). Of what this repo calls, onlyconsumer_pins.pychanges;RUFF_VERSION, the torch pins and the CI image tags stay where they were.torch 2.14.0 / torchvision 0.29.0, and the ROCm extra is
pytorch-rocm10(breaking:--extra pytorch-rocm71is gone). The pins now equal ANTfrastructure’sPYTORCH_VERSION/TORCHVISION_VERSION, which its image force-installs over this lock; until now a dev box ran 2.13.0 and the image 2.14.0. The hub’s consumer-pins gate holds them there from 2026-09-29, with the riscv64 source pins and onnxruntime-genai. The ROCm extra targets ROCm 10 systems but takes pytorch.org’srocm7.14wheel line, as the hub’s ROCm image does: no torch 2.14 is built against ROCm 10 (AMD’s own ROCm 10 wheels stop at 2.13.0), androcm7.1never got 2.14. That torch pullsrocm[device-all,libraries], so the extra lists its 28 runtime packages to route them to that index, and installing it downloads the device libraries of every GPU architecture. The hub still passespytorch-rocm71, because it builds tag v0.0.28 (APP_REF), and switches whenAPP_REFmoves. The lock also moves triton and triton-rocm to 3.8.0 and, through the cu130 wheel, nvidia-cudnn-cu13 to 9.24.0.43 and nvidia-nccl-cu13 to 2.30.7; nothing else changes. Unit tests on torch 2.14.0+cpu: 913 passed, 32 skipped.ANTfrastructure to
4ae8ef81, and the benchmarks lane’s pins, byrenovate-local.sh --apply(2026-09-29). The gitlink takes the hub’sdeveloptip, 38 commits on fromeba03218. Of what this repo calls, only action pins in the hub’s reusable workflows and composite actions, the llm-stack’s open-webui (v0.11.4) and glances (4.5.7) images, and new Windows-only FFmpeg codec keys inversions.envchange. The four Python drivers,run-lint-gates.sh,renovate-local.sh,ci-image-ref.sh,backends.json, the gateway renderer and plugin and the imported PowerShell modules are byte-identical, andRUFF_VERSIONstays 0.16.8.benchmarks.ymlmovesastral-sh/setup-uvv9.0.0 → v10.2.0, pinned to its commit as the hub’s lanes are; v10’s one breaking change turns the cache off forpull_request_target,workflow_runandrelease, none of which trigger this workflow. The contract job’s ollama service moves 0.34.0 → 0.34.4 by digest, by hand: the tool has no locator for aservices:image. Still held byrenovate.json’s approval rules: ruff 0.16.9 (the hub pins 0.16.8), onnxruntime-genai 0.17.1 (the hub pins 0.15.2), and torch 2.14.0 / torchvision 0.29.0, whichwhl/rocm7.1does not carry (whl/rocm7.2does).ANTfrastructure to
eba03218, the hub’sdevelopwith the gateway merged.38b91aa1mergesfeature/apisix-gateway(6bf205b2, the previous pin) into the hub’sdevelop(1dc4a776).eba03218on top of it rewrites the twogateway-e2ematrix rows of the hub’sllm-stack-serving.ymlas block mappings, which the hub’s workflow-conventions gate can read; nothing here calls that workflow. The pin now sits on the branch.gitmodulestracks again:branch = developsince750d455, whichrenovate-local.sh --applyandgit submodule update --remotefollow. Of what this repo calls, the 20 develop commits it adds change onlyci-image-ref.sh,versions.envand the CI image ref gate, all to add the Windows arm64 cross bundle (--windows-arm64,CI_IMAGE_WINDOWS_ARM64_TAG=winarm64), which no lane here uses. The four Python drivers,run-lint-gates.sh,renovate-local.sh, the llm-stackserve-stack.sh,backends.json, the gateway renderer and plugin, the three imported modules, the two Windows Python drivers,Invoke-Lint.ps1, the two shared templates and the four reusable workflows are byte-identical to6bf205b2, andRUFF_VERSIONstays 0.16.8. The hub’sci-build-triggers.mdnow says every platform lane runs on every push, so the AGENTS.md § 2 row names it for that; AGENTS.md, README.md andrenovate-local.shnow saybranch = develop.ANTfrastructure to
6bf205b2, and ruff 0.16.7 → 0.16.8 with it. The pin takes the hub’s llm-stack gateway and the 145 hub commits sincef65ccae9.6bf205b2is the tip of the hub’sfeature/apisix-gateway, the commit the gateway was accepted on; it reached the hub’sdevelopwith the merge38b91aa1(the entry above). The hub now pinsRUFF_VERSION=0.16.8, sopyproject.toml,.pre-commit-config.yamlanduv.lockmove together;ruff format --checkandruff check0.16.8 are clean over the tree, measured on Windows, where ruff’s EXE rules are compiled out (the Linux lane’s 22 EXE001 are in Fixed). The registry the lab reads moved with the pin:geniex-gpuservesunsloth/Qwen3-4B-Instruct-2507-GGUF:Q4_0(reports before 2026-09-25 measured the base Qwen3-4B GGUF under that name),geniex-cpu-9bis the CPU lane with the 9B distill, and sixlab-*entries reach the lanes through the gateway. Every hub path the wrappers,Build-Windows.ps1,Invoke-Lint.ps1and the workflows name still exists. The pin also movesCI_IMAGE_LINUX_TAGfromlatest-crosstolatest, and the CI image ref gate (run-lint-gates.sh, actionlint + CI image refs) failed on the one place this repo spelled the old tag out: a comment inubuntu-26.04-amd64-arm64.yml, which now namesci-image-ref.shinstead.tests/unit/benchmark/test_serving_evidence.pyholds three sha256s equal: the tools prompt’s raw bytes, the registry’s pin for them, andsystem_prompt_sha256in both P8.1 reports.The speed runner’s correctness probe splits integrity from capability, and
--correctness-only’s exit code now reads the integrity items only — a behaviour change. Its OK/DEGRADED/BROKEN verdict blamed “broken kernels or an over-aggressive quant” for any wrong answer. On 2026-09-24 that read Llama-3.2-3B and Phi-4-mini as BROKEN (3/6) and the Qwen3-4B-Instruct-2507 GGUF and Qwen2.5-Coder-7B as DEGRADED (5/6) on healthy lanes: every miss was strawberry, Canberra, the 5-machines puzzle or 9.9 vs 9.11, and every model answered both arithmetic items. Each probe item now has a kind.Integrity items decide the verdict, the
--correctness-onlyexit code,upgrade_check’s speed step,bench_sweep’s gate and the viewer’s banner: 23 * 17, 17 squared, plus four new one-step items (100 − 37, the next number after 2, 4, 6, 8, days in a week, a word copied back from the prompt).--correctness-onlyexits 0 when only capability items are wrong or cut, where it exited 1 on any wrong answer and 2 on any cut one. It exits 1 on a wrong integrity answer, or when no integrity answer came back even if capability items answered, and 2 when an integrity answer was cut.Capability items are printed and recorded apart, as
capability: n/m -- not a kernel verdict. A quant that costs only reasoning items now readsOK(Qwen3-4B’sQ2_K, 4/6, lost two reasoning items); it shows inbench_compare’s capability line against the same model’s earlier report, not in the verdict.The four new items had not been asked to any model when they were added; the first
--correctnessrun on each lane is their check. Live check, 2026-09-25 (--correctness-onlyon every campaign model,benchmarks/benchmark_results/2026-09-25-probe-kinds/): all ten – the NPU instruct 4B and Qwen3-8B, the thinking and instruct 4B, Coder-7B, Llama-3.2-3B, Phi-4-mini, the 9B and 2B distills on the CPU lane, the instruct 4B on the GPU lane – answer all six integrity items, the four new ones included, and readOK; their capability scores run from 4/4 down to Phi-4-mini’s 0/4.The report’s
correctnessblock keeps its old fields, counted over every item (a new report’sscore/totalcount 10 items, an older one’s 6), and addsintegrity,capability,verdictand akindper item.Older reports are split by prompt, and
bench_comparecompares a speed report’s integrity items paired by prompt: the first old-to-new comparison pairs the two arithmetic items only, and capability answers are listed when they move, never judged. An integrity verdict that becomesBROKENis a REGRESSION whatever the pairing shows. Two paired items cannot separate even a total loss (2 worse / 0 better is p=0.5), and before kinds the unpaired score read 6/6 → 0/6 as a REGRESSION.report manifestwrites both kinds’ counts for older reports too (correctness.annotate), so the viewer’s banner judges the integrity answers of every report; a manifest built before 2026-09-25 is judged on every answer until it is rebuilt.The probe moved to
orchestrant/benchmark/correctness.py, which the speed report’stool_sha256now covers, so the first comparison against a speed report written before this printsBENCHMARK SOURCE CHANGED.
bench_compare.pyis under 800 lines: the suspect-case block and--dirare modules of their own.is_control,suspect_cases,mark_suspect_cases, its_recount_*helpers andmeasuredmoved unchanged tobenchmarks/compare_suspect.py;pair_directories,baseline_path/BASELINE_DIRand the--dirloop tobenchmarks/compare_dirs.py(1011 → 762 lines; itsfile-size.allowrow is gone).bench_comparestill serves every moved name, so no caller had to change an import. Neither module importsbench_compare: the--dirloop is handed itscompare()andload()instead, sobench_compare.py --dirnever loads a second copy of itself. The split alone changed no output: output and exit codes are byte-identical before and after it over 532 runs on the tracked reports — every 2026-09-2* report against itself, same-benchmark pairs of speed, tools, coding, lanes, contract, chat and agent reports,--dirin both directions between2026-09-23-geniex-upgradeand2026-09-24-upgrade-check-v070(they share no report names, so that pair exercises only the “no report names in common” exit) plus each directory against itself,--baseline,--save-baselineand the error paths.With a control among the candidates,
bench_tools,bench_codingandbench_chathashcompare_suspect.pyintotool_sha256. Its recount sets every other row’spassed/total/effective_n/effective_k, group tables and walls before the report is written, so an edit to it moves a score the way a grader edit does. No fingerprint covered it while it sat insidebench_compare.py, and hashing that file would have been wrong: most of it is the comparison, which decides no report’s numbers. Runs without a control keep their file set. Theirtool_sha256still moves once, as after any edit, becausebench_tools.py,bench_coding.pyandbench_chat.pythemselves changed (a defaultbench_toolsrun:898fcac8c796e51a→b76294d033397b9b, the--turn-growthfix below included). The first comparison of anybench_tools,bench_codingorbench_chatreport against one written before this printsBENCHMARK SOURCE CHANGED; a control run’s line also names the added file. Re-save those baselines and read the nextupgrade_check--dirwith that in mind. No tracked report or baseline has a control.The prompt-variant helpers live in
benchmarks/bench_variants.py, andmark_suspect_cases()owns the recount.variant_spread,variant_spread_lines,variant_report_fields,phrasing_agreementandVARIANT_FIELDSmoved unchanged out ofbench_tools.py.bench_tools.rescore_variantsis gone:mark_suspect_cases()re-deriveseffective_n/effective_kand the spread throughvariant_spread()whenever a report carriesvariant_case_count, so the producers call nothing after it.effective_kstill means observed through the prompt as written. Under--prompt-variants,bench_toolsandbench_codinghashbench_variants.pyintotool_sha256;bench_coding’s hash never covered the variant arithmetic before.Decision, 2026-09-24: the workflows follow the fleet naming convention.
windows-2025.ymliswindows-x64.yml(“Windows x64 · build + test”; its concurrency group follows the file name), and the display names areLint gates,Submodule pinsandBenchmarks, spelled as every repo in the family spells them. Triggers, jobs and job ids are unchanged, so check-run names are too; the README badge follows the new file.ubuntu-26.04-amd64-arm64.ymlkeeps its name for now: splitting it intolinux-x64.ymlandlinux-arm64.ymlneeds a hub reusable-lane input that is not on hubmainyet.No tool fingerprints
provenance.pyany more (review OPS-9). The determinism probe moved toorchestrant/benchmark/determinism.py(re-exported fromprovenance), and only the tools that run it hash it.source_changed_during_runis now recorded asfalsewhen checked and unchanged, where it used to be absent. The first comparison against any baseline saved before this change prints BENCHMARK SOURCE CHANGED for every tool, because each one’s file set or source changed (contract.py,lanes.pyand, for the speed runner,hostload.pywere all edited); re-save the baselines after merging.bench_agentrefuses an override file in any directory from the protected tests up to the workspace root, not only in the tests’ own directory: pytest loads the rootdir’s conftest fortests/too, which the flat fixtures never had to consider. Flat fixtures are unaffected. The report’stasks_runnow counts distinct tasks (unchanged at--repeats 1) andtrials_runcounts rows;tool_sha256covers the two medium-fixture modules.orchestrant.benchmark.report.build_manifestgives every entryruntime,base_url,energy,cpu_threads,backend,modelandtimestamp(run_fields()), and takes the host record from the first realhardwareblock (host_hardware()) rather than from whichever file sorts first. The viewer’s per-prompt drill-down printscutinstead of-for a reply stopped atmax_tokens.ruff skips
third_party/and.claude/(extend-excludeinpyproject.toml): the submodule is ANTfrastructure’s code under its own lint profile and agent worktrees are other checkouts, and a bareruff formatat the root rewrote both on 2026-09-24. Explicit paths still reach them.The Python 3.13 test legs are gone, on both lanes.
test-python-versionsin.github/workflows/ubuntu-26.04-amd64-arm64.ymland$PythonVersionsinscripts/windows/Build-Windows.ps1are now3.14 3.14t, and the docs index links and includes the 3.14 report instead of the 3.13 one. ANTfrastructure’s ONNX Runtime single-source rule (2026-09-23) moves a synced venv’s ORT onto the image’s chain wheels, which are cp314 only, so a 3.13 leg inside the images fails its sync; the images carry CPython 3.14 only (owner decision). The classifiers keep 3.13, and ruff/ty still target it.third_party/ANTfrastructure→35960a87, and NO re-sync with it. The shared-config templates did not move between3c83a5e3and this pin, and the drift gate is what says so rather than this sentence assuming it:sync-shared-config.sh --repo-root . --checkreports both declared assets OK andShared config in sync.What the pin carries for this repo is the bandit argument fix and the Windows lane’s two new inputs, both below.bandit reaches
benchmarks/,frontend/,bench/andexamples/for the first time, andBANDIT_EXCLUDES/-BanditExcludesowns its-xlist. Both hub drivers spelled the extra paths as one-rper target, and bandit’s-ris astore_trueflag against a singlenargs='*'positional, so the gate died withunrecognized arguments: benchmarks frontend bench examples(exit 2) every time the knob was set. Measured here against bandit 1.9.4, reported upstream, fixed upstream in17a4733fon the Linux driver and its Windows twin; the two comment blocks that documented the breakage are deleted with it. With the knob working, the hub’s default exclude list leaves 1025 findings — 973 of them inbenchmarks/tests, 959 of those B101 (assertused in a test). The default already drops this repo’s top-leveltests/for that reason and its entries are anchored at the working directory, so the lab’s own suite has to be named: both lanes now pass the hub default plusbenchmarks/tests, which is the same judgementpyproject.tomlalready records for ruff (benchmarks/tests/*.pyignoresS101). 52 findings remain (43 low, 9 medium, 0 high, over 18699 lines) and they are NOT triaged: 33 inbenchmarks/and the 19 underorchestrant/benchmarkthat the extra-paths entry above already recorded as the static-analysis lane’s pre-existing red, beside vulture’s 18, codespell’s 5 and ty’s 6. They are the same owner decision, not a new one, and the shape of it is now visible:pyproject.tomlalready ignores ruff’s twins of these exact codes in these exact trees (S101,S105,S108,S310,S603,S607forbenchmarks/*.py;S110,S112,S310,S603,S607fororchestrant/benchmark/*.py) as a frozen port baseline with a tracked follow-up. bandit has no per-file-ignore table, so saying the same thing to it means either a--skiplist or ~52 inline# nosectwins — a suppression-policy call, which an adoption pass does not get to make.The PowerShell lint job is
lint-powershell: trueon the hub’s reusable Windows lane, and.github/workflows/powershell-lint.ymlis deleted. The hub grew thelint-powershell/lint-pathinputs (acf85514) because this repo and OxidANT hand-wrote the same job within a week. The deleted file agreed with the lane on everything that matters —windows-2025, the same pinned checkout SHA withsubmodules: trueandfetch-depth: 1, PSScriptAnalyzer pinned to1.25.0, the hub’s ownInvoke-Lint.ps1with-FailOnAnalyzer— and differed only in the directory, which islint-path: scripts/windows. Two things the input does not carry were kept rather than dropped with the file: theconcurrencygroup moves towindows-2025.yml, where it now also covers the container build that never had one, and the gate loses itspaths:filter, so it runs on every push and pull request to main instead of only onscripts/windows/**— strictly more coverage, never less.scripts/windows/Invoke-Lint.ps1stays, for the reasonscripts/linux/run-lint-gates.shstays: it is the dev-box twin of a lane that cannot call it. The separate README badge goes with the workflow.third_party/ANTfrastructure→3c83a5e3, and the shared config re-synced with it. The pin carries the knobs above plus the reusable workflows below. Hub08137b27rewroteshared/linux/templates/antfrastructure.sh, which this repo vendors asscripts/linux/lib/antfrastructure.sh, so the shared-config drift gate goes red the moment the pin moves: both halves are in one commit. The body gained a three-placeANTFRASTRUCTURE_DIRsearch (explicit environment answer, submodule, plainantfrastructure-toolssibling) and a not-found message that branches on whether.gitmodulesdeclares the submodule at all.lint-gates.ymlandsubmodule-pins.ymlareuses:lines now. Both jobs were copies, and both headers said so — the lint one carried the instruction verbatim (“replace this job with auses:when ANTfrastructure grows a reusable lint lane”). 108 lines of workflow become 16 lines of configuration; the runner pins, the SHA-pinned checkout, the uv install, the Pester 3.4.0 measurement and the pin-suite wiring all move upstream. What stays is this repo’s own: when each lane runs, andratchets: true.Every code and prose cross-reference names the tree its page is in. The hub’s doc-links gate takes
--rootnow, so this repo can grade its own pages; it found twenty dangling references, all of them already wrong for a reader. The hub’s pages are spelledthird_party/ANTfrastructure/docs/...and the lab’sbenchmarks/docs/...;AGENTS.mdno longer points a hub heading at its own file; two bare§ 1n/§ 1minbenchmarks/README.mdnow name the GenieX page they belong to; and the two generated-file rules in.gitignoreare anchored (/docs/source/README.md), which is both more precise and no longer shaped like a pointer.Build-Windows.ps1’sWrite-LogisWrite-LogInfo. It shadowed a cmdlet PowerShell ships (PSAvoidOverwritingBuiltInCmdlets), which is the one finding the new PowerShell lane had; fixing it first is what lets the lane start green instead of starting with an exception. The name now also matches its three siblings.onnxruntime-genai/onnxruntime-genai-cudafollow ANTfrastructure again:0.14.0→0.15.2. Three pins carried the comment “keep in sync with ANTfrastructure ONNXRUNTIME_GENAI_VERSION” while that key had already moved tov0.15.2. 0.15.2 resolvesonnxruntime1.29.0, matching the hub’sONNXRUNTIME_VERSION=v1.29.0.onnxruntime-genai-directmlstays unpinned — PyPI publishes nothing at that version for it — and its comment now says so instead of naming the supersededv0.14.0.ruffconfig:CPY001(missing-copyright-notice) is explicitly disabled. It andPLR0917left preview in ruff 0.16, so pinningruff==0.16.4took aselect = ["ALL"]project from 3 findings to 63 with no code change. This project states its licence once inLICENSE, not per file; the fourPLR0917findings were fixed in the code instead.vulturenow runs atmin_confidence = 100, andcodespellskips generated Cython.coutput and knowsnd(tvm.nd.array) andDocumANTation— each with the reason inpyproject.toml.Renovate’s “moves with ANTfrastructure” rule covers every spelling of those pins.
matchDepNamesnamed onlyruffandonnxruntime-genai-cuda, so the.pre-commit-config.yamlrev (which the pre-commit manager reports asastral-sh/ruff-pre-commit, notruff) and the plainonnxruntime-genaipin could still be bumped unattended — the exact drift the rule’s own description says it prevents.Decision, 2026-09-15: the repository declares all four of its topics, and nothing is split out. It is the
orchestrantpackage, the family’s LLM benchmark lab (benchmarks/plus theorchestrant.benchmarkrunner), the template a new Python AI project starts from, and the Reflex viewer infrontend/.pyproject.toml’s description and keywords,README.md§ About The Project andAGENTS.md§ 1 now all say so; no code moved.Decision, 2026-09-15: the one large tracked binary is documented, not rewritten out of history.
resources/models/yolov26m.onnx(78 MiB) stays tracked because it is the YOLO monitor’s default--model; the reason and the explicit refusal to runfilter-repo/filter-branch/BFG/LFS are in the newresources/models/README.md, and.gitignorenow excludes*.onnx,*.gguf,*.ptand*.pthwith a single exception for that file, so nothing new joins it.Build-Windows.ps1calls the hub’s Windows Python drivers instead of re-inlining them. Static analysis is nowthird_party/ANTfrastructure/windows/scripts/python/Invoke-CiStaticAnalysis.ps1and both packaging steps areInvoke-CiPackaging.ps1, each launched as a child process with-RepoRoot(and-PackageName orchestrant, without which the driver would derive the distribution name). Tool-list parity with the Linux lane is structural now rather than a rule restated in a comment. The pytest matrix stays local: the hub’sInvoke-CiTests.ps1was removed upstream in ANTfrastructure 6f5aef72.The Windows bench demos are soft, like the Linux lane’s.
bench/demo_*.pyfailures log<demo> skippedinstead of failing the step, matchingci_tests.sh:113-120; the unit tests above them stay hard..github/copilot-instructions.mdis Copilot’s slice only, in English, ~85 lines instead of 200: typing policy, theuv/ruff/tycommands, the commit format and Do/Don’t, with the gate mechanics linked toAGENTS.md§ 4 rather than retold. Thearchive/directory it named does not exist.tests/fuzzy/is gone, with itsAGENTS.mdentry. It held one.gitkeepand never a hypothesis or atheris test; git history keeps it.Dependabot is kept deliberately, and its comment is true now. The claim that the config had “never actually opened a PR” was false (#13-#33), and its retirement condition — the shared Renovate preset not being on ANTfrastructure’s default branch — no longer holds. It stays as the only unattended watcher, because the Renovate GitHub App is installed nowhere in this family.
Fixed#
codespell is clean over the static-analysis scope. The last of the lane’s codespell findings was
openai_api.py:821Ans ==> And, which is the speed table’s"Ans(s)"(answer-seconds) column header, not a typo. It is excused inpyproject.tomlby anignore-regexfor that exact literal, not inline:openai_api.py’s bytes are part of the speed runner’stool_sha256.ans/Ansstay reportable everywhere else. codespell 2.4.2 over CI’s paths: exit 0.vulture is clean over the static-analysis scope. Its 16 findings were
tests/unit/benchmark/test_v1_api.pyasking for the side-effect fixtureswait_for_ollamaandwait_for_modelas parameters it never read. The 15 test methods now name them in@pytest.mark.usefixtures(method level, so the error-path tests do not start waiting for a model). Theavailable_modelsfixture keeps its parameter for ordering, because pytest 9 rejects marks on fixtures, anddels it, which is vulture’s marker for a kept argument. The same requests run in the same order. vulture 2.16 over CI’s paths: exit 0.ty is clean on Linux and on a win32 target. It had 5 errors.
gpu.pyimportspynvmlthroughimportlib.import_module. Animportstatement declares the name as the module, so ty rejected theNonefallback. Its# type: ignorehad been dead since ty 0.0.25. The suppression-free form keeps everypynvml is not Noneguard enforced: an unguarded use is still reported.gpu_amd.py’s_AdlApideclares_lib/_msvcrtin its class body. The declarations are annotations only; no attribute is created.__init__binds them after a non-Windowsraisethat a Linux-targeted checker cannot see past.provenance.py’s tworesource.getrusagecalls carry aty: ignore[unresolved-attribute]. The Windows lane will report them once it reaches ty, and they are already guarded byexcept Exceptionat runtime.Checked with ty 0.0.59 in a venv synced like CI’s (
--all-extras), with the default and--python-platform win32targets. Runtime is unchanged: 36 GPU tests pass.pyproject.toml’s ty comment no longer says the Windows lane syncs without extras.
ruff check is clean on Linux: no EXE001 (22 before). The files flagged had a
#!/usr/bin/env python3line but were tracked100644. One of them,orchestrant/benchmark/gateway.py(added ina387f2e), was first linted by CI onb25f0eb.The 16 that keep the shebang get the executable bit, set with
git update-index --chmod=+x, which works withcore.fileMode=false.Ten are hashed into a
tool_sha256:bench_agent,bench_chat,bench_coding,bench_embeddings,bench_tasks,bench_tools,bench_variants,geniex_toolcall_shim,lanesandopenai_api.inspect_gguf.pyis sent to the model as--context-tokenspadding (PAD_SOURCES).provenance.py’s line count is frozen infile-size.allow.bench_compare,bench_sweep,nas_censusandupgrade_checkare entry points run aspython3 <path>.
The 6 helpers lose the shebang:
client.py,gateway.py,stats.py,report.py,compare_lanes.pyandcompare_speed.py. They are in no fingerprint set and noPAD_SOURCESlist, and every documented way of running them ispython …or-m.No
tool_sha256moves, and the wheel still ships 0644.py. The sdist now carries 0755 forlanes.py,openai_api.pyandprovenance.py.Why nobody saw them: ruff compiles its EXE rules out on Windows and skips them under WSL, so every check before the Linux container read clean.
benchmarks/prompts/tool-disambiguation.mdis the bytes P8.1 measured in every checkout. The blob was LF (1786 bytes, sha25630296646…) while P8.1 read the Windows working copy (CRLF, 1823 bytes,970a8e4f…): a WSL, CI or fresh LF checkout had other bytes, and the gateway, which pins the raw bytes, would refuse to render from it. A-textrule in.gitattributesstores and serves the CRLF bytes unconverted.A cut reply with no
<think>marker has an unknown thinking share, not 0 %. Qwen3 templates open<think>in the prompt, so a reply cut before</think>carries no marker. The 9B distill’s two CUT rows ofcpu-9b-classic-r3.jsonread 0 % beside 42–96 % on the seven that finished.answers.thinking_sharerecords such a reply asthinking_char_share: nullwith athinking_share_note, for the speed runner and forbench_coding, whose log printsthink= ?%. A finished reply with no marker keeps 0.0.answers.row_thinking_shareand the viewer treat an older report’s cut 0.0 as unknown. The summary line and the viewer’s Think columns average the known shares and say how many are unknown ((N unknown),?per prompt). The rule is per row, so the cut rows of instruct runs read unknown too — 37 in 15 tracked reports — though their 0.0 was probably true.A tool call written as text fails the cases that want none. Llama-3.2-3B writes its calls as text JSON:
cpu-llama3b-tools-r1.jsonpassed all seven restraint and irrelevance cases as “no call”, and in its--accept-text-jsonrun every one was a call written as text. This is shown forno_tool_arithmetic, whose reply both runs share byte for byte, and inferred for the other six. Those cases, and the follow-upsuse_result,long_resultanddeep_history, now read the reply with the flag’s own parser whether or not the flag is set. Such a row fails with(written as text)and the start of the text after any</think>indetail, and recordsrecovered: true.grade_followuploses itsaccept_text_jsonparameter.geniex_toolcall_shim.pyis hashed into every case-suite report’stool_sha256, so a baseline saved before printsBENCHMARK SOURCE CHANGED.orchestrant-bench contractno longer times out a slow lane’s cold prefill (defect 7). Every request had 600 s, and Ollama at 4 threads (11.7–11.9 tok/s on the 9B) needed ~613 s for the 8000-token prefix check.--timeoutsets each request’s timeout (default 600). The prefix-cache and overflow requests get at least their prompt’s tokens / 6 s, about half the slowest cold prefill measured: 1334 s at 8000 tokens, while the 2000-token default keeps 600. That is a floor: an explicit--timeoutcan raise it but not lower it. The output cap keeps its 1800 s floor. The stream-usage and/v1/completionsstop checks, which had a fixed 300 s, now take the same timeout. The report’s config recordstimeout_s,prefix_timeout_sandoverflow_timeout_s.A reply that arrives in one burst has no per-row decode rate. Ollama sent three 8–12-token replies of the roadmap run’s
ollama-t8-4b-instruct-speed-answer.jsonin one burst (latency == TTFT), and their 0.36–0.72 ms decode windows stored 9,733–26,712 tok/s. The viewer’s per-prompt table,bench_compare’s per-prompt pairing and the summary’s per-request range all read those rows. A speed row now also recordsdecode_s, its decode window. Where that window is under 50 ms, or the reply has under 2 tokens,decode_tok_per_secis null anddecode_rate_notesays why (answers.decode_fields). The shortest real window in the 25 tracked speed reports is 174 ms, and 50 ms is also above three ticks of the 15.6 ms clocktime.monotonichad on Windows before Python 3.13. The floor withholds a real rate too where a short reply decodes fast: an 8-token reply keeps one only up to 140 tok/s. The pooled Decode figure readsdecode_s, so it counts the same rows as before: every tracked speed report pools to the same figures, and the t8 run still reads 24.7 tok/s. Reports written before this change keep their stored per-row values, and the viewer’s per-prompt table and the summary’s per-request range still show them: the t8 run still reads 21.6–26712.0.bench_compare’s per-prompt pairing does not use them:answers.row_decode_ratereads such a row’s window back as(completion_tokens - 1) / rateand pairs no rate under 50 ms. Paired as stored, the t8 run’s three bursts set the noise band, and a rerun that lost 20 % on the other six prompts readnoise +/-40%and passed.bench_tools --turn-growthrecords why a turn failed. Turn 9 of the 9B run (cpu-9b-turn-growth.json) recorded onlyHTTP Error 400: Bad Requestand loggedERROR HTTPError. A turn that fails with an HTTP error now addshttp_statusandresponse_body(the first 500 characters) to its row, and the log line prints both.errorstill holds the status line, and nothing else a row records changes. The body is read byorchestrant.benchmark.client.http_error_detail, the readerpost_json’s callers can share. The case suite’s errored rows now record them too (next entry).bench_toolsrecords why a case errored, and its turn-growth loop answers every call. All three draws of the NPU lane’slong_result_find_failurein the upgrade check of 2026-09-24 (geniex-npu-tools.json), and its one draw innpu-tools-variants.json, recordedrequest failed: HTTP Error 400: Bad Requestand loggedERROR HTTPError. An errored case row whose request failed with an HTTP error now addshttp_statusandresponse_body(the first 500 characters, read byclient.http_error_detail), and its log line prints both on one line.detail, the score and a row that failed any other way are unchanged. The turn-growth loop answered only a turn’s first call (tool_calls[0]), so a turn with two calls sent the next request an assistant call with no tool message, which an OpenAI-compatible server may refuse. Every call now gets its own tool message, one pertool_call_idin call order; a call without an id is given one on the echoed call and its answer alike. Each turn recordstool_call_count, and its line readsN tool_callswhen N > 1. A turn is still one request andwall_sis its time.approx_context_tokensis still content characters over 4 and now counts every answer, so after a multi-call turn it reads higher than the old loop’s history would have. Whether this caused the 9B’s turn-9 400 is not known: its replies were not stored. The 9B does make several calls in one turn on that lane — it passed all threeparallelcases ofcpu-9b-tools-opencode.json, behind the same preamble — so a two-call turn 8 is possible.One speed summary, printed the same everywhere (roadmap OPS-6). The speed runner’s table,
orchestrant-bench report summary/report tableand the Reflex viewer each averaged a run’s rows their own way and printed different headline tok/s for one report — for the trackedv070-npu-speed.json,Tokens/sec 18.3 avgandT/s: 18.3(a mean of per-request rates, which the viewer charted as “overall”),Overall … 25.4 tok/s(the 427 prompt tokens counted as output) andDecode only 19.7 tok/s avg.orchestrant/benchmark/speed_summary.pynow computes each figure once: Decode, the tokens after each first one over the seconds spent decoding them; Overall, completion tokens over the summed request time (each request’s latency, sent one after another); Prefill, prompt tokens over the summed TTFTs; TTFT, a mean. The rates are pooled across requests, like the CPU-rail J/token. Every printer reads it, the viewer through aspeedblock thatreport manifestnow writes per run; that report reads Decode 19.6 and Overall 19.3 in all of them. Pooled, because a mean of per-request rates let 8-token replies weigh as much as 256-token ones: it put the GenieX v0.6.1 → v0.7.0 NPU decode loss at −13.4 % where the per-prompt medianbench_compareprints is −14.7 % (pooled −14.8 %), and read 19.2 tok/s for a 2048-token CPU run that decoded at 13.9. Errored requests (anyerrorkey, even an empty message) leave every figure and are counted in the summary header and thereportlines; the viewer now counts such a row as errored too, where it had listed it as a served request, averaged it into Answer and left it out of the Errors count. The runner’sTokens/sec min/avg/maxline is gone; the per-request range is printed beside Overall and Decode.report.summarise()returns the summariser’s keys (requests,errored,decode_tok_s,overall_tok_s, …) in place ofnandtokens_per_sec. The viewer’s columns are now “Decode tok/s” and “Overall tok/s”, and its model card shows “Avg overall tok/s”. A manifest written before this change shows “-” there; rebuild it withorchestrant-bench report manifest. The published decode figures this moves are corrected inbenchmarks/docs/geniex-v0.7.0-cpu-npu-2026-09-24.md.tests/unit/frontend/test_viewer_job.pyfails any viewer test that imports the package, which the viewer CI job’s environment cannot.bench_compareexited 0 on a speed verdict it had not judged.compare_speed.py’s per-row check printed a CPU lane’s decodeslower, NOT judgedwhen the new run’s own requests ran over 0.3 other cores, orfaster, NOT judgedagainst a loaded baseline, and when neither start shut the load gate the pairing still exited 0, “no regression detected”. Such a decode verdict is now withheld through the pairing’s gate (WITHHELD for load: … decode tok/s (its requests' load)), so the run exits 4 unless--allow-load-difference: the tracked GenieX v0.6.1 → v0.7.0 CPU speed pair (+9.2 %,faster, NOT judged) now does. A flat rate against a loaded baseline isunchanged, NOT judgedtoo: a busy baseline understates the old rate and hides a real drop as well as it fakes a gain. A slower new run against a loaded baseline is still judged — the real drop is only larger.bench_compareread a changed lane set as a runtime slowdown. When twolanesreports ran different sets of lanes, every tok/s was judged anyway: a dropped lane read as the aggregate*** SLOWER ***, and an added one as the surviving lane slowing (NPU 22.9 → 8.8 tok/s beside the CPU lane). No tok/s is judged across a changed lane set now (NOT judged: the lane set changed) – neither a regression nor a load withholding, so exit 4 keeps one meaning – and a lanes pair with nothing else like-for-like isNOTHING COMPARED(exit 3), not “no regression detected”. Theupgrade_checkMANIFEST legend now reads its exit-4 reasons fromcompare_verdict, so it names the per-request case too.bench_sweepmeasured a backend’s own URL when the candidate overrode it. A candidates entry naming abackendand abase_urlwas gated at the override, and then every tool got--backendalone and measured the registry’s URL. Both flags are passed now: the URL wins, and the backend’s entry still supplies headers and keys. With--baseline,_sweep.jsonrecordsconditions_differ(bench_compareexit 4) besideregressed.bench_sweepnever passed--repeatstobench_agent(the P7.4 review): a sweep asked for three draws measured the agent once.bench_tools,bench_chatandbench_agentnow share one argv builder, and--repeatsbelow 1 is refused before anything runs (bench_agent rejects it; bench_coding, bench_tools and bench_chat would measure nothing).bench_codingrecorded a wrong non-Python reply as CUT.looks_truncated()checked an unclosed final fence with Python’scompile()for every language, and no PowerShell, bash, CMake or Dockerfile answer parses as Python. So a wrong reply that stopped on its own (finish_reason: stop) and forgot its closing fence was left out of the rate, the interval and the rank as unmeasured, instead of counted FAIL. The probe is now Python-only. For other languages the finish reason, the token cap and whether the fence is closed decide. Python grading is unchanged. Older reports cannot be re-graded: rows store neither the finish reason nor the raw reply.upgrade_checkrecorded no model files for any lane. It calledruntime_info(base_url)without the lane’s model, before and after its steps. A GenieX runtime therefore named nomodel_files, and weights re-pulled under the same id mid-check read as the same lane. Both probes now pass the lane’sbackends.jsonmodel, and a lane whoseprovenance.model_files_notes()reports a change during the check (MODEL FILES CHANGED behind …, an editedgenie_config.jsonor HTP extensions file) fails the check.hostload.LaneProcessreported a listener whose pid is hidden (another user’s process) as “no local process listens on port N”. It now says the pid is hidden and recordslistens_here, so a lane inside WSL is never attributed to a Windows process on the same port.bench_coding’sthinking_char_shareread a reply cut off inside<think>as 0 % thinking. It followed only a closed</think>; it now uses the speed runner’s rule (answers.split_answer: an unclosed<think>is all thinking). Found on Qwen3-8B W4A16 on the NPU lane, CUT on every coding task with think=0 %.A suspect case’s seconds leave every wall statistic (roadmap P1.3’s known gap).
mark_suspect_cases()never recomputedwall_measured_s, the fieldbench_compare’s timing verdict prefers, so after a case the control also fails was excluded, the full wall was divided by the reduced attempt count: 26 s over 2 kept attempts read 13 s each, where the kept rows say 3 s.median_wall_swas also only redone whentotal_wall_swas present. Nowtotal_wall_s,wall_measured_sandavg/median/stdev_wall_sare recomputed over the kept rows.unmeasured_wall_sis left alone on purpose: it goes with the errored and truncated counts, which are not recounted either, and no verdict reads it.--prompt-variantsinflatedeffective_n: it counted each(case, variant)pair, or each attempt, as its own observation. A case’s phrasings are now one observation per round, observed through the prompt as written, and this still holds after a control’s suspect cases are removed (mark_suspect_casesre-derives it throughbench_variants.variant_spread).bench_agentrefused aconftest.pyorpytest.iniadded beside the protected tests but accepted a newpyproject.toml,.pytest.iniorsetup.cfg. Any of them could deselectfix_failing_test’s red test throughaddopts, and the run scored a PASS. All six pytest config files are override files now, and--self-testhas apytest config addedcheat row.tool_sha256missed two graders and depended on the checkout path.bench_coding’s now coversbench_tasks.py(the extended and language sets) andbench_tools’ coversgeniex_toolcall_shim.pyunder--accept-text-json; before, an edit to either moved scores with no BENCHMARK SOURCE CHANGED. Files are ordered by name, so the same source no longer hashes differently depending on where the checkout lives.The benchmark viewer page never compiled.
import frontend.frontendpassed, but building the page raised five errors in turn: a foreach over anAny-typed var, a Pythonifon a Var, a table cell passed ten arguments,.length()on an untyped chart item, andcard()passed awidth=it did not accept.The viewer never found its manifest when started as documented (
cd frontend; reflex run): relative paths were read fromfrontend/. They are now read from the repository root.The viewer’s Hardware card showed a contract report’s provenance (“? cores / ? threads”, “? GB”) whenever that report sorted before a speed run.
Two GenieX v0.7.0 (and v0.6.1) defects the lab had read as properties of the models.
temperature: 0is treated as “unset” on both lanes and the default sampler runs (top_k: 1gives greedy decoding;temperature: 0.01is honoured as a low temperature); and an identical request sent twice in a row takes a cache path that changes the reply — the llama.cpp lane prefills 0 tokens and samples the first token from the previous reply’s logits, the QAIRT lane reuses part of the dialog. Every--repeatsattempt after the first, the determinism probe and the contract’s T=0 and seed checks measured that path; the QAIRT lane’s “order dependence” was this.bench_toolsandbench_codingnow send a throwaway request between repeats of one case (client.spacer, recorded asconfig.repeat_spacer), and the probes are spaced. Live probes and their output are in the run directory’sv070r2-probes/.Thinking share and “time to a finished answer” were computed over cut-off replies: an unclosed
<think>scored 0 %, the summary averaged only the rows that closed it, and time to the token cap was the ranking metric. On the v0.7.0 CPU lane that printed 86 % where the run was ~95 %, and 12.5 s “to a finished answer” for a run in which 6 of 9 replies never left<think>.Net energy against one 5-second idle baseline. Two NPU runs 15 minutes apart read 1.26 and 1.84 W, which turned a +21 % gross difference into a published “+70 %”. The baseline is now taken before and after the requests, rows are netted against the mean, and a drift over 0.2 W is reported.
Per-request energy edge cases: the EMI energy and timestamp counters are read in one PDH collection (two queries could pair a new stamp with the previous second’s energy), a counter that went backwards or stood still is not a sample,
--idle-seconds 0no longer divides by zero, and a lane process that exits reads as unknown rather than 0 CPU-seconds.lanessummed per-lane rates over unequal windows (published 0.65×/0.66× for NPU+CPU; 0.54×/0.56× delivered). The report’s aggregatetok_per_secis now delivered throughput (the sum stays assummed_tok_per_sec, andbench_comparecompares sums when one side is an older report); it sends a fresh prompt per phase (the “together” request was a cache hit) and counts thinking tokens.Contract answers on insufficient evidence: an empty
/v1/completionsreply was “stop honoured”, any 5xx mentioning “context” was a clean overflow, a 0.4 s cold prefill decided the cache rows, and thinking inreasoning_contentwas “no<think>”. The first three now answerinconclusiveorno; thinking inreasoning_content/reasoning(or counted inreasoning_tokens) answersyes.contract --base-urldetected the model on the module default URL, not the lane it probed.power_mode_understoodasks whether the value is validated, times the reload it causes and ends with a plain request so the lane is left as launched.effective_ncounted agreeing repeats as independent trials. When every repeat agrees on pass/fail the case is the unit, whatever the text did.tool_sha256depended on line endings (a Windows and a WSL checkout of one commit disagreed); it is normalised for every tool. The speed runner also takes it at the start and names a mid-run change; since the run-start record under Added, every other tool does too.Energy was recorded for remote lanes, from this host’s rails; the meter now runs only when the lane’s process is local. A counter that went backwards restarts the curve instead of unmetering the rest of the run, and a single idle baseline reports
net_reliable: null, nottrue.mark_suspect_casesand the viewer undid the counting rule: excluding a control’s suspect cases reseteffective_nto attempts for a lane whose repeats agree, and the viewer roundedpassed × n / totalinto a count; both now use the cases andeffective_k.cp1252 crashes on Windows: a redirected
contract --difforbench_comparedied on→with exit 1 — their “changed”/”REGRESSION” code. Every CLI now writes UTF-8.bench_tools --turn-growthdropped--toolsand--context-tokens, so an opencode-preamble turn-growth run measured the eight default tools.bench_sweepput the repository’s parent onPYTHONPATHfor the tools it runs.The determinism probe measured the wrong thing. It sent two identical requests back to back, and on GenieX an identical follow-up takes a cache path that changes the reply (above): two open-ended T=0 requests to the v0.6.1 QAIRT lane came back different, while the old 8-token “ready” reply happened to survive it. It now asks for 48 open-ended tokens with an unrelated request between the draws. Spaced, the QAIRT lane is reproducible — its bundle samples from a fixed seed, re-seeded per request — and the probe records it deterministic, so
bench_compare’s strict per-case mode applies there; the llama.cpp lanes, which sample at T=0 (“unset”), record not deterministic.cpu_percentnever saw the request. It averaged a sample taken before the request and one taken after it; a CPU lane pinning 7.5 of 8 cores read as idle. It is now integrated over the request wherever the lane shares the harness’s host (cpu_percent_methodsays which).orchestrant-bench speedno longer guesses a model from a listing. GenieX answers/v1/modelswith its whole local cache, so--base-urlalone benchmarked — and made the lane load — whichever cached model sorted first. Several listed ids are now a refusal that names them.Windows hosts: ~33 s of dead time per prompt, ~40 s per report, and a paid host probed on every run. A refused localhost connect costs 2–4 s on Windows; the sampler asked four Glances URLs twice per prompt and
busy_lanes()asked every registry entry in turn — including the one markedprobe: false, whose discovery request costs money. A failed Glances is now asked once per run, the lane probe is parallel and honoursprobe: false, andSystem Idle Process(pid 0) no longer ranks as the busiest process.The Linux static-analysis gate could never start, so it graded nothing. Every Linux lane run from 2026-09-12 to 2026-09-15 ended
static analysis (orchestrant) FAILED (6 of 6)with six identicalerror: Failed to spawn: codespell/bandit/vulture/ruff/tylines — not one finding, on either arch. The 231-packageuv synclanded correctly in.venv_static_analysis, becauseuv_sync_projectreaches it through_CURRENT_VENV_PATH; the tools then looked somewhere else.uv_venv_createdeliberately does not activate, and this wrapper clearsVIRTUAL_ENVto keep the sync off the image’s root-owned/opt/venv, so the driver’suv run --activehad no active environment, fell back to the project default.venv, created it, installed the 28 base dependencies and found none of the analysers — they are declared in thetestEXTRA, which that fallback sync does not install. It could only ever pass on a box where.venv_static_analysisalready existed, sinceuv_venv_ensureactivates on its reuse branch; on clean CI it could not.scripts/linux/ci_static_analysis.shnow exportsUV_PROJECT_ENVIRONMENT(the driver’s ownVENV_DIR, computed by mirroringdetect_workspace) andUV_NO_SYNC=1— the second is not optional, becauseuv runwould otherwise re-sync that environment with the DEFAULT extras and uninstall the tools it is about to spawn. All six analysers now read this tree. They are not silent about it: ruff check and ruff format are clean, and codespell, bandit (52 findings — 43 low, 9 medium, 0 high), vulture and ty report real ones. Triaging those is a suppression-policy decision and is deliberately left open rather than excluded away.The
pytestconsole script can collecttests/unit/frontendagain.tests/unit/andtests/unit/frontend/are packages buttests/is not, so under pytest’s default prepend import mode the basedir oftests/unit/frontend/test_benchmark_data.pywastests/itself: the repo root never reachedsys.path, and itsfrom frontend.frontend import benchmark_dataraisedModuleNotFoundError: No module named 'frontend'. That is the two-error collection failure that failed the Windows lane’s Python 3.13 test step, and it would have failed the Linux lane’s too as soon as the static-analysis step above stopped aborting the job before the tests ran.python -m pytesthid it by putting the CWD onsys.pathregardless — which is whybenchmarks.ymlnever saw it and the CI drivers, which calluv_run pytest, always did. Fixed withpythonpath = ["."]in[tool.pytest.ini_options](andminversionraised to the 7.0 that option needs, so a pytest 6 cannot ignore it silently). An__init__.pyintests/fixes the same import and is the wrong fix: it renames these modules totests.unit.*, which collides withbenchmarks/tests/— also a package, also imported astests— in the single sessionbenchmarks.ymlruns over both trees. Measured rather than assumed: that swap turns the green benchmarks job intoModuleNotFoundError: No module named 'tests.unit'.CI grades the tree as committed. The lanes ran
ruff check --fixand a bareruff format, which repair the checkout CI is about to delete: every auto-fixable finding was invisible, and formatting could never fail. Both now runruff check --no-fixandruff format --check --diff.Real type and correctness findings the silent gate had been hiding.
orchestrant/monitoring/gpu.pycalled elevenpynvmlattributes through amodule | Nonethat thePYNVML_AVAILABLEbool never narrowed;pipeline/capture/gstreamer.pyread.stdoutoff aPopen | Noneand called.readintobehind ahasattrthat narrows nothing;smoke/checks.pycalled.tolist()on the non-tensor arms ofInferenceSession.run’s return union;streaming/app.pycached its Flask app on a function attribute. Several of these were “handled” by# type: ignore[...]comments in mypy syntax, whichtydoes not read.The viewer
render()API takes its nine telemetry arguments by keyword. They were positional-or-keyword (ruffPLR0917); every call site already passed them by keyword except one internalwx.CallAfter.
Security#
orchestrant/pipeline/capture/gstreamer.pycarries explicit, justified# nosec B404/B603markers on the gst-launch spawn instead of relying on bandit’s result being discarded. Bandit is now part of the gate, so a NEW finding fails CI.
0.0.28 - 2026-09-06#
Changed#
Project renamed
Orchestr-ANT-ion→OrchestrANT. The old name splicedANTinto “orchestration” with hyphens; every other repo in the family (ANThology,OxidANT,AccelerANTgine,OmniAccelerANT) capitalises anANTthe base word already contains, with no separators and noKataglyphis-prefix. What moved:distribution
Orchestr-ANT-ion→OrchestrANTimport package
orchestr_ant_ion→orchestrant, sofrom orchestr_ant_ion.pipeline import Xbecomesfrom orchestrant.pipeline import Xconsole script
orchestr-ant-ion-smoke→orchestrant-smokerepository
Kataglyphis/Kataglyphis-Orchestr-ANT-ion→Kataglyphis/OrchestrANT
0.0.27 - 2026-07-14#
Added#
IREE (iree.dev) is now a declared dependency of the
ml-ai*extras:iree-base-compiler+iree-base-runtime(guardedplatform_machine != 'riscv64'). PyPI shipscp312-abi3wheels for x86_64/aarch64 that install on Python 3.14, soiree.compiler+iree.runtimeare now actually present for the existingcheck_ireesmoke to exercise (MLIR compile + local-task run,abs(-5)=5). On riscv64 there is no PyPI wheel — ANTfrastructure source-builds the runtime wheel into/opt/wheels(compiler stays absent there, socheck_ireedegrades to optional-fail, non-gating). Kept in sync with ANTfrastructureIREE_VERSION(v3.11.0).
[0.0.26] - 2026-07-14#
Added#
Wheel smoke:
check_opencvnow round-trips JPEG (.jpg) alongside PNG — JPEG is the app’s live MJPEG streaming codec, so it is exercised directly rather than assumed from the PNG result.Wheel smoke:
check_onnxruntimeassertsCPUExecutionProvideris inget_available_providers()(catches an execution provider silently dropped from the on-target build).Wheel smoke: new optional checks
check_opencv_dnn(cv2.dnn module + protobuf link viablobFromImage),check_opencv_codecs(TIFF/WEBP/OpenEXR round-trip; surfaces per-arch codec drops), andcheck_opencv_freetype(cv2.freetype text rendering — validates the source-built freetype on riscv64). All three are non-gating (WARN) so a per-arch feature gap is visible without failing the smoke.
[0.0.22] - 2026-07-12#
Added#
orchestr_ant_ion.smoke— a shipped wheel smoke-test module. Each check does real work (torch autograd + a linear forward/backward, torchvisionops.nms, an embedded ONNX Add inference, an OpenCV encode/decode/cvtColor round-trip, Pillow, a torch↔numpy ABI bridge) rather than a bare import, so a mislinked compiled extension is caught even whenimportsucceeds. Run it with:python -m orchestr_ant_ion.smoke # text report, exit 1 on failure python -m orchestr_ant_ion.smoke --json # machine-readable
Also exposed as the
orchestr-ant-ion-smokeconsole script. Container images (Kataglyphis-ANTfrastructure) invoke it under emulation to verify the ML stack.LiteRT is checked as an optional runtime (WARN, not a gate failure) and probes both module names (
ai_edge_litertupstream /tflite_runtimecustom).
Changed#
Bumped PyTorch to 2.13.0 and TorchVision to 0.28.0 across every backend extra (
pytorch-cpu/-cu130/-rocm71/-custom, thetorchvisionbuild-deps, and the riscv64 sourcegitrefs@v2.13.0/@v0.28.0).Regenerated
uv.lockfor the new torch/vision pins.
[0.0.21] - 2026-07-12#
Added#
pytorch-customoptional-dependencies extra — a “bring your own wheels” PyTorch backend.torch/torchvisionare declared as plain pins with no index or git source override, so a prebuilt/custom wheelhouse satisfies them directly:uv sync --extra pytorch-custom --find-links /path/to/wheels # or: export UV_FIND_LINKS=/path/to/wheels && uv sync --extra pytorch-custom
Ideal for platforms without upstream binaries (e.g. riscv64) or your own optimized torch build — zero source builds, and no per-package
--no-install-package/ force-reinstall dance. Mutually exclusive withpytorch-cpu/pytorch-cu130/pytorch-rocm71(wired into[tool.uv] conflicts).
Changed#
Scoped the resolved lock to the environments whose PyTorch is resolvable from public indexes via
[tool.uv] environments(linux non-riscv64, macOS, Windows). riscv64 has no upstream torch wheels, so it now resolves fresh atuv synctime and picks the custom wheels up from--find-links;uv sync --frozenfalls back to a live resolve automatically.Regenerated
uv.lock(resolves 262 packages; verified consistent withuv lock --check).
[0.0.20] - 2026-07-11#
Added#
Container/runtime-oriented ML dependency extras
ml-ai-webgpu,ml-ai-nvidia, andml-ai-rocm, mirroring the ONNX Runtime / PyTorch backend combinations that Kataglyphis-ANTfrastructure previously patched intopyproject.tomlat install time. Selecting a backend is now a first-class extra rather than an install-time patch.
Changed#
Regenerated
uv.lockfor the new extras (resolves 261 packages; verified consistent withuv lock --locked).