Orchestrata Ultimate › Plans › grpc-retirement-design
gRPC retirement — design
Source: plans/grpc-retirement-design.md at 8a2cb42ad · last changed 2026-08-07 · first committed 2026-08-07 · 115 lines.
Prose pending.
Plan text
gRPC retirement — design
Branch: feat/cxp-direct-transport · Authorized: 2026-08-07 ("agreed merge,
retire grpc, proceed as you recommend") · RUL-022 design-of-record for this change.
Why
Two independent drivers converge on the same removal.
- SPEC-023 (GIL-off).
grpc.aio's EventEngine threads re-enable the GIL.cxpt— the vendored free-threaded direct transport — exists to replace it, and as of 2026-08-07 prod runs on it (env/prod.env: CXP_TRANSPORT=cxpt, both units verified serving).grpcis now dead weight on the RPC path. - The merge is blocked by it. MR !8's
architecture-checkjob fails atuv sync --dev:failed to query metadata of file vendor/wheels/grpcio-1.82.1-cp314-cp314t-linux_x86_64.whl: No such file or directory.gitignore:36 wheels/makes the vendored wheels untracked by design ("fresh clones rebuild it"). CI has no wheel-build step, so any pinned[tool.uv.sources]path intovendor/wheels/is an unresolvable dependency for a fresh clone.
The measured dependency split
grpcio (the RPC runtime) and protobuf (the message codec) are separable,
and only the former is entangled with the transport.
| file | grpc contact | verdict |
|---|---|---|
cxp/proto/cxp_pb2.py |
none — imports google.protobuf only |
keep |
cxp/grpc_server.py lines 1-504 |
grpc.aio.ServicerContext in annotations only (grep 'context\.' → zero hits) |
extract, neutral |
cxp/grpc_server.py lines 505-599 (CXPServer) |
grpc.aio.server(), servicer registration |
delete |
cxp/grpc_client.py GRPCClient |
3 sites: aio.Channel, aio.insecure_channel, grpc.RpcError |
delete |
cxp/grpc_client.py converters + GRPCClientConfig |
none | extract, neutral |
cxp/proto/cxp_pb2_grpc.py |
generated stubs, import grpc |
delete |
That context is annotation-only is exactly why direct_cxp_adapter can already
pass a bare sentinel into CXPServicer — the cxpt path has been running the
"grpc" servicer in production all along. Extraction is therefore a move, not a
rewrite: no behaviour changes hands.
Target shape
cxp/servicer.py (new) CXPServerConfig, CXPStats, CXPServicer, _env_*, _*_str_to_proto
— transport-neutral; `context: Any`
cxp/proto_codec.py (new) CXPClientConfig, packet<->proto converters
cxp/transport.py cxpt is the ONLY transport; CXP_TRANSPORT=grpc now RAISES
cxp/direct_cxp_adapter.py imports from servicer/proto_codec, not grpc_*
cxp/grpc_server.py DELETED
cxp/grpc_client.py DELETED
cxp/proto/cxp_pb2_grpc.py DELETED
Call sites to rewire (11): agents/runtime_main.py, cxp/transport.py ×2,
cxp/direct_cxp_adapter.py ×2, kernel/cli_main.py, kernel/dispatcher.py,
knowledge_graph/server.py, gateway/server.py, cxp/proto/__init__.py,
scripts/start_test_services.py.
Test modules to retire or repoint (6): tests/cxp/test_grpc{,_client,_fields,_kernel_endpoint}.py,
tests/cxp/test_proto_grpc.py, tests/gateway/test_kernel_reply_consumer.py.
grpcio-tools — the second wheel, and why it cannot just move to PyPI
scripts/gen_cxp.py regenerates the stubs with grpc_tools.protoc, and
tests/scripts/test_gen_cxp_cli.py drives it through a real protoc
subprocess (RUL-002: no mocks). So grpcio-tools cannot simply be dropped.
It also cannot come from PyPI: measured 2026-08-07, grpcio-tools 1.83.0 publishes
cp314 wheels but no cp314t. Under python3.14t that means a source build —
which is why it was vendored FT in the first place.
Resolution: take it out of the dependency graph entirely. protoc is a build
tool that emits text in a child process; it has zero runtime coupling to this
package and no reason to be free-threaded. gen_cxp.py invokes it through an
ephemeral GIL-enabled interpreter:
uv run --isolated --no-project --with grpcio-tools --python 3.14 \
python -m grpc_tools.protoc ...
Both grpcio and grpcio-tools then leave [project.dependencies] and
[tool.uv.sources], vendor/wheels/ stops being load-bearing for uv sync, and
CI's fresh clone resolves.
Order of work
- Extract the neutral core (
servicer.py,proto_codec.py); delete the grpc shells; rewire the 11 call sites.transport.pyraises onCXP_TRANSPORT=grpcrather than silently falling back — a removed transport must fail loudly. - Repoint/retire the 6 grpc test modules.
gen_cxp.py→ ephemeral protoc; verify regeneration is byte-identical to the committed stubs (minuscxp_pb2_grpc.py, which stops being generated).- Drop
grpcio+grpcio-toolsfrompyproject.tomldeps and[tool.uv.sources];uv lock; confirmvendor/wheels/is no longer referenced by any resolver input. - RUL-008 review battery; full observed gate; commit; push; retry
glab mr merge 8.
Verification
grep -rn '^import grpc\|^from grpc' src/ tests/→ zero hits.grep -n 'vendor/wheels' pyproject.toml uv.lock→ zero grpc entries.- Full gate green at 14×2 with raw numbers reported.
- Prod kernel + gateway still serving over cxpt after the change is deployed.
- CI
architecture-checkjob green on MR !8.
Revert
Prod transport reverts by deleting CXP_TRANSPORT=cxpt from env/prod.env and
restarting both units — but only while grpc_server.py/grpc_client.py still
exist. After step 1 lands there is no grpc transport to revert to; the revert
is git revert of the retirement commit plus rebuilding the vendored wheels.