Codex, but with partial compaction.
The product shape is Codex on both sides: Codex TUI is the frontend, Codex app-server/model path is the backend, and pcodx sits between them to do partial compaction.
The wrapper changes context only in two cases:
- append a minimal turn id after a completed turn
- replace a compacted range with the summary supplied by the agent
The Rust proxy now owns the live future-turn boundary. It ingests completed native Codex turns into the durable ledger. After a successful partial_compact, it maps the next frontend turn to a fresh ephemeral app-server thread and injects only the compacted ledger render before forwarding the turn. Exit and codex resume --last reconstruct the same compacted future context from the PCODX database.
This is fresh-thread replacement, not an in-place rewrite of the stock Codex thread. The original native transcript and TUI history remain unchanged, and PCODX does not claim KV-cache preservation across replacement. See docs/model-context.md and docs/live-proof.md.
pcodx init --session work
pcodx turn --session work --text "first human prompt"
pcodx turn --session work --text-file prompt.md
pcodx record --session work --role assistant --text "large stale discovery"
pcodx compact --session work --from msg2 --to msg2 --summary "discovery was stale; durable fact ..."
pcodx compact-many --session work --range "msg1..msg1=old setup" --range "msg4..msg5=old tool output"
pcodx current-session-message-ids --session work
pcodx resume --session work
pcodx resume --last --text "continue from the compacted future context"
pcodx interactive --session work
pcodx-model-turn --session work --text "continue from compacted context"resume renders stored compacted context, so it is not an empty session. The storage detail is an implementation detail of the prototype, not the product framing.
pcodx-model-turn appends safe token and context-window metadata to a durable JSONL usage log. See docs/observability.md for its location, format, privacy boundary, and limits.
Commands:
init: create or refresh a pcodx wrapper sessionturn: record an exact human prompt and render future contextrecord: record a system, developer, assistant, tool, or user entrycompact: replace a visiblemsg...orcmp...range with a summarycompact-many: atomically replace multiple disjoint visible ranges with summariesids: list visible range endpointscurrent-session-message-ids: print the shared agent helper text for endpoint selectionshow: render current future contextresume: reopen an existing session in the local CLI and optionally append a human prompt firstinteractive: open a Codex-like line interface; plain text records a user turn, and slash commands include/record,/compact,/ids,/show,/current-session-message-ids, and/exitprompts: list or print shared prompt fragmentsserve: run a Codex TUI to Codex app-server proxy;--enable-pcodx-toolsenables live turn ingestion, partial-compaction tools, and fresh-thread compacted-context routing
--text is one exact CLI string. --text-file PATH reads exact text from a file, and --text-file - reads stdin where the command does not own stdin for its local loop. This avoids joining separate argv words, which can alter whitespace.
For pcodx interactive, the optional initial prompt supports --text or --text-file PATH; it rejects --text-file - because stdin is reserved for the interactive command loop.
In this CLI, input means the bytes pcodx records for one turn. For turn and resume --text..., that input is a human prompt. For record, it can be a system, developer, user, assistant, or tool message.
rendered context means the future Codex context after applying compactions. Preserved turns are printed verbatim with an appended id marker like <aboveturn id="msg1"/>; compacted ranges are printed as summaries with ids like <aboveturn id="cmp1"/>.
KV-cache reuse means reusing a model server's cached computation for an unchanged prefix of a conversation. PCODX preserves the active upstream thread between ordinary turns. A compaction deliberately starts a fresh upstream thread, so native active-thread KV state is not retained; any provider-reported cached input on that turn is provider-side prefix caching only.
dynamic tools means tools registered with a future app-server session at runtime, such as partial-compaction tools the model could call. It does not mean redefining slash commands in this CLI prototype.
pcodx interactive is the local Codex-like CLI path for this prototype. It uses the same durable store and validation as record, compact, and show, so it can perform partial compaction without live websocket fixture capture. pcodx resume requires exactly one selector: --session NAME or --last. It resolves an existing wrapper session, prints its compacted render, and enters the same loop; it never creates a session. Resume uses the session's stored absolute working directory unless global --cwd DIR explicitly overrides it. A legacy relative stored directory is rejected until --cwd is supplied. A legacy timestamp tie with no stored write order is rejected for --last; choose --session and make a new write before using --last. It is intentionally a local command loop, not a replacement for the real Codex TUI proxy.
When scripting resume, pipe /exit after the prompt sequence because the resumed local loop owns stdin.
Run the Codex-like partial-compaction demo in tmux:
scripts/pcodx_codex_like_demo.sh
tmux attach -t pcodx-codex-like-demoThe pane opens pcodx interactive, reads three files through that local frontend, compacts selected file reads, exits, and resumes. It remains a storage/rendering demonstration; use the real Codex middleware demo for answering-model evidence.
Run the real Codex middleware path in tmux:
scripts/pcodx_real_codex_proxy_demo.sh
tmux attach -t pcodx-real-codex-proxy-demoThe left pane is the installed pcodx serve with PCODX dynamic tools enabled against an isolated demo database. The right pane is real Codex TUI connected through --remote ws://127.0.0.1:48570. Completed native turns are ingested automatically. After /exit, the script runs codex resume --last through the same proxy; the first resumed model turn is routed through a fresh ephemeral upstream thread seeded from the durable compacted ledger.
Codex CLI 0.147.0's product-catalog decoder can log failed to decode models response: missing field models when an OpenAI-compatible provider returns {"object":"list","data":[...]}. The same warning appears without pcodx, so it is not a proxy decode failure. See docs/observability.md for the verified contracts.
Build/install with:
git submodule update --init --recursive
cargo install --path . --locked --root ~/.localThis installs pcodx to ~/.local/bin if that directory is on PATH.
Default prototype database: $PCODX_DB, else $XDG_DATA_HOME/pcodx/pcodx.sqlite3, else ~/.local/share/pcodx/pcodx.sqlite3.
Tables:
sessions: session id, working directory, update time, upstream Codex id placeholder, andkv_cache_boundarymessages: full message text, role, stablemsg1ids, source, and a human-prompt flagcompactions: stablecmp1ids, covered message range, summary, and replacement count
Human prompts are stored exactly as supplied. Compaction allows any range, including user prompts that contain bulky logs. If the range includes system, developer, or user messages, pcodx compact prints a warning that the summary must preserve active instructions and human intent.
Validation rejects ranges that split an assistant/tool pair. If a range ends on the assistant turn immediately before a tool result, the error says to extend to the tool turn. If a range starts on that tool result, the error says to include the assistant turn or start after the tool turn. This ports the OpenCode POC's tool-use/tool-result boundary rule into the Rust turn model.
src/tool_endpoint.rs contains the Codex-facing tool shape over the same storage core:
partial_compact_json(store, session_id, args_json, config)parses OpenCode-styleranges, rejects cross-session selectors, truncates long summaries, callscompact_ranges, and returns an OpenCode-style JSON resultcurrent_session_message_ids_tool(store, session_id)returns the shared current-session ID helper text for endpoint selection
The endpoint layer does not rewrite stored history. It records only compaction summaries and leaves original messages available for history/recovery.
With pcodx serve --enable-pcodx-tools:
- the proxy opts into Codex's experimental app-server API during
initializeand adds PCODXdynamicToolsonly tothread/start - each completed ordinary turn is stored as a user row followed by one assistant row; that assistant row atomically keeps the final response with any native-tool transcript and PCODX receipts, labeling tool data as untrusted rather than promoting it to user authority
- a successful
partial_compactinvalidates every mapped upstream thread - the next
turn/startfirst creates a fresh ephemeral upstreamthread/start, then appends one compacted-ledger item withthread/inject_items, then forwards the turn to that fresh thread - a frontend
thread/resumeorthread/forkis mapped for UI continuity, but its next answering turn is likewise replaced with a fresh ephemeral upstream thread so dynamic tools and the durable ledger remain authoritative - non-PCODX messages continue through the native websocket boundary, and one
serveprocess remains bound to one PCODX session
thread/inject_items is append-only. It removes nothing from an existing thread; PCODX achieves reduction by injecting into a new empty thread. The original native rollout remains available to Codex history UI and may still display compacted-away text after resume. The answering model does not receive that rollout on the replaced turn.
Set PCODX_LIVE_CONTEXT_DIR only for a controlled audit. Each replacement then writes the exact injected item array to a collision-safe, owner-only create-new file and prints its path. The directory must also be owner-only. Those files contain conversation content. Without that environment variable, serve prints only aggregate per-turn counts. PCODX_WS_FIXTURE_DIR remains available for inbound websocket protocol fixtures.
vendor/agent_partial_compact_common is a Git submodule containing shared partial-compaction prompt fragments. The test suite checks embedded prompt bytes against that submodule.
- in-place selective replacement of a stock Codex active thread, which Codex app-server does not expose
- KV-cache preservation across the required fresh-thread boundary
- reconstructing PCODX compacted state from an arbitrary native Codex thread without its PCODX database
- multi-session routing inside one
pcodx serveprocess
Historical JSON migration is a non-goal. Those files were evidence for earlier experiments; this prototype starts with a clean durable history.