feat: built-in boot-md hook — run BOOT.md on gateway startup (#3733)
The gateway now ships with a built-in boot-md hook that checks for ~/.hermes/BOOT.md on every startup. If the file exists, the agent executes its instructions in a background thread. No installation or configuration needed — just create the file. No BOOT.md = zero overhead (the hook silently returns). Implementation: - gateway/builtin_hooks/boot_md.py: handler with boot prompt, background thread, [SILENT] suppression, error handling - gateway/hooks.py: _register_builtin_hooks() called at the start of discover_and_load() to wire in built-in hooks - Docs updated: hooks page documents BOOT.md as a built-in feature
This commit is contained in:
1
gateway/builtin_hooks/__init__.py
Normal file
1
gateway/builtin_hooks/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Built-in gateway hooks that are always registered."""
|
||||||
86
gateway/builtin_hooks/boot_md.py
Normal file
86
gateway/builtin_hooks/boot_md.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
"""Built-in boot-md hook — run ~/.hermes/BOOT.md on gateway startup.
|
||||||
|
|
||||||
|
This hook is always registered. It silently skips if no BOOT.md exists.
|
||||||
|
To activate, create ``~/.hermes/BOOT.md`` with instructions for the
|
||||||
|
agent to execute on every gateway restart.
|
||||||
|
|
||||||
|
Example BOOT.md::
|
||||||
|
|
||||||
|
# Startup Checklist
|
||||||
|
|
||||||
|
1. Check if any cron jobs failed overnight
|
||||||
|
2. Send a status update to Discord #general
|
||||||
|
3. If there are errors in /opt/app/deploy.log, summarize them
|
||||||
|
|
||||||
|
The agent runs in a background thread so it doesn't block gateway
|
||||||
|
startup. If nothing needs attention, it replies with [SILENT] to
|
||||||
|
suppress delivery.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
logger = logging.getLogger("hooks.boot-md")
|
||||||
|
|
||||||
|
HERMES_HOME = Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes"))
|
||||||
|
BOOT_FILE = HERMES_HOME / "BOOT.md"
|
||||||
|
|
||||||
|
|
||||||
|
def _build_boot_prompt(content: str) -> str:
|
||||||
|
"""Wrap BOOT.md content in a system-level instruction."""
|
||||||
|
return (
|
||||||
|
"You are running a startup boot checklist. Follow the BOOT.md "
|
||||||
|
"instructions below exactly.\n\n"
|
||||||
|
"---\n"
|
||||||
|
f"{content}\n"
|
||||||
|
"---\n\n"
|
||||||
|
"Execute each instruction. If you need to send a message to a "
|
||||||
|
"platform, use the send_message tool.\n"
|
||||||
|
"If nothing needs attention and there is nothing to report, "
|
||||||
|
"reply with ONLY: [SILENT]"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_boot_agent(content: str) -> None:
|
||||||
|
"""Spawn a one-shot agent session to execute the boot instructions."""
|
||||||
|
try:
|
||||||
|
from run_agent import AIAgent
|
||||||
|
|
||||||
|
prompt = _build_boot_prompt(content)
|
||||||
|
agent = AIAgent(
|
||||||
|
quiet_mode=True,
|
||||||
|
skip_context_files=True,
|
||||||
|
skip_memory=True,
|
||||||
|
max_iterations=20,
|
||||||
|
)
|
||||||
|
result = agent.run_conversation(prompt)
|
||||||
|
response = result.get("final_response", "")
|
||||||
|
if response and "[SILENT]" not in response:
|
||||||
|
logger.info("boot-md completed: %s", response[:200])
|
||||||
|
else:
|
||||||
|
logger.info("boot-md completed (nothing to report)")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("boot-md agent failed: %s", e)
|
||||||
|
|
||||||
|
|
||||||
|
async def handle(event_type: str, context: dict) -> None:
|
||||||
|
"""Gateway startup handler — run BOOT.md if it exists."""
|
||||||
|
if not BOOT_FILE.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
content = BOOT_FILE.read_text(encoding="utf-8").strip()
|
||||||
|
if not content:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("Running BOOT.md (%d chars)", len(content))
|
||||||
|
|
||||||
|
# Run in a background thread so we don't block gateway startup.
|
||||||
|
thread = threading.Thread(
|
||||||
|
target=_run_boot_agent,
|
||||||
|
args=(content,),
|
||||||
|
name="boot-md",
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
thread.start()
|
||||||
@@ -51,14 +51,33 @@ class HookRegistry:
|
|||||||
"""Return metadata about all loaded hooks."""
|
"""Return metadata about all loaded hooks."""
|
||||||
return list(self._loaded_hooks)
|
return list(self._loaded_hooks)
|
||||||
|
|
||||||
|
def _register_builtin_hooks(self) -> None:
|
||||||
|
"""Register built-in hooks that are always active."""
|
||||||
|
try:
|
||||||
|
from gateway.builtin_hooks.boot_md import handle as boot_md_handle
|
||||||
|
|
||||||
|
self._handlers.setdefault("gateway:startup", []).append(boot_md_handle)
|
||||||
|
self._loaded_hooks.append({
|
||||||
|
"name": "boot-md",
|
||||||
|
"description": "Run ~/.hermes/BOOT.md on gateway startup",
|
||||||
|
"events": ["gateway:startup"],
|
||||||
|
"path": "(builtin)",
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[hooks] Could not load built-in boot-md hook: {e}", flush=True)
|
||||||
|
|
||||||
def discover_and_load(self) -> None:
|
def discover_and_load(self) -> None:
|
||||||
"""
|
"""
|
||||||
Scan the hooks directory for hook directories and load their handlers.
|
Scan the hooks directory for hook directories and load their handlers.
|
||||||
|
|
||||||
|
Also registers built-in hooks that are always active.
|
||||||
|
|
||||||
Each hook directory must contain:
|
Each hook directory must contain:
|
||||||
- HOOK.yaml with at least 'name' and 'events' keys
|
- HOOK.yaml with at least 'name' and 'events' keys
|
||||||
- handler.py with a top-level 'handle' function (sync or async)
|
- handler.py with a top-level 'handle' function (sync or async)
|
||||||
"""
|
"""
|
||||||
|
self._register_builtin_hooks()
|
||||||
|
|
||||||
if not HOOKS_DIR.exists():
|
if not HOOKS_DIR.exists():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,26 @@ Handlers registered for `command:*` fire for any `command:` event (`command:mode
|
|||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
#### Boot Checklist (BOOT.md) — Built-in
|
||||||
|
|
||||||
|
The gateway ships with a built-in `boot-md` hook that looks for `~/.hermes/BOOT.md` on every startup. If the file exists, the agent runs its instructions in a background session. No installation needed — just create the file.
|
||||||
|
|
||||||
|
**Create `~/.hermes/BOOT.md`:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Startup Checklist
|
||||||
|
|
||||||
|
1. Check if any cron jobs failed overnight — run `hermes cron list`
|
||||||
|
2. Send a message to Discord #general saying "Gateway restarted, all systems go"
|
||||||
|
3. Check if /opt/app/deploy.log has any errors from the last 24 hours
|
||||||
|
```
|
||||||
|
|
||||||
|
The agent runs these instructions in a background thread so it doesn't block gateway startup. If nothing needs attention, the agent replies with `[SILENT]` and no message is delivered.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
No BOOT.md? The hook silently skips — zero overhead. Create the file whenever you need startup automation, delete it when you don't.
|
||||||
|
:::
|
||||||
|
|
||||||
#### Telegram Alert on Long Tasks
|
#### Telegram Alert on Long Tasks
|
||||||
|
|
||||||
Send yourself a message when the agent takes more than 10 steps:
|
Send yourself a message when the agent takes more than 10 steps:
|
||||||
|
|||||||
Reference in New Issue
Block a user