feat(QUANT-QWEN38-27B-NVFP4-ARM): the second 27B NVFP4 artifact is a ModelOpt checkpoint, and nothing in this tree read its config - #1603
Merged
Conversation
…ModelOpt checkpoint, and nothing in this tree read its config `r0b0tlab/Qwen3.8-27B-NVFP4-MTP-sm121` @ `36f717a2` is a second published NVFP4 quant of Qwen3.8-27B, and campaign #1574 needs it. It is not a second revision of the artifact W4 pinned. It declares `quant_method: "modelopt"` and `quant_algo: "MIXED_PRECISION"` with a `quantized_layers` map of 401 exact module names, where the other declares `format: "mixed-precision"` under compressed-tensors with regex `targets` and a 303-entry `ignore`. None of W4's four recorded blockers applies to it. They were properties of the unsloth artifact rather than of the format. This one ships per-tensor STATIC FP8 (`weight_scale` and `input_scale` both `F32 []`, 208 modules) and ModelOpt W4A16_NVFP4 group_size 16 (`weight` U8 + `weight_scale` F8_E4M3 + `weight_scale_2` `F32 []`, 193 modules including `lm_head`), which are the two spellings this loader already reads, so both halves load. The blocker it exposed instead is that nothing read the config at all. `ct::Config::FromQuantizationConfig` stops at `quant_method != "compressed-tensors"`, so W4's whole-checkpoint read answers "" for a ModelOpt checkpoint. Every routing decision then falls to the loader's per-projection tensor-NAME probe. That probe cannot be wrong about the bytes and it can be wrong about the checkpoint, in both directions, and both load silently: a module declared FP8 that ships an NVFP4 spelling takes the NVFP4 arm, and a module declared unquantized that ships scales takes a quantized arm. Both produce plausible numbers and matching tokens while moving the wrong bytes. So `LoadQwen3_5Dense` now reads the ModelOpt config once for the whole checkpoint and refuses by name when the declared algorithm and the shipped operand names disagree, or when the declared algorithm has no loader here. This is the first production consumer `modelopt_mixed_precision.h` has ever had; until now its only reachable entry points were its own two unit tests. The refusal CHECKS the probe rather than replacing it. Routing by the declaration would move an arm on `nvidia/Qwen3.6-27B-NVFP4` @ `0893e160`, a gate model with the same ModelOpt shape that declares 193 modules `W4A16_NVFP4` while shipping an `input_scale` on each, which `VT_MODELOPT_W4A4=1` reads to select the fp4-activation GEMM. That is a measurement change wearing a correctness change; #1597 owns it. The gate is this artifact's own 2001-name accounting, from four committed header-only manifests. W4's 1968-name accounting is untouched, because a gate that accepted both name sets by loosening would have stopped measuring either. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
… NOT be refused, and one case now says so `nvidia/Qwen3.6-27B-NVFP4` @ `0893e160` reaches the same new call site: it declares the same `quant_method: "modelopt"`, the same `quant_algo: "MIXED_PRECISION"` and the same 401-entry split of 208 `FP8` and 193 `W4A16_NVFP4`. It is the FP8-tower gate model of #466 and the checkpoint every recorded 27B NVFP4 ratio was taken on, so "does this refusal fire on something that loads today" is a question about it and not about the artifact this row pins. It differs in exactly three ways, each of which could have made the refusal fire. Its `exclude_modules` is `["mtp*", "mtp.layers.0*"]`, matched by `fnmatch` rather than exactly. Its 193 NVFP4 modules also ship an `input_scale`, which is the operand `VT_MODELOPT_W4A4=1` reads. And its `config.json` declares a `kv_cache_scheme` for which it ships zero `k_scale`/`v_scale`. The new case rebuilds that shape and asserts the refusal is silent on all three, which turned a claim in the pull request body into something executable. It discriminates rather than decorating: narrowing `ModeloptNvfp4()` to reject a module that also ships an `input_scale` reds this case alone and leaves the other fourteen green. The records carry the measured numbers rather than the estimate they were written with: 15 cases and 1649 assertions, and eight negative mutations all detected. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
localai-bot
force-pushed
the
row/QUANT-QWEN38-27B-NVFP4-ARM-W5-LOAD
branch
from
August 21, 2026 15:02
0649063 to
70f57a1
Compare
The branch was 24 commits behind, and preflight SKIPS its commit-trailers and commit-style gates on a branch that is behind, so neither had read a single commit of this tree. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…ed green, and the KV skip asserted nothing at all Six findings from the fresh review of #1603, repaired. The loader behaviour is unchanged for every checkpoint that agrees with its own config; what changes is what the gate can SEE. THE BLOCKING ONE. The NVFP4 refusal branch is the entire cross-check for the 193 `W4A16_NVFP4` modules, 48% of the 401 this artifact declares and 48% of the sibling's, and mutating it to `if (false && ...)` left the suite fully green at 15 cases / 1649 assertions. Every case exercised the FP8, the unquantized, the MXFP8 or the unimplemented-algo arm; the sibling-shape case pinned only the direction in which the NVFP4 branch must NOT fire. Two cases now assert the refusal itself, one per shape a checkpoint can ship under that declaration: the per-tensor static FP8 spelling, and plain bf16. Both run through `LoadQwen3_5Dense`. The reviewer's exact mutation now reds 2 cases. THE KV SKIP ASSERTED NOTHING. `Refusal` skipped `k_scale`/`v_scale` before any module was built, and `ModuleOperands::Add` had no branch for either, so the skip changed no verdict and deleting it left the suite green. Its sibling test carried the comment "the SUFFIXES must stay out of the cross-check whatever a checkpoint does with them", which is an assertion no assertion could read. The skip is gone and the decision now lives in one branch: `Add` RECORDS a KV scale and `AnyQuantOperand` deliberately leaves it out, because `kv_cache_quant_algo` is a sibling of `quantized_layers` and a bf16 tower shipping a KV scale must not be refused for "shipping a quantized spelling". Two cases hold both directions. AN UNSEEN OPERAND FAMILY IS REFUSED, NOT SKIPPED. `SplitOperand` documents that `false` means "this resolver has never seen the family, and the caller must NOT read that as unquantized", and `Refusal` did exactly that with a `continue`: the name belonged to no module, so nothing cross-checked it in either direction. A declared-FP8 module shipping `qweight`/`qzeros`/`scales` LOADED, silently. Both pinned artifacts classify every name they ship — 2001 of 2001 and 2194 of 2194 — so this refusal cannot fire on either, and a case asserts that. THE MANIFEST COUNT WAS A HAND-MAINTAINED LITERAL that `Append` iterated, so a row deleted without its count was an out-of-bounds read: a SIGSEGV inside case 1 that aborted the binary with 14 cases never run, and a crash is neither a pass nor a fail. The count is derived with `std::size` and the literal is now asserted against the array instead of trusted by it. Two record repairs ride along. The file header's item (2) gave the per-scheme composition as 208/624, 193/579 and 584/798 while the case below it asserts 256/720, 193/579 and 536/702 — a difference of exactly the 48 `linear_attn` containers and their 96 tensors, which resolve to FP8 through upstream's strategy-3 prefix scan rather than staying unlisted. The asserted set is the measured one. And `docs/FEATURES.md`, the spec and the quantization matrix named #1185 as W6's open blocker; it closed on 2026-08-18 as local-only. #1632 files what actually blocks W6 and the five citations name it. New cases are split one per shape rather than grouped under `SUBCASE`, because a `REQUIRE` aborts its whole TEST_CASE and a subcase that reds hides every subcase after it. RED before the header repair, on the final test file: 22 cases, 3 failing — the AWQ triple and the block-wise scale both answering with an EMPTY refusal, and the KV case reporting "they ship weight" for a module that also ships `k_scale`. GREEN after: 22 cases / 1687 assertions. W4's 1968-name gate is untouched and re-ran 9 cases / 194 assertions green on the same tree. Repairs the fresh review of #1603. Issue #821, blocker record #1632. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
… counts of a gate that has since grown The spec's W5 outcome and the matrix row quoted "15 cases / 1649 assertions", "Eight negative mutations" and a call-site deletion that reds five cases. All three were measured on the reviewed head. The repair commit added six cases and re-measured every mutation on the tree that landed, so the numbers are 22 / 1687, twelve mutations, and eight cases red on the deleted call site. Two of those twelve did not exist before, and they are the point rather than padding: `if (false && ...)` on the NVFP4 refusal branch, which used to leave the suite fully green while disabling the whole cross-check for 193 of the 401 declared modules, and an unseen operand family skipped instead of refused, which `SplitOperand`'s own contract forbids reading as unquantized. The suffix-list and manifest-row mutations red more cases than before for the same reason: the new cases sit on the same names. The spec gains a section recording what the review found and what each repair changed, because the outcome section is where a reader looks for why a default has its value and the six findings are exactly that. `docs/FEATURES.md` gains the third refusal reason on the ModelOpt row. The cell is trimmed to stay inside the 220-character budget rather than growing past it. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
origin/main moved during the gate run, and preflight SKIPS its commit-trailers and commit-style gates on a branch that is behind, so a run that reported nothing about this tree is not a green one. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
… and reconcile the two rows it invalidated Third merge in one session, because origin/main moved twice during the gate runs and preflight reports nothing about a branch that is behind. TWO SEMANTIC CONFLICTS, both from work that landed while this branch was open. #1591 (`BENCH-QWEN38-27B-SOTA`) added a `docs/FEATURES.md` row for the same artifact reading `☐ UNTRIED`, which textually conflicted with the two rows this branch adds. Its key and its facts are kept -- 208 `input_scale`, an `F32` scalar `weight_scale`, `"dynamic": false`, the three `unsloth` blockers absent -- and only its STATE moves to `◐ LOADS, never RUN`, because leaving `UNTRIED` beside two rows saying the checkpoint loads would put a contradiction in a projection document. The cell stays inside the 220-character budget. #1593 (`KV-FP8`) landed the CUDA fp8 KV store, so the comment above `IsKvCacheScaleSuffix` saying "this tree has no quantized KV cache to apply it to" is no longer true. It now says what is still true and narrower: no path in THIS loader reads a checkpoint `k_scale`/`v_scale` tensor, and feeding the store from a checkpoint's scales is a different job. Nothing about the refusal changes. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
localai-bot
marked this pull request as ready for review
August 21, 2026 21:47
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
W5 of #821, and campaign #1574 needs it.
r0b0tlab/Qwen3.8-27B-NVFP4-MTP-sm121@36f717a22990e82c54c1d48ee77c491b87825680is a second published NVFP4 quant of Qwen3.8-27B, and campaign #1574 needs it. It is not a second revision of the artifact W4 pinned, and it is not the same format.unsloth/Qwen3.8-27B-NVFP4@7d6f8d4d(W4)r0b0tlab/...-MTP-sm121@36f717a2(W5)quant_methodcompressed-tensorsmodeloptformat: "mixed-precision"quant_algo: "MIXED_PRECISION"config_groups, regextargetsquantized_layers, 401 exact module namesignoreBF16 [out, 1]F32 []input_scaleinput_scaleF32 []lm_headWhat this changes
None of W4's four recorded blockers applies to the new artifact. They were properties of the unsloth checkpoint rather than of the format. This one ships the per-tensor static FP8 that
LoadFp8Rawalready reads and the ModelOpt NVFP4 thatLoadNvfp4AnyNamingalready reads, so both halves load.The blocker it exposed instead is that nothing in this tree read its config at all.
ct::Config::FromQuantizationConfigstops atquant_method != "compressed-tensors", so W4's whole-checkpoint read answers""for a ModelOpt checkpoint, and every routing decision falls to the loader's per-projection tensor-NAME probe. That probe cannot be wrong about the bytes and it can be wrong about the checkpoint, in both directions, and both directions load silently: a module declared FP8 that ships an NVFP4 spelling takes the NVFP4 arm, and a module declared unquantized that ships scales takes a quantized arm. Both produce plausible numbers and matching tokens while moving the wrong bytes, which is the defect classAGENTS.mdsays a token gate cannot see.LoadQwen3_5Densenow reads the ModelOpt config once for the whole checkpoint, through the sharedQuantizationConfigOflookup, and refuses by name when the declared algorithm and the shipped operand names disagree, when the declared algorithm has no loader here, or when a shipped tensor name belongs to an operand family this resolver has never seen. This is the first production consumermodelopt_mixed_precision.hhas ever had; until now its only reachable entry points were its own two unit tests, which is the state W4's record named and did not change.Why a cross-check and not a router
Routing by the declaration instead of by the probe would move an arm on a gate model.
nvidia/Qwen3.6-27B-NVFP4@0893e160has the same ModelOpt shape and the same 208/193 split; it declares all 193 NVFP4 modulesW4A16_NVFP4and also ships aninput_scaleon each, whichVT_MODELOPT_W4A4=1reads to select the fp4-activation GEMM. That is a measurement change wearing a correctness change and it needs its own before/after. #1597 owns it, and## Owednames it.The cross-check moves no arm: a checkpoint whose config and tensors agree loads exactly as before. Both ModelOpt checkpoints this loader sees were read from the hub before the call site was written, and on both, all 937 weight-bearing modules agree AND all 2001 / 2194 shipped names classify, so both answer
"". Every other checkpoint failsIsMixedPrecisionatquant_methodand reads nothing.The MXFP8 line in vLLM's loader log is not about this checkpoint
Running the artifact under vLLM prints
Detected ModelOpt MXFP8 checkpoint. At the pin5559679229bc961848b121ccdeaa8fa5d79bec98,ModelOptMixedPrecisionConfig._from_config(modelopt.py:2371-2400) unconditionally constructs all four candidate sub-configs and each warns from its own__init__(:386,:1035twice,:1708), so the lines report what was constructed and not what was selected. Selection isModelOptMxFp8Config.override_quantization_method(:1724-1731), which fires only when the algo string containsMXFP8; per-module dispatch isget_quant_method(:2525-2535), which hands outmxfp8_configonly for an entry that says MXFP8. This artifact's 401 entries are 208FP8and 193W4A16_NVFP4, it ships noF8_E8M0tensor, and its only non-scalar scales are the 193 NVFP4 group scales. No MXFP8 arm is implemented; a declared MXFP8 module is refused by name, and here that refusal never fires. Both halves are asserted so nobody builds an arm on a log line.Gate
New:
tests/vllm/models/test_qwen38_27b_modelopt_mtp_arm.cpp, hermetic, 22 cases / 1687 assertions, over four committed header-only manifests (970 + 976 + 40 + 15 = 2001) and two committed config documents. Every shipped name classifies, nothing is unaccounted in either direction, and the buckets sum to 2001: 720 tensors over 256 modules toFP8, 579 over 193 toW4A16_NVFP4, 702 over 536 unlisted, zero KV-cache scales, zero unclassified. The 256 is separated from the 208 the map names — 48 arelinear_attncontainers reached by upstream's strategy-3 prefix scan, asserted as 401kDirectagainst 48kPrefix— so no count is right for the wrong reason.W4's 1968-name accounting is untouched and re-ran green on the same tree at 9 cases / 194 assertions. A gate that accepted both name sets by loosening would have stopped measuring either.
Four shards verified semantically, per
AGENTS.md, rather than by a remote hash: header parse plus8 + header_len + max(data_offsets[1])equal to the published size for each, and the four sizes exceeding the index'smetadata.total_sizeby exactly the four headers plus their 8-byte prefixes.One case exists only to say the refusal is SILENT on
nvidia/Qwen3.6-27B-NVFP4's shape — wildcardexclude_modules, aninput_scaleon every NVFP4 module, and akv_cache_schemewith zero scales — because each of those three could have made it fire on a gate model.What the fresh review found, and what the second commit repaired
The review mutated each claimed guarantee, and six findings came back. None of the repairs changes what the loader DOES for a checkpoint whose config and tensors agree. What changed is what the gate can SEE.
if (false && ...)on the NVFP4 refusal branch left the suite fully green at 15 cases / 1649 assertions, W4's gate green beside it. That branch is the entire cross-check for the 193W4A16_NVFP4modules — 48% of this artifact's 401 and 48% of the sibling's,lm_headincluded — and nothing asserted it: every case exercised the FP8, the unquantized, the MXFP8 or the unimplemented-algo arm, and the sibling-shape case pins only the direction in which the branch must NOT fire. Two cases now assert the refusal itself, one per shape a checkpoint can ship under that declaration, both entering throughLoadQwen3_5Dense. The review's exact mutation now reds both.SplitOperanddocuments thatfalsemeans "this resolver has never seen the family, and the caller must NOT read that as unquantized", andRefusaldid read it as exactly that: the name belonged to no module, so nothing cross-checked it in either direction. A declared-FP8 module shippingqweight/qzeros/scalesLOADED through the production entry point, silently. It is refused by name now, and the blast radius is measured rather than assumed — both artifacts that reach this resolver classify EVERY name they ship, 2001 of 2001 and 2194 of 2194, so the arm cannot fire on either, and a case asserts the first of those.Refusalskippedk_scale/v_scalebefore any module was built andModuleOperands::Addhad no branch for either, so the skip changed no verdict and deleting it left the suite green — while the sibling case carried the comment "the SUFFIXES must stay out of the cross-check whatever a checkpoint does with them", an assertion no assertion could read. The skip is gone and the decision lives in one branch now:AddRECORDS a KV scale andAnyQuantOperanddeliberately leaves it out, becausekv_cache_quant_algois a sibling ofquantized_layersand a bf16 tower shipping a KV scale must not be refused for "shipping a quantized spelling". Two cases hold the two directions and two mutations red them.Appenditerated, so a row deleted without its count was an out-of-bounds read: a SIGSEGV inside case 1 that aborted the binary with 14 cases never run, and a crash is neither a pass nor a fail. The row count is derived withstd::sizeand the literal is asserted against the array rather than trusted by it. The same mutation now reds 5 cases with all 22 having run.linear_attncontainers and their 96 tensors, which resolve to FP8 through the prefix scan rather than staying unlisted. The asserted set is the measured one and the prose says so.rclease ondgx:gpu0, and a model run is still UNTESTED #1185 closed on 2026-08-18 as local-only, three days before the reviewed head, and five sites cited it as the open blocker. W6's NVFP4 token gate cites a CLOSED blocker (#1185); what still blocks it is the DENOMINATOR configuration in a lease, and the 20.4 GiB artifact not being staged #1632 files what actually blocks W6 — the recorded DENOMINATOR configuration surviving a lease, and this artifact's ~20.4 GiB being staged — and the five citations name it. The oracle itself does run a model in a lease; it did so atmax_num_batched_tokens512 against a denominator of 8192, which is a different engine setup rather than a smaller measurement.Two smaller things ride along. The branch was 24 commits behind
origin/main, which is why preflight SKIPPED itscommit-trailersandcommit-stylegates and reported nothing about this tree; it is merged up toorigin/maina8f19a5d8— three merges, becausemainmoved twice during the gate runs — every merge is BUILT, and both gates now run and pass. And the new cases are one per shape rather than grouped underSUBCASE, because aREQUIREaborts its whole TEST_CASE and a subcase that reds hides every subcase after it — which is how the first red-before run reported one failure where there were three.Two rows the last merge invalidated
mainmoved three times while this branch was open, and two of its landingstouched what this one says.
BENCH-QWEN38-27B-SOTA) added adocs/FEATURES.mdrow for this same artifact reading☐ UNTRIED. Its key and its facts are kept — 208input_scale, anF32scalarweight_scale,"dynamic": false, the threeunslothblockers absent — and only its STATE moves to◐ LOADS, never RUN, becauseUNTRIEDsitting beside two rows saying the checkpoint loads is a contradiction inside a projection document.KV-FP8) landed the CUDA fp8 KV store, which falsifies the comment aboveIsKvCacheScaleSuffixsaying "this tree has no quantized KV cache to apply it to". It now says the narrower thing that is still true: no path in THIS loader reads a checkpointk_scale/v_scaletensor, and feeding that store from a checkpoint's scales is a different job. Nothing about the refusal changes.Red before, and the mutations
RED before the call site was wired: 13 cases, 5 failing, every failure
refusal was: <empty>— the loader accepted every config/tensor disagreement and both unimplemented algorithms. RED again before the review repair, on the final test file: 22 cases, 3 failing — the AWQ triple and the block-wise scale both answering with an EMPTY refusal, and the KV case reportingthey ship weightfor a module that also shipsk_scale.Twelve negative mutations on the final tree, every one detected. Each printed its applied
git diff --stat, its compile return code and its restore sha256, because a mutation that never applied and a mutation that failed to build both read as a passing test.LoadQwen3_5DenseOperandSuffixesdrops.weight_scale_2StaticFp8()accepts any spellingIsKvCacheScaleSuffixstops recognising the two suffixesModeloptNvfp4()rejects a module that also shipsinput_scaleRestored tree: 22 cases / 1687 assertions green, and W4's gate 9 cases / 194 assertions green beside it.
Full suite on the same tree:
ctest587 tests, 586 passed, 1 failed, plus three env-gated skips (test_modelopt_mixed_precision_checkpoint,test_voxtral_e2e,test_qwen35_paged_engine). The failure istest_runner, which is RED onmain— a block-size case validated on gfx1151/ROCm that a CPU build's attention-backend SELECTOR refuses beforeCheckKvCacheShapecan reach it. It is in a file this change does not touch, filed as #1602 and owned byBACKEND-ROCM. Run on the tree merged up toorigin/main0822160f5, with the whole suite built from scratch (rc 0). The same suite gave the same one failure at the earlier merge pointc020347a7(586 tests), so the verdict is not an artifact of oneorigin/main.Not in this change
docs/USAGE.mdrecords the semantic verification and## Owedrecords the hash as unpaid. No remote-reported hash was written down as one.hf_quant_config.jsonasks forkv_cache_quant_algo: "FP8"and the checkpoint ships zerok_scale/v_scale; no production path here reads that file. Refusing it would refusenvidia/Qwen3.6-27B-NVFP4, which declares the same thing in itsconfig.jsonand also ships none. Owned byKV-FP8(KV-FP8 W2 and W3: the CUDA fp8 KV store, its paged-attention read, and the runner integration — the only thing now gating every cell of the #1574 three-way #1593) and named under## Owed.scripts/agent-preflight.shon the final tree: 92 gates ok, 0 SKIPPED, 1 FAIL —test_cpu_x86_llamacpp_floor, the load-dependent red tracked by #618, which reportedNO_QUIET_WINDOWon a box carrying two other agent sessions at a one-minute load average above 60. It passed in thectestrun above on the same tree. The branch is merged up toorigin/mainc020347a7, socommit-trailersandcommit-styleRAN on this tree for the first time — on the reviewed head they were SKIPPED for being behind and reported nothing at all.Closes nothing. #821 stays open: its NVFP4 acceptance bullets still need a token gate. #1632 stays open and owns the blocker.
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]