Skip to content

feat: add native RPC client and wire types - #897

Merged
AlexanderZ-Band merged 39 commits into
mainfrom
feat/omp-native-rpc-client-INT-1890
Oct 23, 2026
Merged

AlexanderZ-Band merged 39 commits into
mainfrom
feat/omp-native-rpc-client-INT-1890

Conversation

@bandzalkin

@bandzalkin bandzalkin commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Why

A native OMP adapter needs direct access to sessions, model selection, host tool calls and structured UI requests without changing the ACP transport used by existing omp_acp callers.

What

  • Add the persistent JSONL OmpRpcClient, typed wire models and native launch/MCP plugin support, plus per-room native session history conversion and the omp extra.
  • Support RPC v2 negotiation, chunk reassembly, correlated commands, host tools and structured ask questions/answers, including empty answer selections. Keep protocol tags and plugin command names derived from their shared definitions.
  • Bound the event backlog at 256 entries. Overflow fails the turn and retires the transport; slow event callbacks do not block correlated RPC control traffic.
  • Preserve process custody through cancellation and failed close. Capture identity-checked, discoverable descendants before and across EOF, and retain ownership until cleanup completes.

Scope

Transport and process ownership only; the native adapter is the next PR. POSIX descendant cleanup is best-effort ownership tracking, not a sandbox or a guarantee against unobservable process escape. Existing omp_acp behavior and its public adapter remain supported. Base: main.

@linear-code

linear-code Bot commented Oct 21, 2026

Copy link
Copy Markdown

INT-1890

@bandzalkin
bandzalkin added this pull request to stack #903 October 21, 2026 15:41
@bandzalkin
bandzalkin force-pushed the feat/omp-native-rpc-client-INT-1890 branch from 0c68d15 to 68580bc Compare October 21, 2026 16:13
bandzalkin added a commit to bandzalkin/band-sdk-python that referenced this pull request Oct 21, 2026
…s tree

close() killed the child's session with SIGKILL before closing stdin, so
Codex never saw the EOF it exits on and could do nothing at shutdown.
Stdin now closes first and the session gets one second to end on its
own; the tree kill remains the fallback. The session id stays reserved
while any member lives, so a launcher shim's real CLI child that keeps
the room directory is still reaped.
@bandzalkin
bandzalkin force-pushed the feat/omp-native-rpc-client-INT-1890 branch from 68580bc to 4b01955 Compare October 21, 2026 17:52
The ACP bridge hides OMP's native lifecycle: sessions, model and thinking
selection, host tool calls, permission dialogs and ask questions. This
adds the transport a native adapter needs: OmpRpcClient (JSONL RPC over
the omp process stdio, with process-tree reaping), typed wire frames,
the native launch settings and SDK-owned MCP plugin projection in
band.integrations.ompprocess, and the per-room session converter. The
shared normalizer gains a namespace keyword so plugin-projected tool
names map back to Band tools; omp_acp's spellings are unchanged.
@bandzalkin
bandzalkin force-pushed the feat/omp-native-rpc-client-INT-1890 branch from 4b01955 to 0b6c1e4 Compare October 21, 2026 17:56
bandzalkin added a commit to bandzalkin/band-sdk-python that referenced this pull request Oct 21, 2026
github-actions Bot pushed a commit to bandzalkin/band-sdk-python that referenced this pull request Oct 21, 2026
github-actions Bot pushed a commit to bandzalkin/band-sdk-python that referenced this pull request Oct 21, 2026
github-actions Bot pushed a commit to bandzalkin/band-sdk-python that referenced this pull request Oct 21, 2026

@AlexanderZ-Band AlexanderZ-Band left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Blocking findings cover process-tree cleanup, event backpressure, UI ask payloads, and test synchronization; see inline comments.

Comment thread src/band/integrations/ompprocess.py Outdated
Comment thread src/band/integrations/ompprocess.py Outdated
Comment thread src/band/integrations/ompwire.py
Comment thread src/band/integrations/omprpc.py Outdated
Comment thread tests/integrations/omprpc/test_interruption.py Outdated
Comment thread src/band/integrations/omprpc.py Outdated
Comment thread src/band/integrations/ompwire.py
@AlexanderZ-Band

