Skip to content

feat(plugins): add before_agent_reply hook (claiming pattern)#20067

Merged
jalehman merged 2 commits intoopenclaw:mainfrom
JoshuaLelon:feat/before-agent-reply-hook-clean
Apr 1, 2026
Merged

feat(plugins): add before_agent_reply hook (claiming pattern)#20067
jalehman merged 2 commits intoopenclaw:mainfrom
JoshuaLelon:feat/before-agent-reply-hook-clean

Conversation

@JoshuaLelon
Copy link
Copy Markdown
Contributor

@JoshuaLelon JoshuaLelon commented Feb 18, 2026

Summary

  • Adds a before_agent_reply plugin hook that fires after slash commands but before the LLM agent runs
  • Plugins can return { handled: true, reply } to short-circuit agent processing (forms, wizards, approval gates, etc. as plugins without touching core)
  • Uses the runClaimingHook pattern (sequential by priority, first { handled: true } wins) — same pattern as inbound_claim
  • Populates full PluginHookAgentContext including trigger, channelId, messageProvider

Motivation

Per VISION.md, core stays lean and optional capability should ship as plugins. Right now there's no way for a plugin to intercept an inbound message and return a synthetic reply before the LLM runs — anything that needs pre-LLM interception has to modify core. This hook fills that gap.

Closes #8807.

Design

Hook name: before_agent_reply

When it fires: After handleInlineActions returns kind: "continue", before stageSandboxMedia / runPreparedReply. This means /help and other slash commands still work normally, even during a plugin dialog.

Event type:

{ cleanedBody: string }  // final user message heading to LLM

Result type (claiming pattern):

{
  handled: boolean;      // true = claim this message, short-circuit the LLM
  reply?: ReplyPayload;  // synthetic reply (omit to silently swallow)
  reason?: string;       // for logging/debugging
}

Context: Full PluginHookAgentContext (agentId, sessionKey, sessionId, workspaceDir, messageProvider, trigger, channelId).

Execution: runClaimingHook — async, sequential by priority (highest first). First handler to return { handled: true } wins; remaining handlers are not called. When handled: true without reply, the message is swallowed via SILENT_REPLY_TOKEN.

Changes

File Lines What
src/plugins/types.ts +21 Hook name, event/result types (with handled: boolean), handler map entry
src/plugins/hooks.ts +21 runBeforeAgentReply using runClaimingHook, imports, re-exports
src/auto-reply/reply/get-reply.ts +26 Hook call site after inline actions, before LLM
src/plugins/hooks.before-agent-reply.test.ts +123 8 tests: single claim, no hooks, first-claim-wins, swallow, decline-then-claim, all decline, error handling, hasHooks

Test plan

  • pnpm test -- src/plugins/hooks.before-agent-reply.test.ts — all 8 tests pass
  • pnpm test -- src/plugins/hooks — all 27 existing hook tests unaffected
  • pnpm test -- src/auto-reply/reply/get-reply — existing get-reply tests pass
  • pnpm oxlint / pnpm format — clean
  • pnpm tsgo — no new type errors (pre-existing upstream errors only)
  • git diff upstream/main --stat — exactly 4 files, no unrelated changes, no deletions of inbound_claim code

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@JoshuaLelon
Copy link
Copy Markdown
Contributor Author

JoshuaLelon commented Feb 18, 2026

Note: I opened this PR before noticing that CONTRIBUTING.md asks new features to start as a Discussion first — sorry about the wrong order! Retroactive discussion here: https://github.com/openclaw/openclaw/discussions/20069

@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added the stale Marked as stale due to inactivity label Feb 28, 2026
@JoshuaLelon JoshuaLelon force-pushed the feat/before-agent-reply-hook-clean branch 2 times, most recently from 6c6b075 to e75a3e8 Compare March 2, 2026 16:01
@JoshuaLelon
Copy link
Copy Markdown
Contributor Author

The check CI failure is a pre-existing TS error on main (not introduced by this PR):

extensions/zalouser/src/monitor.ts(141,57): error TS2448: Block-scoped variable 'chatId' used before its declaration.

