Skip to content

fix(trace): key the tool-part decoders by the harness names that arrive - #955

Merged
drewstone merged 1 commit into
mainfrom
fix/kimi-tool-part-decoder
Aug 21, 2026
Merged

fix(trace): key the tool-part decoders by the harness names that arrive#955
drewstone merged 1 commit into
mainfrom
fix/kimi-tool-part-decoder

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Problem

toolPartDecoders (src/runtime/supervise/trace-source.ts:151) is the harness → tool-call-decoder registry. It held an entry under kimi, and no caller can produce that name.

Every in-repo path to decodeToolPart runs through one field:

hopfile:linetype
the decode sitesupervise/sandbox-session.ts:154decodeToolPart(part, args.harness)
the fieldsupervise/sandbox-session.ts:100readonly harness: BackendType
bound atsupervise/runtime.ts:1277, runtime.ts:4815spec.harness as BackendType
BackendType@tangle-network/sandboxExclude<HarnessType, 'gemini'>
the harness kimi is served under@tangle-network/agent-interfaceharness.d.ts:23, and src/sandbox-backend.ts:31kimi-code

The literal 'kimi' appears nowhere in src/, tests/, bench/ or docs/. In the real producer it is a config toggle name and a binary name, never a wire value: cli-bridge/src/server.ts:169 registers the backend with harness: 'kimi-code'.

The entry was also wrong about the wire. kimi-code streams both shapes on one session — an Anthropic tool_use content block (cli-bridge/src/backends/kimi.ts:281) and a top-level OpenAI tool_calls entry (:307). The registry named decodeOpenAiPart, which reads only the second. The module's own docstrings already knew: :112 says decodeAnthropicPart covers "kimi's tool_use variant" and :128 says decodeOpenAiPart covers "kimi's top-level form".

Three more keys were never harness names either: anthropic, openai, router.

Why nothing broke, and why that is the dangerous part

An unregistered harness falls through to the try-all loop (:168), which is a Set of the three distinct decoder functions — including the Anthropic one. So no kimi tool call is dropped today; the measured trace is correct. Verified before any edit:

--- kimi tool_use block ---
harness="kimi" -> undefined <<< the dead key, if anything could reach it
harness="kimi-code" -> {"toolName":"read","args":{"file":"a.ts"},"callId":"k-1"} (try-all)
--- kimi top-level tool_call ---
harness="kimi-code" -> {"toolName":"bash","args":{"cmd":"ls"},"callId":"call_1"} (try-all)

The defect is the trap. The obvious repair — rename the key to kimi-code, keep the decoder the entry names — makes the specific adapter win and silences the tool_use half. Measured on that repair:

 tool_use, harness="kimi-code" -> undefined <<< now DROPPED

Half of a kimi worker's tool calls would disappear with no error: the trace simply reports fewer calls, and every rate computed from it — repeated-action detection, tool-waste, error streaks — is wrong by an unknown amount. No test would have caught it: every kimi case in tests/kernel/trace-source.test.ts calls decodeToolPart(part) with no harness, so the kimi key was never exercised.

Change

  • decodeKimiPart reads both shapes, and kimi-code maps to it.
  • The registry is typed Partial<Record<HarnessType, ToolPartDecoder>>, so a key no caller can produce does not compile — the same guard src/runtime/sandbox-backend.ts:40 already uses for the sibling list. This is what makes the defect unrepeatable; a lint rule or a drift test would only report it.
  • anthropic, openai and router are removed. A part carrying any of those wire shapes decodes identically through the try-all path, so no behavior moves.
  • decodeToolPart's harness and sandboxSessionTraceSource's harness option narrow from string to HarnessType. A caller with a harness this package does not register omits the argument, which is what try-all is for.

Proof

pnpm run lint 615 files, no fixes
pnpm run typecheck clean
pnpm run build clean
pnpm run check:api-surface 2120 exports / 17 entry points, record current
bench: 223 exports / 41 entry points, record current
pnpm run check:testing-fixture fixtures are current
pnpm run check:version-bump package.json: 0 manifest and 2 export change(s) needing a minor
bump, paid for by 0.154.0 -> 0.155.0 (minor)
pnpm run docs:check exit 0
pnpm test 2943 passed / 32 failed across 10 files
Clean origin/main on this machine: 2797 passed / 171 failed across 21 files.
Nine of the ten failing files are in that baseline set; the tenth,
tests/kernel/workspace.test.ts, is a 20s git-worktree timeout in the same
macOS class and touches nothing this change goes near. CI on Linux is the
authority.

The version gate named exactly the two symbols this change moves and nothing else:

shape changed: ./kernel decodeToolPart: shape 3122064402c0 -> c252d715cf84
shape changed: ./kernel sandboxSessionTraceSource: shape 62f065aca3b3 -> 063155b911a2

That is #953 working on its first real change: before it, this pull request would have reported "consumer surface unchanged at 0.154.0" and shipped a narrowed public signature under a version the registry already holds.

Simplification

Simplification: four unreachable keys removed from a seven-key registry; the registry's key vocabulary now has one owner (HarnessType) instead of being a free Record<string, …> that no compiler checked.
Net: +14 / -8 lines in one source file, +18 in one test; 4 registry entries removed, 1 added.
Not done here: the other unregistered HarnessType members (nanoclaw, pi, prime, hermes, openclaw, amp, factory-droids, forge, cursor, acp, cli-base) keep using the try-all path. Registering one needs its real wire shape confirmed against the bridge backend that serves it, which is a per-harness measurement, not a list edit.

Tests: +1 (both kimi-code wire shapes decode when the harness is named — the case that fails the moment anyone binds kimi-code to a single decoder, which is exactly the repair the old entry invited). It is green before this change too, because kimi-code was not a key at all; what fails before this change is the compiler, on the kimi key itself. -0 deleted.

Refs #954

toolPartDecoders held an entry under `kimi`. Every in-repo caller reaches
decodeToolPart through SteerableSandboxSession.harness, which is BackendType,
and the harness kimi is served under is `kimi-code`, so no caller could select
that entry. It was also wrong about the wire: kimi-code streams an Anthropic
tool_use content block and a top-level OpenAI tool_calls entry on one session,
and the entry named the OpenAI decoder alone.
An unknown harness falls through to the try-all path, so no tool call was lost.
The defect was a trap: renaming the key to kimi-code while keeping the decoder
it named would have made the specific adapter win and dropped the tool_use half
in silence.
The registry is now typed against HarnessType, so a key no caller can produce
does not compile, and kimi-code maps to a decoder that reads both shapes. The
three keys that were never harness names are gone; those wire shapes decode
identically through the try-all path.

@tangletoolstangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 5d81251b

This PR was opened by the trusted drewstone account.

This approval is provisional and was applied by the local stand-in because the pr-reviewer webhook host is unreachable (2026-08-21). CI on this head is fully green. The full PR reviewer audit re-runs via the resweep when the service returns and will publish findings if it detects issues.

@drewstone
drewstone merged commit c8038d2 into mainAug 21, 2026
4 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@drewstone@tangletools
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix(trace): key the tool-part decoders by the harness names that arrive by drewstone · Pull Request #955 · tangle-network/agent-runtime · GitHub
Skip to content

fix(trace): key the tool-part decoders by the harness names that arrive - #955

Merged
drewstone merged 1 commit into
mainfrom
fix/kimi-tool-part-decoder
Aug 21, 2026
Merged

fix(trace): key the tool-part decoders by the harness names that arrive#955
drewstone merged 1 commit into
mainfrom
fix/kimi-tool-part-decoder

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Problem

toolPartDecoders (src/runtime/supervise/trace-source.ts:151) is the harness → tool-call-decoder registry. It held an entry under kimi, and no caller can produce that name.

Every in-repo path to decodeToolPart runs through one field:

hopfile:linetype
the decode sitesupervise/sandbox-session.ts:154decodeToolPart(part, args.harness)
the fieldsupervise/sandbox-session.ts:100readonly harness: BackendType
bound atsupervise/runtime.ts:1277, runtime.ts:4815spec.harness as BackendType
BackendType@tangle-network/sandboxExclude<HarnessType, 'gemini'>
the harness kimi is served under@tangle-network/agent-interfaceharness.d.ts:23, and src/sandbox-backend.ts:31kimi-code

The literal 'kimi' appears nowhere in src/, tests/, bench/ or docs/. In the real producer it is a config toggle name and a binary name, never a wire value: cli-bridge/src/server.ts:169 registers the backend with harness: 'kimi-code'.

The entry was also wrong about the wire. kimi-code streams both shapes on one session — an Anthropic tool_use content block (cli-bridge/src/backends/kimi.ts:281) and a top-level OpenAI tool_calls entry (:307). The registry named decodeOpenAiPart, which reads only the second. The module's own docstrings already knew: :112 says decodeAnthropicPart covers "kimi's tool_use variant" and :128 says decodeOpenAiPart covers "kimi's top-level form".

Three more keys were never harness names either: anthropic, openai, router.

Why nothing broke, and why that is the dangerous part

An unregistered harness falls through to the try-all loop (:168), which is a Set of the three distinct decoder functions — including the Anthropic one. So no kimi tool call is dropped today; the measured trace is correct. Verified before any edit:

--- kimi tool_use block ---
harness="kimi" -> undefined <<< the dead key, if anything could reach it
harness="kimi-code" -> {"toolName":"read","args":{"file":"a.ts"},"callId":"k-1"} (try-all)
--- kimi top-level tool_call ---
harness="kimi-code" -> {"toolName":"bash","args":{"cmd":"ls"},"callId":"call_1"} (try-all)

The defect is the trap. The obvious repair — rename the key to kimi-code, keep the decoder the entry names — makes the specific adapter win and silences the tool_use half. Measured on that repair:

 tool_use, harness="kimi-code" -> undefined <<< now DROPPED

Half of a kimi worker's tool calls would disappear with no error: the trace simply reports fewer calls, and every rate computed from it — repeated-action detection, tool-waste, error streaks — is wrong by an unknown amount. No test would have caught it: every kimi case in tests/kernel/trace-source.test.ts calls decodeToolPart(part) with no harness, so the kimi key was never exercised.

Change

  • decodeKimiPart reads both shapes, and kimi-code maps to it.
  • The registry is typed Partial<Record<HarnessType, ToolPartDecoder>>, so a key no caller can produce does not compile — the same guard src/runtime/sandbox-backend.ts:40 already uses for the sibling list. This is what makes the defect unrepeatable; a lint rule or a drift test would only report it.
  • anthropic, openai and router are removed. A part carrying any of those wire shapes decodes identically through the try-all path, so no behavior moves.
  • decodeToolPart's harness and sandboxSessionTraceSource's harness option narrow from string to HarnessType. A caller with a harness this package does not register omits the argument, which is what try-all is for.

Proof

pnpm run lint 615 files, no fixes
pnpm run typecheck clean
pnpm run build clean
pnpm run check:api-surface 2120 exports / 17 entry points, record current
bench: 223 exports / 41 entry points, record current
pnpm run check:testing-fixture fixtures are current
pnpm run check:version-bump package.json: 0 manifest and 2 export change(s) needing a minor
bump, paid for by 0.154.0 -> 0.155.0 (minor)
pnpm run docs:check exit 0
pnpm test 2943 passed / 32 failed across 10 files
Clean origin/main on this machine: 2797 passed / 171 failed across 21 files.
Nine of the ten failing files are in that baseline set; the tenth,
tests/kernel/workspace.test.ts, is a 20s git-worktree timeout in the same
macOS class and touches nothing this change goes near. CI on Linux is the
authority.

The version gate named exactly the two symbols this change moves and nothing else:

shape changed: ./kernel decodeToolPart: shape 3122064402c0 -> c252d715cf84
shape changed: ./kernel sandboxSessionTraceSource: shape 62f065aca3b3 -> 063155b911a2

That is #953 working on its first real change: before it, this pull request would have reported "consumer surface unchanged at 0.154.0" and shipped a narrowed public signature under a version the registry already holds.

Simplification

Simplification: four unreachable keys removed from a seven-key registry; the registry's key vocabulary now has one owner (HarnessType) instead of being a free Record<string, …> that no compiler checked.
Net: +14 / -8 lines in one source file, +18 in one test; 4 registry entries removed, 1 added.
Not done here: the other unregistered HarnessType members (nanoclaw, pi, prime, hermes, openclaw, amp, factory-droids, forge, cursor, acp, cli-base) keep using the try-all path. Registering one needs its real wire shape confirmed against the bridge backend that serves it, which is a per-harness measurement, not a list edit.

Tests: +1 (both kimi-code wire shapes decode when the harness is named — the case that fails the moment anyone binds kimi-code to a single decoder, which is exactly the repair the old entry invited). It is green before this change too, because kimi-code was not a key at all; what fails before this change is the compiler, on the kimi key itself. -0 deleted.

Refs #954

toolPartDecoders held an entry under `kimi`. Every in-repo caller reaches
decodeToolPart through SteerableSandboxSession.harness, which is BackendType,
and the harness kimi is served under is `kimi-code`, so no caller could select
that entry. It was also wrong about the wire: kimi-code streams an Anthropic
tool_use content block and a top-level OpenAI tool_calls entry on one session,
and the entry named the OpenAI decoder alone.
An unknown harness falls through to the try-all path, so no tool call was lost.
The defect was a trap: renaming the key to kimi-code while keeping the decoder
it named would have made the specific adapter win and dropped the tool_use half
in silence.
The registry is now typed against HarnessType, so a key no caller can produce
does not compile, and kimi-code maps to a decoder that reads both shapes. The
three keys that were never harness names are gone; those wire shapes decode
identically through the try-all path.

@tangletoolstangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 5d81251b

This PR was opened by the trusted drewstone account.

This approval is provisional and was applied by the local stand-in because the pr-reviewer webhook host is unreachable (2026-08-21). CI on this head is fully green. The full PR reviewer audit re-runs via the resweep when the service returns and will publish findings if it detects issues.

@drewstone
drewstone merged commit c8038d2 into mainAug 21, 2026
4 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@drewstone@tangletools
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(trace): key the tool-part decoders by the harness names that arrive by drewstone · Pull Request #955 · tangle-network/agent-runtime · GitHub
Skip to content

fix(trace): key the tool-part decoders by the harness names that arrive - #955

Merged
drewstone merged 1 commit into
mainfrom
fix/kimi-tool-part-decoder
Aug 21, 2026
Merged

fix(trace): key the tool-part decoders by the harness names that arrive#955
drewstone merged 1 commit into
mainfrom
fix/kimi-tool-part-decoder

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Problem

toolPartDecoders (src/runtime/supervise/trace-source.ts:151) is the harness → tool-call-decoder registry. It held an entry under kimi, and no caller can produce that name.

Every in-repo path to decodeToolPart runs through one field:

hopfile:linetype
the decode sitesupervise/sandbox-session.ts:154decodeToolPart(part, args.harness)
the fieldsupervise/sandbox-session.ts:100readonly harness: BackendType
bound atsupervise/runtime.ts:1277, runtime.ts:4815spec.harness as BackendType
BackendType@tangle-network/sandboxExclude<HarnessType, 'gemini'>
the harness kimi is served under@tangle-network/agent-interfaceharness.d.ts:23, and src/sandbox-backend.ts:31kimi-code

The literal 'kimi' appears nowhere in src/, tests/, bench/ or docs/. In the real producer it is a config toggle name and a binary name, never a wire value: cli-bridge/src/server.ts:169 registers the backend with harness: 'kimi-code'.

The entry was also wrong about the wire. kimi-code streams both shapes on one session — an Anthropic tool_use content block (cli-bridge/src/backends/kimi.ts:281) and a top-level OpenAI tool_calls entry (:307). The registry named decodeOpenAiPart, which reads only the second. The module's own docstrings already knew: :112 says decodeAnthropicPart covers "kimi's tool_use variant" and :128 says decodeOpenAiPart covers "kimi's top-level form".

Three more keys were never harness names either: anthropic, openai, router.

Why nothing broke, and why that is the dangerous part

An unregistered harness falls through to the try-all loop (:168), which is a Set of the three distinct decoder functions — including the Anthropic one. So no kimi tool call is dropped today; the measured trace is correct. Verified before any edit:

--- kimi tool_use block ---
harness="kimi" -> undefined <<< the dead key, if anything could reach it
harness="kimi-code" -> {"toolName":"read","args":{"file":"a.ts"},"callId":"k-1"} (try-all)
--- kimi top-level tool_call ---
harness="kimi-code" -> {"toolName":"bash","args":{"cmd":"ls"},"callId":"call_1"} (try-all)

The defect is the trap. The obvious repair — rename the key to kimi-code, keep the decoder the entry names — makes the specific adapter win and silences the tool_use half. Measured on that repair:

 tool_use, harness="kimi-code" -> undefined <<< now DROPPED

Half of a kimi worker's tool calls would disappear with no error: the trace simply reports fewer calls, and every rate computed from it — repeated-action detection, tool-waste, error streaks — is wrong by an unknown amount. No test would have caught it: every kimi case in tests/kernel/trace-source.test.ts calls decodeToolPart(part) with no harness, so the kimi key was never exercised.

Change

  • decodeKimiPart reads both shapes, and kimi-code maps to it.
  • The registry is typed Partial<Record<HarnessType, ToolPartDecoder>>, so a key no caller can produce does not compile — the same guard src/runtime/sandbox-backend.ts:40 already uses for the sibling list. This is what makes the defect unrepeatable; a lint rule or a drift test would only report it.
  • anthropic, openai and router are removed. A part carrying any of those wire shapes decodes identically through the try-all path, so no behavior moves.
  • decodeToolPart's harness and sandboxSessionTraceSource's harness option narrow from string to HarnessType. A caller with a harness this package does not register omits the argument, which is what try-all is for.

Proof

pnpm run lint 615 files, no fixes
pnpm run typecheck clean
pnpm run build clean
pnpm run check:api-surface 2120 exports / 17 entry points, record current
bench: 223 exports / 41 entry points, record current
pnpm run check:testing-fixture fixtures are current
pnpm run check:version-bump package.json: 0 manifest and 2 export change(s) needing a minor
bump, paid for by 0.154.0 -> 0.155.0 (minor)
pnpm run docs:check exit 0
pnpm test 2943 passed / 32 failed across 10 files
Clean origin/main on this machine: 2797 passed / 171 failed across 21 files.
Nine of the ten failing files are in that baseline set; the tenth,
tests/kernel/workspace.test.ts, is a 20s git-worktree timeout in the same
macOS class and touches nothing this change goes near. CI on Linux is the
authority.

The version gate named exactly the two symbols this change moves and nothing else:

shape changed: ./kernel decodeToolPart: shape 3122064402c0 -> c252d715cf84
shape changed: ./kernel sandboxSessionTraceSource: shape 62f065aca3b3 -> 063155b911a2

That is #953 working on its first real change: before it, this pull request would have reported "consumer surface unchanged at 0.154.0" and shipped a narrowed public signature under a version the registry already holds.

Simplification

Simplification: four unreachable keys removed from a seven-key registry; the registry's key vocabulary now has one owner (HarnessType) instead of being a free Record<string, …> that no compiler checked.
Net: +14 / -8 lines in one source file, +18 in one test; 4 registry entries removed, 1 added.
Not done here: the other unregistered HarnessType members (nanoclaw, pi, prime, hermes, openclaw, amp, factory-droids, forge, cursor, acp, cli-base) keep using the try-all path. Registering one needs its real wire shape confirmed against the bridge backend that serves it, which is a per-harness measurement, not a list edit.

Tests: +1 (both kimi-code wire shapes decode when the harness is named — the case that fails the moment anyone binds kimi-code to a single decoder, which is exactly the repair the old entry invited). It is green before this change too, because kimi-code was not a key at all; what fails before this change is the compiler, on the kimi key itself. -0 deleted.

Refs #954

toolPartDecoders held an entry under `kimi`. Every in-repo caller reaches
decodeToolPart through SteerableSandboxSession.harness, which is BackendType,
and the harness kimi is served under is `kimi-code`, so no caller could select
that entry. It was also wrong about the wire: kimi-code streams an Anthropic
tool_use content block and a top-level OpenAI tool_calls entry on one session,
and the entry named the OpenAI decoder alone.
An unknown harness falls through to the try-all path, so no tool call was lost.
The defect was a trap: renaming the key to kimi-code while keeping the decoder
it named would have made the specific adapter win and dropped the tool_use half
in silence.
The registry is now typed against HarnessType, so a key no caller can produce
does not compile, and kimi-code maps to a decoder that reads both shapes. The
three keys that were never harness names are gone; those wire shapes decode
identically through the try-all path.

@tangletoolstangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 5d81251b

This PR was opened by the trusted drewstone account.

This approval is provisional and was applied by the local stand-in because the pr-reviewer webhook host is unreachable (2026-08-21). CI on this head is fully green. The full PR reviewer audit re-runs via the resweep when the service returns and will publish findings if it detects issues.

@drewstone
drewstone merged commit c8038d2 into mainAug 21, 2026
4 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@drewstone@tangletools
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(trace): key the tool-part decoders by the harness names that arrive by drewstone · Pull Request #955 · tangle-network/agent-runtime · GitHub
Skip to content

fix(trace): key the tool-part decoders by the harness names that arrive - #955

Merged
drewstone merged 1 commit into
mainfrom
fix/kimi-tool-part-decoder
Aug 21, 2026
Merged

fix(trace): key the tool-part decoders by the harness names that arrive#955
drewstone merged 1 commit into
mainfrom
fix/kimi-tool-part-decoder

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Problem

toolPartDecoders (src/runtime/supervise/trace-source.ts:151) is the harness → tool-call-decoder registry. It held an entry under kimi, and no caller can produce that name.

Every in-repo path to decodeToolPart runs through one field:

hopfile:linetype
the decode sitesupervise/sandbox-session.ts:154decodeToolPart(part, args.harness)
the fieldsupervise/sandbox-session.ts:100readonly harness: BackendType
bound atsupervise/runtime.ts:1277, runtime.ts:4815spec.harness as BackendType
BackendType@tangle-network/sandboxExclude<HarnessType, 'gemini'>
the harness kimi is served under@tangle-network/agent-interfaceharness.d.ts:23, and src/sandbox-backend.ts:31kimi-code

The literal 'kimi' appears nowhere in src/, tests/, bench/ or docs/. In the real producer it is a config toggle name and a binary name, never a wire value: cli-bridge/src/server.ts:169 registers the backend with harness: 'kimi-code'.

The entry was also wrong about the wire. kimi-code streams both shapes on one session — an Anthropic tool_use content block (cli-bridge/src/backends/kimi.ts:281) and a top-level OpenAI tool_calls entry (:307). The registry named decodeOpenAiPart, which reads only the second. The module's own docstrings already knew: :112 says decodeAnthropicPart covers "kimi's tool_use variant" and :128 says decodeOpenAiPart covers "kimi's top-level form".

Three more keys were never harness names either: anthropic, openai, router.

Why nothing broke, and why that is the dangerous part

An unregistered harness falls through to the try-all loop (:168), which is a Set of the three distinct decoder functions — including the Anthropic one. So no kimi tool call is dropped today; the measured trace is correct. Verified before any edit:

--- kimi tool_use block ---
harness="kimi" -> undefined <<< the dead key, if anything could reach it
harness="kimi-code" -> {"toolName":"read","args":{"file":"a.ts"},"callId":"k-1"} (try-all)
--- kimi top-level tool_call ---
harness="kimi-code" -> {"toolName":"bash","args":{"cmd":"ls"},"callId":"call_1"} (try-all)

The defect is the trap. The obvious repair — rename the key to kimi-code, keep the decoder the entry names — makes the specific adapter win and silences the tool_use half. Measured on that repair:

 tool_use, harness="kimi-code" -> undefined <<< now DROPPED

Half of a kimi worker's tool calls would disappear with no error: the trace simply reports fewer calls, and every rate computed from it — repeated-action detection, tool-waste, error streaks — is wrong by an unknown amount. No test would have caught it: every kimi case in tests/kernel/trace-source.test.ts calls decodeToolPart(part) with no harness, so the kimi key was never exercised.

Change

  • decodeKimiPart reads both shapes, and kimi-code maps to it.
  • The registry is typed Partial<Record<HarnessType, ToolPartDecoder>>, so a key no caller can produce does not compile — the same guard src/runtime/sandbox-backend.ts:40 already uses for the sibling list. This is what makes the defect unrepeatable; a lint rule or a drift test would only report it.
  • anthropic, openai and router are removed. A part carrying any of those wire shapes decodes identically through the try-all path, so no behavior moves.
  • decodeToolPart's harness and sandboxSessionTraceSource's harness option narrow from string to HarnessType. A caller with a harness this package does not register omits the argument, which is what try-all is for.

Proof

pnpm run lint 615 files, no fixes
pnpm run typecheck clean
pnpm run build clean
pnpm run check:api-surface 2120 exports / 17 entry points, record current
bench: 223 exports / 41 entry points, record current
pnpm run check:testing-fixture fixtures are current
pnpm run check:version-bump package.json: 0 manifest and 2 export change(s) needing a minor
bump, paid for by 0.154.0 -> 0.155.0 (minor)
pnpm run docs:check exit 0
pnpm test 2943 passed / 32 failed across 10 files
Clean origin/main on this machine: 2797 passed / 171 failed across 21 files.
Nine of the ten failing files are in that baseline set; the tenth,
tests/kernel/workspace.test.ts, is a 20s git-worktree timeout in the same
macOS class and touches nothing this change goes near. CI on Linux is the
authority.

The version gate named exactly the two symbols this change moves and nothing else:

shape changed: ./kernel decodeToolPart: shape 3122064402c0 -> c252d715cf84
shape changed: ./kernel sandboxSessionTraceSource: shape 62f065aca3b3 -> 063155b911a2

That is #953 working on its first real change: before it, this pull request would have reported "consumer surface unchanged at 0.154.0" and shipped a narrowed public signature under a version the registry already holds.

Simplification

Simplification: four unreachable keys removed from a seven-key registry; the registry's key vocabulary now has one owner (HarnessType) instead of being a free Record<string, …> that no compiler checked.
Net: +14 / -8 lines in one source file, +18 in one test; 4 registry entries removed, 1 added.
Not done here: the other unregistered HarnessType members (nanoclaw, pi, prime, hermes, openclaw, amp, factory-droids, forge, cursor, acp, cli-base) keep using the try-all path. Registering one needs its real wire shape confirmed against the bridge backend that serves it, which is a per-harness measurement, not a list edit.

Tests: +1 (both kimi-code wire shapes decode when the harness is named — the case that fails the moment anyone binds kimi-code to a single decoder, which is exactly the repair the old entry invited). It is green before this change too, because kimi-code was not a key at all; what fails before this change is the compiler, on the kimi key itself. -0 deleted.

Refs #954

toolPartDecoders held an entry under `kimi`. Every in-repo caller reaches
decodeToolPart through SteerableSandboxSession.harness, which is BackendType,
and the harness kimi is served under is `kimi-code`, so no caller could select
that entry. It was also wrong about the wire: kimi-code streams an Anthropic
tool_use content block and a top-level OpenAI tool_calls entry on one session,
and the entry named the OpenAI decoder alone.
An unknown harness falls through to the try-all path, so no tool call was lost.
The defect was a trap: renaming the key to kimi-code while keeping the decoder
it named would have made the specific adapter win and dropped the tool_use half
in silence.
The registry is now typed against HarnessType, so a key no caller can produce
does not compile, and kimi-code maps to a decoder that reads both shapes. The
three keys that were never harness names are gone; those wire shapes decode
identically through the try-all path.

@tangletoolstangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 5d81251b

This PR was opened by the trusted drewstone account.

This approval is provisional and was applied by the local stand-in because the pr-reviewer webhook host is unreachable (2026-08-21). CI on this head is fully green. The full PR reviewer audit re-runs via the resweep when the service returns and will publish findings if it detects issues.

@drewstone
drewstone merged commit c8038d2 into mainAug 21, 2026
4 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@drewstone@tangletools
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix(trace): key the tool-part decoders by the harness names that arrive by drewstone · Pull Request #955 · tangle-network/agent-runtime · GitHub
Skip to content

fix(trace): key the tool-part decoders by the harness names that arrive - #955

Merged
drewstone merged 1 commit into
mainfrom
fix/kimi-tool-part-decoder
Aug 21, 2026
Merged

fix(trace): key the tool-part decoders by the harness names that arrive#955
drewstone merged 1 commit into
mainfrom
fix/kimi-tool-part-decoder

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Problem

toolPartDecoders (src/runtime/supervise/trace-source.ts:151) is the harness → tool-call-decoder registry. It held an entry under kimi, and no caller can produce that name.

Every in-repo path to decodeToolPart runs through one field:

hopfile:linetype
the decode sitesupervise/sandbox-session.ts:154decodeToolPart(part, args.harness)
the fieldsupervise/sandbox-session.ts:100readonly harness: BackendType
bound atsupervise/runtime.ts:1277, runtime.ts:4815spec.harness as BackendType
BackendType@tangle-network/sandboxExclude<HarnessType, 'gemini'>
the harness kimi is served under@tangle-network/agent-interfaceharness.d.ts:23, and src/sandbox-backend.ts:31kimi-code

The literal 'kimi' appears nowhere in src/, tests/, bench/ or docs/. In the real producer it is a config toggle name and a binary name, never a wire value: cli-bridge/src/server.ts:169 registers the backend with harness: 'kimi-code'.

The entry was also wrong about the wire. kimi-code streams both shapes on one session — an Anthropic tool_use content block (cli-bridge/src/backends/kimi.ts:281) and a top-level OpenAI tool_calls entry (:307). The registry named decodeOpenAiPart, which reads only the second. The module's own docstrings already knew: :112 says decodeAnthropicPart covers "kimi's tool_use variant" and :128 says decodeOpenAiPart covers "kimi's top-level form".

Three more keys were never harness names either: anthropic, openai, router.

Why nothing broke, and why that is the dangerous part

An unregistered harness falls through to the try-all loop (:168), which is a Set of the three distinct decoder functions — including the Anthropic one. So no kimi tool call is dropped today; the measured trace is correct. Verified before any edit:

--- kimi tool_use block ---
harness="kimi" -> undefined <<< the dead key, if anything could reach it
harness="kimi-code" -> {"toolName":"read","args":{"file":"a.ts"},"callId":"k-1"} (try-all)
--- kimi top-level tool_call ---
harness="kimi-code" -> {"toolName":"bash","args":{"cmd":"ls"},"callId":"call_1"} (try-all)

The defect is the trap. The obvious repair — rename the key to kimi-code, keep the decoder the entry names — makes the specific adapter win and silences the tool_use half. Measured on that repair:

 tool_use, harness="kimi-code" -> undefined <<< now DROPPED

