Skip to content

Instantly share code, notes, and snippets.

@audreyt
Last active June 24, 2026 06:40
Show Gist options
  • Select an option

  • Save audreyt/fd11a2733671d205a4d6b05df95ebc10 to your computer and use it in GitHub Desktop.

Select an option

Save audreyt/fd11a2733671d205a4d6b05df95ebc10 to your computer and use it in GitHub Desktop.
Mnemon extension for https://omp.sh — builds on Claude Code hook in https://github.com/mnemon-dev/mnemon
// ~/.omp/agent/extensions/mnemon.js
import { existsSync } from "node:fs";
import { execFileSync, spawnSync } from "node:child_process";
import { homedir } from "node:os";
import { join } from "node:path";
const HOME = homedir();
const HOOK_DIR = join(HOME, ".claude", "hooks", "mnemon");
const PROMPT_DIR = join(process.env.MNEMON_DATA_DIR ?? join(HOME, ".mnemon"), "prompt");
const MAX_HOOK_OUTPUT_CHARS = 12_000;
const MAX_ASSISTANT_TEXT_CHARS = 8_000;
const MAX_RECALL_QUERY_CHARS = 500;
const MNEMON_EXT_VERSION = "2026-06-22-squashed-ui";
const SILENT_PRIME_MAX_CHARS = 2_400;
function mnemonSuppressCallRender() {
return { render() { return []; } };
}
function mnemonResultStatusLine(theme, { glyph, tool, preview, ok }) {
const mark = ok
? (typeof theme?.fg === "function" ? theme.fg("success", "✔") : "✔")
: (typeof theme?.fg === "function" ? theme.fg("error", "✘") : "✘");
const label = typeof theme?.fg === "function" ? theme.fg("accent", `${glyph} ${tool}`) : `${glyph} ${tool}`;
const line = String(preview ?? "").split("\n")[0].trim() || (ok ? "Done" : "Failed");
return {
render(width) {
const limit = Math.max(10, width - (6 + tool.length + glyph.length));
const clipped = line.length > limit ? `${line.slice(0, limit)}...` : line;
const body = clipped.startsWith("\"") && clipped.endsWith("\"") ? clipped : `"${clipped}"`;
return [`${mark} ${label}: ${body}`];
},
};
}
function normalizeHookOutput(result) {
if (typeof result === "string") return result;
if (Array.isArray(result?.content)) {
return result.content
.map((chunk) => (typeof chunk === "string" ? chunk : chunk?.text ?? ""))
.join("");
}
return result?.stdout ?? result?.output ?? result?.text ?? "";
}
function runHookSync(name) {
const script = join(HOOK_DIR, name);
if (!existsSync(script)) return "";
const result = spawnSync("/bin/bash", [script], {
encoding: "utf8",
env: process.env,
timeout: 5_000,
maxBuffer: 256 * 1024,
});
if (result.error || result.status !== 0) {
return "";
}
return String(result.stdout ?? "").slice(0, MAX_HOOK_OUTPUT_CHARS).trim();
}
async function runHook(pi, name) {
const script = join(HOOK_DIR, name);
if (!existsSync(script)) return "";
if (typeof pi?.exec === "function") {
try {
const result = await pi.exec(`/bin/bash ${JSON.stringify(script)}`);
return normalizeHookOutput(result).slice(0, MAX_HOOK_OUTPUT_CHARS).trim();
} catch {
// Fall through to the local process runner. Hook failure should not block OMP.
}
}
return runHookSync(name);
}
function silentPrimeOps() {
return `Before answering: When the message may need past context, call the "mnemon_recall" tool. After substantive replies: if durable memory is warranted, call the "mnemon_remember" tool in a follow-up turn to store it. If nothing to store on the remember follow-up turn: emit a single zero-width space ("\u200b") as the entire response.`;
}
function recallBlockSilent(query) {
const q = String(query ?? "").trim().slice(0, MAX_RECALL_QUERY_CHARS);
if (!q) return "";
try {
execFileSync("mnemon", ["status"], { stdio: "ignore", timeout: 2_000 });
} catch {
return "";
}
try {
const raw = execFileSync("mnemon", ["recall", q, "--limit", "3"], {
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
timeout: 8_000,
maxBuffer: 128 * 1024,
});
const parsed = JSON.parse(raw);
const results = parsed.results ?? [];
if (!results.length) return "";
const lines = results.slice(0, 3).map((r) => {
const body = String(r.content ?? "").trim().replace(/\s+/g, " ");
const clipped = body.length > 320 ? `${body.slice(0, 317)}…` : body;
return `- (${r.category ?? "general"}, imp ${r.importance ?? "?"}) ${clipped}`;
});
return lines.join("\n");
} catch {
return "";
}
}
function collectText(value, out = []) {
if (typeof value === "string") {
out.push(value);
return out;
}
if (!value || typeof value !== "object") return out;
if (Array.isArray(value)) {
for (const item of value) collectText(item, out);
return out;
}
for (const [key, child] of Object.entries(value)) {
if (key === "thinking" || key === "thoughtSignature") continue;
collectText(child, out);
}
return out;
}
function latestUserText(ctx) {
const branch = ctx?.sessionManager?.getBranch?.() ?? [];
for (let i = branch.length - 1; i >= 0; i -= 1) {
const entry = branch[i];
if (entry?.type === "message" && entry?.message?.role === "user") {
const text = collectText(entry.message.content).join("\n").trim();
if (text) return text;
}
if (entry?.role === "user") {
const text = collectText(entry.content ?? entry).join("\n").trim();
if (text) return text;
}
}
return "";
}
function latestAssistantText(ctx) {
const branch = ctx?.sessionManager?.getBranch?.() ?? [];
for (let i = branch.length - 1; i >= 0; i -= 1) {
const entry = branch[i];
let raw = "";
try {
raw = JSON.stringify(entry);
} catch {
raw = String(entry ?? "");
}
if (!raw.includes("assistant")) continue;
const text = collectText(entry).join("\n").trim();
if (text) return text.slice(-MAX_ASSISTANT_TEXT_CHARS);
return raw.slice(-MAX_ASSISTANT_TEXT_CHARS);
}
return "";
}
const REMEMBER_ALREADY_HANDLED_RE =
/mnemon remember|mnemon_remember|Stored.*imp=|mnemon memory writer|Saved.*mnemon|Stored insight|Spawned agent.*[Mm]nemon/i;
const NO_MEMORY_OUTPUT_RE = /^\[mnemon\]\s*no memory needed\.?$/i;
const SILENT_ASSISTANT_OUTPUT_RE =
/^(?:\[mnemon\]\s*no memory needed\.?|Saved the .* to mnemon\s*\([^)]+\)\.?\s*)$/i;
function isSilentRememberReply(text) {
const t = String(text ?? "").replace(/\u200b/g, "").trim();
return !t || NO_MEMORY_OUTPUT_RE.test(t) || SILENT_ASSISTANT_OUTPUT_RE.test(t);
}
function rememberFollowupContent() {
return `<system-directive attribution="system" display="false">
Evaluate the exchange that just ended (user message + your prior reply). Guide: ~/.mnemon/prompt/guide.md decision tree.
If durable memory is warranted: call the "mnemon_remember" tool with the memory text, category, and importance. Review the candidates returned, and call "mnemon_link" if a causal or semantic relationship exists. Do NOT spawn a subagent or call "task".
If nothing durable: emit a single zero-width space ("\u200b") as the entire response (no status line, no "Saved...", no other characters/rationales).
FORBIDDEN in visible output: thinking blocks, step-by-step rationale.
</system-directive>`;
}
function injectionMessage(parts) {
const content = parts.filter(Boolean).join("\n\n");
if (!content) return undefined;
return {
customType: "mnemon",
content: `<system-directive attribution="system" display="false">\n${content}\n</system-directive>`,
display: false,
attribution: "system",
};
}
export default function mnemonExtension(pi) {
let inputSerial = 0;
let remindedSerial = -1;
let primeInjected = false;
let suppressFollowupThinking = false;
if (typeof pi.registerAssistantThinkingRenderer === "function") {
pi.registerAssistantThinkingRenderer(() => {
if (!suppressFollowupThinking) return undefined;
return null;
});
}
if (typeof pi.registerMessageRenderer === "function") {
pi.registerMessageRenderer("mnemon-followup", () => null);
pi.registerMessageRenderer("mnemon", () => null);
}
const { z } = pi.zod;
if (typeof pi.registerTool === "function") {
pi.registerTool({
name: "mnemon_remember",
label: "Mnemon Remember",
renderShell: "self",
description: "Write a durable memory to the persistent store.",
parameters: z.object({
memory: z.string().describe("Verbatim text of the memory to store"),
category: z.enum(["preference", "decision", "insight", "fact", "context"]).describe("Memory category"),
importance: z.number().min(1).max(5).describe("Significance rating 1-5"),
entities: z.string().optional().describe("Comma-separated associated entities"),
}),
renderCall() {
return mnemonSuppressCallRender();
},
renderResult(result, options, theme, args) {
const ok = result?.details?.success !== false && !result?.isError;
const preview = ok
? String(result?.details?.summary ?? args?.memory ?? "Stored successfully")
: String(result?.details?.summary ?? args?.memory ?? "Failed");
return mnemonResultStatusLine(theme, { glyph: "💾", tool: "mnemon_remember", preview, ok });
},
async execute(toolCallId, params, signal, onUpdate, ctx) {
const memoryText = params?.memory ?? "";
if (!memoryText) {
return {
content: [{ type: "text", text: "No memory provided" }],
details: { success: false, summary: "No memory provided" },
};
}
const args = ["remember", memoryText];
if (params.category) {
args.push("--cat", params.category);
}
if (params.importance) {
args.push("--imp", String(params.importance));
}
if (params.entities) {
args.push("--entities", params.entities);
}
args.push("--source", "agent");
try {
let output = "";
if (typeof pi?.exec === "function") {
const result = await pi.exec("mnemon", args, { signal });
output = typeof result === "string" ? result : (result?.stdout ?? result?.output ?? result?.text ?? "");
} else {
const result = spawnSync("mnemon", args, {
encoding: "utf8",
env: process.env,
timeout: 5_000,
});
if (result.error || result.status !== 0) {
const errMsg = result.stderr?.trim() || result.error?.message || "Execution failed";
return {
content: [{ type: "text", text: `Error: ${errMsg}` }],
details: { success: false, summary: `Error: ${errMsg}` },
};
}
output = String(result.stdout ?? "").trim();
}
let summary = params.memory;
try {
const parsed = JSON.parse(output);
if (parsed.content) {
summary = parsed.content;
}
} catch {
// fallback to parameter
}
return {
content: [{ type: "text", text: output }],
details: { success: true, summary },
};
} catch (err) {
const errMsg = err.stderr || err.message || String(err);
return {
content: [{ type: "text", text: `Error: ${errMsg}` }],
details: { success: false, summary: errMsg },
};
}
}
});
pi.registerTool({
name: "mnemon_link",
label: "Mnemon Link",
renderShell: "self",
description: "Create an association link between two memories.",
parameters: z.object({
id1: z.string().describe("First memory ID"),
id2: z.string().describe("Second memory ID"),
type: z.enum(["causal", "semantic", "temporal", "entity"]).describe("Association type"),
weight: z.number().min(0).max(1).describe("Association weight (0-1)"),
}),
renderCall() {
return mnemonSuppressCallRender();
},
renderResult(result, options, theme, args) {
const ok = result?.details?.success !== false && !result?.isError;
const fallback = `${args?.id1 ?? "?"} <-> ${args?.id2 ?? "?"} (${args?.type ?? "?"})`;
const preview = String(result?.details?.summary ?? fallback);
return mnemonResultStatusLine(theme, { glyph: "🔗", tool: "mnemon_link", preview, ok });
},
async execute(toolCallId, params, signal, onUpdate, ctx) {
const { id1, id2, type, weight } = params;
const args = ["link", id1, id2, "--type", type, "--weight", String(weight)];
try {
let output = "";
if (typeof pi?.exec === "function") {
const result = await pi.exec("mnemon", args, { signal });
output = typeof result === "string" ? result : (result?.stdout ?? result?.output ?? result?.text ?? "");
} else {
const result = spawnSync("mnemon", args, {
encoding: "utf8",
env: process.env,
timeout: 5_000,
});
if (result.error || result.status !== 0) {
const errMsg = result.stderr?.trim() || result.error?.message || "Execution failed";
return {
content: [{ type: "text", text: `Error: ${errMsg}` }],
details: { success: false, summary: `Error: ${errMsg}` },
};
}
output = String(result.stdout ?? "").trim();
}
let summary = `${id1.slice(0, 8)} <-> ${id2.slice(0, 8)} (${type})`;
try {
const parsed = JSON.parse(output);
if (parsed.source_id && parsed.target_id) {
summary = `${parsed.source_id.slice(0, 8)} <-> ${parsed.target_id.slice(0, 8)} (${parsed.edge_type ?? type})`;
}
} catch {
// fallback
}
return {
content: [{ type: "text", text: output }],
details: { success: true, summary },
};
} catch (err) {
const errMsg = err.stderr || err.message || String(err);
return {
content: [{ type: "text", text: `Error: ${errMsg}` }],
details: { success: false, summary: errMsg },
};
}
}
});
pi.registerTool({
name: "mnemon_recall",
label: "Mnemon Recall",
renderShell: "self",
description: "Retrieve relevant memories from the persistent store by semantic query.",
parameters: z.object({
query: z.string().describe("Natural-language query to match memories against"),
limit: z.number().min(1).max(50).optional().describe("Maximum memories to return (default 10)"),
}),
renderCall() {
return mnemonSuppressCallRender();
},
renderResult(result, options, theme, args) {
const ok = result?.details?.success !== false && !result?.isError;
const preview = ok
? String(result?.details?.summary ?? args?.query ?? "Recalled")
: String(result?.details?.summary ?? args?.query ?? "Failed");
return mnemonResultStatusLine(theme, { glyph: "🔎", tool: "mnemon_recall", preview, ok });
},
async execute(toolCallId, params, signal, onUpdate, ctx) {
const query = String(params?.query ?? "").trim();
if (!query) {
return {
content: [{ type: "text", text: "No query provided" }],
details: { success: false, summary: "No query provided" },
};
}
const args = ["recall", query, "--limit", String(params.limit ?? 10)];
try {
let output = "";
if (typeof pi?.exec === "function") {
const result = await pi.exec("mnemon", args, { signal });
output = typeof result === "string" ? result : (result?.stdout ?? result?.output ?? result?.text ?? "");
} else {
const result = spawnSync("mnemon", args, {
encoding: "utf8",
env: process.env,
timeout: 8_000,
});
if (result.error || result.status !== 0) {
const errMsg = result.stderr?.trim() || result.error?.message || "Execution failed";
return {
content: [{ type: "text", text: `Error: ${errMsg}` }],
details: { success: false, summary: `Error: ${errMsg}` },
};
}
output = String(result.stdout ?? "").trim();
}
let summary = "Recalled";
let count = 0;
try {
const parsed = JSON.parse(output);
count = Array.isArray(parsed?.results) ? parsed.results.length : 0;
summary = count ? `${count} memor${count === 1 ? "y" : "ies"} recalled` : "No memories found";
} catch {
summary = output ? "Recalled" : "No memories found";
}
return {
content: [{ type: "text", text: output }],
details: { success: true, summary, count },
};
} catch (err) {
const errMsg = err.stderr || err.message || String(err);
return {
content: [{ type: "text", text: `Error: ${errMsg}` }],
details: { success: false, summary: errMsg },
};
}
}
});
}
pi.on("session_start", async () => {
primeInjected = false;
await runHook(pi, "prime.sh");
});
pi.on("message_end", async (event) => {
if (!suppressFollowupThinking) return;
const msg = event?.message;
if (msg?.role !== "assistant") return;
const text = collectText(msg.content).join("\n").trim();
if (!isSilentRememberReply(text)) {
suppressFollowupThinking = false;
return;
}
suppressFollowupThinking = false;
if (msg) {
if ("display" in msg) msg.display = false;
msg.content = typeof msg.content === "string" ? "\u200b" : [{ type: "text", text: "\u200b" }];
}
return { message: msg };
});
pi.on("input", async () => {
inputSerial += 1;
});
pi.on("before_agent_start", async (_event, ctx) => {
const parts = [];
if (!primeInjected) {
parts.push(silentPrimeOps());
primeInjected = true;
}
const userText = latestUserText(ctx);
const recalled = recallBlockSilent(userText);
if (recalled) parts.push(recalled);
const message = injectionMessage(parts);
return message ? { message } : undefined;
});
pi.on("agent_end", async (_event, ctx) => {
if (suppressFollowupThinking) {
return;
}
const serial = inputSerial;
if (remindedSerial === serial) return;
remindedSerial = serial;
const assistant = latestAssistantText(ctx);
if (REMEMBER_ALREADY_HANDLED_RE.test(assistant)) return;
const text = rememberFollowupContent();
if (typeof pi.sendMessage === "function") {
try {
suppressFollowupThinking = true;
await pi.sendMessage(
{
customType: "mnemon-followup",
content: text,
display: false,
attribution: "system",
},
{ deliverAs: "followUp", triggerTurn: true, hideThinking: true },
);
} catch {
suppressFollowupThinking = false;
}
}
});
pi.on("session_before_compact", async () => {
return {
customInstructions:
"Preserve only critical continuity via mnemon remember when justified; do not store the full transcript.",
};
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment