When building an OMP extension that needs to filter or rewrite conversation history before it reaches a provider (e.g. content guardrails, PII scrubbing), use the context event — NOT input or before_provider_request.
Key behaviors discovered from source:
context event receives a structuredClone() deep copy of the full AgentMessage[] array. Returning { messages: modifiedArray } replaces what the LLM sees without touching persisted session history. This is the only event that gives read-write access to the full conversation context.
input event fires before user text becomes a message — it can rewrite user input but cannot see or modify past messages. Use it for input-time filtering only.
before_provider_request gives the raw provider payload (already converted from AgentMessage to provider-specific format) — too late for semantic message-level filtering.
Extensions cannot delete persisted session entries. sessionManager is read-only from ExtensionContext. Agent.replaceMessages() and SessionManager.rewriteEntries() exist in core but are not exposed to extensions. The context event's copy-on-write design is intentional: the local session file is never sent to the provider, so scrubbing the LLM-bound copy is sufficient.
ctx.model in event handlers is a lazy getter that reflects /model switches without needing a separate listener. Gate on it to make extensions model-specific (e.g. ctx.model?.id?.includes('fable')).
For assistant messages, content is an array of typed blocks (TextContent, ThinkingContent, RedactedThinkingContent, ToolCall). Only text and thinking blocks have scannable text — RedactedThinkingContent is opaque. User messages have content: string | (TextContent | ImageContent)[] — handle both shapes.