Context
Surfaced by Emmy's /video-create ideation fold (D#15673, 2026-07-22T00:50Z): she challenged my incident claim that "NL call_method cannot carry object payloads," citing the OpenAPI contract — and asked the claimant to either reproduce a harness-specific stringification boundary as a focused defect ticket or keep it as incident history. Reproduction exists — and the follow-up root-cause the same evening found the defect sits one layer away from where the original body put it.
Amended 2026-07-22T01:5xZ after live root-cause: the server schema is correct; the defect is client-side schema normalization. The write-lock half of the original body split to #15681 (child of #13056).
The Problem (corrected decomposition)
The chain, with receipts:
- Server emits the correct schema. Running the actual conversion (
buildZodSchema → toOpenApiJsonSchema over CallMethodRequest:2685-2688) yields args: {type: 'array', items: {}, description: 'heterogeneous — any JSON-serializable value'} — exactly the HTTP contract, x-pass-as-object: true honored.
- The agent-facing tool listing collapses it. The Kimi Code CLI renders the same tool's args as
items: {type: 'string'} — an empty/unknown items schema is normalized to string by the client. Agents then either pass strings (forwarded verbatim — tonight's two failed wireFleetBridge variants: bare URL string and JSON-string, both dying at installFleetBridge's validator) or are blocked from passing native objects by client-side validation.
- The in-process path is healthy.
NeuralLink_InstanceService.callMethod({args: [{url, bearerToken}]}) (a real object) ack'd {"result":true} on the same method, same evening. The e2e fixture uses this path exclusively — which is why CI never sees the boundary.
So: no Neo server code fix exists for the marshalling — the defect lives in the MCP client's schema normalizer.
The Architectural Reality
- A server-side "revive strings that parse as objects" is the classic coercion trap (legitimate
'{…}' data strings must survive verbatim) — rejected except under a future signature-aware contract that does not exist today.
- The sanctioned workaround already exists and is documented by the fleet harness: the in-process service call (
test/playwright/e2e/agentos/authenticatedFleetHarness.mjs uses it for exactly this object-bearing wire).
- The write-lock sequel (persistent MCP writer monopolizing components; no release-on-error; no TTL) is a designed minimum with the TTL lease already named in
WriteGuard.mjs JSDoc as a follow-up slice — now ticketed as #15681 (child of #13056) with tonight's live reproduction.
The Fix (what remains, honestly)
- Docs line in
openapi.yaml call_method description: name the arg-marshalling reality — pass values natively where the client supports heterogeneous items; strings are forwarded verbatim; the in-process service is the object-bearing workaround.
- Upstream defect report to the Kimi Code project (the
items: {} → {type: 'string'} normalization collapse), linking this ticket. Optional but the only true repair.
- The write-lock sequel: #15681 owns it.
Acceptance Criteria
Out of Scope
- Server-side schema changes (already correct).
- Server-side string-revive (coercion trap without signature awareness).
/video-create content (D#15673 owns the film-side transport-naming rule).
Related
- #15681 (write-lock lease — split out),
#13056 (its parent epic)
- D#15673 (fold that surfaced this), #15658 (the lane where the boundary bit),
test/playwright/e2e/agentos/authenticatedFleetHarness.mjs (the sanctioned object-bearing path)
- Origin Session ID: eb9be68e-9401-4ecd-9762-ef519b4091ed
Sweeps: live latest-open sweep at 2026-07-22T01:4xZ via gh issue list (latest 20) — no equivalent. Conversion receipt produced by executing buildZodSchema/toOpenApiJsonSchema on origin/dev.
Retrieval Hint: "neural-link call_method kimi client schema normalization items empty string collapse verbatim args in-process workaround".
Amendment 2026-07-22T02:2xZ — Euclid's D+S on PR #15683 reshaped the prescription
The docs-only PR was dropped (ticket-prescription-off). Three upheld drifts: "no Neo server code fix exists" was unproven (the explicit typed-union path was never tested); "silently stringifying objects" overclaimed the receipt (string-narrowed listing ≠ proven coercion); the in-process pointer is unreachable for generic MCP consumers.
Corrected prescription (in order):
- Explicit JsonValue union in
CallMethodRequest.args.items — the substrate already supports it: buildZodSchemaFromNode maps oneOf → z.union (openApiValidator.mjs:143-145), and this server already ships a oneOf string/object precedent (openapi.yaml:575-584). Euclid's in-memory probe: replacing items: {} with the union (nullable string, number, boolean, open object, array) emits args.items.anyOf covering every JSON class.
- The decisive falsifier is the Kimi projection of that explicit union (seat-restart probe — the same restart as
#15580 AC6): if Kimi preserves the union, the defect is repaired server-side; if it still collapses, the upstream defect report becomes the path and the docs line (truthful, caller-facing only) returns.
- No server-side string-revive (coercion trap), no in-process pointers in consumer-facing descriptions.
Contract Ledger (added per the D+S audit):
| Contract |
Source of Authority |
Required Behavior |
Fallback / Evidence |
| Server emission |
openapi.yaml CallMethodRequest |
args.items = explicit JsonValue union (oneOf: nullable string, number, boolean, open object, array) |
raw tools/list probe; compliance spec assertion |
| Runtime validation |
buildZodSchemaFromNode → z.union |
parses every representative JSON class (string, number, boolean, null, object, nested array) |
validator spec additions |
| Kimi projection |
Kimi CLI tool listing |
preserves the explicit union (gate) |
seat-restart receipt; collapse → upstream defect + truthful docs |
| Generic MCP access |
the tool description |
names heterogeneous args only; NO in-process/repo-internal pointers |
description stays within the caller's reach |
| Write-lock sequel |
WriteGuard |
TTL/release-on-error |
#15681 (child of #13056) owns |
Context
Surfaced by Emmy's
/video-createideation fold (D#15673, 2026-07-22T00:50Z): she challenged my incident claim that "NLcall_methodcannot carry object payloads," citing the OpenAPI contract — and asked the claimant to either reproduce a harness-specific stringification boundary as a focused defect ticket or keep it as incident history. Reproduction exists — and the follow-up root-cause the same evening found the defect sits one layer away from where the original body put it.Amended 2026-07-22T01:5xZ after live root-cause: the server schema is correct; the defect is client-side schema normalization. The write-lock half of the original body split to #15681 (child of
#13056).The Problem (corrected decomposition)
The chain, with receipts:
buildZodSchema→toOpenApiJsonSchemaoverCallMethodRequest:2685-2688) yieldsargs: {type: 'array', items: {}, description: 'heterogeneous — any JSON-serializable value'}— exactly the HTTP contract,x-pass-as-object: truehonored.items: {type: 'string'}— an empty/unknownitemsschema is normalized to string by the client. Agents then either pass strings (forwarded verbatim — tonight's two failedwireFleetBridgevariants: bare URL string and JSON-string, both dying atinstallFleetBridge's validator) or are blocked from passing native objects by client-side validation.NeuralLink_InstanceService.callMethod({args: [{url, bearerToken}]})(a real object) ack'd{"result":true}on the same method, same evening. The e2e fixture uses this path exclusively — which is why CI never sees the boundary.So: no Neo server code fix exists for the marshalling — the defect lives in the MCP client's schema normalizer.
The Architectural Reality
'{…}'data strings must survive verbatim) — rejected except under a future signature-aware contract that does not exist today.test/playwright/e2e/agentos/authenticatedFleetHarness.mjsuses it for exactly this object-bearing wire).WriteGuard.mjsJSDoc as a follow-up slice — now ticketed as #15681 (child of#13056) with tonight's live reproduction.The Fix (what remains, honestly)
openapi.yamlcall_methoddescription: name the arg-marshalling reality — pass values natively where the client supports heterogeneous items; strings are forwarded verbatim; the in-process service is the object-bearing workaround.items: {}→{type: 'string'}normalization collapse), linking this ticket. Optional but the only true repair.Acceptance Criteria
openapi.yamlcall_methoddescription carries the marshalling reality + the in-process workaround (docs-only PR).#15678-class incident is citable as the falsifier if any future client-side contract is proposed (anchor: this body's receipts).Out of Scope
/video-createcontent (D#15673 owns the film-side transport-naming rule).Related
#13056(its parent epic)test/playwright/e2e/agentos/authenticatedFleetHarness.mjs(the sanctioned object-bearing path)Sweeps: live latest-open sweep at 2026-07-22T01:4xZ via
gh issue list(latest 20) — no equivalent. Conversion receipt produced by executingbuildZodSchema/toOpenApiJsonSchemaonorigin/dev.Retrieval Hint: "neural-link call_method kimi client schema normalization items empty string collapse verbatim args in-process workaround".
Amendment 2026-07-22T02:2xZ — Euclid's D+S on PR #15683 reshaped the prescription
The docs-only PR was dropped (ticket-prescription-off). Three upheld drifts: "no Neo server code fix exists" was unproven (the explicit typed-union path was never tested); "silently stringifying objects" overclaimed the receipt (string-narrowed listing ≠ proven coercion); the in-process pointer is unreachable for generic MCP consumers.
Corrected prescription (in order):
CallMethodRequest.args.items— the substrate already supports it:buildZodSchemaFromNodemapsoneOf→z.union(openApiValidator.mjs:143-145), and this server already ships aoneOfstring/object precedent (openapi.yaml:575-584). Euclid's in-memory probe: replacingitems: {}with the union (nullable string, number, boolean, open object, array) emitsargs.items.anyOfcovering every JSON class.#15580AC6): if Kimi preserves the union, the defect is repaired server-side; if it still collapses, the upstream defect report becomes the path and the docs line (truthful, caller-facing only) returns.Contract Ledger (added per the D+S audit):
openapi.yamlCallMethodRequestargs.items= explicit JsonValue union (oneOf: nullable string, number, boolean, open object, array)tools/listprobe; compliance spec assertionbuildZodSchemaFromNode→z.unionWriteGuard#13056) owns