Proposal: Assess replacing the laptop Go forward proxy with Praxis — blocked on three upstream capabilities
维护者通常 1 天内回复
@esnible 已经在做这个了。
开始于 2026年10月27日。
评估
- 难度
- 5/5
- 预计耗时
- 一周以上
- 新手友好度
- 15/100
调研方向
Read the assessment against Cortex main at 2362f7eb and Praxis v0.7.4 at a50e58e5, then inspect the referenced core/listener/forwardproxy, core/tlsbridge, and core/peerproc components. The issue recommends postponing migration until Praxis issues #781 and #782 are merged; its proposed migration is estimated at 35–52 engineer-weeks. Done means reassessing the migration after those upstream capabilities land.
由索引模型根据 Issue 内容生成。
描述
Summary
This issue records an assessment of replacing the laptop Go forward proxy (cortex, run by agentop setup on 127.0.0.1:47600) with Praxis.
Conclusion: it is not possible today. Three capabilities the laptop depends on are missing from Praxis:
- HTTP
CONNECTand forwarding to the destination the client named. - HTTPS interception, meaning a TLS certificate minted per host from a local CA.
- Access, from a filter, to the client's TCP port.
Two of these have open Praxis issues. None has a merged design, and only one has an assignee. Once all three exist, the remaining work on the Cortex side is estimated at 35–52 engineer-weeks.
Scope is the laptop only. Kubernetes shapes (inbound reverse proxy, iptables capture, mTLS) are out of scope.
Assessed against Cortex main @ 2362f7eb, Praxis v0.7.4 (a50e58e5) and praxis-ai v0.7.0 on 2026-10-17. Prior issues: #504, #681 (closed 2026-10-05: on hold because Praxis has no forward or transparent proxy), #682.
What the laptop proxy does today
The laptop install runs only the forward proxy role. Per request it:
- Accepts plain
HTTP_PROXYrequests andHTTPS_PROXYCONNECTtunnels on one port. - Decrypts a tunnel with a certificate it mints for that host from
~/.cortex/ca. Whether to decrypt is decided per program: port, TLS peek, passthrough hosts, and a skip memory for programs that refuse the CA. - Attributes the request to a local program by looking up the owner of the TCP connection, then to a session from client headers, the program, or client affinity.
- Runs the outbound pipeline:
inference-parser,mcp-parser,a2a-parser,tool-prune, andinference-routerwhenagentop serverhas added a server. This includes:- body buffering;
- three SSE relay paths;
- redirects;
- resending a refused request with a changed model.
- Records request, response, denied, failure and tunnel events. These feed the session store, the cost ledger, the usage aggregator, the session archive, and the APIs on
:47601and:47602.
Size, non-comment Go lines:
| Component | Lines |
|---|---|
Forward proxy (core/listener/forwardproxy server and process) |
1,414 |
TLS bridge (core/tlsbridge) |
1,140 |
Process lookup (core/peerproc) |
632 |
Generic forwarding is a small part of this. Most of it is interception, attribution and recording.
Findings
Verified by running Praxis v0.7.4 on macOS arm64:
| Check | Result |
|---|---|
| Native release build | Succeeds: 2m43s, 21 MB binary |
| Health | /healthy returns 200 on admin.metrics_address |
curl --proxy http://127.0.0.1:<port> https://example.com/ |
405 Method Not Allowed. The Pingora fork rejects CONNECT unless allow_connect_method_proxying is set, and Praxis does not set it |
curl --proxy http://127.0.0.1:<port> http://example.com/ |
Forwarded to the configured cluster endpoint, not to example.com. There is no "connect where the client asked" routing |
Read from source, not exercised:
| Capability | Praxis v0.7.4 | Where |
|---|---|---|
| Certificate generation in the shipping binary | None. rcgen is only a dev-dependency (crates/protocol) or behind the opt-in bench-utils feature (crates/tls) |
crates/{protocol,tls}/Cargo.toml |
| Client address available to filters | IP only: client_addr: Option<IpAddr>, no port |
crates/filter/src/context.rs |
| Async response-body hook | No. on_response_body is a plain fn. Tracked in praxis-proxy/praxis#1105, which waits on a published Pingora release (praxis-proxy/praxis#1353) |
crates/filter/src/filter.rs |
| General ext_proc client | Removed from Praxis core in v0.4.1. The only one left, in praxis-ai, is scoped to llm-d and rejects every response_body_mode except none |
ai/integrations/llmd/ext-proc/src/lib.rs |
| Filter-registered admin endpoints | No. Admin routes are hard-coded (/api/kv/*, /api/pipelines, /api/log-level, /api/stats) |
crates/protocol/src/http/pingora/health/service.rs |
| Retry with a modified request | No. Retries resend the same request | router retry_policy |
| macOS in upstream CI | No workflow runs on a macOS runner | .github/workflows/ |
Blockers
All three are changes to Praxis itself; none can be done as a Cortex plugin or filter.
| # | Blocker | Laptop impact | Upstream status (2026-10-17) |
|---|---|---|---|
| B1 | CONNECT and destination-from-request forwarding | No agent traffic can pass | praxis-proxy/praxis#781: assigned to esnible, milestone v0.8.0 (due 2026-11-16; 150 open issues on it). Maintainer agreed scope: HTTP/1.1 only, a destination allowlist required. No proposal in praxis-proxy/enhancements and no PR |
| B2 | HTTPS interception (CA, per-host minting, cert cache, decrypted traffic re-entering the HTTP pipeline) | No parsing, cost, tool-prune or routing for HTTPS, which is all LLM traffic | praxis-proxy/praxis#782: unassigned, no comments, milestone v0.8.0 |
| B3 | Connection identity in filters (client port; per-connection state shared by a tunnel and the requests decrypted from it) | No program attribution, per-program decrypt decision, or self-traffic filter | Filed 2026-10-24 as praxis-proxy/praxis#1447 (client port and local address to HTTP filters). Praxis already has the port and drops it before filters see it. Per-connection state for decrypted requests is left to #782's design |
Not blockers for this design:
- macOS: builds and runs.
- The synchronous response-body hook: avoided if the Go pipeline runs in-process; see Option A.
- Private and loopback upstreams: allowed by
insecure_options.allow_private_endpoints, which logs a warning. (Corrected 2026-10-24:allow_private_upstreamsis a different key, covering DNS-resolved names only.)
Migration tracker
Living table, updated as asks get owners. Each row is one Cortex capability. It moves from Cortex only to both running (Praxis's version runs beside Cortex's on real traffic and outputs are compared) to Praxis only (Cortex's copy is deleted one release later). A row moves on when Praxis passes the same saved test cases as Cortex, not on belief.
Status as of 2026-10-25. No Cortex–Praxis integration exists yet. The interim path (LLM traffic to praxis-ai as an inference server, or all traffic through Praxis via #1491) is not decided.
| Cortex capability | Praxis equivalent today | Gap | Upstream ask | State |
|---|---|---|---|---|
| CONNECT (forward proxy) | none | no CONNECT support | praxis-proxy/praxis#781 (esnible) | Cortex only |
| Decryption (TLS interception) | none | no per-host certificate minting | praxis-proxy/praxis#782 (no owner) | Cortex only |
| Client port to filters | has the port, drops it | not passed to filters | praxis-proxy/praxis#1447 | Cortex only |
| Which agent / which session | none | stays in Cortex for good | — | Cortex only |
| Inference parser | praxis-ai token_usage |
model and tokens only; no messages, tools or tool calls | praxis-ai Discussion (drafted, not posted) | Cortex only |
| MCP parser | praxis-ai agentic/mcp |
method and tool name only; no arguments or results | same Discussion | Cortex only |
| A2A parser | praxis-ai agentic/a2a |
method and task id only; no message content | same Discussion | Cortex only |
| opa | PPE opa |
probably none, not verified | — | Cortex only; could move first |
| jwt-validation | PPE identity_jwt |
probably none, not verified | — | Cortex only; could move first |
| static-inject | Praxis header / credential_injection |
probably none, not verified | — | Cortex only; could move first |
| Pricing | Tokenomics (proposal only) | no prices in code | praxis-proxy/enhancements#32 | Cortex only |
| tool-prune, ibac, sparc, inference-router | none | need parsed content; ibac, sparc and the router also need session history | — | Cortex only |
| Session store, archive, agentop | none | not a proxy concern; stays in Cortex | — | Cortex only |
Cortex-side prep, none of which depends on the path decision: #1497, #1498, #1499, #1500, #1501, #1502, #1503, #1504.
Responsibilities
Praxis maintainers (praxis-proxy/praxis)
- Decide whether B1, B2 and B3 are in Praxis's scope.
- Own their design: the proposal process in
praxis-proxy/enhancements, review, merge, and long-term maintenance. - B2 needs a security decision only Praxis can make: CA key handling and trust distribution.
- Own the async response-body work (praxis-proxy/praxis#1105). It is not required by Option A.
Cortex team (rossoctl/cortex)
- Give Praxis written requirements for B1. Plain and CONNECT requests on one port, HTTP/1.1 is sufficient, and tunnel open/close hooks that carry byte counts.
- File B3 and the modified-request retry gap upstream, with the use cases.
- Decide whether Cortex offers engineers to implement B2 upstream. Today nobody owns it, and it is the critical path for the laptop.
- If B1–B3 land, everything in Work breakdown marked "Cortex".
- Until a decision to migrate, keep shipping the Go proxy. No Cortex release depends on Praxis.
praxis-ai maintainers (praxis-proxy/ai)
- No work is required for this migration.
- Optional, independent of it: Cortex could route to a praxis-ai instance as an inference backend for API translation (Anthropic Messages ⇄ Chat Completions). That uses praxis-ai as it ships today.
Joint
- praxis-proxy/praxis#781 already has an assignee on the Praxis side. Cortex's part there is requirements and testing against real agents: Claude Code, Codex, OpenCode, Bob.
Work breakdown (only once B1–B3 are available)
Design: Praxis owns the sockets, CONNECT and interception. The Go pipeline, session store and APIs are linked into the same binary as a C library, the reverse of how cortex-cpex links libcpex_ffi.a. agentop stays unchanged.
| Work | Code lives in | Estimate |
|---|---|---|
| CONNECT and forwarding to the client's destination (B1) | Praxis | 4–6 wk |
HTTPS interception (B2): CA create/renew, bundle.crt, minting, HTTP/1.1 and HTTP/2 on decrypted connections |
Praxis | 8–12 wk |
| Connection identity for filters (B3) | Praxis | 1–2 wk |
| Decrypt decision: ports, TLS peek, passthrough hosts, per-program skip memory, unread report | Cortex (Rust filter) | 3–4 wk |
| Program lookup port (macOS and Linux kernel tables), self-traffic filter, Go-on-macOS trust prediction | Cortex (Rust) | 2–4 wk |
| Go pipeline bridge: request, response, per-frame and end-of-request hooks; denials, body edits, redirect, resend | Cortex | 6–8 wk |
| Event recording: request, response, denied, failure and tunnel rows; byte counts; session-ID headers; affinity | Cortex | 3–4 wk |
| Packaging: one Go+Rust binary, darwin and linux release builds with cgo, supervisor and 502 fallback, existing CLI flags and ports | Cortex | 4–6 wk |
| agentop adjustments (small if flags, ports and CA paths are kept) | Cortex | 1–2 wk |
| Black-box parity tests against real agents. The existing Go proxy tests exercise Go internals and do not carry over | Cortex | 3–4 wk |
| Total | 35–52 engineer-weeks |
Of the total, 13–20 weeks is code in Praxis's own repository. Its schedule depends on Praxis review.
These are estimates from reading code, not from a prototype. The largest unknown is in B2: whether Praxis's Pingora fork can feed a decrypted connection back into its HTTP handling.
Benefits and costs
What adoption would bring:
- Proxy features maintained by others: WebSocket upgrade passthrough (Cortex strips
Upgradetoday), retries, circuit breakers, rate limiting. - Ecosystem alignment. The Praxis Policy Engine is the successor of CPEX, which
cortex-cpexlinks today. - Rust's safety profile and a FIPS build option.
What it would not change:
- Latency. A 2026-10-02 measurement put Cortex at about 1% of an LLM call's wall time, mostly parsing and recording, which would stay in Go.
- The plugins, session store or agentop. These stay in Go under this design.
Costs:
- The effort above.
- Two languages and cgo in every release build.
- Cross-repository review for proxy changes. The Go proxy, TLS bridge and process lookup had about 90 commits in the last three months, each shipped in a single PR.
- Upstream churn: Praxis went from v0.5.3 to v0.7.4 between 2026-08-19 and 2026-10-12.
Recommendation
- Do not start a migration now. Revisit when praxis-proxy/praxis#781 and praxis-proxy/praxis#782 are merged.
- Cortex, now:
- post laptop requirements on praxis-proxy/praxis#781;
- file B3 and the modified-retry gap;
- make an explicit decision on whether to offer engineers for praxis-proxy/praxis#782.
- Optional, independent of this issue: evaluate praxis-ai as an inference-router backend for cross-API server switching.
Open questions
- Is HTTPS interception (praxis-proxy/praxis#782) in Praxis's intended scope, and would Praxis accept an external implementation?
- Would Praxis expose connection-level identity (client port, per-connection state) to HTTP filters?
- Is a Go+Rust single binary acceptable for the laptop release, or must the release stay a static Go build?
References
- Praxis issues: praxis-proxy/praxis#781 (CONNECT), #782 (TLS interception), #1105 and #1353 (async response-body hook)
- Earlier Cortex work: #504, #681, #682;
cmd/cortex-praxisandcore/praxis(config converter, paused)
Assisted-By: Claude (Anthropic AI) [email protected]
- 主要语言
- Go
- 星标
- 23
- 派生
- 44
- 平均合并
- 16 小时 32 分钟
- 30 天内合并 PR
- 227
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
rossoctl/cortex 的其他 Issue
-
UI shows costs for all sessions even when drilled into a session可能已有人在做 @esnible 于 22 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 70/100
rossoctl/cortex#1116 · 2 条评论 · 已指派 1 人 ·
维护者通常 1 天内回复
-
Docs: Fix stale claims in CLAUDE.md and remove duplicated text可能已有人在做 @huang195 今天认领。 未关闭
rossoctl/cortex#1663 · 已指派 1 人 ·
维护者通常 1 天内回复
-
Fix: Stop the live and draining pipelines on shutdown, not the boot ones可能已有人在做 @evaline-ju 今天认领。 未关闭bug
rossoctl/cortex#1662 · 已指派 1 人 ·
维护者通常 1 天内回复
-
feedback laptop
难度 5/5 一周以上 新手友好度 28/100
维护者通常 1 天内回复
-
chore: Remove the decline_responses_websockets translation after a deprecation release可能已有人在做 @Alan-Cha 于 2 天前认领。 未关闭
难度 3/5 1-2 天 新手友好度 55/100
rossoctl/cortex#1625 · 已指派 1 人 ·
维护者通常 1 天内回复
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 83/100
txn2/mcp-data-platform#2493 ·
维护者通常 1 天内回复
-
area/docs framework/hugo help wanted language/markdown
难度 1/5 1 小时以内 新手友好度 88/100
维护者通常 1 天内回复
-
clawsweeper:fix-shape-clear clawsweeper:queueable-fix clawsweeper:source-repro impact:other issue-rating: 🦞 diamond lobster no-stale P2
难度 2/5 1-3 小时 新手友好度 75/100
openclaw/crabbox#3060 · 1 条评论 · 1 个 reaction ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 74/100
radius-project/radius#13698 · 1 条评论 ·
维护者通常 1 天内回复