From 95f99ea4b9b7c4c23cb5f456430eebe25b486247 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sun, 29 Mar 2026 10:19:54 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20built-in=20boot-md=20hook=20=E2=80=94?= =?UTF-8?q?=20run=20BOOT.md=20on=20gateway=20startup=20(#3733)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- gateway/builtin_hooks/__init__.py | 1 + gateway/builtin_hooks/boot_md.py | 86 +++++++++++++++++++++++ gateway/hooks.py | 19 +++++ website/docs/user-guide/features/hooks.md | 20 ++++++ 4 files changed, 126 insertions(+) create mode 100644 gateway/builtin_hooks/__init__.py create mode 100644 gateway/builtin_hooks/boot_md.py diff --git a/gateway/builtin_hooks/__init__.py b/gateway/builtin_hooks/__init__.py new file mode 100644 index 000000000..37da09db9 --- /dev/null +++ b/gateway/builtin_hooks/__init__.py @@ -0,0 +1 @@ +"""Built-in gateway hooks that are always registered.""" diff --git a/gateway/builtin_hooks/boot_md.py b/gateway/builtin_hooks/boot_md.py new file mode 100644 index 000000000..fced0b5e1 --- /dev/null +++ b/gateway/builtin_hooks/boot_md.py @@ -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() diff --git a/gateway/hooks.py b/gateway/hooks.py index 15ecd3fee..c50394b20 100644 --- a/gateway/hooks.py +++ b/gateway/hooks.py @@ -51,14 +51,33 @@ class HookRegistry: """Return metadata about all 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: """ 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: - HOOK.yaml with at least 'name' and 'events' keys - handler.py with a top-level 'handle' function (sync or async) """ + self._register_builtin_hooks() + if not HOOKS_DIR.exists(): return diff --git a/website/docs/user-guide/features/hooks.md b/website/docs/user-guide/features/hooks.md index 0ae8905de..87c7f9846 100644 --- a/website/docs/user-guide/features/hooks.md +++ b/website/docs/user-guide/features/hooks.md @@ -88,6 +88,26 @@ Handlers registered for `command:*` fire for any `command:` event (`command:mode ### 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 Send yourself a message when the agent takes more than 10 steps: