openclaw - 💡(How to fix) Fix Feature: `transform_tool_result` plugin hook for modifying tool results before model context [2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#58021Fetched 2026-04-08 01:54:49
View on GitHub
Comments
2
Participants
2
Timeline
2
Reactions
0
Participants
Timeline (top)
commented ×2

Add a new plugin hook that fires after a tool executes but before the result enters the model's context, allowing plugins to modify what the model sees.

Root Cause

Add a new plugin hook that fires after a tool executes but before the result enters the model's context, allowing plugins to modify what the model sees.

Code Example

before_tool_call  →  tool execution  →  result sent to model  →  after_tool_call (observe)
                                                          tool_result_persist (transcript)

---

// Follows the existing plugin hook pattern
transform_tool_result: (
  event: PluginHookTransformToolResultEvent,
  ctx: PluginHookToolContext
) => Promise<PluginHookTransformToolResultResult | void> | PluginHookTransformToolResultResult | void;

type PluginHookTransformToolResultEvent = {
  toolName: string;
  toolCallId?: string;
  result: unknown;
  runId?: string;
};

type PluginHookTransformToolResultResult = {
  result?: unknown;  // modified result to send to model; undefined = pass through
};

---

before_tool_call  →  tool execution  →  transform_tool_result (NEW)  →  result sent to model  →  after_tool_call (observe)
                                                                                          tool_result_persist (transcript)
RAW_BUFFERClick to expand / collapse

Feature Proposal: transform_tool_result plugin hook

Summary

Add a new plugin hook that fires after a tool executes but before the result enters the model's context, allowing plugins to modify what the model sees.

Current state

The plugin hook lifecycle around tool calls:

before_tool_call  →  tool execution  →  result sent to model  →  after_tool_call (observe)
                                                          tool_result_persist (transcript)
  • before_tool_call: intercepts before execution. Can modify params, block, require approval. Returns a value.
  • after_tool_call: fires after the result has already been sent to the model. Fire-and-forget, runs in parallel, intentionally late. Designed for side effects — telemetry, logging, audit. Returns void.
  • tool_result_persist: transforms the result before writing to the session transcript on disk. Synchronous. Returns a value, but only affects persistence — not what the model sees on the current turn.

There's a gap: nothing sits between tool execution and the model receiving the result. Plugins can control what goes into a tool and what gets persisted, but not what the model actually sees.

Proposal

Add transform_tool_result as a new plugin hook:

// Follows the existing plugin hook pattern
transform_tool_result: (
  event: PluginHookTransformToolResultEvent,
  ctx: PluginHookToolContext
) => Promise<PluginHookTransformToolResultResult | void> | PluginHookTransformToolResultResult | void;

type PluginHookTransformToolResultEvent = {
  toolName: string;
  toolCallId?: string;
  result: unknown;
  runId?: string;
};

type PluginHookTransformToolResultResult = {
  result?: unknown;  // modified result to send to model; undefined = pass through
};

Updated lifecycle:

before_tool_call  →  tool execution  →  transform_tool_result (NEW)  →  result sent to model  →  after_tool_call (observe)
                                                                                          tool_result_persist (transcript)

If the handler returns void or undefined, the original result passes through unchanged. Fully backwards-compatible with all existing hooks — after_tool_call and tool_result_persist continue to work exactly as they do today.

Why not extend after_tool_call?

after_tool_call is explicitly designed as a fire-and-forget observation hook. It runs in parallel, fires after the result is already sent to the model, and returns void by design. Changing its semantics would alter the contract for existing plugins. A separate hook at a different point in the pipeline is cleaner.

Use cases

  • Context dedup: A plugin tracks content the model has already seen and strips duplicate chunks from tool results (e.g., memory search returning the same chunks across multiple calls in a session)
  • Result summarization: Compress verbose outputs (long file reads, web page fetches) to save context window budget
  • Redaction: Strip sensitive data from the model's view while keeping the full result in the persisted transcript via tool_result_persist
  • Annotation: Enrich results with metadata before the model processes them

Scope

  • One new hook name added to PLUGIN_HOOK_NAMES
  • One new type (PluginHookTransformToolResultEvent + result type), following established patterns
  • One new call site in the agent loop, between tool execution and result emission
  • No changes to existing hooks — before_tool_call, after_tool_call, and tool_result_persist are untouched
  • Backwards-compatible — plugins that don't register for this hook see zero behavior change

Open questions

  • Should multiple plugins chain transforms (each seeing the prior plugin's output), or first-write-wins? Chaining (like tool_result_persist) seems more composable.
  • Any tool names that should be exempt from transformation for safety reasons?

Happy to submit a PR for this.

extent analysis

Fix Plan

To implement the proposed transform_tool_result plugin hook, follow these steps:

  • Add a new hook name to PLUGIN_HOOK_NAMES.
  • Define the PluginHookTransformToolResultEvent and PluginHookTransformToolResultResult types.
  • Create a new call site in the agent loop between tool execution and result emission.
  • Implement the hook handler function:
transform_tool_result: (
  event: PluginHookTransformToolResultEvent,
  ctx: PluginHookToolContext
) => Promise<PluginHookTransformToolResultResult | void> | PluginHookTransformToolResultResult | void {
  // Example implementation: strip duplicate chunks from tool results
  if (event.result && ctx.model.seenContent) {
    const filteredResult = filterDuplicateChunks(event.result, ctx.model.seenContent);
    return { result: filteredResult };
  }
  // Pass through original result if no transformation is needed
  return undefined;
}
  • Register the hook handler in the plugin initialization code:
const plugin = {
  // ...
  transform_tool_result: myTransformToolResultHandler,
};

Verification

To verify the fix, test the following scenarios:

  • A plugin registers for the transform_tool_result hook and returns a modified result.
  • A plugin registers for the transform_tool_result hook and returns undefined to pass through the original result.
  • Multiple plugins register for the transform_tool_result hook and chain their transformations.

Extra Tips

  • Consider implementing a chaining mechanism for multiple plugins to transform the result, similar to tool_result_persist.
  • Review the list of tool names that should be exempt from transformation for safety reasons and add them to the hook implementation.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING