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.
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