Half of a kimi worker's tool calls would disappear with no error: the trace simply reports fewer calls, and every rate computed from it — repeated-action detection, tool-waste, error streaks — is wrong by an unknown amount. No test would have caught it: every kimi case in tests/kernel/trace-source.test.ts calls decodeToolPart(part) with no harness, so the kimi key was never exercised.

Change

  • decodeKimiPart reads both shapes, and kimi-code maps to it.
  • The registry is typed Partial<Record<HarnessType, ToolPartDecoder>>, so a key no caller can produce does not compile — the same guard src/runtime/sandbox-backend.ts:40 already uses for the sibling list. This is what makes the defect unrepeatable; a lint rule or a drift test would only report it.
  • anthropic, openai and router are removed. A part carrying any of those wire shapes decodes identically through the try-all path, so no behavior moves.
  • decodeToolPart's harness and sandboxSessionTraceSource's harness option narrow from string to HarnessType. A caller with a harness this package does not register omits the argument, which is what try-all is for.

Proof

pnpm run lint 615 files, no fixes
pnpm run typecheck clean
pnpm run build clean
pnpm run check:api-surface 2120 exports / 17 entry points, record current
bench: 223 exports / 41 entry points, record current
pnpm run check:testing-fixture fixtures are current
pnpm run check:version-bump package.json: 0 manifest and 2 export change(s) needing a minor
bump, paid for by 0.154.0 -> 0.155.0 (minor)
pnpm run docs:check exit 0
pnpm test 2943 passed / 32 failed across 10 files
Clean origin/main on this machine: 2797 passed / 171 failed across 21 files.
Nine of the ten failing files are in that baseline set; the tenth,
tests/kernel/workspace.test.ts, is a 20s git-worktree timeout in the same
macOS class and touches nothing this change goes near. CI on Linux is the
authority.

The version gate named exactly the two symbols this change moves and nothing else:

shape changed: ./kernel decodeToolPart: shape 3122064402c0 -> c252d715cf84
shape changed: ./kernel sandboxSessionTraceSource: shape 62f065aca3b3 -> 063155b911a2

That is #953 working on its first real change: before it, this pull request would have reported "consumer surface unchanged at 0.154.0" and shipped a narrowed public signature under a version the registry already holds.

Simplification

Simplification: four unreachable keys removed from a seven-key registry; the registry's key vocabulary now has one owner (HarnessType) instead of being a free Record<string, …> that no compiler checked.
Net: +14 / -8 lines in one source file, +18 in one test; 4 registry entries removed, 1 added.
Not done here: the other unregistered HarnessType members (nanoclaw, pi, prime, hermes, openclaw, amp, factory-droids, forge, cursor, acp, cli-base) keep using the try-all path. Registering one needs its real wire shape confirmed against the bridge backend that serves it, which is a per-harness measurement, not a list edit.

Tests: +1 (both kimi-code wire shapes decode when the harness is named — the case that fails the moment anyone binds kimi-code to a single decoder, which is exactly the repair the old entry invited). It is green before this change too, because kimi-code was not a key at all; what fails before this change is the compiler, on the kimi key itself. -0 deleted.

Refs #954

toolPartDecoders held an entry under `kimi`. Every in-repo caller reaches
decodeToolPart through SteerableSandboxSession.harness, which is BackendType,
and the harness kimi is served under is `kimi-code`, so no caller could select
that entry. It was also wrong about the wire: kimi-code streams an Anthropic
tool_use content block and a top-level OpenAI tool_calls entry on one session,
and the entry named the OpenAI decoder alone.
An unknown harness falls through to the try-all path, so no tool call was lost.
The defect was a trap: renaming the key to kimi-code while keeping the decoder
it named would have made the specific adapter win and dropped the tool_use half
in silence.
The registry is now typed against HarnessType, so a key no caller can produce
does not compile, and kimi-code maps to a decoder that reads both shapes. The
three keys that were never harness names are gone; those wire shapes decode
identically through the try-all path.

@tangletoolstangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 5d81251b

This PR was opened by the trusted drewstone account.

This approval is provisional and was applied by the local stand-in because the pr-reviewer webhook host is unreachable (2026-08-21). CI on this head is fully green. The full PR reviewer audit re-runs via the resweep when the service returns and will publish findings if it detects issues.

@drewstone
drewstone merged commit c8038d2 into mainAug 21, 2026
4 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@drewstone@tangletools
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(trace): key the tool-part decoders by the harness names that arrive by drewstone · Pull Request #955 · tangle-network/agent-runtime · GitHub
Skip to content

fix(trace): key the tool-part decoders by the harness names that arrive - #955

Merged
drewstone merged 1 commit into
mainfrom
fix/kimi-tool-part-decoder
Aug 21, 2026
Merged

fix(trace): key the tool-part decoders by the harness names that arrive#955
drewstone merged 1 commit into
mainfrom
fix/kimi-tool-part-decoder

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Problem

toolPartDecoders (src/runtime/supervise/trace-source.ts:151) is the harness → tool-call-decoder registry. It held an entry under kimi, and no caller can produce that name.

Every in-repo path to decodeToolPart runs through one field:

hopfile:linetype
the decode sitesupervise/sandbox-session.ts:154decodeToolPart(part, args.harness)
the fieldsupervise/sandbox-session.ts:100readonly harness: BackendType
bound atsupervise/runtime.ts:1277, runtime.ts:4815spec.harness as BackendType
BackendType@tangle-network/sandboxExclude<HarnessType, 'gemini'>
the harness kimi is served under@tangle-network/agent-interfaceharness.d.ts:23, and src/sandbox-backend.ts:31kimi-code

The literal 'kimi' appears nowhere in src/, tests/, bench/ or docs/. In the real producer it is a config toggle name and a binary name, never a wire value: cli-bridge/src/server.ts:169 registers the backend with harness: 'kimi-code'.

The entry was also wrong about the wire. kimi-code streams both shapes on one session — an Anthropic tool_use content block (cli-bridge/src/backends/kimi.ts:281) and a top-level OpenAI tool_calls entry (:307). The registry named decodeOpenAiPart, which reads only the second. The module's own docstrings already knew: :112 says decodeAnthropicPart covers "kimi's tool_use variant" and :128 says decodeOpenAiPart covers "kimi's top-level form".

