Intercepting AI Events Using Hooks
The Oxygen AI Positron exposes a hook system that lets developers intercept events fired during the AI lifecycle. A hook handler can observe fire-and-forget events for auditing or analytics, or veto blocking events to prevent an action from running.
Typical use cases include:
- Auditing every AI action, tool call, or shell command to an external log or compliance system.
- Gating risky operations (for example, blocking shell commands that modify production files).
- Controlling sub-agent execution to enforce organizational policies on autonomous AI tasks.
- Collecting token usage metrics after each assistant turn.
Implementing a Hook Handler
- Add
oxygen-ai-positron-apias a provided dependency in your plugin project's pom.xml:<dependency> <groupId>com.oxygenxml</groupId> <artifactId>oxygen-ai-positron-api</artifactId> <version>8.4.0</version> <scope>compile</scope> </dependency>The artifact is published at https://oxygenxml.com/maven/. Add the repository to your pom.xml (if it is not already present):<repository> <id>oxygen-public-repo</id> <url>https://www.oxygenxml.com/maven/</url> </repository> - Implement the
com.oxygenxml.positron.api.hook.AIHooksHandlerinterface in your plugin's libraries. The single methodeventOccurred(String eventName, String payloadJson)is called for every hook event.eventNameis the string identifier of the event (for example,beforeShellExecution);payloadJsonis the JSON serialization (snake_case keys) of the event-specific payload, which you can deserialize into the matchingBaseHookPayloadsubclass using Jackson to access its fields. Throw anAIHookVetoExceptionfrom this method to veto a blocking event and prevent the associated action from running.package my.pack; import java.util.List; import com.fasterxml.jackson.databind.ObjectMapper; import com.oxygenxml.positron.api.hook.AIHookEvent; import com.oxygenxml.positron.api.hook.AIHookVetoException; import com.oxygenxml.positron.api.hook.AIHooksHandler; import com.oxygenxml.positron.api.hook.payload.BeforeShellExecutionPayload; /** * Hook handler that blocks shell commands considered dangerous for * a documentation project (destructive file operations, package * installations, and network-fetching utilities). */ public class ShellGuardHooksHandler implements AIHooksHandler { private static final ObjectMapper MAPPER = new ObjectMapper(); private static final List<String> BLOCKED_PATTERNS = List.of( "rm ", "rmdir", "del ", "rd ", "npm install", "pip install", "mvn install", "curl", "wget" ); @Override public void eventOccurred(String eventName, String payloadJson) throws AIHookVetoException { if (AIHookEvent.BEFORE_SHELL_EXECUTION == AIHookEvent.fromEventName(eventName)) { try { BeforeShellExecutionPayload data = MAPPER.readValue(payloadJson, BeforeShellExecutionPayload.class); blockDangerousCommands(data); } catch (Exception e) { // log and continue — deserialization failures must not block the action } } } private void blockDangerousCommands(BeforeShellExecutionPayload data) throws AIHookVetoException { String command = data.getCommand(); if (command == null) { return; } String lowerCommand = command.toLowerCase(); for (String pattern : BLOCKED_PATTERNS) { if (lowerCommand.contains(pattern)) { throw new AIHookVetoException( "Shell command blocked by policy: '" + command + "'. " + "Matched restricted pattern: '" + pattern + "'." ); } } } } - In the plugin descriptor file, declare the extension point
that points to your implementation:
<extension type="AIHooksHandler" class="my.pack.ShellGuardHooksHandler"/>
A ready-to-use implementation is available via the Oxygen AI Positron Hooks add-on. It implements this extension point and dispatches hook events to external scripts configured in a JSON file, without requiring any Java code.
You can use the sample plugin available on GitHub as a reference when building your own hook handler: https://github.com/oxygenxml/oxygen-ai-positron-hooks-handler-sample-plugin.
Hook Events Overview
The following tables list all hook events grouped by category.
Blocking events can be vetoed by throwing an AIHookVetoException;
fire-and-forget events are informational only.
Session
| Event | Type | Description |
|---|---|---|
sessionStart |
Fire-and-forget | Fired when a new AI session is created. |
sessionEnd |
Fire-and-forget | Fired when an AI session ends. |
sessionReload |
Fire-and-forget | Fired when the user restores a previous conversation from chat history. |
Actions and prompts
| Event | Type | Description |
|---|---|---|
beforeStartAction |
Blocking | Fired before an AI action is launched. Veto to prevent it from running. |
beforeSubmitPrompt |
Blocking | Fired before a user prompt is submitted to the AI model from the chat UI. |
afterAgentThought |
Fire-and-forget | Fired after the agent completes a thinking block (extended thinking mode). |
afterAgentResponse |
Fire-and-forget | Fired after the agent completes an assistant turn. |
agentFinish |
Fire-and-forget | Fired when an AI agent interaction finishes (completed, aborted, or error). |
subagentStart |
Blocking | Fired before a sub-agent is started. Veto to prevent it from being created. |
subagentStop |
Fire-and-forget | Fired when a sub-agent finishes, errors, or is abandoned. |
Tool, shell, and MCP execution
| Event | Type | Description |
|---|---|---|
preToolUse |
Blocking | Fired before any AI tool is invoked. Veto to prevent the tool from running. |
postToolUse |
Fire-and-forget | Fired after a tool completes successfully. |
postToolUseFailure |
Fire-and-forget | Fired after a tool invocation fails, times out, or is denied. |
beforeShellExecution |
Blocking | Fired before a shell command is executed. Use this to enforce shell command policies. |
afterShellExecution |
Fire-and-forget | Fired after a shell command finishes executing. |
beforeMCPExecution |
Blocking | Fired before an MCP tool is invoked. Veto to prevent it from running. |
afterMCPExecution |
Fire-and-forget | Fired after an MCP tool execution completes, whether successfully or with an error. |
File and skill operations
| Event | Type | Description |
|---|---|---|
beforeReadFile |
Blocking | Fired before the agent reads a file. Veto to block file access. |
afterReadFile |
Fire-and-forget | Fired after the agent successfully reads a file. |
beforeEditFile |
Blocking | Fired before the agent edits or creates a file. Veto to prevent the write. |
afterEditFile |
Fire-and-forget | Fired after the agent attempts to edit or create a file. |
beforeLoadSkill |
Blocking | Fired before the agent loads a skill. Veto to prevent the skill from being loaded. |
afterLoadSkill |
Fire-and-forget | Fired after the agent successfully loads a skill. |
User actions
| Event | Type | Description |
|---|---|---|
afterUserInsertResponse |
Fire-and-forget | Fired after the user inserts or replaces an AI suggestion in the document. |
afterUserPreviewResponse |
Fire-and-forget | Fired after the preview dialog box closes or when an error occurs while applying the changes. |
afterCopyResponse |
Fire-and-forget | Fired after the user copies an AI response to the clipboard. |
afterUserAcceptChanges |
Fire-and-forget | Fired after the user accepts AI-proposed file changes. |
afterUserRejectChanges |
Fire-and-forget | Fired after the user rejects AI-proposed file changes. |
Hook Events Reference
The following tables list all events defined in
AIHookEvent, grouped by lifecycle category. Events marked as
Blocking can be vetoed by throwing an AIHookVetoException inside
eventOccurred. Events marked as Fire-and-forget are informational
(throwing an exception has no effect on the AI action).
Every event delivers its data as a JSON string
(payloadJson) with snake_case keys. Deserialize it with Jackson into the
event-specific BaseHookPayload subclass (listed under each event below) to
access its fields. All payload types share the following base fields:
| Field | Type | Description |
|---|---|---|
session_id |
string | Stable identifier for the current chat session, constant across all turns. |
action_or_agent_id |
string | Identifier of the AI action or agent (chat mode) that produced this event (e.g.
agent, ask, or a custom chat mode ID). |
generation_id |
string | Identifier that changes with each user message/AI generation round. |
connection_id |
string | Identifier of the AI connection as configured by the user in Preferences. |
model |
string | The AI model currently in use (e.g. claude-opus-4-7). |
hook_event_name |
string | The name of the hook event that fired (e.g.
beforeShellExecution). |
positron_version |
string | Running version of the AI Positron plugin (e.g.
8.3.0). |
oxygen_version |
string | Running version of the host application (e.g.
27.1.0). |
oxygen_distribution |
string | Host product distribution (e.g. Oxygen XML Editor). |
workspace_root |
string | Absolute path to the current project or workspace root. |
user_id |
string | System identifier of the user. |
sessionStart: Fire-and-forget- Fired when a new AI session is created. This includes interactive chat conversations, batch AI refactoring sessions, and AI XPath function invocations.
sessionEnd: Fire-and-forget- Fired when an AI session ends, whether a chat conversation is closed or cleared, a batch AI refactoring run finishes, or an AI XPath function invocation completes.
sessionReload: Fire-and-forget- Fired when the user restores a previous conversation from the chat history.
beforeStartAction: Blocking- Fired before an AI action is launched. Throwing an exception vetoes the action and
prevents it from running. The action identifier is available via the base field
action_or_agent_id. beforeSubmitPrompt: Blocking- Fired right before a user prompt is submitted to the AI model from the chat UI. Throwing an exception prevents the prompt from being sent.
agentFinish: Fire-and-forget- Fired when the AI agent interaction finishes, regardless of whether it completed successfully, was aborted by the user, or terminated by an error.
preToolUse: Blocking- Fired before any AI tool (function or action) is invoked. Throwing an exception prevents the tool from running.
postToolUse: Fire-and-forget- Fired after a tool has completed successfully. Use it for auditing, analytics, or collecting tool execution metrics.
postToolUseFailure: Fire-and-forget- Fired after a tool invocation has failed, timed out, or was denied.
subagentStart: Blocking- Fired before a sub-agent or sub-action is started. Throwing an exception prevents the sub-agent from being created.
subagentStop: Fire-and-forget- Fired when a sub-agent finishes, errors, or is abandoned.
beforeShellExecution: Blocking- Fired before a shell command is executed by the AI agent. Throwing an exception prevents the command from running. This is the primary hook to use when enforcing shell command policies.
afterShellExecution: Fire-and-forget- Fired after a shell command has finished executing. Use it for auditing or collecting metrics from command output.
beforeMCPExecution: Blocking- Fired before an MCP (Model Context Protocol) tool is invoked. Throwing an exception prevents the tool from running.
afterMCPExecution: Fire-and-forget- Fired after an MCP tool execution completes, whether successfully or with an error. Includes the tool's input parameters, the JSON result, and the execution outcome.
beforeReadFile: Blocking- Fired before the agent reads a file. Throwing an exception blocks the agent's access
to that file. Use this hook to implement file-level access control (for example, to
prevent the AI from reading files that contain secrets). Also fired before the agent
reads the DITA map structure context (the Get DITA Map Structure
Context tool), with
file_pathset to the URL of the DITA map currently open in the DITA Maps Manager. afterReadFile: Fire-and-forget- Fired after the agent successfully reads a file. Use it to audit file-access
patterns or log the content retrieved by the agent. Also fired after the
Get DITA Map Structure Context tool completes successfully,
with
contentset to the computed hierarchy JSON (not the raw DITA map XML). beforeEditFile: Blocking- Fired before the agent edits or creates a file. Throwing an exception prevents the
write from happening (the exception message is returned to the AI model as an
error).Note:The event is fired only when the content to be written has been successfully computed. For replacement-based edits, this means the replacement strings were located in the existing file content. This event is also fired for DITA map structure additions (inserting a topic reference via the Add to DITA Map Structure tool).
afterEditFile: Fire-and-forget- Fired after the agent attempts to edit or create a file, both on success and on failure. Also fired after a DITA map structure addition (inserting a topic reference). Use it to audit AI-generated file changes.
beforeLoadSkill: Blocking- Fired before the agent loads a skill by name. Throwing an exception prevents the skill from being loaded.
afterLoadSkill: Fire-and-forget- Fired after the agent successfully loads a skill. Use it to audit which skills are used during an AI session.
afterAgentResponse: Fire-and-forget- Fired after the agent completes an assistant turn, whether the turn contained only tool calls or produced a visible text response.
afterAgentThought: Fire-and-forget- Fired after the agent completes a thinking block (extended thinking mode). Use it to observe the agent's reasoning process.
afterUserInsertResponse: Fire-and-forget- Fired after the user clicks the Insert/Replace button in the chat panel to apply an AI suggestion in the document.
afterUserPreviewResponse: Fire-and-forget- Fired after the user clicks the Preview button in the chat panel and the preview dialog box closes, or when an error occurs while applying the changes. The preview opens a diff view comparing the current editor content with the result of applying the AI response, so the user can review the changes before deciding whether to apply them.
afterUserCopyResponse: Fire-and-forget- Fired after the user clicks the Copy button in the chat panel to copy an AI response to the clipboard.
afterUserAcceptChanges: Fire-and-forget- Fired after the user accepts AI-proposed file changes. This event is emitted both when the user accepts changes from the agentic chat flow and when the user accepts files in the batch AI refactoring diff dialog.
afterUserRejectChanges: Fire-and-forget- Fired after the user rejects AI-proposed file changes. This event is emitted both when the user rejects changes from the agentic chat flow and when files are rejected in the batch AI refactoring diff dialog (including when the dialog is cancelled, in which case all files in the batch are treated as rejected).
beforeInlineCompletion: Blocking- Fired before an inline autocomplete suggestion is requested from the AI model. Throwing an exception suppresses the suggestion — no AI call is made and no ghost text is shown.
afterInlineCompletion: Fire-and-forget- Fired after an inline autocomplete suggestion has been generated by the AI model. Use it for auditing, token-usage tracking, or analytics on autocomplete activity. Note that this event fires regardless of whether the user accepts or dismisses the ghost text.
AIHooksHandler extension point is available
starting with version 28.0 of Oxygen XML.