Design: thread context pins for preserving important context across compaction
Problem
Long-running Codex sessions can lose user-important continuity after /compact or automatic compaction. Several existing reports describe the same underlying need from different angles:
- #26321 asks for
/compacthints so users can say which context should be retained. - #25083 asks for "Do Not Compact" areas.
- #25618 asks for configurable compaction modes with user-selected retention priorities.
- #23263 describes a PDF-heavy Codex App thread where transient document content overwhelmed the important long-term workflow instructions.
- #16839 describes compaction-driven re-read loops for context that remains important across compaction cycles.
- #14347 asks for better cumulative retention across repeated compactions.
- #5957 describes auto-compaction causing Codex to forget it was mid-task and had already edited files.
These are not all the same feature request, but they point to a common missing primitive: users need a small, explicit, thread-scoped way to mark text that should remain available to future turns even after compaction.
Proposal
Add thread context pins: bounded text snippets attached to a materialized thread and supplied to future turns as user-provided context.
The primitive should be deliberately narrow:
- persisted per thread, not global;
- text-only in the first version;
- bounded by count and bytes;
- injected as untrusted user-provided context, not as system/developer instructions;
- not an approval mechanism and not a way to bypass policy;
- available after compaction because active pins are re-supplied on future turns.
This complements compaction improvements rather than replacing them. /compact hints, do-not-compact blocks, pinned files, and UI affordances could build on this later, but the first mergeable piece can be the storage/API/runtime primitive.
Suggested first PR
Current implementation: compare main...pdurlej:feature/thread-context-pins-refresh-v2.
A first PR can stay server-side and avoid UI scope:
- add sqlite persistence for thread-scoped context pins;
- add app-server JSON-RPC CRUD methods;
- enforce conservative limits and reject oversize input instead of truncating stored text;
- reject ephemeral threads;
- inject active pins into
turn/startandturn/steeras untrusted additional context; - ensure unchanged pins are re-emitted after compaction clears the additional-context baseline;
- update generated app-server schemas and README docs.
This gives app clients a concrete primitive to experiment with while keeping the initial surface small enough to review.
Implementation-validated constraints
A working server-side implementation and focused tests surfaced several details that are easy to miss in a design-only version:
- Creating a pin before the first turn requires materializing and flushing the rollout before state reconciliation, so the thread row exists for the pin's foreign key.
- Strict thread deletion must explicitly remove context pins alongside the other thread-scoped state.
- Updating a pin must preserve
createdAt, because list ordering uses creation time withpinIdas a deterministic tie-breaker. - Stored text can retain the conservative byte limit, while model injection should use the existing user-context rendering bound (currently roughly 1000 tokens per pin).
- Pin CRUD must preserve archived thread state.
- Per-thread exclusive request serialization makes count and aggregate-size validation deterministic within one app-server process.
The focused test surface now covers CRUD, archived-thread preservation, reinjection after compaction, deletion cleanup, count and byte boundaries, update replacement accounting, and pagination order/cursors.
One bounded follow-up remains: repeated resume-with-turn cycles can temporarily accumulate duplicate rendered pin fragments in history until compaction. Avoiding that correctly requires history-level deduplication; simply suppressing persistence or reinjection makes pins disappear after the first turn. That broader history contract does not need to expand the initial storage/API primitive.
Non-goals for the first PR
- App UI for selecting messages or pinning text.
- Pinned files or attachments.
- Compact-summary editing or preview.
- Treating pins as trusted instructions.
- Notifications for every pin change.
6 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Implementation branch / compare link:
https://github.com/openai/codex/compare/main...pdurlej:feature/thread-context-pins
This branch implements the server-side first slice described above: sqlite persistence, app-server CRUD methods, bounded text-only pins, untrusted additional-context injection for future turns, compaction re-emission behavior, generated schemas, and README docs.
I attempted to open it as a draft PR from the fork, but GitHub rejected
CreatePullRequestfor the current account/integration. Leaving the compare link here so the implementation can still be inspected alongside the design discussion.Thanks. I agree these are closely related, but I don't think this is a direct duplicate.
#26321 proposes a
/compacthint for one compaction pass.#25618 proposes configurable
/compactmodes.This issue is trying to define a smaller underlying primitive: bounded thread-scoped text pins that are persisted and re-supplied as untrusted user context on future turns, including after compaction.
That primitive could support the workflows described in #26321 and #25618, but it does not replace those feature requests. UI affordances like
/compacthints, retention modes, or "do not compact" blocks could be layered on top later.Refreshed implementation branch:
https://github.com/openai/codex/compare/main...pdurlej:feature/thread-context-pins-refresh
I rebased the implementation onto current
main(0ffcefaf3d) via a fresh branch, resolved the app-serverturn_processor.rsconflict, and re-ran the focused checks:cargo fmtcargo test -p codex-app-server-protocol client_request_serialization_scope_covers_keyed_familiescargo test -p codex-app-server-protocol schema_fixturescargo test -p codex-state create_list_update_and_delete_thread_context_pincargo test -p codex-core replace_history_with_reference_context_item_reinjects_additional_context_after_compactioncargo test -p codex-app-server thread_context_pinscargo check -p codex-app-servergit diff --check origin/main...HEADI also tried opening this as a draft PR from the fork again, but GitHub still rejects the request with
CreatePullRequestpermissions for this account/integration. Leaving the refreshed compare link here so the current branch can still be reviewed against latestmain.Quick update: I refreshed the implementation branch onto current
main(b668abb488).Conflicts resolved during the refresh:
thread/deletewiring and the newthread/contextPins/*processor wiring;message_processorstate DB ownership after newer upstream app-server processors also needed the state DB handle;0036to0040, after upstream's current migration sequence through0039_threads_recency_at.Validation:
codex-app-server-protocoltests pass, including generated schema fixture checks.codex-statetests pass, including the context pins runtime test and upstream migration compatibility tests.I still think this primitive is useful beyond the initial "remember this after compaction" case. As goal/loop-style long-running workflows become more important, users and agents need a bounded, explicit way to control durable context without rewriting conversation history. Thread context pins give the app/client a small API surface that could support UI affordances or higher-level retention policies later.
Related community requests this builds on: #26321 and #25618. Their duplicate chains also point at related context-retention/compaction requests such as #25083, #25394, #24779, and #25563.
Closing this as not planned. The implementation branch remains available as a reference.