AlexanderZ-Band commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Could we make the two OMP transports an intentional supported surface? I want to keep the actual OMP-over-ACP wire path alongside the native RPC path. The Python import/API name is not part of that requirement and may change; please do not keep a compatibility wrapper solely to preserve it.

Upstream OMP exposes distinct modes: omp acp speaks ACP, while omp --mode rpc uses its custom JSONL protocol (not JSON-RPC 2.0) (RPC protocol docs at the pinned source revision). I checked the source revision resolved by this PR's @oh-my-pi/pi-coding-agent@18.8.9 pin: it contains separate ACP mode and RPC mode implementations. Keeping both wire paths therefore appears feasible at the pinned version; the native adapter complements the ACP path rather than replacing its protocol.

Definition of done for maintaining both OMP transports

  • Document which Band-facing capabilities and limitations belong to each transport, including supported OMP versions and configuration.
  • Keep behavioral coverage at each real wire boundary. Cover the shared promises on both paths: ordinary replies, Band/custom tools, allow/deny, workspace and room-scoped MCP, multi-turn behavior, cancellation, background-work settlement, and cleanup. Cover asks/activity on the native path where supported; explicitly record protocol-specific gaps rather than assuming parity.
  • Keep live OMP baseline coverage for both transports and require the native backend lane to pass on supported CI platforms. The current feat: add native RPC client and wire types #897 Windows run still leaves descendants alive after native client.close(); resolve that before treating native lifecycle coverage as complete.
  • Keep the generic ACP integration and OMP-specific ACP wire support, while allowing the Python adapter/config API to be renamed or changed. Revisit removing ACP support only if its wire-level use case is deliberately ended and migration is planned.

#898, #899, #900, #902, and #907 cover native RPC work. Please track the dual-transport support matrix and the remaining parity/lifecycle work as explicit follow-ups with owners.

@bandzalkin bandzalkin changed the title feat(omp): add native RPC client and wire types feat: add native RPC client and wire types Oct 22, 2026
@bandzalkin
bandzalkin marked this pull request as ready for review October 22, 2026 08:20
github-actions Bot pushed a commit to bandzalkin/band-sdk-python that referenced this pull request Oct 22, 2026
@AlexanderZ-Band

AlexanderZ-Band commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Root cause found on this exact PR head: WindowsProcess.close() calls TerminateJobObject() and then Job.wait_until_idle() treats ActiveProcesses == 0 as proof that every member has exited; OmpRpcClient.close() returns after that path. The Windows 3.13 logs show seven failures (five process-tree cases and two generated lifecycle cases), all observing owned descendants still alive after cleanup returned.

That counter is not a documented completion barrier after forced termination. TerminateJobObject behaves like TerminateProcess, whose completion must be established by waiting on process handles; Microsoft staff has also reproduced ActiveProcesses == 0 while a descendant process handle remains unsignaled. This explains the observed early return. TerminateJobObject documentation, Microsoft staff confirmation and reproduction.

@AlexanderZ-Band AlexanderZ-Band left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Requesting changes on the four blocking items above; suggestions and no-fix records are non-blocking context.

Comment thread tests/integrations/omppermissionpeer.py Outdated
Comment thread tests/integrations/omprpc/test_callbacks.py
Comment thread tests/integrations/ompprocess/test_mcp_startup.py
Comment thread src/band/converters/omp.py
Comment thread src/band/integrations/omprpc/client.py
Comment thread src/band/integrations/ompprocess.py
Comment thread src/band/integrations/omprpc/client.py
Comment thread src/band/integrations/omprpc/client.py
Comment thread src/band/integrations/omprpc/client.py
Comment thread src/band/integrations/omprpc.py Outdated

@AlexanderZ-Band AlexanderZ-Band left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One non-blocking maintainability suggestion; the frame-size boundary hypothesis was checked against pinned OMP 18.8.9 and dropped.

