Proposal: Expose a Plugin Extension Point for Custom Compaction

Open 💬 7 comments Opened May 20, 2026 by qWaitCrypto

Summary

While looking into Codex's compaction flow, I noticed that Codex already has internal support for replacing model-visible history during compaction.

From my understanding, ContextManager owns the ordered conversation history and exposes prompt-facing history through for_prompt(). The existing compact flow can also build a replacement_history and apply it through Codex's internal compact replacement path.

However, this capability currently appears to be internal to Codex's built-in compaction flow. Plugins and hooks can observe lifecycle events and add additional model-visible context, but they cannot provide their own compacted replacement.

I think exposing a small, constrained extension point here could make Codex's plugin system more powerful and unlock interesting community experimentation around compaction behavior.

Motivation

Codex already has a plugin and hook system that allows external code to extend parts of the agent lifecycle. This is useful for adding context, enforcing policies, reacting to tool usage, and integrating project-specific workflows.

Compaction seems like another place where extensibility could be valuable.

Different users, projects, and organizations may want different compaction behavior. Some may want compact summaries optimized for debugging continuity. Others may want summaries that preserve architectural decisions, active TODOs, file-level changes, test results, or project-specific constraints. Some plugins may want to experiment with cheaper local models, rule-based summarizers, domain-specific compactors, or other strategies that Codex itself does not need to own directly.

Today, a plugin can append additional context, but it cannot participate in the actual replacement of model-visible history during compaction. That limits what plugin authors can do. A plugin can say "please also consider this summary", but it cannot provide a real compacted replacement through the same mechanism Codex already uses internally.

If Codex exposed a safe plugin extension point for custom compaction, the core system could keep control over validation and history integrity while allowing the community to explore more specialized compaction strategies.

Proposal

Would the Codex team consider exposing a plugin extension point for custom compaction?

The high-level idea is:

  • keep the existing built-in compact behavior as the default;
  • allow a trusted plugin or hook to optionally provide a compacted replacement during a compact operation;
  • have Codex core validate and apply the replacement through the existing compaction replacement path;
  • keep normal hooks additive-only, so history replacement remains limited to explicit compaction semantics.

This could be implemented either by extending PreCompact or by introducing a dedicated custom compaction hook/provider.

The important part is that plugins would not directly mutate ContextManager. Instead, they would return a constrained request or compact result, and Codex core would remain responsible for applying it safely.

A possible high-level shape could be:

{
  "operation": "replace_range",
  "startItemId": "...",
  "endItemId": "...",
  "summary": "Compact representation for the replaced span."
}

Codex core would still be responsible for:

  • validating the selected range;
  • preserving tool call / tool result invariants;
  • constructing valid model-visible history items;
  • normalizing the resulting history;
  • deciding whether the custom replacement is safe to apply;
  • falling back to the current compact path if the plugin output is invalid or absent.

Why this might be useful

This would allow plugins to experiment with new compaction strategies without requiring Codex itself to own every strategy.

Possible examples include:

  • project-specific compaction policies;
  • different summary formats for different workflows;
  • compactors optimized for debugging sessions;
  • compactors optimized for code review sessions;
  • compactors that preserve test status and file-change summaries;
  • compactors that use small local or cheaper background models;
  • compactors that integrate with external project memory or issue-tracking systems.

The main benefit is extensibility: Codex keeps the safe core history machinery, while plugins can provide custom compaction policy.

This could allow the ecosystem to discover useful patterns organically. Some of those patterns may eventually inform future built-in Codex behavior, while others can remain as specialized plugins.

Compatibility

This should not change default behavior.

If no plugin provides a custom replacement, Codex would continue using the existing compaction implementation.

If a plugin does provide one, it should be treated as an optional experimental extension, ideally behind the existing plugin/hook trust model or a feature flag.

Possible API Shape

This is only a sketch, not a final API proposal.

A PreCompact hook or dedicated custom compaction hook could return something like:

{
  "hookSpecificOutput": {
    "hookEventName": "PreCompact",
    "decision": "replace",
    "operations": [
      {
        "type": "replace_range",
        "startItemId": "item-start",
        "endItemId": "item-end",
        "summary": "Summary to insert for the replaced span."
      }
    ]
  }
}

