Skip to content
Chapter 6 of 8

Hooks and automation: acting without being asked

Hooks are how LifeOS does things automatically at the right moment. The harness emits lifecycle events across a session (SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, SubagentStop, PreCompact, SessionEnd), and each event runs the hooks registered to it in a settings file. A hook is just a small program: on SessionStart one loads your context so your DA knows who you are, another might sync state; on UserPromptSubmit a guard can scan the prompt; on a tool call a tracker records activity; on Stop a hook can capture what was learned. This is the automation layer, the part of the system that runs continuously without you asking. In this lab you build a tiny event bus, register sample hooks against events, fire a realistic slice of a session, and prove that each event runs exactly its registered hooks and that an event with none registered is a clean no-op.

The lab: read it, then run it

labs/lifeos/lo6-hook-dispatch.py
#!/usr/bin/env python3
"""
LAB LO6: A hook firing on a lifecycle event.

Hooks are how LifeOS acts without being asked. The harness emits lifecycle events
(SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, SessionEnd, and
more), and each event runs the hooks registered to it. In this lab you build a
tiny event bus, register sample hooks against events, fire a session, and prove
that firing SessionStart runs exactly its registered hooks and nothing else. All
handlers are harmless stand-ins that just record that they ran.

Run: python3 modules/academy-content/labs/lifeos/lo6-hook-dispatch.py
"""
import sys

# STEP 1: a registry mapping each event to its ordered list of hooks.
ran = []                                    # global record of what fired

def load_context():   ran.append("LoadContext")
def scan_prompt():    ran.append("PromptGuard")
def track_tool():     ran.append("ToolActivityTracker")
def capture_learn():  ran.append("WorkCompletionLearning")

registry = {
    "SessionStart":     [load_context],
    "UserPromptSubmit": [scan_prompt],
    "PreToolUse":       [track_tool],
    "Stop":             [capture_learn],
}

def fire(event):
    fired = []
    for hook in registry.get(event, []):    # unknown event -> nothing fires
        hook()
        fired.append(hook.__name__)
    return fired

# STEP 2: fire a realistic slice of a session.
print("STEP 1-2: fire lifecycle events")
for ev in ["SessionStart", "UserPromptSubmit", "PreToolUse", "Stop"]:
    fired = fire(ev)
    print(f"  {ev:18} -> {fired}")

# STEP 3: an event with no registered hooks fires nothing (no surprises).
none_fired = fire("PreCompact")
print(f"  {'PreCompact':18} -> {none_fired}  (no hooks registered)")

# INVARIANT: SessionStart ran exactly LoadContext, the total fired count matches
# the four registered events, and the unregistered event was a clean no-op.
sessionstart_only_loadcontext = ran[:1] == ["LoadContext"]
total_ok = ran == ["LoadContext", "PromptGuard", "ToolActivityTracker", "WorkCompletionLearning"]
noop_ok = none_fired == []
ok = sessionstart_only_loadcontext and total_ok and noop_ok
print("")
print(f"HOOKS FIRE ON THEIR EVENTS ONLY (registered fire, unknown no-ops): {'YES' if ok else 'NO'}")
if not ok:
    sys.exit(1)
print("Events drive hooks; hooks drive automation. Next: where you see it all, the Pulse dashboard.")
Runnable lab
lo6-hook-dispatch.py

Build an event bus, register hooks, and prove each event fires only its own hooks.

Proves: HOOKS FIRE ON THEIR EVENTS ONLY (registered fire, unknown no-ops): YES

Open the notebook

Runs in your browser via Pyodide. First run loads the runtime once; no install, no server.

Each event ran exactly its registered hooks, in order, and the event with nothing registered did nothing at all. That predictability is what makes hooks safe to rely on: the automation is scoped to a specific moment in the session, so it never surprises you. In the real system this is how your context loads the instant a session starts and how learnings get captured the moment work stops, all without a single instruction from you. Next you see the surface where all of this becomes visible, the Pulse dashboard.
Check your understanding
  1. 1. What triggers a hook to run?

  2. 2. Which of these is a real hook lifecycle event?

  3. 3. In the lab, what happened when an event had no hooks registered?