Fix submitted in #31868 — once that merges, this PR's CI should go green.

@JoshuaLelon JoshuaLelon force-pushed the feat/before-agent-reply-hook-clean branch 2 times, most recently from 0a2459c to a9463af Compare March 4, 2026 04:15
@JoshuaLelon JoshuaLelon force-pushed the feat/before-agent-reply-hook-clean branch from a9463af to 99b6039 Compare March 5, 2026 14:56
@bolander72
Copy link
Copy Markdown

+1 — this is the most complete solution for local model routing. I have a working message:preprocessed hook that classifies via local Qwen and generates responses for simple tasks, but can't short-circuit the agent since that hook is fire-and-forget. A before_agent_reply hook that returns a synthetic reply would let plugins handle the full flow: classify, respond locally, skip Claude entirely. Combined with #32802 and #17614 this would give the community everything needed for cost-optimized routing.

@dknoodle
Copy link
Copy Markdown

dknoodle commented Mar 12, 2026

We run 26 agents on OpenClaw in production. This is the hook we need most.

Without it we can not do slash command acknowledgments (instant reply, skip the LLM), multi-agent routing (forward to a different session without burning tokens), or approval gates (hold a message for human sign-off before the LLM touches it).

The stale label is misleading. The feature gap is real, the code is clean, CI is green. Would love to see a maintainer take another look.

Happy to help test against our fleet.

@JoshuaLelon JoshuaLelon force-pushed the feat/before-agent-reply-hook-clean branch from 27c28ac to a6d6255 Compare March 15, 2026 18:47
@openclaw-barnacle openclaw-barnacle bot removed the docs Improvements or additions to documentation label Mar 15, 2026
@JoshuaLelon JoshuaLelon force-pushed the feat/before-agent-reply-hook-clean branch from a6d6255 to 0f43aaa Compare March 16, 2026 17:07
@JoshuaLelon JoshuaLelon changed the title feat(plugins): add before_agent_reply hook for message interception feat(plugins): add before_agent_reply hook (claiming pattern) Mar 16, 2026
@JoshuaLelon JoshuaLelon force-pushed the feat/before-agent-reply-hook-clean branch 5 times, most recently from c5afa58 to 2ab4581 Compare March 26, 2026 12:29
@JoshuaLelon JoshuaLelon force-pushed the feat/before-agent-reply-hook-clean branch 2 times, most recently from c090a5f to f77eca1 Compare March 30, 2026 03:34
@JoshuaLelon JoshuaLelon force-pushed the feat/before-agent-reply-hook-clean branch 2 times, most recently from 28a08f9 to 726dc48 Compare April 1, 2026 17:34
@jalehman jalehman self-assigned this Apr 1, 2026
@openclaw-barnacle openclaw-barnacle bot added the docs Improvements or additions to documentation label Apr 1, 2026
@jalehman jalehman force-pushed the feat/before-agent-reply-hook-clean branch from a275528 to e40dfbd Compare April 1, 2026 20:29
@jalehman jalehman merged commit 7cb323d into openclaw:main Apr 1, 2026
41 checks passed
@jalehman
Copy link
Copy Markdown
Contributor

jalehman commented Apr 1, 2026

Merged via squash.

Thanks @JoshuaLelon!

ancientitguybot-dev pushed a commit to KaiWalter/openclaw that referenced this pull request Apr 3, 2026
…aw#20067)

Merged via squash.

Prepared head SHA: e40dfbd
Co-authored-by: JoshuaLelon <[email protected]>
Co-authored-by: jalehman <[email protected]>
Reviewed-by: @jalehman
sypherin pushed a commit to sypherin/openclaw that referenced this pull request Apr 4, 2026
steipete pushed a commit to duncanita/openclaw that referenced this pull request Apr 4, 2026
…aw#20067)

Merged via squash.

Prepared head SHA: e40dfbd
Co-authored-by: JoshuaLelon <[email protected]>
Co-authored-by: jalehman <[email protected]>
Reviewed-by: @jalehman
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation size: M stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Implement message:received and message:sent hooks

6 participants