Codex core would then:

  1. Validate that startItemId and endItemId exist.
  2. Validate that the selected range can be safely replaced.
  3. Ensure tool call / tool output pairs are not broken.
  4. Construct a valid replacement history item.
  5. Apply the result through the same internal history replacement path used by compact.
  6. Record the compaction result for debugging and auditability.

Non-goals

This proposal is not asking for a general beforeModelCall(messages) -> messages hook.

A fully general prompt-rewrite hook would be powerful, but it would also make prompt construction harder to reason about and harder to debug.

The suggested scope is intentionally narrower: allow custom replacement only during explicit compaction semantics, where history rewriting is already expected.

This proposal is also not asking plugins to directly mutate ContextManager. Codex core should remain responsible for validating and applying all history changes.

Willingness to contribute

If this direction fits Codex's design goals, I would be happy to work on a focused PR for the extension point and tests.

I wanted to open the discussion first because this touches compaction behavior and plugin boundaries, and I understand that the team may have specific design constraints here.

View original on GitHub ↗

7 Comments

Keesan12 · 2 months ago

A custom compaction hook sounds useful, but I would be careful to make the output contract auditable rather than only flexible.

The risky version is "anything can rewrite context however it wants." The safer version is more like:

  • explicit input slice
  • explicit output budget / format
  • provenance on what was dropped or merged
  • a receipt the runtime can attach to the run when compaction changes the available evidence

That keeps compaction from becoming a silent source of task drift.

Keesan12 · 2 months ago

Interesting direction. If custom compactors are allowed, I'd keep the plugin output split into two artifacts: the semantic summary the model will see, and a machine-readable run receipt the model never sees (open tasks, failed verifications, pending approvals, stop reason, active branch/worktree).

Most compaction regressions I've seen come from preserving prose but dropping control-state, so keeping that boundary explicit would make experimentation a lot safer.

qWaitCrypto · 2 months ago
Interesting direction. If custom compactors are allowed, I'd keep the plugin output split into two artifacts: the semantic summary the model will see, and a machine-readable run receipt the model never sees (open tasks, failed verifications, pending approvals, stop reason, active branch/worktree). Most compaction regressions I've seen come from preserving prose but dropping control-state, so keeping that boundary explicit would make experimentation a lot safer.

Good point. I agree that the output contract should probably distinguish between model-visible summary and machine-readable execution/control state.

My main concern is also that custom compaction should not become an opaque rewrite layer. A structured receipt for things like open tasks, failed checks, pending approvals, stop reason, and active branch/worktree would make the result much more auditable and safer to experiment with.

This could also give Codex core a stable surface to validate or display what changed during compaction, instead of treating the plugin output as just another summary string.

qWaitCrypto · 2 months ago

Thanks, this discussion helped clarify the shape of a safer implementation.

I think the proposal can be narrowed to a concrete contract:

A plugin/hook should be able to propose a custom compaction replacement candidate, but Codex core should remain the component that validates, normalizes, applies, or rejects that candidate.

So the plugin would not directly mutate session history. It would only return a structured candidate for Codex core to consider.

A possible output contract could split the result into two artifacts:

  1. model_visible_summary

The compacted content that may become part of the model-visible replacement history.

  1. runtime_receipt

A machine-readable receipt that records what the compaction did, so the replacement is auditable instead of being an opaque rewrite.

For example, the receipt could include fields such as:

  • input_item_range
  • output_budget
  • carried_forward_tasks
  • failed_or_pending_verifications
  • pending_approvals_or_ask_user_states
  • stop_or_blocked_reason
  • active_branch
  • active_worktree
  • execution_surface
  • dropped_or_merged_item_ranges

These fields are just examples, but the important property is that Codex can inspect what was preserved, dropped, or merged before accepting the candidate.

The application boundary would be:

  • plugin/hook proposes a CompactionCandidate
  • Codex core validates the candidate shape
  • Codex core preserves tool-call / tool-result invariants
  • Codex core constructs the final replacement history
  • Codex core emits or stores the receipt for debugging/auditing
  • Codex core falls back to the built-in compaction path if the candidate is invalid

This keeps the extension point scoped to compaction, without exposing a general arbitrary prompt/history rewrite hook.

