Productivity
Hook
Hooks
What nagisanzenin-engram 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 nagisanzenin/engram > /plugin install engram@engram
Ships with nagisanzenin-engram. Installing the plugin gets these hooks.
What fires, and when
SessionStart
Fires once when a session begins, and again after a context compaction. It is where a plugin sets up its environment, or restores state the compaction dropped.
- Matches
startup|resume|clear|compact"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh"
Where it lives
- hooks/session-start-dsh.shGitHub
Read the script
#!/usr/bin/env bash # Engram re-anchor hook for DeepSeek Harness's Claude Code hook bridge. # dsh's bridge consumes ONLY the JSON hookSpecificOutput.additionalContext # shape — plain SessionStart stdout is discarded (documented dsh limitation), # so this wrapper emits what Claude Code's JSON hook contract allows and dsh # actually reads. Degrades to silence on any failure; prints nothing (valid: # no output) when no reviews are due. set -u command -v python3 >/dev/null 2>&1 || exit 0 ROOT="${CLAUDE_PLUGIN_ROOT:-}" if [ -z "$ROOT" ] || [ ! -f "$ROOT/scripts/engram.py" ]; then ROOT="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")/.." 2>/dev/null && pwd)" fi [ -f "$ROOT/scripts/engram.py" ] || exit 0 NUDGE="$(python3 "$ROOT/scripts/engram.py" session-start 2>/dev/null || true)" [ -n "$NUDGE" ] || exit 0 python3 - "$NUDGE" <<'PY' 2>/dev/null || true import json, sys print(json.dumps({"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": sys.argv[1]}})) PY exit 0 - hooks/session-start-hermes.shGitHub
Read the script
#!/usr/bin/env bash # Engram re-anchor hook for Hermes Agent. # Two modes, auto-detected: # hook mode (stdin carries Hermes' pre_llm_call JSON payload): emit # {"context": "<nudge>"} once per session, {} on every other call. # Register in ~/.hermes/config.yaml: # hooks: # pre_llm_call: # - command: "/path/to/engram/hooks/session-start-hermes.sh" # timeout: 15 # plain mode (stdin empty — e.g. `hermes cron create --no-agent --script …`): # print the nudge as plain text (nothing when nothing is due). # On Hermes, /learn is the built-in skill-authoring command, so the nudge's # "/learn" is rewritten to "/skill learn" in both modes. # Contract (Constitution art. 8): ambient, never nagging — at most one nudge # per session, and on ANY failure degrade to silence, never to repetition. set -u command -v python3 >/dev/null 2>&1 || { printf '{}\n'; exit 0; } payload="$(cat - 2>/dev/null || true)" emit_nudge() { # prints the rewritten nudge (empty when nothing is due) python3 "$ROOT/scripts/engram.py" session-start 2>/dev/null | sed 's|/learn|/skill learn|g' || true } # Engine resolution: env override, else self-resolve from this script's location. ROOT="${ENGRAM_ROOT:-}" if [ -z "$ROOT" ] || [ ! -f "$ROOT/scripts/engram.py" ]; then ROOT="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")/.." 2>/dev/null && pwd)" fi [ -f "$ROOT/scripts/engram.py" ] || { [ -n "$payload" ] && printf '{}\n'; exit 0; } # Plain mode: no stdin payload (cron --no-agent, manual run). No JSON, no dedupe # (each scheduled run SHOULD deliver); stdout goes to the delivery target verbatim. if [ -z "$payload" ]; then emit_nudge exit 0 fi # Hook mode. Dedupe key: the sanitized session id; if extraction fails, fall back # to the parent (Hermes) PID so the guard fails CLOSED — at most one nudge per # Hermes process — never open (one per LLM call). session_id="$(printf '%s' "$payload" | python3 -c 'import sys,json try: print(json.load(sys.stdin).get("session_id") or "") except Exception: print("")' 2>/dev/null | tr -c 'A-Za-z0-9_-' '_' | cut -c1-80)" || session_id="" [ -n "$session_id" ] || session_id="pid-${PPID:-0}" marker="${TMPDIR:-/tmp}/engram-nudge-${session_id}" if [ -e "$marker" ]; then printf '{}\n'; exit 0; fi # Unwritable marker dir → we could not remember having nudged, so stay silent: # silence over repetition, per the contract. if ! { : > "$marker"; } 2>/dev/null; then printf '{}\n'; exit 0; fi out="$(emit_nudge)" if [ -n "$out" ]; then printf '%s' "$out" | python3 -c 'import sys,json; print(json.dumps({"context": sys.stdin.read().strip()}))' 2>/dev/null || printf '{}\n' else printf '{}\n' fi exit 0 - hooks/session-start.shRunsGitHub
Read the script
#!/usr/bin/env bash # Engram re-anchor hook: surfaces due reviews at session start. # ONE registration (hooks/hooks.json), consumed differently per platform: # - Claude Code / Codex / OpenClaw's codex bundle inject PLAIN stdout into # context, so default output stays plain text. # - ZCode discards plain SessionStart stdout — its runner parses ONLY JSON # (hookSpecificOutput.additionalContext). Its plugin context also exports # ZCODE_PLUGIN_ROOT alongside the legacy CLAUDE_PLUGIN_ROOT, so that var is # both the runtime tell AND a working root: when present, emit the JSON # shape ZCode actually reads. Root resolution never assumes which of the # platform roots identified the runtime correctly, because they all point # at the same install path. # - The manual/config-file route has no plugin-root variable at all; users # who wire this script by hand set ENGRAM_HOOK_FORMAT=json themselves # (INSTALL-ZCODE.md documents exactly that command line). # Prints at most two lines (or nothing) — ambient, never nagging (art. 8). # Must never break a session: degrade to silence on any failure. # Portable across Claude Code and Codex: uses the plugin-root env var if set, # else self-resolves relative to this script's own location. set -u command -v python3 >/dev/null 2>&1 || exit 0 emit_json() { python3 - "$1" <<'PY' 2>/dev/null || true import json, sys print(json.dumps({"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": sys.argv[1]}})) PY } ROOT="${ZCODE_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-}}}" if [ -z "$ROOT" ] || [ ! -f "$ROOT/scripts/engram.py" ]; then ROOT="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")/.." 2>/dev/null && pwd)" fi [ -f "$ROOT/scripts/engram.py" ] || exit 0 NUDGE="$(python3 "$ROOT/scripts/engram.py" session-start 2>/dev/null || true)" [ -n "$NUDGE" ] || exit 0 # silent when nothing is due — case "${ENGRAM_HOOK_FORMAT:-}" in # valid output on every consumer json) emit_json "$NUDGE"; exit 0 ;; esac if [ -n "${ZCODE_PLUGIN_ROOT:-}" ]; then # ZCode eats plain text; give it emit_json "$NUDGE" # the one shape it will parse exit 0 fi printf '%s\n' "$NUDGE" exit 0 - hooks/session-start.tsGitHub
Read the script
/** * Engram — Session Start Hooks * ============================= * * Two hooks injected into OpenCode's session lifecycle: * * system.transform — fires once per session (firstTransform guard). * Runs `engram.py session-start` for the review-due nudge, then calls * readUpdateSummary() to check for pending plugin updates. Injects both * messages into the system prompt. * * readUpdateSummary() reads .engram-update.jsonc at the target directory * (project-level .opencode/ or global ~/.config/opencode/) and returns: * null — no manifest → silent * "pending" → "Updates Engram Available!\nRun /engram-update" * "in_progress" → "Update partially applied. Run /engram-update to continue" * corrupt/absent → null (gracefully degrades) * * event(session.idle) — fires a TUI toast when an update manifest exists on disk. * Guard: toastShown (one per session). Calls readUpdateSummary() — if non-null, * shows client.tui.showToast() with "Updates Engram Available!". * Best-effort — all errors caught via .catch(() => {}). * * Both hooks are wrapped in try/catch — never crash the host. */ import { resolve } from "node:path" import { existsSync, readFileSync } from "node:fs" /** * Creates and returns system.transform and event hooks for session start. * @param $ OpenCode bash executor (tagged template) * @param root package root directory * @param client OpenCode client (for tui.showToast) */ export function createSessionStartHooks($: any, root: string, client: any) { let firstTransform = true let toastShown = false function computeTarget(): string { const home = process.env.HOME || process.env.USERPROFILE || "/tmp" const cwd = process.cwd() const projectJson = resolve(cwd, "opencode.json") const projectJsonc = resolve(cwd, "opencode.jsonc") if (existsSync(projectJson) || existsSync(projectJsonc)) { return resolve(cwd, ".opencode") } return resolve(home, ".config", "opencode") } function readUpdateSummary(): string | null { const target = computeTarget() const f = resolve(target, ".engram-update.jsonc") if (!existsSync(f)) return null try { const m = JSON.parse(readFileSync(f, "utf-8")) if (m.state === "in_progress") { return "Update partially applied. Run /engram-update to continue" } return "Updates Engram Available!\nRun /engram-update" } catch { return null } } return { async "experimental.chat.system.transform"(_input: any, output: { system: string[] }) { try { if (!firstTransform) return firstTransform = false const engramPy = resolve(root, "scripts", "engram.py") const result = await $`python3 ${engramPy} session-start`.nothrow().quiet() const nudge = result.stdout.toString().trim() if (nudge) output.system.push(`\n[engram] ${nudge}`) const updateSummary = readUpdateSummary() if (updateSummary) { output.system.push(`\n[engram] ${updateSummary}`) } } catch {} }, async event(input: { event: any }) { try { if (toastShown) return if (input.event.type !== "session.idle") return const updateSummary = readUpdateSummary() if (!updateSummary) return toastShown = true client.tui.showToast({ body: { title: "Engram", message: "Updates Engram Available!\nRun /engram-update", variant: "info", duration: 30000 }, }).catch(() => {}) } catch {} }, } } - hooks/shell-env.tsGitHub
Read the script
/** * Engram — Shell Environment Hook * ================================ * * Injects OPENCODE_PLUGIN_ROOT and ENGRAM_ROOT into every shell execution. * Resolves to the extracted .opencode/ target if engram.py exists there, * falling back to the npm package root (pre-extract). * * Also forwards ENGRAM_HOME and ENGRAM_TODAY from the process environment * to child shells (used by the engine for state isolation and time-travel tests). */ import { existsSync } from "node:fs" import { resolve } from "node:path" /** * Creates the shell.env hook. Injects ENGRAM_ROOT and OPENCODE_PLUGIN_ROOT * at every shell execution. * @param packageRoot npm cache path (fallback when not yet extracted) */ export function createShellEnvHook(packageRoot: string) { return { async "shell.env"(input: any, output: { env: Record<string, string> }) { try { const cwd = input.cwd || process.cwd() const target = extractTarget(cwd) const pluginRoot = existsSync(resolve(target, "scripts", "engram.py")) ? target : packageRoot output.env["ENGRAM_ROOT"] = pluginRoot output.env["OPENCODE_PLUGIN_ROOT"] = pluginRoot if (process.env.ENGRAM_HOME) output.env["ENGRAM_HOME"] = process.env.ENGRAM_HOME if (process.env.ENGRAM_TODAY) output.env["ENGRAM_TODAY"] = process.env.ENGRAM_TODAY } catch {} }, } } function extractTarget(cwd: string): string { const home = process.env.HOME || process.env.USERPROFILE || "/tmp" const projectJson = resolve(cwd, "opencode.json") const projectJsonc = resolve(cwd, "opencode.jsonc") if (existsSync(projectJson) || existsSync(projectJsonc)) { return resolve(cwd, ".opencode") } return resolve(home, ".config", "opencode") }
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 withnagisanzenin-engram
Evidence-based learning engine — first-principles curricula, free-recall verification with receipts, FSRS-scheduled memory, and explorable artifacts. Learn anything; keep it.
Get the whole plugin

