Overview
Inspired by the Pi coding agent (GitHub), this proposes significantly enhancing Hermes Agent's hook/extension system to support tool interception, context modification, event cancellation, and richer lifecycle events. Pi's extension system is its deepest architectural strength -- it provides 30+ lifecycle events with the ability to block tool calls, modify tool results, transform context before it reaches the LLM, and even replace UI components.
Hermes already has a hook system (gateway/hooks.py) with 7 event types, but it's fire-and-forget with no return values or cancellation. This proposal would evolve it into a full extension API that enables powerful plugins: approval gates, audit logging, custom sandboxing, context injection, tool result transformation, and more.
This builds on the existing hook infrastructure rather than replacing it, and complements the existing tool registry and skills systems.
Research Findings
How Pi's Extension System Works
Pi extensions are TypeScript modules that receive an ExtensionAPI object with deep hooks into the agent lifecycle:
Event System (30+ events):
| Category |
Events |
Can Cancel/Modify? |
| Session lifecycle |
session_start, session_before_switch, session_switch, session_before_compact, session_compact, session_shutdown |
Yes (before_ events) |
| Agent lifecycle |
before_agent_start, agent_start, agent_end, turn_start, turn_end |
Yes (before_ events) |
| Message streaming |
message_start, message_update, message_end |
No (observe only) |
| Tool interception |
tool_call (can BLOCK), tool_result (can MODIFY) |
Yes -- this is key |
| Input processing |
input (can transform or handle user input) |
Yes |
| Context |
context (can modify messages before LLM) |
Yes -- modify context |
| Model events |
model_select |
Yes |
| Bash execution |
user_bash (can replace execution) |
Yes |
Tool Interception Pattern:
// Extension can intercept any tool call
pi.on('tool_call', async (event) => {
// Block dangerous commands
if (event.tool === 'bash' && event.args.command.includes('rm -rf')) {
event.block('Dangerous command blocked by safety extension');
return;
}
// Or modify arguments before execution
event.args.command = wrapInSandbox(event.args.command);
});
// Modify tool results before they reach the LLM
pi.on('tool_result', async (event) => {
event.result = sanitize(event.result);
});
Context Modification Pattern:
// Inject or modify messages right before they go to the LLM
pi.on('context', async (event) => {
// Add dynamic context based on current state
event.messages.push({
role: 'user',
content: `Current git branch: ${await getGitBranch()}`
});
});
Registration APIs:
pi.registerTool() -- add LLM-callable tools dynamically
pi.registerCommand() -- add slash commands
pi.registerShortcut() -- add keyboard shortcuts
pi.registerProvider() -- add model providers with OAuth support
pi.setActiveTools() / pi.getActiveTools() -- dynamic tool management
pi.events -- shared EventBus for inter-extension communication
Extension Discovery:
- Project-local:
.pi/extensions/
- Global:
~/.pi/agent/extensions/
- Package manager:
pi install npm:@foo/pi-tools
Key Design Decisions
- Before/after event pattern --
before_X events can cancel the operation; X events fire after completion (observe only). Clean separation of concerns.
- Tool interception as first-class -- Any tool call can be blocked or modified by extensions. This enables approval gates, sandboxing, logging without modifying tool code.
- Context as an event -- The message array going to the LLM is exposed as a modifiable event, enabling dynamic context injection from any extension.
- Extension isolation -- Extensions share an EventBus but can't directly access each other. Communication is event-driven.
Current State in Hermes Agent
Existing Hook System (gateway/hooks.py)
Hermes has a lightweight hook system:
- Discovery: File-based,
~/.hermes/hooks/<hook-name>/ with HOOK.yaml + handler.py
- 7 event types:
gateway:startup, session:start, session:reset, agent:start, agent:step, agent:end, command:*
- Dispatch:
emit(event_type, context) fires all matching handlers
- Sync + async handler support
- Wildcard matching (
command:* matches any command:X)
- Fire-and-forget -- errors caught and logged, never block pipeline
- No return values, no cancellation, no modification
Other Extension Points
- Tool Registry (
tools/registry.py): Self-registering tools at import time. ToolEntry with schema, handler, check_fn. Toolset-based grouping.
- Skills System: On-demand knowledge docs, agentskills.io compatible, Skills Hub for install
- Context Files:
SOUL.md, AGENTS.md, .cursorrules loaded into system prompt
- Callbacks:
tool_progress_callback, clarify_callback, step_callback in AIAgent
- Memory System: Persistent MEMORY.md + USER.md
The Gap
The hook system is observe-only. There's no way for extensions to:
- Block or approve tool calls before execution
- Modify tool results before they reach the LLM
- Inject dynamic context into the message array
- Register new tools at runtime
- Add custom slash commands from extensions
- Communicate between extensions via events
Implementation Plan
Skill vs. Tool Classification
This is a core codebase change -- it enhances the fundamental extension architecture of the agent. It touches gateway/hooks.py, run_agent.py (the agent loop), tools/registry.py (tool dispatch), and gateway/run.py (command handling). Cannot be expressed as a skill or tool.
What We'd Need
- Enhanced event system with return values, cancellation, and priority ordering
- Tool call/result interception points in the agent loop
- Context modification hook before LLM API calls
- Extension manifest format (beyond HOOK.yaml)
- Dynamic tool and command registration APIs
- Inter-extension event bus
Phased Rollout
Phase 1: Enhanced Event System
- Upgrade
HookRegistry to support return values from handlers
- Add
before_ event pattern: before_tool_call, before_agent_start, before_compress
before_ handlers can return {"cancel": True, "reason": "..."} to block the operation
- Add priority ordering to handlers (lower number = earlier execution)
- Add
tool_call and tool_result events with modification capability
- Maintain backward compatibility -- existing hooks work unchanged
Phase 2: Context & Tool Hooks
- Add
context event fired in run_agent.py before building API messages
- Handlers receive the message array and can modify it (add/remove/edit messages)
- Enables dynamic context injection (git state, environment info, custom data)
- Add
input event for user message preprocessing
- Add runtime tool registration:
register_tool(name, schema, handler) via extension API
- Add runtime command registration:
register_command(name, handler) via extension API
Phase 3: Extension Ecosystem
- Extension manifest format (
EXTENSION.yaml with metadata, dependencies, events)
- Extension package manager (
hermes extensions install <source>)
- Inter-extension event bus for extension-to-extension communication
- Extension UI hooks for CLI mode (status bar, custom widgets)
- Built-in extensions: audit logger, safety gate, git checkpoint
Pros & Cons
Pros
- Powerful plugin ecosystem -- Enables approval gates, sandboxing, audit logging, custom providers without modifying core code
- Safety without rigidity -- Tool interception allows per-project or per-user security policies via extensions rather than baked-in permission popups
- Dynamic context -- Extensions can inject real-time project state (git branch, test results, linter output) into LLM context
- Backward compatible -- Existing hooks continue working; new capabilities are opt-in
- Community extensibility -- Users can share extensions for specific workflows, tools, or integrations
Cons / Risks
- Complexity cost -- More extension points = more surface area for bugs, harder to reason about behavior
- Performance -- Synchronous hook chains before tool calls add latency; need careful async design
- Security -- Extensions with tool interception have significant power; need trust model
- Ordering issues -- Multiple extensions modifying the same event can create conflicts; priority system helps but doesn't eliminate
- Maintenance burden -- Extension API becomes a contract that's hard to change once published
Open Questions
- Should extension-registered tools be visible alongside built-in tools, or namespaced (e.g.,
ext:tool_name)?
- Should there be an extension permissions model (which events an extension can subscribe to)?
- How should extension conflicts be handled (two extensions modifying the same tool result)?
- Should extensions be able to modify the system prompt, or only the message array?
- What's the right trust boundary -- should extensions run in a sandbox or have full access?
References
Overview
Inspired by the Pi coding agent (GitHub), this proposes significantly enhancing Hermes Agent's hook/extension system to support tool interception, context modification, event cancellation, and richer lifecycle events. Pi's extension system is its deepest architectural strength -- it provides 30+ lifecycle events with the ability to block tool calls, modify tool results, transform context before it reaches the LLM, and even replace UI components.
Hermes already has a hook system (
gateway/hooks.py) with 7 event types, but it's fire-and-forget with no return values or cancellation. This proposal would evolve it into a full extension API that enables powerful plugins: approval gates, audit logging, custom sandboxing, context injection, tool result transformation, and more.This builds on the existing hook infrastructure rather than replacing it, and complements the existing tool registry and skills systems.
Research Findings
How Pi's Extension System Works
Pi extensions are TypeScript modules that receive an
ExtensionAPIobject with deep hooks into the agent lifecycle:Event System (30+ events):
session_start,session_before_switch,session_switch,session_before_compact,session_compact,session_shutdownbefore_agent_start,agent_start,agent_end,turn_start,turn_endmessage_start,message_update,message_endtool_call(can BLOCK),tool_result(can MODIFY)input(can transform or handle user input)context(can modify messages before LLM)model_selectuser_bash(can replace execution)Tool Interception Pattern:
Context Modification Pattern:
Registration APIs:
pi.registerTool()-- add LLM-callable tools dynamicallypi.registerCommand()-- add slash commandspi.registerShortcut()-- add keyboard shortcutspi.registerProvider()-- add model providers with OAuth supportpi.setActiveTools()/pi.getActiveTools()-- dynamic tool managementpi.events-- shared EventBus for inter-extension communicationExtension Discovery:
.pi/extensions/~/.pi/agent/extensions/pi install npm:@foo/pi-toolsKey Design Decisions
before_Xevents can cancel the operation;Xevents fire after completion (observe only). Clean separation of concerns.Current State in Hermes Agent
Existing Hook System (
gateway/hooks.py)Hermes has a lightweight hook system:
~/.hermes/hooks/<hook-name>/withHOOK.yaml+handler.pygateway:startup,session:start,session:reset,agent:start,agent:step,agent:end,command:*emit(event_type, context)fires all matching handlerscommand:*matches anycommand:X)Other Extension Points
tools/registry.py): Self-registering tools at import time.ToolEntrywith schema, handler, check_fn. Toolset-based grouping.SOUL.md,AGENTS.md,.cursorrulesloaded into system prompttool_progress_callback,clarify_callback,step_callbackin AIAgentThe Gap
The hook system is observe-only. There's no way for extensions to:
Implementation Plan
Skill vs. Tool Classification
This is a core codebase change -- it enhances the fundamental extension architecture of the agent. It touches
gateway/hooks.py,run_agent.py(the agent loop),tools/registry.py(tool dispatch), andgateway/run.py(command handling). Cannot be expressed as a skill or tool.What We'd Need
Phased Rollout
Phase 1: Enhanced Event System
HookRegistryto support return values from handlersbefore_event pattern:before_tool_call,before_agent_start,before_compressbefore_handlers can return{"cancel": True, "reason": "..."}to block the operationtool_callandtool_resultevents with modification capabilityPhase 2: Context & Tool Hooks
contextevent fired inrun_agent.pybefore building API messagesinputevent for user message preprocessingregister_tool(name, schema, handler)via extension APIregister_command(name, handler)via extension APIPhase 3: Extension Ecosystem
EXTENSION.yamlwith metadata, dependencies, events)hermes extensions install <source>)Pros & Cons
Pros
Cons / Risks
Open Questions
ext:tool_name)?References
extensions.ts,wrapper.ts(tool interception)gateway/hooks.py, events used ingateway/run.py