[PR #17] [MERGED] Add client-side cross-node task_handle decoder + TaskHandle dataclass #20

Closed
opened 2026-10-26 22:28:58 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/Comfy-Org/comfy-remote-nodes/pull/17
Author: @Kosinkadink
Created: 5/17/2026
Status: ✅ Merged
Merged: 5/17/2026
Merged by: @Kosinkadink

Base: main ← Head: feat/task-handle-decoder


📝 Commits (1)

  • 1f13652 Add client-side cross-node task_handle decoder + TaskHandle dataclass

📊 Changes

4 files changed (+616 additions, -6 deletions)

View changed files

📝 client.py (+14 -0)
➕ notes/run_task_handle_decoder.py (+392 -0)
📝 protocol.py (+53 -1)
📝 serialization.py (+157 -5)

📄 Description

Client-side counterpart to comfy-rnp-server #39. Adds the surfaces needed for chained partner provider nodes (Tripo Texture/Refine/Rig/Retarget/Conversion, Kling VideoExtend, future partner chained nodes) to pass vendor-native task / video / job IDs through a workflow with their lineage metadata intact.

Surfaces

  • protocol.Capability.IO_TASK_HANDLE = "io:task_handle" and "task_handle" added to protocol.HEAVY_TYPES so is_envelope recognises the new envelope.
  • serialization.TaskHandle dataclass with the wire-shape fields (vendor, kind, native_id, origin_node_id, parent_chain). __str__ surfaces native_id for log / UI preview without leaking the lineage chain.
  • serialization.decode_task_handle_envelope validates every field (mirroring server-side _validate_task_handle_ref so a bad envelope surfaces the same error message on both sides) and returns the dataclass. Rejects:
    • wrong encoding (only vendor_inline recognised);
    • missing / empty top-level fields;
    • comma-union kind strings (the input-acceptance string used by some provider nodes, NOT an emitted handle kind);
    • non-list parent_chain;
    • malformed parent_chain entries (missing fields, non-dict, comma-union kind in lineage).
  • serialization.encode_task_handle re-encodes the dataclass back to wire shape — symmetric counterpart of the decoder so a handle decoded from one provider's output and fed into another provider's input round-trips losslessly without proxy_node having to know anything provider-specific.
  • decode_envelope dispatcher routes type="task_handle" to the new decoder so the generic _deserialize_output path picks it up.
  • client.CLIENT_CAPABILITIES advertises Capability.IO_TASK_HANDLE so server-side capability-gated descriptors negotiate cleanly.

Why a dataclass, not a plain string

A plain str round-trip would silently drop parent_chain when the value is passed through the workflow, breaking the lineage the server uses for future cascading-replay semantics. The dataclass costs the consumer almost nothing (existing log lines / UI previews see native_id via __str__) but preserves the chain through downstream submits.

Cascading replay is deferred to a follow-up PR alongside server-side replay-cache storage — see comfy-rnp-server #39 for the replay design notes. Current per-node IdempotencyMode.CLIENT_KEY TASK_LOST semantics are unchanged.

Verification

Smoke test notes/run_task_handle_decoder.py mirrors the pattern used by notes/run_model3d_bundle_decoder.py: stubs out the client import for the relative from . import client as rnp_client at the top of serialization.py, then exercises 9 contract points end-to-end. The end-to-end check imports the server's make_task_handle_envelope from the comfy-rnp-server worktree and verifies the wire shape round-trips losslessly through decode -> encode, plus that server-side rejections (comma-union kind, parent_chain over depth cap) fire as expected.

All 9 checks pass:

PASS: Capability.IO_TASK_HANDLE = 'io:task_handle'.
PASS: 'task_handle' in HEAVY_TYPES and is_envelope recognises it.
PASS: decode_task_handle_envelope returns TaskHandle with full lineage.
PASS: str(TaskHandle) surfaces the native_id.
PASS: encode_task_handle round-trips lossless (including empty chain).
PASS: decode_envelope dispatches 'task_handle' -> decode_task_handle_envelope.
PASS: malformed task_handle envelopes raise RnpProtocolError(INTERNAL).
PASS: end-to-end server make_task_handle_envelope -> client decode_task_handle_envelope -> encode_task_handle round-trips losslessly, and server-side rejections fire.
PASS: CLIENT_CAPABILITIES advertises Capability.IO_TASK_HANDLE.
ALL CHECKS PASSED.

Amp-Thread-ID: https://ampcode.com/threads/T-019e3431-c416-742b-973c-e262ab13f2ff


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/Comfy-Org/comfy-remote-nodes/pull/17 **Author:** [@Kosinkadink](https://github.com/Kosinkadink) **Created:** 5/17/2026 **Status:** ✅ Merged **Merged:** 5/17/2026 **Merged by:** [@Kosinkadink](https://github.com/Kosinkadink) **Base:** `main` ← **Head:** `feat/task-handle-decoder` --- ### 📝 Commits (1) - [`1f13652`](https://github.com/Comfy-Org/comfy-remote-nodes/commit/1f136526cf3b4559c64e3ab0fbc146439784c043) Add client-side cross-node task_handle decoder + TaskHandle dataclass ### 📊 Changes **4 files changed** (+616 additions, -6 deletions) <details> <summary>View changed files</summary> 📝 `client.py` (+14 -0) ➕ `notes/run_task_handle_decoder.py` (+392 -0) 📝 `protocol.py` (+53 -1) 📝 `serialization.py` (+157 -5) </details> ### 📄 Description Client-side counterpart to comfy-rnp-server [#39](https://github.com/Comfy-Org/comfy-rnp-server/pull/39). Adds the surfaces needed for chained partner provider nodes (Tripo Texture/Refine/Rig/Retarget/Conversion, Kling VideoExtend, future partner chained nodes) to pass vendor-native task / video / job IDs through a workflow with their lineage metadata intact. ## Surfaces - **`protocol.Capability.IO_TASK_HANDLE = "io:task_handle"`** and `"task_handle"` added to `protocol.HEAVY_TYPES` so `is_envelope` recognises the new envelope. - **`serialization.TaskHandle`** dataclass with the wire-shape fields (`vendor`, `kind`, `native_id`, `origin_node_id`, `parent_chain`). `__str__` surfaces `native_id` for log / UI preview without leaking the lineage chain. - **`serialization.decode_task_handle_envelope`** validates every field (mirroring server-side `_validate_task_handle_ref` so a bad envelope surfaces the same error message on both sides) and returns the dataclass. Rejects: - wrong encoding (only `vendor_inline` recognised); - missing / empty top-level fields; - comma-union `kind` strings (the input-acceptance string used by some provider nodes, NOT an emitted handle kind); - non-list `parent_chain`; - malformed parent_chain entries (missing fields, non-dict, comma-union kind in lineage). - **`serialization.encode_task_handle`** re-encodes the dataclass back to wire shape — symmetric counterpart of the decoder so a handle decoded from one provider's output and fed into another provider's input round-trips losslessly without `proxy_node` having to know anything provider-specific. - **`decode_envelope`** dispatcher routes `type="task_handle"` to the new decoder so the generic `_deserialize_output` path picks it up. - **`client.CLIENT_CAPABILITIES`** advertises `Capability.IO_TASK_HANDLE` so server-side capability-gated descriptors negotiate cleanly. ## Why a dataclass, not a plain string A plain `str` round-trip would silently drop `parent_chain` when the value is passed through the workflow, breaking the lineage the server uses for future cascading-replay semantics. The dataclass costs the consumer almost nothing (existing log lines / UI previews see `native_id` via `__str__`) but preserves the chain through downstream submits. Cascading replay is **deferred** to a follow-up PR alongside server-side replay-cache storage — see comfy-rnp-server #39 for the replay design notes. Current per-node `IdempotencyMode.CLIENT_KEY` `TASK_LOST` semantics are unchanged. ## Verification Smoke test `notes/run_task_handle_decoder.py` mirrors the pattern used by `notes/run_model3d_bundle_decoder.py`: stubs out the `client` import for the relative `from . import client as rnp_client` at the top of `serialization.py`, then exercises 9 contract points end-to-end. The end-to-end check imports the server's `make_task_handle_envelope` from the comfy-rnp-server worktree and verifies the wire shape round-trips losslessly through `decode -> encode`, plus that server-side rejections (comma-union `kind`, parent_chain over depth cap) fire as expected. All 9 checks pass: ``` PASS: Capability.IO_TASK_HANDLE = 'io:task_handle'. PASS: 'task_handle' in HEAVY_TYPES and is_envelope recognises it. PASS: decode_task_handle_envelope returns TaskHandle with full lineage. PASS: str(TaskHandle) surfaces the native_id. PASS: encode_task_handle round-trips lossless (including empty chain). PASS: decode_envelope dispatches 'task_handle' -> decode_task_handle_envelope. PASS: malformed task_handle envelopes raise RnpProtocolError(INTERNAL). PASS: end-to-end server make_task_handle_envelope -> client decode_task_handle_envelope -> encode_task_handle round-trips losslessly, and server-side rejections fire. PASS: CLIENT_CAPABILITIES advertises Capability.IO_TASK_HANDLE. ALL CHECKS PASSED. ``` Amp-Thread-ID: https://ampcode.com/threads/T-019e3431-c416-742b-973c-e262ab13f2ff --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-26 22:28:58 +00:00
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Comfy-Org/comfy-remote-nodes#20
No description provided.