From a quick read of the current code, the gap seems relatively focused:

  • run_pre_compact_hooks can currently continue or stop compaction.
  • compact.rs later constructs replacement history and installs it through the existing compacted-history replacement path.
  • So a minimal implementation could extend the compact hook/provider outcome from continue/stop to continue/stop/propose candidate, then validate the candidate before using the existing replacement path.

I’m going to prepare a focused prototype branch for this contract and post it here with a compare link, scope notes, and targeted test results.

Prototype scope:

  • add typed CompactionCandidate / CompactionReceipt shapes
  • extend the compact hook/provider outcome to optionally return a replacement candidate
  • validate the candidate in core before applying replacement
  • keep the built-in compact path as the default and fallback
  • add regression coverage for:
  • a plugin-proposed candidate can replace compacted history
  • an invalid candidate falls back to built-in compaction
  • required receipt fields are enforced
  • tool-call/tool-result ordering is not broken
  • dropped/merged ranges are recorded
  • default behavior is unchanged when no plugin candidate is returned

I’m going to try implementing the minimal version of this contract in a focused prototype branch: candidate shape, core-side validation, fallback behavior, and targeted regression tests.
I’ll post the branch here with a compare link, scope notes, and test results. I won’t open an upstream PR without invitation per the contribution policy. Once the prototype is ready, maintainers can either invite a focused PR or adapt/cherry-pick the approach internally.

Keesan12 · 2 months ago

If Codex does expose a compaction extension point, one extra safety rail I would want is a core-generated diff/receipt even when the plugin candidate is accepted. Not just the candidate payload itself, but a normalized record from core that says: input range, carried-forward tasks, pending approvals, dropped/merged ranges, and exact rejection reason if the candidate was partially ignored. That gives operators one auditable artifact regardless of which compactor produced the proposal.

qWaitCrypto · 1 month ago
If Codex does expose a compaction extension point, one extra safety rail I would want is a core-generated diff/receipt even when the plugin candidate is accepted. Not just the candidate payload itself, but a normalized record from core that says: input range, carried-forward tasks, pending approvals, dropped/merged ranges, and exact rejection reason if the candidate was partially ignored. That gives operators one auditable artifact regardless of which compactor produced the proposal.

absolutely, but I doubt the Codex team will see our discussion. 😂

robert-claypool · 4 days ago

I re-audited the current compaction paths on openai/codex main at 03bb3b12367397e14a8facc2e018d645ff4d8e83. One implementation asymmetry seems important to settle before prototyping this extension:

  • Local compaction constructs plaintext summary/replacement history before replace_compacted_history.
  • Remote v1 receives replacement ResponseItems, while remote v2 appends an opaque ResponseItem::Compaction { encrypted_content } after retaining a bounded visible prefix. A plugin cannot safely inspect or rewrite semantic bytes it cannot see.
  • PreCompact runs before candidate/provider work. PostCompact runs after history installation. There is currently no common pre-install inspect-or-replace gate. A post hook cannot roll back the installed history.

Would the team accept a narrower, path-aware pre-install contract along these lines?

  1. For a local/plaintext candidate, a trusted extension may inspect metadata and propose a structured replacement; Codex core validates and installs it.
  2. For an opaque remote candidate, the extension receives bounded metadata only and can accept or reject. It never receives or fabricates encrypted_content.
  3. With no configured extension, behavior is byte-for-byte the stock path.
  4. In a configured fail-closed mode, timeout, malformed output, rejection, or validation failure leaves the old history installed atomically. A fallback to built-in compaction occurs only when policy explicitly selects fallback.
  5. Codex core emits a body-safe receipt containing path kind, trigger, bounded item counts/digests, extension decision, reason code, and resulting compaction item/window correlation. It excludes prompts, private reasoning, and encrypted content.

The regression matrix I would expect is manual and automatic compaction, mid-turn compaction, local/remote v1/remote v2, hook ordering, unchanged history on failed candidate, and duplicate/retry behavior. This also complements #28633: observability proves which gate ran; this seam determines whether a candidate can be inspected or replaced before mutation.

Would maintainers prefer this as an extension of PreCompact, a dedicated compaction-candidate provider, or another shape? I will not open an implementation PR without invitation under the contribution policy.