SichangHe/partial_compact_codex

★ 0Forks 0RustGitHub ↗Compare

README

pcodx

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.

cli

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 session
  • turn: record an exact human prompt and render future context
  • record: record a system, developer, assistant, tool, or user entry
  • compact: replace a visible msg... or cmp... range with a summary
  • compact-many: atomically replace multiple disjoint visible ranges with summaries
  • ids: list visible range endpoints
  • current-session-message-ids: print the shared agent helper text for endpoint selection
  • show: render current future context
  • resume: reopen an existing session in the local CLI and optionally append a human prompt first
  • interactive: 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 /exit
  • prompts: list or print shared prompt fragments
  • serve: run a Codex TUI to Codex app-server proxy; --enable-pcodx-tools enables 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.

demo

Run the Codex-like partial-compaction demo in tmux:

scripts/pcodx_codex_like_demo.sh
tmux attach -t pcodx-codex-like-demo

The 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-demo

The 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.

install

Build/install with:

git submodule update --init --recursive
cargo install --path . --locked --root ~/.local

This installs pcodx to ~/.local/bin if that directory is on PATH.

storage

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, and kv_cache_boundary
  • messages: full message text, role, stable msg1 ids, source, and a human-prompt flag
  • compactions: stable cmp1 ids, 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.

tool endpoint shape

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-style ranges, rejects cross-session selectors, truncates long summaries, calls compact_ranges, and returns an OpenCode-style JSON result
  • current_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.

live context boundary

With pcodx serve --enable-pcodx-tools:

  • the proxy opts into Codex's experimental app-server API during initialize and adds PCODX dynamicTools only to thread/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_compact invalidates every mapped upstream thread
  • the next turn/start first creates a fresh ephemeral upstream thread/start, then appends one compacted-ledger item with thread/inject_items, then forwards the turn to that fresh thread
  • a frontend thread/resume or thread/fork is 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 serve process 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.

prompt source

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.

deferred

  • 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 serve process

Historical JSON migration is a non-goal. Those files were evidence for earlier experiments; this prototype starts with a clean durable history.

Contributors

SichangHe

Issues