Three more keys were never harness names either: anthropic, openai, router.

Why nothing broke, and why that is the dangerous part

An unregistered harness falls through to the try-all loop (:168), which is a Set of the three distinct decoder functions — including the Anthropic one. So no kimi tool call is dropped today; the measured trace is correct. Verified before any edit:

--- kimi tool_use block ---
harness="kimi" -> undefined <<< the dead key, if anything could reach it
harness="kimi-code" -> {"toolName":"read","args":{"file":"a.ts"},"callId":"k-1"} (try-all)
--- kimi top-level tool_call ---
harness="kimi-code" -> {"toolName":"bash","args":{"cmd":"ls"},"callId":"call_1"} (try-all)

The defect is the trap. The obvious repair — rename the key to kimi-code, keep the decoder the entry names — makes the specific adapter win and silences the tool_use half. Measured on that repair:

 tool_use, harness="kimi-code" -> undefined <<< now DROPPED

Half of a kimi worker's tool calls would disappear with no error: the trace simply reports fewer calls, and every rate computed from it — repeated-action detection, tool-waste, error streaks — is wrong by an unknown amount. No test would have caught it: every kimi case in tests/kernel/trace-source.test.ts calls decodeToolPart(part) with no harness, so the kimi key was never exercised.

Change

  • decodeKimiPart reads both shapes, and kimi-code maps to it.
  • The registry is typed Partial<Record<HarnessType, ToolPartDecoder>>, so a key no caller can produce does not compile — the same guard src/runtime/sandbox-backend.ts:40 already uses for the sibling list. This is what makes the defect unrepeatable; a lint rule or a drift test would only report it.
  • anthropic, openai and router are removed. A part carrying any of those wire shapes decodes identically through the try-all path, so no behavior moves.
  • decodeToolPart's harness and sandboxSessionTraceSource's harness option narrow from string to HarnessType. A caller with a harness this package does not register omits the argument, which is what try-all is for.

Proof

pnpm run lint 615 files, no fixes
pnpm run typecheck clean
pnpm run build clean
pnpm run check:api-surface 2120 exports / 17 entry points, record current
bench: 223 exports / 41 entry points, record current
pnpm run check:testing-fixture fixtures are current
pnpm run check:version-bump package.json: 0 manifest and 2 export change(s) needing a minor
bump, paid for by 0.154.0 -> 0.155.0 (minor)
pnpm run docs:check exit 0
pnpm test 2943 passed / 32 failed across 10 files
Clean origin/main on this machine: 2797 passed / 171 failed across 21 files.
Nine of the ten failing files are in that baseline set; the tenth,
tests/kernel/workspace.test.ts, is a 20s git-worktree timeout in the same
macOS class and touches nothing this change goes near. CI on Linux is the
authority.

The version gate named exactly the two symbols this change moves and nothing else:

shape changed: ./kernel decodeToolPart: shape 3122064402c0 -> c252d715cf84
shape changed: ./kernel sandboxSessionTraceSource: shape 62f065aca3b3 -> 063155b911a2

That is #953 working on its first real change: before it, this pull request would have reported "consumer surface unchanged at 0.154.0" and shipped a narrowed public signature under a version the registry already holds.

Simplification

Simplification: four unreachable keys removed from a seven-key registry; the registry's key vocabulary now has one owner (HarnessType) instead of being a free Record<string, …> that no compiler checked.
Net: +14 / -8 lines in one source file, +18 in one test; 4 registry entries removed, 1 added.
Not done here: the other unregistered HarnessType members (nanoclaw, pi, prime, hermes, openclaw, amp, factory-droids, forge, cursor, acp, cli-base) keep using the try-all path. Registering one needs its real wire shape confirmed against the bridge backend that serves it, which is a per-harness measurement, not a list edit.

Tests: +1 (both kimi-code wire shapes decode when the harness is named — the case that fails the moment anyone binds kimi-code to a single decoder, which is exactly the repair the old entry invited). It is green before this change too, because kimi-code was not a key at all; what fails before this change is the compiler, on the kimi key itself. -0 deleted.

Refs #954

toolPartDecoders held an entry under `kimi`. Every in-repo caller reaches
decodeToolPart through SteerableSandboxSession.harness, which is BackendType,
and the harness kimi is served under is `kimi-code`, so no caller could select
that entry. It was also wrong about the wire: kimi-code streams an Anthropic
tool_use content block and a top-level OpenAI tool_calls entry on one session,
and the entry named the OpenAI decoder alone.
An unknown harness falls through to the try-all path, so no tool call was lost.
The defect was a trap: renaming the key to kimi-code while keeping the decoder
it named would have made the specific adapter win and dropped the tool_use half
in silence.
The registry is now typed against HarnessType, so a key no caller can produce
does not compile, and kimi-code maps to a decoder that reads both shapes. The
three keys that were never harness names are gone; those wire shapes decode
identically through the try-all path.

@tangletoolstangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 5d81251b

This PR was opened by the trusted drewstone account.

This approval is provisional and was applied by the local stand-in because the pr-reviewer webhook host is unreachable (2026-08-21). CI on this head is fully green. The full PR reviewer audit re-runs via the resweep when the service returns and will publish findings if it detects issues.

@drewstone
drewstone merged commit c8038d2 into mainAug 21, 2026
4 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@drewstone@tangletools
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); fix(trace): key the tool-part decoders by the harness names that arrive by drewstone · Pull Request #955 · tangle-network/agent-runtime · GitHub
Skip to content

fix(trace): key the tool-part decoders by the harness names that arrive - #955

Merged
drewstone merged 1 commit into
mainfrom
fix/kimi-tool-part-decoder
Aug 21, 2026
Merged

fix(trace): key the tool-part decoders by the harness names that arrive#955
drewstone merged 1 commit into
mainfrom
fix/kimi-tool-part-decoder

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Problem

toolPartDecoders (src/runtime/supervise/trace-source.ts:151) is the harness → tool-call-decoder registry. It held an entry under kimi, and no caller can produce that name.

Every in-repo path to decodeToolPart runs through one field:

