Skip to content
Agent Memory
Hook

Hooks

What ai-memory runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.

From plugin
ai-memory
9.4k7 skills1 hook1 MCP
Install
> /plugin marketplace add akitaonrails/ai-memory
> /plugin install ai-memory@ai-memory

Ships with ai-memory. Installing the plugin gets these hooks.

Where it lives

  • hooks/_lib.shGitHub
    Read the script
    # ai-memory hook helper — find marker file + parse minimal TOML.
    # Sourced by per-agent lifecycle hook scripts. POSIX shell only —
    # no bash-isms, no non-standard deps (no jq, no toml crate). Keep changes
    # byte-trivial because every supported agent (claude-code, codex,
    # cursor, gemini-cli, kimi-code, kiro-cli, antigravity-cli, opencode,
    # omp, pool) sources this same file.
    
    # Set `_amhome` to $HOME without trailing slashes ("/" stays "/"), so the
    # string patterns below treat `HOME=/home/u/` as the native walk does.
    ai_memory_home() {
        _amhome="${HOME:-}"
        while [ "${_amhome%/}" != "$_amhome" ] && [ -n "${_amhome%/}" ]; do
            _amhome="${_amhome%/}"
        done
    }
    
    # Walk up from "$1" toward $HOME (or /) looking for `.ai-memory.toml`.
    # Prints the absolute path of the first marker found, or nothing.
    # Stops at $HOME to avoid leaking declarations from a shared system
    # user's home into another user's session on multi-user boxes. When cwd is
    # outside HOME, stop at the nearest checkout root (`.git` file/dir); a plain
    # non-git directory checks only cwd. This keeps unrelated parent markers out.
    ai_memory_find_marker() {
        dir="$1"
        [ -z "$dir" ] && return 0
        boundary=""
        ai_memory_home
        if [ -n "$_amhome" ]; then
            case "$dir" in
                "$_amhome"|"${_amhome%/}"/*) boundary="$_amhome" ;;
                *)
                    probe="$dir"
                    while [ -n "$probe" ] && [ "$probe" != "/" ]; do
                        if [ -e "$probe/.git" ]; then
                            boundary="$probe"
                            break
                        fi
                        parent=$(dirname "$probe")
                        [ "$parent" = "$probe" ] && break
                        probe="$parent"
                    done
                    [ -n "$boundary" ] || boundary="$dir"
                    ;;
            esac
        fi
        while [ -n "$dir" ] && [ "$dir" != "/" ]; do
            if [ -f "$dir/.ai-memory.toml" ]; then
                printf '%s\n' "$dir/.ai-memory.toml"
                return 0
            fi
            if [ -n "$boundary" ] && [ "$dir" = "$boundary" ]; then
                return 0
            fi
            parent=$(dirname "$dir")
            [ "$parent" = "$dir" ] && return 0
            dir="$parent"
        done
    }
    
    # Parse `key = "value"` at the TOML root (no nesting, no arrays, no
    # tables). Returns the first match or nothing. Ignores comments and
    # blank lines by construction (the regex only matches the `key = "..."`
    # shape).
    ai_memory_parse_toml_key() {
        file="$1"; key="$2"
        [ -f "$file" ] || return 0
        sed -n -E "s/^[[:space:]]*${key}[[:space:]]*=[[:space:]]*\"([^\"]*)\".*/\1/p" \
            "$file" | head -n 1
    }
    
    # Like ai_memory_parse_toml_key but also accepts a BARE value
    # (`key = true` / `key = 6000`), so section-style flags such as
    # `[briefing] inject_on_session_start = true` work quoted or not.
    # Parity with `parse_toml_flag` in hook_capture.rs: line-based (section
    # headers are ignored), first match wins, trailing `# comment` stripped.
    ai_memory_parse_toml_flag() {
        file="$1"; key="$2"
        [ -f "$file" ] || return 0
        sed -n -E "s/^[[:space:]]*${key}[[:space:]]*=[[:space:]]*\"?([^\"#]*)\"?.*/\1/p" \
            "$file" | head -n 1 | sed 's/[[:space:]]*$//'
    }
    
    # A resolved marker's `[profile]` flag as the explicit value the hook sends:
    # 0 for a falsy value (0/false/no/off, any case), 1 for anything else, an
    # absent key included. Parity with `profile_flag_value` in hook_capture.rs.
    ai_memory_profile_flag() {
        case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
            0 | false | no | off) printf '0' ;;
            *) printf '1' ;;
        esac
    }
    
    # Whether "$1" (a marker file) declares anything beyond a `[capture]`
    # section: any root-level scope key (workspace/project/project_strategy), or
    # any of the other settings ai_memory_marker_qs / ai_memory_briefing_qs
    # forward (drop_subagent_captures, default_global, [briefing] and [profile]
    # keys). Mirrors
    # `declares_more_than_capture` in marker.rs. A marker with any of these is a
    # resolution boundary; only a marker whose only content is `[capture]` (e.g.
    # ignore_paths) is scope/settings-transparent (#668).
    ai_memory_marker_declares_settings() {
        file="$1"
        [ -f "$file" ] || return 1
        for key in workspace project project_strategy drop_subagent_captures identity identity_style; do
            [ -n "$(ai_memory_parse_toml_key "$file" "$key")" ] && return 0
        done
        LC_ALL=C grep -Eq '^[[:space:]]*aliases[[:space:]]*=' "$file" && return 0
        ai_memory_marker_declares_server "$file" && return 0
        for key in default_global inject_on_session_start max_chars contribute consume; do
            [ -n "$(ai_memory_parse_toml_flag "$file" "$key")" ] && return 0
        done
        return 1
    }
    
    # Whether marker "$1" declares a `server = ...` profile selection (#992), in
    # any value shape. Line-based and section-blind like marker.rs
    # `server_selection_in`; the leading class also skips a UTF-8 BOM, which is
    # not whitespace to grep. An unreadable marker counts as declaring one.
    ai_memory_marker_declares_server() {
        [ -r "$1" ] || return 0
        LC_ALL=C grep -Eq '^[^A-Za-z0-9_#"]*server[[:space:]]*=' "$1"
    }
    
    # Whether any marker on the walk from "$1" (default: the process cwd)
    # selects a server profile (#992). These script hooks cannot route profiles
    # — only native `ai-memory hook` commands can — so a routed repository must
    # emit nothing rather than reach the install-default server. Mirrors the
    # native walk: inside $HOME it stops at $HOME; outside it continues past the
    # checkout root, so an organisation-level marker above a repository counts.
    ai_memory_server_routed() {
        _amsr_dir="${1:-${PWD:-}}"
        [ -z "$_amsr_dir" ] && return 1
        _amsr_boundary=""
        ai_memory_home
        if [ -n "$_amhome" ]; then
            case "$_amsr_dir" in
                "$_amhome"|"${_amhome%/}"/*) _amsr_boundary="$_amhome" ;;
            esac
        fi
        while [ -n "$_amsr_dir" ]; do
            if [ -f "$_amsr_dir/.ai-memory.toml" ] \
                && ai_memory_marker_declares_server

Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.

Ships withai-memory

Solution for long term memory for agent coding CLIs and to facilitate handoff between different agent vendors

Get the whole plugin