Comment thread src/band/integrations/ompprocess.py Outdated
Comment thread src/band/integrations/omprpc/client.py
Fail closed on unmatched host frames and MCP startup UI, log transport
failures, and track Windows descendants with the same env-marker scope
POSIX already used. Shared process helpers now live with the Windows
job helpers instead of the Codex client.

Co-authored-by: Cursor <cursoragent@cursor.com>
@AlexanderZ-Band

Copy link
Copy Markdown
Collaborator

On the Windows descendant cleanup in CI run 39746971790: CONFIRMED. 13ee3f3 keeps OmpProcessScope on Windows, stamps the same env marker the POSIX path already used, and reaps marked descendants after the job closes. That is ownership tracking for discoverable children, not a redesign of Windows job assignment. I did not re-run the Windows job locally.

On dual-transport: out of scope for this transport-only ticket (INT-1890). Existing omp_acp is unchanged; the native adapter and the later stack PRs are the place for the dual-transport matrix.

Co-authored-by: Cursor <cursoragent@cursor.com>
AlexanderZ-Band and others added 10 commits October 22, 2026 19:51
The previous guidance named coverage in the abstract, so reviews treated plumbing assertions, private patches, and wall-clock grace as style nits instead of blocking test-quality issues.

Co-authored-by: Cursor <cursoragent@cursor.com>
The reader must honor negotiated frame limits immediately, stdin close must wait for in-flight writes, and fail-closed parse errors must log through the transport. Tests now inject public close/join seams and cover the complementary settlement and cancel branches.

Co-authored-by: Cursor <cursoragent@cursor.com>
Group D kept finding leftover OS skips, in-test ifs, and private seams
as a family. Drive those scenarios from public hooks, OS tables, and
shared peer constants so each branch is a named claim.

Co-authored-by: Cursor <cursoragent@cursor.com>
Nested jobs and breakaway look like missing features on this path.
They are out of scope while close() drains the existing kill-on-close job.

Co-authored-by: Cursor <cursoragent@cursor.com>
Overflow now records the transport ERROR, protocol failures omit frame
payloads from logs, and close no longer waits on a drain that holds the
write lock.

Co-authored-by: Cursor <cursoragent@cursor.com>
…logy

Co-authored-by: Cursor <cursoragent@cursor.com>
@AlexanderZ-Band
AlexanderZ-Band marked this pull request as ready for review October 22, 2026 16:54
Base automatically changed from feat/fix-preserve-accepted-turn-finalizers-during-shutd-INT-1904 to main October 22, 2026 17:45
bandzalkin and others added 8 commits October 22, 2026 21:21
Record root-exit as a transport failure, join callbacks that start during
the wait, and stop treating an empty UI reply as cancelled. Tighten plugin
and converter tests onto product names.

Co-authored-by: Cursor <cursoragent@cursor.com>
create_process wrappers that used create_subprocess_exec skipped the
Windows job, so generated restart sequences could not reap descendants.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the wire vocabulary in one enum and send commands through the same
frame encoder as host-tool replies.

Co-authored-by: Cursor <cursoragent@cursor.com>
A five-second connection timeout was proving Windows children had arrived.
Identify them as descendants of the current root instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
@bandzalkin
bandzalkin force-pushed the feat/omp-native-rpc-client-INT-1890 branch from 614a7b5 to 0b0620e Compare October 22, 2026 21:11
github-actions Bot pushed a commit to bandzalkin/band-sdk-python that referenced this pull request Oct 22, 2026
github-actions Bot pushed a commit to bandzalkin/band-sdk-python that referenced this pull request Oct 22, 2026
@AlexanderZ-Band
AlexanderZ-Band enabled stacked auto-merge October 23, 2026 02:10
@AlexanderZ-Band
AlexanderZ-Band added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit fd241f0 Oct 23, 2026
35 checks passed
@AlexanderZ-Band
AlexanderZ-Band deleted the feat/omp-native-rpc-client-INT-1890 branch October 23, 2026 02:35
Sign up for free to 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