hopfile:linetype
the decode sitesupervise/sandbox-session.ts:154decodeToolPart(part, args.harness)
the fieldsupervise/sandbox-session.ts:100readonly harness: BackendType
bound atsupervise/runtime.ts:1277, runtime.ts:4815spec.harness as BackendType
BackendType@tangle-network/sandboxExclude<HarnessType, 'gemini'>
the harness kimi is served under@tangle-network/agent-interfaceharness.d.ts:23, and src/sandbox-backend.ts:31kimi-code

The literal 'kimi' appears nowhere in src/, tests/, bench/ or docs/. In the real producer it is a config toggle name and a binary name, never a wire value: cli-bridge/src/server.ts:169 registers the backend with harness: 'kimi-code'.

The entry was also wrong about the wire. kimi-code streams both shapes on one session — an Anthropic tool_use content block (cli-bridge/src/backends/kimi.ts:281) and a top-level OpenAI tool_calls entry (:307). The registry named decodeOpenAiPart, which reads only the second. The module's own docstrings already knew: :112 says decodeAnthropicPart covers "kimi's tool_use variant" and :128 says decodeOpenAiPart covers "kimi's top-level form".

Three more keys were never harness names either: anthropic, openai, router.

Why nothing broke, and why that is the dangerous part

An unregistered harness falls through to the try-all loop (:168), which is a Set of the three distinct decoder functions — including the Anthropic one. So no kimi tool call is dropped today; the measured trace is correct. Verified before any edit:

--- kimi tool_use block ---
harness="kimi" -> undefined <<< the dead key, if anything could reach it
harness="kimi-code" -> {"toolName":"read","args":{"file":"a.ts"},"callId":"k-1"} (try-all)
--- kimi top-level tool_call ---
harness="kimi-code" -> {"toolName":"bash","args":{"cmd":"ls"},"callId":"call_1"} (try-all)

The defect is the trap. The obvious repair — rename the key to kimi-code, keep the decoder the entry names — makes the specific adapter win and silences the tool_use half. Measured on that repair:

 tool_use, harness="kimi-code" -> undefined <<< now DROPPED

Half of a kimi worker's tool calls would disappear with no error: the trace simply reports fewer calls, and every rate computed from it — repeated-action detection, tool-waste, error streaks — is wrong by an unknown amount. No test would have caught it: every kimi case in tests/kernel/trace-source.test.ts calls decodeToolPart(part) with no harness, so the kimi key was never exercised.

Change

  • decodeKimiPart reads both shapes, and kimi-code maps to it.
  • The registry is typed Partial<Record<HarnessType, ToolPartDecoder>>, so a key no caller can produce does not compile — the same guard src/runtime/sandbox-backend.ts:40 already uses for the sibling list. This is what makes the defect unrepeatable; a lint rule or a drift test would only report it.
  • anthropic, openai and router are removed. A part carrying any of those wire shapes decodes identically through the try-all path, so no behavior moves.
  • decodeToolPart's harness and sandboxSessionTraceSource's harness option narrow from string to HarnessType. A caller with a harness this package does not register omits the argument, which is what try-all is for.

Proof

pnpm run lint 615 files, no fixes
pnpm run typecheck clean
pnpm run build clean
pnpm run check:api-surface 2120 exports / 17 entry points, record current
bench: 223 exports / 41 entry points, record current
pnpm run check:testing-fixture fixtures are current
pnpm run check:version-bump package.json: 0 manifest and 2 export change(s) needing a minor
bump, paid for by 0.154.0 -> 0.155.0 (minor)
pnpm run docs:check exit 0
pnpm test 2943 passed / 32 failed across 10 files
Clean origin/main on this machine: 2797 passed / 171 failed across 21 files.
Nine of the ten failing files are in that baseline set; the tenth,
tests/kernel/workspace.test.ts, is a 20s git-worktree timeout in the same
macOS class and touches nothing this change goes near. CI on Linux is the
authority.

The version gate named exactly the two symbols this change moves and nothing else:

shape changed: ./kernel decodeToolPart: shape 3122064402c0 -> c252d715cf84
shape changed: ./kernel sandboxSessionTraceSource: shape 62f065aca3b3 -> 063155b911a2

That is #953 working on its first real change: before it, this pull request would have reported "consumer surface unchanged at 0.154.0" and shipped a narrowed public signature under a version the registry already holds.

Simplification

Simplification: four unreachable keys removed from a seven-key registry; the registry's key vocabulary now has one owner (HarnessType) instead of being a free Record<string, …> that no compiler checked.
Net: +14 / -8 lines in one source file, +18 in one test; 4 registry entries removed, 1 added.
Not done here: the other unregistered HarnessType members (nanoclaw, pi, prime, hermes, openclaw, amp, factory-droids, forge, cursor, acp, cli-base) keep using the try-all path. Registering one needs its real wire shape confirmed against the bridge backend that serves it, which is a per-harness measurement, not a list edit.

Tests: +1 (both kimi-code wire shapes decode when the harness is named — the case that fails the moment anyone binds kimi-code to a single decoder, which is exactly the repair the old entry invited). It is green before this change too, because kimi-code was not a key at all; what fails before this change is the compiler, on the kimi key itself. -0 deleted.

Refs #954

toolPartDecoders held an entry under `kimi`. Every in-repo caller reaches
decodeToolPart through SteerableSandboxSession.harness, which is BackendType,
and the harness kimi is served under is `kimi-code`, so no caller could select
that entry. It was also wrong about the wire: kimi-code streams an Anthropic
tool_use content block and a top-level OpenAI tool_calls entry on one session,
and the entry named the OpenAI decoder alone.
An unknown harness falls through to the try-all path, so no tool call was lost.
The defect was a trap: renaming the key to kimi-code while keeping the decoder
it named would have made the specific adapter win and dropped the tool_use half
in silence.
The registry is now typed against HarnessType, so a key no caller can produce
does not compile, and kimi-code maps to a decoder that reads both shapes. The
three keys that were never harness names are gone; those wire shapes decode
identically through the try-all path.

@tangletoolstangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 5d81251b

This PR was opened by the trusted drewstone account.

This approval is provisional and was applied by the local stand-in because the pr-reviewer webhook host is unreachable (2026-08-21). CI on this head is fully green. The full PR reviewer audit re-runs via the resweep when the service returns and will publish findings if it detects issues.

@drewstone
drewstone merged commit c8038d2 into mainAug 21, 2026
4 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@drewstone@tangletools