Skip to content

[pi-plugin] expose ctx-flush and ctx-wrapup triggers + results via the pi.events bus. #679

Closed

Description

@Shrub24

Short description

Expose the /ctx-flush and /ctx-wrapup actions on the Pi event bus so other extensions can request them and read a structured result.

What problem does this solve?

  • Both ctx-flush and ctx-wrapup actions are only reachable as slash commands. An extension cannot trigger either, and cannot tell whether a request was applied, refused (compaction-off, nothing to wrap up) or failed without parsing chat output.
  • The right moment or trigger is often known outside Magic Context: a task or workflow boundary, a handoff, an idle or scheduled window, a session being parked, a provider or model switch, an orchestrator choice. Concretely, as an example, any cache bust presents an opportunity to flush - and this allows more complex cache aware behaviour (variable thinking/tool change behaviour, dynamic costs/TTL) without embedding a cache tracker.
  • Virtual models make this concrete. Pi's registerVirtualModel hides the physical model from extensions (earendil-works/pi#10318), so Magic Context's per-model cache assumptions (cache_ttl, thresholds) resolve against the virtual model, and a physical switch that invalidates the cache is not observable. Only the registrar knows when it happens.
  • Pi's own cache warming hits the same virtual/physical mismatch (earendil-works/pi#10536).

Proposed solution

Listen on pi.events for magic-context:flush and magic-context:wrapup, run the same code as the commands, and answer on magic-context:flush-result and magic-context:wrapup-result.

  • Request: optional { sessionId?, messagesToKeep? }. No sessionId means the active session; messagesToKeep follows the command's rule (positive integer, default 20); anything else is rejected.
  • Result: { sessionId?, status: "success" | "skipped" | "failed", reason? } plus pendingOps (flush) or messagesWrapped and compartmentsWritten (wrapup). reason is machine-readable (compaction-off, no-live-context, nothing-to-wrapup, invalid-request, ...).
  • Only the session with a live Pi context can be targeted; any other session is refused with no-live-context.
  • The command bodies move into shared functions, so the slash commands behave as before. Nothing runs unless a request arrives, subagents register no listeners, and there is no schema, migration or cache-stability change.

Harness coverage: Pi and OMP (shared pi-plugin). OpenCode 1, OpenCode 2 and the Rust module are not applicable: they have no in-process event bus.

Alternatives considered

  • Resolve per-model settings against the physical model inside Magic Context. Pi does not expose it where Magic Context runs (#10318, open), and even once it does, which identity governs TTL and thresholds, and whether retries, failovers or sticky routes count as one cache, is the registrar's routing policy rather than Magic Context's.
  • pi.sendUserMessage("/ctx-flush", { expandPromptTemplates: true }). It returns void, so there is no outcome, it triggers a turn, and it cannot name a session.
  • Magic Context inferring the moment itself (for example flushing on every model change). It cannot know whether the caller treats that as a boundary, and it would change default behaviour and risks exceeding scope of this plugin.

Area

Pi plugin

Additional context

  • Implementation with tests exists and will be opened as a draft PR once this is design-approved. Has been tested live with virtual model router triggered compaction.
  • Files: packages/pi-plugin/src/ctx-lifecycle-events.ts, commands/ctx-flush.ts, commands/ctx-wrapup.ts; docs in docs/architecture/pi.md and PARITY.md.
  • Open questions: should the event names and reason strings be a documented public contract, and should results echo an optional caller-supplied requestId so concurrent requests for one session can be told apart?

Activity

  1. Shrub24 commented on Oct 14, 2026

    @Shrub24
    Author

    Ah, my mistake - I missed that Magic Context already observes the physical model through assistant metadata. That exposes a deeper issue:
    The next context pass compares that physical identity with the selected virtual route, incorrectly detects a model switch, and clears reasoning-replay state. Previously removed thinking then returns and causes another cache rebuild, even though the physical model hasn’t changed.

    I’ll close this issue and open a separate one for that bug first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature requestNew feature or capability request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions