feat(TOOLS-PARSER-BREADTH): register inkling — the ported engine had no registry face (#608 W1) - #683
Merged
Merged
Conversation
…d no registry face (#608 W1) FOLLOWING_AGENTS_PROTOCOL The Inkling dialect was ALREADY ported and oracle-gated: `parser/inkling.cpp`, `inkling_config()` with its JSON-span arg carver in `engine/configs.cpp`, dispatched by `parser::get_parser_engine("inkling")`, and golden-gated in `test_parser_engine_assembly` (`inkling_think_tool_text_*`, `inkling_nonobject_args_*`). What was missing was the TOOL-PARSER REGISTRY face, so `ResolveToolParserName("inkling", …)` threw at startup and a fully ported dialect was unreachable from `--tool-call-parser`. W1 of #608 was scoped as "port a parser". It was not; it was a one-line gap behind a missing adapter. That is the whole change: * `tool_parsers/parser_engine_adapter.{h,cpp}` — `ParserEngineToolAdapter` (vllm/parser/engine/adapters.py:128, the TOOL half of `make_adapters`, twin of the reasoning adapter we already carried) + `InklingEngineToolParser` (vllm/tool_parsers/inkling_tool_parser.py:7, registered at vllm/tool_parsers/__init__.py:177), all @ 555967922. * the factory branch AND the `tool_parser_names()` entry in the same change, as `abstract.cpp`'s own comment demands. * `ParserRequestFromChatCompletion` relocated out of `serving_chat.cpp`'s anonymous namespace to sit beside `ParserRequest`, so the adapter and the serving path share ONE request projection rather than two copies. `inkling` is EXPLICIT-ONLY in `detect.cpp`, and for a reason no other row has. Every other EXPLICIT-ONLY family is excluded for marker collision; Inkling is excluded because there is nothing to sniff. That table matches a CHAT TEMPLATE string, and Inkling has no jinja template at the pin — rendering is `vllm/renderers/inkling_encoding.py` (mirrored by the Rust `rust/src/chat/src/renderer/inkling/`), and `examples/` carries no `tool_chat_template_inkling.jinja`. Its `<|content_invoke_tool_json|>` marker collides with nothing, so a row would LOOK harmless while being unreachable through the only input the function gets and claiming a template-stability we cannot demonstrate. No structural-tag row either: upstream sets `structural_tag_model = None` / `supports_required_and_named = False`, which our unmapped-family nullopt already is. TESTS MOSTLY PORT; they were not authored wholesale. The spec assumed a missing `tests/tool_parsers/test_inkling_tool_parser.py` meant no upstream test. There is one, in the other place — `tests/parser/engine/test_inkling.py` @ 555967922 — because upstream's class is a bare `make_adapters(InklingParser)` subclass with no behaviour of its own. Of 18 cases here, 15 PORT and keep their upstream names verbatim, with two documented harness adaptations (a `<|message_model|>` prefix reproduces upstream's MESSAGE_HEADER initial state on the CONTENT-seeded tool adapter; streaming uses upstream's `_stream_text_only` character-chunk harness, a strictly stronger split than token boundaries, because our ToolParser seam is text-only). `TestArgConverter` and the token-id/reasoning cases are deliberately not re-ported — they gate the engine layer the assembly goldens already gate. 3 cases ARE authored, and each says so at its site. Two gate OUR packaging surface, which has no upstream analogue to port (upstream's registry is a lazy dict and it has no chat-template marker table at all). The third — "the tool adapter seeds the engine in CONTENT state" — gates upstream behaviour (adapters.py:158/167) that upstream's own suite never exercises, since it drives InklingParser directly and never constructs the adapter. It exists because a mutation SURVIVED without it. RED before: 17 cases, 16 failed. GREEN after: 18 cases, 88 assertions, 0 failed. Counts re-derived from `abstract.cpp`, not incremented: 42 names, 38 distinct factory classes, 27 marker rows unchanged, 39 tool-parser test files. `test_detect.cpp:222`, `docs/USAGE.md:1126`, `docs/STATUS.md`, `docs/FEATURES.md:230`, the engine-matrix row and the roadmap's `--tool-call-parser` sweep row all move with it. #649's separate stale count on `TOOLS-CALLING-CORE` is deliberately NOT touched here. Recorded in the spec, and worth reading before anyone picks up the rest of W1: the SGLang secondary-oracle check for `openai` was run under AGENTS.md §"When vLLM has no implementation" and ANSWERED NO. That section admits a secondary oracle only where vLLM implements nothing, and vLLM implements this path — `vllm/parser/harmony.py` is a 358-line vLLM-owned `HarmonyParser`, tested by `tests/parser/test_harmony.py`, and is exactly what `GptOssToolParser`'s docstring defers TO. The stub is a delegation inside vLLM, not an absence of it, so `openai` mirrors vLLM's Harmony parser and SGLang is not consulted. What the check DID establish is that the residual gap is the out-of-tree `openai_harmony` package under that wrapper — the same shape as Cohere's `cohere_melody`, i.e. a W2-class grammar decision, not the "register the name with an explicit refusal" the spec had assumed. Issue: #608 Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…is false, and it hid four portable cases (#608, #683) FOLLOWING_AGENTS_PROTOCOL The fresh review of #683 returned FAIL on the RECORD, not the code: the shipped C++ is sound, mirrors upstream faithfully, and every guarantee the reviewer mutated is caught. The implementation is not touched here. Six findings, all in what the change SAID about itself, are repaired. F1 — a false claim, and four cases it hid. The PR body, this spec, the landing commit and the test file's own header all said upstream "drives InklingParser directly and never constructs the adapter". It does construct it: tests/parser/engine/test_inkling.py:487 class TestRegisteredAdapters resolves through ToolParserManager and calls extract_tool_calls on the result. The claim was not just wrong, it was load-bearing — it is the sentence that justified not looking for tool-facing cases outside TestNonStreaming/TestStreaming, and two whole classes went unassessed. Ported now: * :488 test_adapters_resolve. Upstream asserts `_parser_engine_cls is InklingParser` and `supports_required_and_named is False`. Our registry returns an instance, so the first is a dynamic_cast to InklingEngineToolParser, whose ctor is get_parser_engine("inkling") and admits no other engine; the second is asserted through the surface that flag controls here, ToolChoiceStructuralTagSpecFor being nullopt for auto, required AND named. Its reasoning half is DECLINED — we have no inkling reasoning registry row (#703). * :498 test_adapter_round_trip, verbatim. * :465 test_tool_choice_none_non_streaming and :477 ..._streaming, from TestToolCallFiltering, which the exclusion rationale never covered either. These are the cases that exercise the tool_choice field of the ParserRequestFromChatCompletion projection W1 MOVED, and a mutation dropping that field is now caught by them. * :436 test_skip_tool_parsing_round_trip is DECLINED with a reason at its site: it needs a skip-tool-parsing setter and extract_reasoning, neither of which is on the ToolParser ABC. Corrected split: 22 cases, 19 PORTED with upstream names verbatim, 3 AUTHORED — not the 15/3 of 18 claimed in four places. Four documented harness adaptations, not two. The authored CONTENT-seed case survives the correction but its stated reason did not: upstream does construct the adapter, and what no upstream case reaches is the SEED, which lives only in extract_tool_calls_streaming (adapters.py:178) while extract_tool_calls (:158) delegates with no seed at all. F2 — a spec sentence contradicted by its own diff. "README.md and docs/FEATURES.md carry no parser count, so neither is owed" was false for both. FEATURES.md carries it and W1 changed it 37 -> 38; README carries it twice and both numbers were ALREADY stale at 43a6c55 (36 families / 40 names against a true 37/41, so README had missed muse_glimmer too). The true values, re-derived from abstract.cpp rather than incremented, are 38 families / 42 names. README is NOT corrected here, and that is a gate decision, not a preference: check-doc-checkpoint.py --staged refuses any README edit in a change that does not touch a landing source, a rule its own comment calls deliberate and directly tested, and weakening a checker to pass is forbidden. Filed as #704 and linked from the roadmap issue table, which is the other option the review offered. #704 records the part worth knowing next: the same rule refuses a README-only fix, so closing it needs a landing-source change or an argued exception. F3 — a comment asserting a record that does not exist. test_inkling.cpp claimed its authored case was "recorded as from-scratch in porting-inventory.md §9". It is not, and it should not be: §9 enumerates forced STRUCTURAL deviations of the port, not test authorship, and the review agreed with that reading. No §9 entry is added. The sentence is deleted. F4 — a header claiming coverage that does not exist. parser_engine_adapter.h said adjust_request is unmodelled because "the engine-backed serving path handles it". Nothing handles it: the shared ToolParser seam has no adjust_request dispatch site at all, and serving_chat.cpp applies no override. It is MATERIAL for Inkling, whose entire grammar is special tokens — at the `true` default the detokenizer strips the markers before the parser runs. The header now uses the sibling "dropped, and that is an OPEN GAP" wording, and docs/USAGE.md tells a user the one thing they can do about it today (pass "skip_special_tokens": false). Tracked as #695; not fixed here. F5 — two coverage gaps: one closed, one measured and left open. StreamTextOnly now calls finish_streaming() and appends its delta exactly as upstream's _stream_text_only does, so the port no longer compensates by appending <|end_message|> to its input. That gives ParserEngineToolAdapter:: finish_streaming() its only caller — and NOT a guarantee. Mutating the body to `return std::nullopt` leaves all 22 cases green. A streaming twin of test_text_after_tool_call was authored to try to move it, did not, and was DELETED rather than shipped with a false rationale: nothing in the ported set is deferred past the last delta. The method ships functionally ungated, and the header, the harness comment and the spec all say so. F6 — anchor rot, re-derived after the merge and not before. origin/main was merged in first (it had moved six commits and touched both CMakeLists this branch also touches). Every catching line in the mutation table was wrong at the old head and moved again on merge; docs/FEATURES.md alone slid :230 -> :232 in the same change that documents the hazard. Mutations for the added cases (aarch64, restored by byte copy + sha256): registry returns HermesToolParser -> CAUGHT (2 passed / 20 failed); inkling gains a structural-tag spec -> CAUGHT at three modes; the moved projection drops tool_choice -> CAUGHT (20 passed / 2 failed); finish_streaming -> nullopt -> SURVIVED, recorded. The first run of that table was itself defective and is worth the warning: restoring with `cp -p` preserves the mtime, ninja skips the relink, and the next mutation runs against the previous one's object — four mutations produced four identical-looking failures. Restore is `cp` + `touch`, and each restore re-runs the suite before the next mutation is applied. Verified on aarch64 (kairos-4db2, sm_110 host, CPU-only build): clean configure + build exit 0 with 0 warnings, focused suite 22/22 cases and 133/133 assertions, test_tool_parser_detect 16/16 and 352/352, test_parser_engine_ assembly 3/3 and 5038/5038, full ctest as recorded in the PR. aarch64 is a supported lane (CI build-test-cpu-arm64); this was not measured on x86_64. Refs #608, #683. Files #703 (--reasoning-parser inkling still throws while the tool parser resolves, owned by #605) and #704 (README counts). Links #695. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
… during the repair) FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
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.
Row:
TOOLS-PARSER-BREADTH· Issue: #608 (W1) · Spec:.agents/specs/tool-parser-breadth.mdWhat this is
--tool-call-parser inklingnow resolves. Registry goes 41 → 42 accepted names, 37 → 38 families.The gap was not what the spec thought
W1 was scoped as "port a parser". It was not. The Inkling dialect was already ported and already gated against the pinned oracle:
src/vllm/parser/inkling.cpp— theInklingParserengine subclassengine/configs.cpp—inkling_config()and its prefix-stable JSON-span arg carverparser::get_parser_engine("inkling")— already dispatched ittest_parser_engine_assembly— golden scenariosinkling_think_tool_text_*,inkling_nonobject_args_*, captured from vLLM555967922What was missing was the tool-parser registry face.
ResolveToolParserName("inkling", …)callsget_tool_parser, which returnednullptr, so the flag threw at startup and a fully ported, oracle-gated dialect was unreachable.The change
tool_parsers/parser_engine_adapter.{h,cpp}ParserEngineToolAdapter(adapters.py:128— the TOOL half ofmake_adapters, twin of the reasoning adapter we already carried) +InklingEngineToolParser(inkling_tool_parser.py:7, registered at__init__.py:177)abstract.cpptool_parser_names()entry, in the same change, as that file's own comment demandsparser_engine.{h,cpp}/serving_chat.cppParserRequestFromChatCompletionmoved out of an anonymous namespace to sit besideParserRequest, so the adapter and the serving path share one request projection instead of two copiesAll upstream anchors read at
555967922.inklingis EXPLICIT-ONLY indetect.cpp— and for a reason no other row hasEvery other EXPLICIT-ONLY family is excluded for marker collision. Inkling is excluded because there is nothing to sniff: that table matches a chat template string, and Inkling has no jinja template at the pin. Rendering is
vllm/renderers/inkling_encoding.py(mirrored by the Rustrust/src/chat/src/renderer/inkling/), andexamples/carries notool_chat_template_inkling.jinja.Its
<|content_invoke_tool_json|>marker collides with nothing, so a row would look harmless — while being unreachable through the only input the function gets, and claiming a template-stability we cannot demonstrate.No structural-tag row either: upstream sets
structural_tag_model = None/supports_required_and_named = False, which our unmapped-familynulloptalready is. Repaired: that equivalence is now gated rather than argued —test_adapters_resolvechecksToolChoiceStructuralTagSpecFor("inkling", …)isnulloptfor auto, required and named, and a mutation that givesinklingthe hermes spec is caught.Tests
The spec said the test was ours to author, on the assumption that a missing
tests/tool_parsers/test_inkling_tool_parser.pymeant no upstream test. There is one, in the other place —tests/parser/engine/test_inkling.py— because upstream's class is a baremake_adapters(InklingParser)subclass with no behaviour of its own.F1 (MEDIUM) — a false claim about upstream, and the four cases it hid
This body, the spec, the landing commit and the test file's header all said upstream "drives
InklingParserdirectly and never constructs the adapter". It does construct it —test_inkling.py:487 class TestRegisteredAdaptersresolves throughToolParserManagerand callsextract_tool_callson the result. The claim was not merely wrong, it was load-bearing: it is the sentence that justified not looking for tool-facing cases outsideTestNonStreaming/TestStreaming, and two whole classes went unassessed.Per-case status for both classes:
:488 test_adapters_resolvetool_cls._parser_engine_cls is InklingParser); our registry hands back an INSTANCE, so the same fact is adynamic_casttoInklingEngineToolParser, whose ctor isget_parser_engine("inkling")and admits no other engine (parser_engine_adapter.cpp:49).supports_required_and_named is Falseis asserted through the surface that flag controls here. Its reasoning half is DECLINED — we have noinklingreasoning registry row (#703, below).:498 test_adapter_round_trip:465 test_tool_choice_none_non_streamingparse()returns reasoning too and this seam does not, so the THINK block and its assertion are dropped; content and tool suppression are kept verbatim. This is the case that exercises thetool_choicefield of the projection this PR moved.:477 test_tool_choice_none_streaming:436 test_skip_tool_parsing_round_tripskip_tool_parsing = Trueon oneInklingParser, callsextract_reasoning, then re-extracts with a second parser. OurToolParserABC exposes neither a skip-tool-parsing setter norextract_reasoning, and the reasoning half has no registry face at all; porting it would mean drivingParserEnginedirectly — the engine layer this file deliberately does not re-gate.Corrected split: 22 cases, 19 PORTED with upstream names verbatim, 3 AUTHORED — not the "15 of 18" claimed in four places. Four documented harness adaptations, not two:
<|message_model|>prefix reproduces upstream'sMESSAGE_HEADERinitial state on the CONTENT-seeded tool adapter, so assertions are preserved rather than weakened;_stream_text_onlycharacter-chunk harness (our ToolParser seam is text-only) — a strictly stronger split than token boundaries, since chunks slice markers mid-marker;dynamic_cast;tool_choice="none"case drops the reasoning third of its assertion.TestArgConverterand the token-id/reasoning cases remain deliberately un-ported: they gate the engine layer the assembly goldens already gate.The authored CONTENT-seed case survives the correction; its stated reason did not. Upstream does construct the adapter — what no upstream case reaches is the seed, which lives only in
extract_tool_calls_streaming(adapters.py:178) whileextract_tool_calls(:158) delegates toextract_tool_calls_from_contentwith no seed at all.F5 (LOW) — two coverage gaps: one closed, one measured and left open
StreamTextOnlyomitted the trailingparser.finish_streaming()that upstream's_stream_text_onlycalls and appends, and compensated by appending<|end_message|>to the authored case's input. The harness now calls it; the compensation is gone.ParserEngineToolAdapter::finish_streaming()its only caller — it has none insrc/,include/orexamples/, and will have none whileMakeToolParserroutes engine-backed names away from this adapter (serving_chat.cpp:546-548) — but not a guarantee. Mutating the body toreturn std::nulloptleaves all 22 cases green. A streaming twin oftest_text_after_tool_callwas authored to try to move that mutation, did not, and was deleted rather than shipped with a false rationale: nothing in the ported set is deferred past the last delta. The method ships functionally ungated, and the header, the harness comment and the spec all say so. An unmoved mutation is a measurement, not a licence to claim coverage.F3 (MEDIUM) — a comment asserting a record that does not exist
test_inkling.cppclaimed the authored case was "recorded as from-scratch in porting-inventory.md §9". No such record exists — and none should: §9 enumerates forced structural deviations of the port, not test authorship, and the reviewer agreed with that reading. No §9 entry is added. The sentence is deleted, and the spec now records that the review agreed and what it actually flagged.F4 (LOW) — a header claiming coverage that does not exist
parser_engine_adapter.hsaidadjust_requestis unmodelled because "the engine-backed serving path handles it". Nothing handles it. The sharedToolParserseam has noadjust_requestdispatch site at all (KimiK2ToolParser::adjust_request,kimi_k2.cpp:87, has no callers either;reasoning_parsers/muse_glimmer.halready records the same seam gap), andserving_chat.cppapplies no override.It is material for Inkling, whose entire grammar is special tokens:
skip_special_tokensdefaultstrue(protocol.h:240/:461), is forwarded verbatim byto_sampling_params(protocol.cpp:583) and honoured atv1/engine/detokenizer.cpp:68, so at server defaults the markers are stripped before the parser runs. The header now uses the sibling "dropped, and that is an OPEN GAP" wording, anddocs/USAGE.mdtells a user the one thing they can do about it today (pass"skip_special_tokens": false). Tracked as #695; not fixed here.RED → GREEN
RED was captured with the implementation stashed (
git stash push -- src include CMakeLists.txt), so the test compiled against unmodifiedmainand failed on behaviour, not on a missing header.The one case passing at RED is the EXPLICIT-ONLY autodetect assertion, a control: it must hold both before and after.
Mutations
F6 (INFO) — every catching line re-derived at the repair head, after the merge. The previously quoted
:174/:248/:138were already wrong before the merge and moved again after it;docs/FEATURES.mdalone slid:230→:232in the same change that documents the hazard.Original four (each restored byte-for-byte, sha256-verified):
ParserEngineoverinkling_config()instead ofInklingParser— drops the trailing-text flush hook (inkling.py:376)test_inkling.cpp:385(test_text_after_tool_call, case at:373)"inkling"fromtool_parser_names(), keep the factory branchtest_inkling.cpp:243(case at:233) — and the pre-existing count pintest_detect.cpp:222initialize_streaming(CONTENT)seedtest_inkling.cpp:284(case at:256). That is why the case exists.inkling_arg_converterreturns the raw{"name":…,"args":…}wrappertest_parser_engine_assemblygoldens (3 cases / 33 assertions), which own it first since the carver is engine-layerFour more for the cases this repair adds (aarch64):
get_tool_parser("inkling")returnsHermesToolParser(breaks the engine-class identity):539/:540,:575/:576, and the 18MakeParser()cases at:121. 2 passed / 20 failedToolChoiceStructuralTagSpecForgivesinklingthe hermes spec (supports_required_and_named = True):557, all three modesParserRequestFromChatCompletiondropstool_choice, hardcodes"auto"(the moved projection):510/:511and:522. 20 passed / 2 failedParserEngineToolAdapter::finish_streaming()returnsstd::nulloptCounts, re-derived not incremented
At the repair head:
tool_parser_names()holds 42 entries;get_tool_parserhas 42name ==branches over 38 distinctstd::make_unique<T>classes; 27 marker rows (unchanged); 39 tool-parser test files.F2 (MEDIUM) — a spec sentence contradicted by its own diff
The spec said "
README.mdanddocs/FEATURES.mdcarry no parser count, so neither is owed." False for both.docs/FEATURES.mddoes carry it, and this PR changed it 37 → 38 families.README.mdcarries it twice (:81,:219) and both were already stale before this PR: at43a6c5518they read 36 families / 40 names against a true 37 / 41, so README had also missedmuse_glimmer. This PR widens the drift to 38 / 42.README is not corrected here, and that is a gate decision, not a preference.
check-doc-checkpoint.py --stagedrefuses any README edit in a change that does not touch a landing source (.agents/mission.md,CMakeLists.txt, the threebenchmarks/demo/*.json,examples/{cli,server}/main.cpp) — "Co-edited public projections can NEVER justify README churn", a rule the checker's own comment calls deliberate and directly tested. Weakening a checker to make a transition pass is forbidden, so the correction is filed as #704 and linked from theroadmap_v1.mdissue table, which is the other option the review offered. #704 records the part worth knowing next: the same rule refuses a README-only fix too, so closing it needs either a change that legitimately moves a landing source or an argued exception in its own commit message.Neither
check-readme-structure.pynorcheck-public-doc-tables.pycross-checks a prose count against the registry, which is why the drift survived two waves. #649 covers only theTOOLS-CALLING-COREengine-matrix row — a different surface — and is still deliberately not touched here.Moved with this PR:
test_detect.cpp:222,docs/USAGE.md:1126,docs/FEATURES.md:232,docs/STATUS.md,.agents/engine-matrix.md, and the roadmap's--tool-call-parsersweep row (82/90 → 84/90 recipe uses).Owed, not fixed here
--reasoning-parser inklingstill throws while--tool-call-parser inklingnow resolves (asymmetry shipped by #608 W1) #703 (filed by this repair) —--reasoning-parser inklingstill throws. Upstream registersinklingin both registries:vllm/reasoning/__init__.py:131namesInklingParserReasoningAdapter, the reasoning half of the samemake_adapters(InklingParser)call (registered_adapters.py:67-70) whose tool half this PR lands. Ours has only the tool half, so a user following an Inkling recipe now gets a tool parser that resolves and a hard startup abort on the reasoning flag in the same command line — an asymmetry this PR creates (before it, both flags refused, which was at least consistent). The fix belongs toSAMPLE-REASONING(vllm-serve rejects 61 of 76 official-recipe --reasoning-parser uses, includingqwen3on our own gate models #605), whose table already listsinkling; out of scope here, recorded in the spec's "Owed by W1's landing", and the reason the reasoning half oftest_adapters_resolvehad to be declined.skip_special_tokens=true. See F4.The SGLang secondary-oracle check for
openai— run, and answered NOWorth reading before anyone picks up the rest of W1. Under AGENTS.md §"When vLLM has no implementation", the question was whether SGLang (registered oracle
sglang, pinf63458b5b…/v0.5.15; local checkout clean at exactly that SHA) should be the source for gpt-oss tool parsing, since it carriesharmony_parser.pyand a test for it.It should not, and reaching for it would have been a rule violation. That section admits a secondary oracle only where vLLM implements nothing. vLLM implements this path:
vllm/parser/harmony.pyis a 358-line vLLM-ownedHarmonyParser(DelegatingParser), tested bytests/parser/test_harmony.py, and is exactly whatGptOssToolParser's docstring defers to. The stub is a delegation inside vLLM, not an absence of it.What the check did establish: the residual gap is the out-of-tree
openai_harmonypackage under that wrapper — the same shape as Cohere'scohere_melody, i.e. a W2-class grammar decision, not the "register the name with an explicit refusal" the spec had assumed. Recorded in the spec so the next agent does not re-run the check.Gates
Verification for the repair ran on
kairos-4db2, aarch64 (14 cores, CPU-only build,-DVLLM_CPP_CUDA=OFF), not x86_64. aarch64 is a supported lane — CI carriesbuild-test-cpu-arm64— so this is legitimate coverage, but it is stated rather than presented as x86.-DVLLM_CPP_CUDA=OFF)FINAL_BUILD_EXIT=0, 0 warnings, 0 errors, 0 ENOSPC markersctest -j 8at the push SHAtest_engine_core_proc, one of the four known-jstarvation tests; passes serially (ctest -R '^test_engine_core_proc$'→ 1/1, exit 0). An earlier full run on the same tree minus main's last six commits was 430/430, exit 0, with that same test green — it starves nondeterministically.test_tool_parser_inklingStatus: SUCCESS!test_tool_parser_detect(the 42-name count pin)test_parser_engine_assembly(the pre-existing oracle goldens)scripts/agent-preflight.shscripts/agent-preflight.sh --stagedcheck-commit-trailers.py --range origin/main..HEADorigin/mainhad moved ahead, which is why main is merged in twice)check-device-leakage.pycheck-windows-portability.pysrc/vllm/multimodal/video_engine.cpp:21,59,64, #648 / #646. Zero hits in this PR's files. Not this PR.The push SHA is
4b319ba7c.test_engine_core_proc,test_async_llm,test_serve_low_toolsandtest_openai_conformanceare the known-jstarvation set; only the first starved here, and it was re-run serially rather than attributed.Deliberately deferred
minimax_m3,openai, and both Cohere names. Shippinginklingalone keeps this reviewable.openaiin particular is now known to be a Harmony-seam row, materially larger than this one.