Skill: spooktheducks/honcho-memory-mux (version 0.1.0)

--- name: honcho-memory-mux-setup description: > Install and enable Honcho + the @alloralabs/honcho-memory-mux extension, migrate legacy file memory, and update workspace instructions so agents use memory via Honcho-first multiplexed retrieval with local-file fallback. metadata: openclaw: emoji: "🧠" optional_env: - name: HONCHO_API_KEY description: "API key. Optional for many local/self-hosted setups." - name: HONCHO_BASE_URL description: "Base URL for self-hosted Honcho (example: http://localhost:8000)." - name: HONCHO_WORKSPACE_ID description: "Honcho workspace ID (default: openclaw)." - name: WORKSPACE_ROOT description: "OpenClaw workspace root. Auto-detected if unset." required_binaries: - node - npm optional_binaries: - git - docker - docker-compose writes_to_disk: true network_access: - "User-configured HONCHO_BASE_URL (self-hosted mode)" homepage: "https://honcho.dev" source: - "https://github.com/plastic-labs/honcho" - "https://github.com/allora-network/honcho-memory-mux" --- # Honcho + Memory Mux Setup This skill installs and enables: 1) `@honcho-ai/openclaw-honcho` (Honcho tools + integration) 2) `@alloralabs/honcho-memory-mux` (multiplexed memory behavior) Then it migrates legacy memory files, updates agent docs, and validates runtime behavior. --- ## What “memory mux” means in practice After setup, memory behavior should be: - **Primary retrieval:** Honcho tools (`honcho_context`, `honcho_search`, etc.) - **Secondary retrieval:** local memory files (`MEMORY.md`, `memory/*.md`) via `memory_search` / `memory_get` - **Durability:** pre-compaction flush writes significant session learnings to `memory/YYYY-MM-DD.md` - **Fallback safety:** if Honcho is unavailable, file-based memory still works This is intentionally **complementary**, not a hard cutover. --- ## Step 0: Preflight and safety checks Before doing anything destructive: - Resolve workspace root from: 1. `WORKSPACE_ROOT` 2. `~/.openclaw/openclaw.json` (`agent.workspace` or `agents.defaults.workspace`) 3. `~/.openclaw/workspace` - Confirm directory exists - Inform user this process can upload memory files to Honcho --- ## Step 1: Install + enable Honcho plugin Use OpenClaw plugin manager (not workspace `npm install`): ```bash openclaw plugins install @honcho-ai/openclaw-honcho openclaw plugins enable openclaw-honcho ``` If gateway logs show missing SDK deps: ```bash cd ~/.openclaw/extensions/openclaw-honcho npm install ``` Restart gateway if required. --- ## Step 2: Install + enable memory mux extension Install and enable the memory-mux extension: ```bash openclaw plugins install @alloralabs/honcho-memory-mux openclaw plugins enable @alloralabs/honcho-memory-mux ``` If the extension is local/unpublished, install from path or git URL per your release flow. After enabling: - Verify plugin appears in `openclaw plugins list` - Verify gateway boots cleanly (no extension load errors) - Verify both Honcho tools and `memory_search` / `memory_get` are present at runtime --- ## Step 3: Verify Honcho connectivity (self-hosted) - Set `HONCHO_BASE_URL` (example: `http://localhost:8000`) - `HONCHO_API_KEY` may be optional depending on auth config If user needs local Honcho quickly: ```bash git clone https://github.com/plastic-labs/honcho cd honcho cp .env.template .env cp docker-compose.yml.example docker-compose.yml docker compose up -d ``` Do not migrate until connection is verified. --- ## Step 4: Detect and classify legacy memory files Scan workspace for: ### User/owner memory inputs - `USER.md` - `IDENTITY.md` - `MEMORY.md` - `memory/**` (recursive) - `canvas/**` (recursive) ### Agent/self memory inputs - `SOUL.md` - `AGENTS.md` (and `AGENT.md` if present in this workspace) - `TOOLS.md` - `BOOTSTRAP.md` - `HEARTBEAT.md` Present a clear preview before upload: - count files per class - total bytes per class - any unreadable/skipped files Ask for explicit confirmation. --- ## Step 5: Upload to Honcho using peer-separated sessions Create/ensure: - workspace (`HONCHO_WORKSPACE_ID` or `openclaw`) - peer `owner` - peer `openclaw` - migration session (e.g. `migration-upload-<timestamp>`) Add both peers to session, then upload files: - user/owner files → `owner` - agent/self files → `openclaw` If any upload fails: - stop - report exact file and error - keep local files unchanged Report summary: - uploaded user file count - uploaded agent file count - failed/skipped files --- ## Step 6: Update workspace docs (including AGENT.md compatibility) Update/create memory instructions in: - `AGENTS.md` - `SOUL.md` - `BOOTSTRAP.md` - `AGENT.md` (if this workspace uses it) Preserve custom content; only replace memory-specific sections. ### Required policy text to add 1. **Memory retrieval order** - First try `honcho_context` / `honcho_search` / `honcho_recall` - If needed, use `memory_search` + `memory_get` for local file recall - Use `honcho_session` for same-session recall 2. **Memory write behavior** - Important durable facts can be written via `honcho_write` - Pre-compaction flush writes concise durable notes to `memory/YYYY-MM-DD.md` 3. **Failure handling** - If Honcho unavailable: continue with local memory tools, state degraded mode - Never claim memory certainty without retrieval evidence 4. **Transparency** - When memory influences an answer, be able to explain source class (Honcho recall vs local file recall vs current-session context) --- ## Step 7: Add/refresh practical memory operating conventions Ensure docs reflect these conventions the agent has been using: - **Do not preload everything** into active reasoning if not needed - **Prefer targeted retrieval** over huge static context injection - **Treat disagreement as signal:** preserve conflicting observations; do not silently average - **Use pre-compaction flushes** to avoid losing high-value context during long sessions - **Separate memory types conceptually:** - durable facts - episodic notes - scratch/working notes Even if physical storage is mixed, behavior should preserve this distinction. --- ## Step 8: Optional (recommended) no-fork observability add-on Without modifying OpenClaw gateway source, the memory-mux extension can emit: - a small **memory load manifest** per session start (what memory sources were pulled) - a **pre-compaction write report** (what was persisted) Store under: - `memory/manifests/YYYY-MM-DD/<session>.json` This makes memory behavior debuggable while keeping upstream updates painless. --- ## Step 9: Validate end-to-end behavior Run a quick live test: 1. Ask agent about a known historical fact only in migrated memory 2. Confirm retrieval path works (Honcho first, fallback available) 3. Trigger/perform pre-compaction flush 4. Confirm new note appears in `memory/YYYY-MM-DD.md` 5. Re-ask fact in a new turn/session to validate durability --- ## Wrap-up report to user Provide: - installed/enabled plugin list - Honcho connectivity mode (managed vs self-hosted) - files discovered, uploaded, skipped - docs updated (`AGENTS.md`, `SOUL.md`, `BOOTSTRAP.md`, `AGENT.md` if present) - any follow-up actions Reference docs: - https://docs.honcho.dev - https://github.com/plastic-labs/honcho --- ## Guardrails - Never delete user memory files automatically unless user explicitly asks - Never proceed with migration on failed Honcho connectivity - Never silently ignore upload errors - Prefer reversible edits and clear summaries

path: extension/README.md
content: # honcho-memory-mux

OpenClaw memory extension + agent skill that:
- stores and retrieves conversational memory via Honcho
- keeps local markdown memory access available as backup via `memory_get`

## Install

```bash
npm install honcho-memory-mux
```

## Configure in OpenClaw

Reference the extension package and provide config:

```json
{
  "memory": {
    "extension": "honcho-memory-mux",
    "config": {
      "apiKey": "YOUR_HONCHO_API_KEY",
      "workspaceId": "openclaw",
      "baseUrl": "https://api.honcho.dev"
    }
  }
}
```

You can also set `HONCHO_API_KEY` as an environment variable.

## Development

```bash
npm install
npm run build
```

Build output is written to `dist/`.


path: extension/dist/index.d.ts
content: /**
 * Memory Multiplexer Plugin for OpenClaw
 *
 * Claims the "memory" slot. Provides:
 * - All honcho_* tools (search, recall, analyze, etc.) for retrieval
 * - memory_get for reading local workspace files
 * - agent_end hook that writes conversations to Honcho
 * - Local markdown files are written by the agent itself (via AGENTS.md behavior)
 *
 * This gives us dual-write (honcho cloud + local files) with honcho-only retrieval.
 * If honcho dies, local files are the backup. If we ditch honcho, swap this plugin
 * for memory-core and everything works.
 */
declare const _default: {
    id: string;
    name: string;
    description: string;
    kind: string;
    register(api: any): void;
};
export default _default;


path: extension/dist/index.js
content: /**
 * Memory Multiplexer Plugin for OpenClaw
 *
 * Claims the "memory" slot. Provides:
 * - All honcho_* tools (search, recall, analyze, etc.) for retrieval
 * - memory_get for reading local workspace files
 * - agent_end hook that writes conversations to Honcho
 * - Local markdown files are written by the agent itself (via AGENTS.md behavior)
 *
 * This gives us dual-write (honcho cloud + local files) with honcho-only retrieval.
 * If honcho dies, local files are the backup. If we ditch honcho, swap this plugin
 * for memory-core and everything works.
 */
import { Type } from "@sinclair/typebox";
import { Honcho } from "@honcho-ai/sdk";
function resolveConfig(pluginConfig) {
    return {
        [REDACTED_SECRET] || process.env.HONCHO_API_KEY || "",
        baseUrl: pluginConfig.baseUrl || "https://api.honcho.dev",
        workspaceId: pluginConfig.workspaceId || "openclaw",
    };
}
// ============================================================================
// Constants
// ============================================================================
const OWNER_ID = "owner";
const OPENCLAW_ID = "openclaw";
// ============================================================================
// Helpers
// ============================================================================
function buildSessionKey(ctx) {
    const base = ctx?.sessionKey ?? "default";
    const provider = ctx?.messageProvider ?? "unknown";
    return `${base}-${provider}`.replace(/[^a-zA-Z0-9-]/g, "-");
}
function cleanMessageContent(content) {
    let cleaned = content;
    cleaned = cleaned.replace(/<honcho-memory[^>]*>[\s\S]*?<\/honcho-memory>\s*/gi, "");
    cleaned = cleaned.replace(/<!--[^>]*honcho[^>]*-->\s*/gi, "");
    cleaned = cleaned.replace(/^\[\w+\s+.+?\s+id:\d+\s+[^\]]+\]\s*/, "");
    cleaned = cleaned.replace(/\s*\[message_id:\s*[^\]]+\]\s*$/, "");
    return cleaned.trim();
}
function extractMessages(rawMessages, ownerPeer, openclawPeer) {
    const result = [];
    for (const msg of rawMessages) {
        if (!msg || typeof msg !== "object")
            continue;
        const m = msg;
        if (m.role !== "user" && m.role !== "assistant")
            continue;
        let content = "";
        if (typeof m.content === "string") {
            content = m.content;
        }
        else if (Array.isArray(m.content)) {
            content = m.content
                .filter((b) => b?.type === "text")
                .map((b) => b.text)
                .filter((t) => typeof t === "string")
                .join("\n");
        }
        if (m.role === "user")
            content = cleanMessageContent(content);
        content = content.trim();
        if (content) {
            const peer = m.role === "user" ? ownerPeer : openclawPeer;
            result.push(peer.message(content));
        }
    }
    return result;
}
// ============================================================================
// Plugin
// ============================================================================
export default {
    id: "honcho-memory-mux",
    name: "Memory (Multiplexer)",
    description: "Dual-write memory: Honcho cloud + local markdown files",
    kind: "memory",
    register(api) {
        const cfg = resolveConfig(api.pluginConfig ?? {});
        const honcho = new Honcho({
            [REDACTED_SECRET],
            baseURL: cfg.baseUrl,
            workspaceId: cfg.workspaceId,
        });
        let ownerPeer = null;
        let openclawPeer = null;
        let initialized = false;
        async function ensureInitialized() {
            await honcho.setMetadata({});
            if (initialized)
                return;
            ownerPeer = await honcho.peer(OWNER_ID, { metadata: {} });
            openclawPeer = await honcho.peer(OPENCLAW_ID, {
                metadata: {},
                config: { observe_me: false },
            });
            initialized = true;
        }
        // ========================================================================
        // HOOK: gateway_start
        // ========================================================================
        api.on("gateway_start", async () => {
            try {
                await ensureInitialized();
                api.logger.info("[mux] Honcho connected");
            }
            catch (err) {
                api.logger.error(`[mux] Honcho init failed: ${err}`);
            }
        });
        // ========================================================================
        // HOOK: before_agent_start — inject honcho context into system prompt
        // ========================================================================
        api.on("before_agent_start", async (event, ctx) => {
            if (!event.prompt || event.prompt.length < 5)
                return;
            const sessionKey = buildSessionKey(ctx);
            try {
                await ensureInitialized();
                const session = await honcho.session(sessionKey, { metadata: {} });
                let context;
                try {
                    context = await session.context({
                        summary: true,
                        tokens: 2000,
                        peerTarget: ownerPeer,
                        peerPerspective: openclawPeer,
                    });
                }
                catch (e) {
                    if (e?.message?.toLowerCase().includes("not found"))
                        return;
                    throw e;
                }
                const sections = [];
                if (context.peerCard?.length)
                    sections.push(`Key facts:\n${context.peerCard.map((f) => `• ${f}`).join("\n")}`);
                if (context.peerRepresentation)
                    sections.push(`User context:\n${context.peerRepresentation}`);
                if (context.summary?.content)
                    sections.push(`Earlier in this conversation:\n${context.summary.content}`);
                if (!sections.length)
                    return;
                return {
                    systemPrompt: `## User Memory Context\n\n${sections.join("\n\n")}\n\nUse this context naturally when relevant.`,
                };
            }
            catch (err) {
                api.logger.warn?.(`[mux] Failed to fetch context: ${err}`);
            }
        });
        // ========================================================================
        // HOOK: agent_end — persist messages to Honcho
        // (local files are written by the agent via normal tool calls)
        // ========================================================================
        api.on("agent_end", async (event, ctx) => {
            if (!event.success || !event.messages?.length)
                return;
            const sessionKey = buildSessionKey(ctx);
            try {
                await ensureInitialized();
                const session = await honcho.session(sessionKey, { metadata: {} });
                let meta = await session.getMetadata();
                if (meta.lastSavedIndex === undefined) {
                    const startIndex = Math.max(0, event.messages.length - 2);
                    await session.setMetadata({ lastSavedIndex: startIndex });
                    meta = { lastSavedIndex: startIndex };
                }
                const lastSaved = meta.lastSavedIndex ?? 0;
                await session.addPeers([
                    [OWNER_ID, { observe_me: true, observe_others: false }],
                    [OPENCLAW_ID, { observe_me: false, observe_others: true }],
                ]);
                if (event.messages.length <= lastSaved)
                    return;
                const newMessages = extractMessages(event.messages.slice(lastSaved), ownerPeer, openclawPeer);
                if (newMessages.length > 0) {
                    await session.addMessages(newMessages);
                }
                await session.setMetadata({ ...meta, lastSavedIndex: event.messages.length });
            }
            catch (err) {
                api.logger.error(`[mux] Failed to save to Honcho: ${err}`);
            }
        });
        // ========================================================================
        // TOOL: memory_get — read local workspace files (the backup)
        // ========================================================================
        api.registerTool({
            name: "memory_get",
            label: "Read Memory File",
            description: "Safe snippet read from MEMORY.md or memory/*.md with optional from/lines; " +
                "use after memory_search to pull only the needed lines and keep context small.",
            parameters: Type.Object({
                path: Type.String({ description: "Relative path to file (e.g. MEMORY.md, memory/2026-02-12.md)" }),
                from: Type.Optional(Type.Number({ description: "Line to start reading from (1-indexed)" })),
                lines: Type.Optional(Type.Number({ description: "Max lines to read" })),
            }),
            async execute(_id, params, _signal) {
                // Delegate to the builtin helper if available
                const tool = api.runtime.tools.createMemoryGetTool({
                    config: api.config,
                    agentSessionKey: "main",
                });
                if (tool) {
                    return tool.execute(_id, params, _signal);
                }
                // Fallback: direct file read
                const fs = await import("node:fs/promises");
                const pathMod = await import("node:path");
                const workspace = api.config?.agents?.defaults?.workspace ?? process.cwd();
                const fullPath = pathMod.resolve(workspace, params.path);
                // Safety: only allow MEMORY.md and memory/ paths
                const rel = pathMod.relative(workspace, fullPath);
                if (rel.startsWith("..") || (!rel.startsWith("memory") && rel !== "MEMORY.md")) {
                    return { content: [{ type: "text", text: `Access denied: ${params.path}` }] };
                }
                try {
                    const raw = await fs.readFile(fullPath, "utf-8");
                    const allLines = raw.split("\n");
                    const start = (params.from ?? 1) - 1;
                    const count = params.lines ?? allLines.length;
                    const slice = allLines.slice(start, start + count).join("\n");
                    return { content: [{ type: "text", text: slice, path: params.path }] };
                }
                catch {
                    return { content: [{ type: "text", text: `File not found: ${params.path}` }] };
                }
            },
        }, { name: "memory_get" });
        // ========================================================================
        // TOOL: honcho_session
        // ========================================================================
        api.registerTool({
            name: "honcho_session",
            label: "Session History",
            description: "Retrieve conversation history from THIS SESSION ONLY. Does NOT access cross-session memory.\n\n" +
                "━━━ SCOPE: CURRENT SESSION ━━━\n" +
                "This tool retrieves messages and summaries from the current conversation session.\n" +
                "It does NOT know about previous sessions or long-term user knowledge.\n\n" +
                "━━━ DATA TOOL ━━━\n" +
                "Returns: Recent messages + optional summary of earlier conversation in this session\n" +
                "Cost: Low (database query only, no LLM)\nSpeed: Fast\n\n" +
                "Best for:\n- \"What did we talk about earlier?\" (in this conversation)\n" +
                "- \"What was that thing you just mentioned?\"\n- Recalling recent conversation context\n\n" +
                "NOT for:\n- \"What do you know about me?\" → Use honcho_context instead\n" +
                "- Long-term user preferences → Use honcho_profile or honcho_context",
            parameters: Type.Object({
                includeMessages: Type.Optional(Type.Boolean({ description: "Include recent messages (default: true)" })),
                includeSummary: Type.Optional(Type.Boolean({ description: "Include conversation summary (default: true)" })),
                searchQuery: Type.Optional(Type.String({ description: "Semantic search within this session" })),
                messageLimit: Type.Optional(Type.Number({ description: "Token budget for messages (default: 4000)" })),
            }),
            async execute(_id, params) {
                await ensureInitialized();
                const { includeMessages = true, includeSummary = true, searchQuery, messageLimit = 4000 } = params;
                try {
                    const session = await honcho.session(params.sessionKey ?? "default");
                    const context = await session.context({
                        summary: includeSummary,
                        tokens: messageLimit,
                        peerTarget: ownerPeer,
                        peerPerspective: openclawPeer,
                        searchQuery,
                    });
                    const sections = [];
                    if (context.summary?.content)
                        sections.push(`## Summary\n\n${context.summary.content}`);
                    if (context.peerCard?.length)
                        sections.push(`## Profile\n\n${context.peerCard.map((f) => `• ${f}`).join("\n")}`);
                    if (includeMessages && context.messages?.length) {
                        const lines = context.messages.map((m) => {
                            const who = m.peerId === ownerPeer.id ? "User" : "Agent";
                            return `**${who}**: ${m.content}`;
                        });
                        sections.push(`## Messages (${context.messages.length})\n\n${lines.join("\n\n---\n\n")}`);
                    }
                    return {
                        content: [{
                                type: "text",
                                text: sections.length ? sections.join("\n\n") : "No history for this session yet.",
                            }],
                    };
                }
                catch (e) {
                    if (e?.message?.toLowerCase().includes("not found"))
                        return { content: [{ type: "text", text: "New session — no history yet." }] };
                    throw e;
                }
            },
        }, { name: "honcho_session" });
        // ========================================================================
        // TOOL: honcho_profile
        // ========================================================================
        api.registerTool({
            name: "honcho_profile",
            label: "User Profile",
            description: "Retrieve the user's peer card — a curated list of their most important facts.\n\n" +
                "━━━ DATA TOOL ━━━\nReturns: Raw fact list\nCost: Minimal\nSpeed: Instant\n\n" +
                "Best for: Quick context, core identity, cost-efficient fact lookup.",
            parameters: Type.Object({}),
            async execute() {
                await ensureInitialized();
                const card = await ownerPeer.card().catch(() => null);
                if (!card?.length)
                    return { content: [{ type: "text", text: "No profile yet. Builds over time." }] };
                return { content: [{ type: "text", text: card.map((f) => `• ${f}`).join("\n") }] };
            },
        }, { name: "honcho_profile" });
        // ========================================================================
        // TOOL: honcho_search
        // ========================================================================
        api.registerTool({
            name: "honcho_search",
            label: "Search Memory",
            description: "Semantic search over Honcho's stored observations. Returns raw memories ranked by relevance.\n\n" +
                "━━━ DATA TOOL ━━━\nReturns: Raw observations matching query\nCost: Low\nSpeed: Fast\n\n" +
                "Best for: Finding specific past context, seeing evidence before conclusions.\n" +
                "Parameters: topK (3-5 focused, 10-20 exploratory), maxDistance (0.3 strict, 0.5 balanced, 0.7 loose)",
            parameters: Type.Object({
                query: Type.String({ description: "Semantic search query" }),
                topK: Type.Optional(Type.Number({ description: "Number of results (default: 10)" })),
                maxDistance: Type.Optional(Type.Number({ description: "Semantic distance 0-1 (default: 0.5)" })),
            }),
            async execute(_id, params) {
                await ensureInitialized();
                const rep = await ownerPeer.representation({
                    searchQuery: params.query,
                    searchTopK: params.topK ?? 10,
                    searchMaxDistance: params.maxDistance ?? 0.5,
                });
                if (!rep)
                    return { content: [{ type: "text", text: `No memories matching: "${params.query}"` }] };
                return { content: [{ type: "text", text: rep }] };
            },
        }, { name: "honcho_search" });
        // ========================================================================
        // TOOL: honcho_context
        // ========================================================================
        api.registerTool({
            name: "honcho_context",
            label: "Broad Context",
            description: "Retrieve everything Honcho knows about this user ACROSS ALL SESSIONS.\n\n" +
                "━━━ DATA TOOL ━━━\nReturns: Broad synthesized representation\nCost: Low\nSpeed: Fast\n\n" +
                "Best for: \"What do you know about me?\", holistic understanding, long-term patterns.",
            parameters: Type.Object({
                includeMostFrequent: Type.Optional(Type.Boolean({ description: "Include frequent observations (default: true)" })),
            }),
            async execute(_id, params) {
                await ensureInitialized();
                const rep = await ownerPeer.representation({
                    includeMostFrequent: params.includeMostFrequent ?? true,
                });
                if (!rep)
                    return { content: [{ type: "text", text: "No context yet. Builds over time." }] };
                return { content: [{ type: "text", text: rep }] };
            },
        }, { name: "honcho_context" });
        // ========================================================================
        // TOOL: honcho_recall — quick factual Q&A
        // ========================================================================
        api.registerTool({
            name: "honcho_recall",
            label: "Quick Recall",
            description: "Ask Honcho a simple factual question. Uses minimal LLM reasoning.\n\n" +
                "━━━ Q&A TOOL ━━━\nCost: ~$0.001\nSpeed: Instant\n\n" +
                "Best for: Single data points (name, timezone, preferred language).\n" +
                "Use honcho_profile for raw facts (cheaper). Use this for direct answers.",
            parameters: Type.Object({
                query: Type.String({ description: "Simple factual question" }),
            }),
            async execute(_id, params) {
                await ensureInitialized();
                return {
                    content: [{
                            type: "text",
                            text: await openclawPeer.chat(params.query, {
                                target: ownerPeer,
                                reasoningLevel: "minimal",
                            }),
                        }],
                };
            },
        }, { name: "honcho_recall" });
        // ========================================================================
        // TOOL: honcho_analyze — complex synthesis Q&A
        // ========================================================================
        api.registerTool({
            name: "honcho_analyze",
            label: "Analyze",
            description: "Ask Honcho a complex question requiring synthesis. Uses medium LLM reasoning.\n\n" +
                "━━━ Q&A TOOL ━━━\nCost: ~$0.05\nSpeed: Fast\n\n" +
                "Best for: Synthesizing patterns, communication style, decision summaries.\n" +
                "Use honcho_search for raw evidence (cheaper). Use this for interpreted answers.",
            parameters: Type.Object({
                query: Type.String({ description: "Complex question requiring synthesis" }),
            }),
            async execute(_id, params) {
                await ensureInitialized();
                return {
                    content: [{
                            type: "text",
                            text: await openclawPeer.chat(params.query, {
                                target: ownerPeer,
                                reasoningLevel: "medium",
                            }),
                        }],
                };
            },
        }, { name: "honcho_analyze" });
        // ========================================================================
        // CLI
        // ========================================================================
        api.registerCli(({ program }) => {
            const cmd = program.command("honcho").description("Honcho memory");
            cmd.command("status").action(async () => {
                try {
                    await ensureInitialized();
                    console.log(`Connected — workspace: ${cfg.workspaceId}`);
                }
                catch (err) {
                    console.error(`Failed: ${err}`);
                }
            });
            cmd.command("ask <question>").action(async (q) => {
                try {
                    await ensureInitialized();
                    console.log(await openclawPeer.chat(q, { target: ownerPeer }) ?? "No info.");
                }
                catch (err) {
                    console.error(`Failed: ${err}`);
                }
            });
            cmd.command("search <query>")
                .option("-k, --top-k <n>", "Results", "10")
                .option("-d, --max-distance <n>", "Distance", "0.5")
                .action(async (q, opts) => {
                try {
                    await ensureInitialized();
                    const rep = await ownerPeer.representation({
                        searchQuery: q,
                        searchTopK: parseInt(opts.topK),
                        searchMaxDistance: parseFloat(opts.maxDistance),
                    });
                    console.log(rep ?? `No results for: "${q}"`);
                }
                catch (err) {
                    console.error(`Failed: ${err}`);
                }
            });
        }, { commands: ["honcho"] });
        api.logger.info("[mux] Memory multiplexer loaded (honcho + local files)");
    },
};
//# sourceMappingURL=index.js.map

path: extension/index.ts
content: /**
 * Memory Multiplexer Plugin for OpenClaw
 *
 * Claims the "memory" slot. Provides:
 * - All honcho_* tools (search, recall, analyze, etc.) for retrieval
 * - memory_get for reading local workspace files
 * - agent_end hook that writes conversations to Honcho
 * - Local markdown files are written by the agent itself (via AGENTS.md behavior)
 *
 * This gives us dual-write (honcho cloud + local files) with honcho-only retrieval.
 * If honcho dies, local files are the backup. If we ditch honcho, swap this plugin
 * for memory-core and everything works.
 */

import { Type } from "@sinclair/typebox";
import { Honcho } from "@honcho-ai/sdk";

// ============================================================================
// Config
// ============================================================================

interface PluginConfig {
  apiKey?: string;
  baseUrl: string;
  workspaceId: string;
}

function resolveConfig(pluginConfig: Record<string, unknown>): PluginConfig {
  return {
    [REDACTED_SECRET] as string) || process.env.HONCHO_API_KEY || "",
    baseUrl:
      (pluginConfig.baseUrl as string) || "https://api.honcho.dev",
    workspaceId:
      (pluginConfig.workspaceId as string) || "openclaw",
  };
}

// ============================================================================
// Constants
// ============================================================================

const OWNER_ID = "owner";
const OPENCLAW_ID = "openclaw";

// ============================================================================
// Helpers
// ============================================================================

function buildSessionKey(ctx: { sessionKey?: string; messageProvider?: string }): string {
  const base = ctx?.sessionKey ?? "default";
  const provider = ctx?.messageProvider ?? "unknown";
  return `${base}-${provider}`.replace(/[^a-zA-Z0-9-]/g, "-");
}

function cleanMessageContent(content: string): string {
  let cleaned = content;
  cleaned = cleaned.replace(/<honcho-memory[^>]*>[\s\S]*?<\/honcho-memory>\s*/gi, "");
  cleaned = cleaned.replace(/<!--[^>]*honcho[^>]*-->\s*/gi, "");
  cleaned = cleaned.replace(/^\[\w+\s+.+?\s+id:\d+\s+[^\]]+\]\s*/, "");
  cleaned = cleaned.replace(/\s*\[message_id:\s*[^\]]+\]\s*$/, "");
  return cleaned.trim();
}

function extractMessages(rawMessages: unknown[], ownerPeer: any, openclawPeer: any) {
  const result: any[] = [];
  for (const msg of rawMessages) {
    if (!msg || typeof msg !== "object") continue;
    const m = msg as any;
    if (m.role !== "user" && m.role !== "assistant") continue;

    let content = "";
    if (typeof m.content === "string") {
      content = m.content;
    } else if (Array.isArray(m.content)) {
      content = m.content
        .filter((b: any) => b?.type === "text")
        .map((b: any) => b.text)
        .filter((t: any) => typeof t === "string")
        .join("\n");
    }

    if (m.role === "user") content = cleanMessageContent(content);
    content = content.trim();

    if (content) {
      const peer = m.role === "user" ? ownerPeer : openclawPeer;
      result.push(peer.message(content));
    }
  }
  return result;
}

// ============================================================================
// Plugin
// ============================================================================

export default {
  id: "honcho-memory-mux",
  name: "Memory (Multiplexer)",
  description: "Dual-write memory: Honcho cloud + local markdown files",
  kind: "memory",

  register(api: any) {
    const cfg = resolveConfig(api.pluginConfig ?? {});

    const honcho = new Honcho({
      [REDACTED_SECRET],
      baseURL: cfg.baseUrl,
      workspaceId: cfg.workspaceId,
    });

    let ownerPeer: any = null;
    let openclawPeer: any = null;
    let initialized = false;

    async function ensureInitialized() {
      await honcho.setMetadata({});
      if (initialized) return;
      ownerPeer = await honcho.peer(OWNER_ID, { metadata: {} });
      openclawPeer = await honcho.peer(OPENCLAW_ID, {
        metadata: {},
        config: { observe_me: false },
      });
      initialized = true;
    }

    // ========================================================================
    // HOOK: gateway_start
    // ========================================================================
    api.on("gateway_start", async () => {
      try {
        await ensureInitialized();
        api.logger.info("[mux] Honcho connected");
      } catch (err) {
        api.logger.error(`[mux] Honcho init failed: ${err}`);
      }
    });

    // ========================================================================
    // HOOK: before_agent_start — inject honcho context into system prompt
    // ========================================================================
    api.on("before_agent_start", async (event: any, ctx: any) => {
      if (!event.prompt || event.prompt.length < 5) return;
      const sessionKey = buildSessionKey(ctx);

      try {
        await ensureInitialized();
        const session = await honcho.session(sessionKey, { metadata: {} });

        let context: any;
        try {
          context = await (session as any).context({
            summary: true,
            tokens: 2000,
            peerTarget: ownerPeer,
            peerPerspective: openclawPeer,
          });
        } catch (e: any) {
          if (e?.message?.toLowerCase().includes("not found")) return;
          throw e;
        }

        const sections: string[] = [];
        if (context.peerCard?.length)
          sections.push(`Key facts:\n${context.peerCard.map((f: string) => `• ${f}`).join("\n")}`);
        if (context.peerRepresentation)
          sections.push(`User context:\n${context.peerRepresentation}`);
        if (context.summary?.content)
          sections.push(`Earlier in this conversation:\n${context.summary.content}`);

        if (!sections.length) return;

        return {
          systemPrompt: `## User Memory Context\n\n${sections.join("\n\n")}\n\nUse this context naturally when relevant.`,
        };
      } catch (err) {
        api.logger.warn?.(`[mux] Failed to fetch context: ${err}`);
      }
    });

    // ========================================================================
    // HOOK: agent_end — persist messages to Honcho
    // (local files are written by the agent via normal tool calls)
    // ========================================================================
    api.on("agent_end", async (event: any, ctx: any) => {
      if (!event.success || !event.messages?.length) return;
      const sessionKey = buildSessionKey(ctx);

      try {
        await ensureInitialized();
        const session = await honcho.session(sessionKey, { metadata: {} });

        let meta = await session.getMetadata();
        if (meta.lastSavedIndex === undefined) {
          const startIndex = Math.max(0, event.messages.length - 2);
          await session.setMetadata({ lastSavedIndex: startIndex });
          meta = { lastSavedIndex: startIndex };
        }

        const lastSaved = meta.lastSavedIndex ?? 0;
        await session.addPeers([
          [OWNER_ID, { observe_me: true, observe_others: false }],
          [OPENCLAW_ID, { observe_me: false, observe_others: true }],
        ]);

        if (event.messages.length <= lastSaved) return;

        const newMessages = extractMessages(
          event.messages.slice(lastSaved),
          ownerPeer,
          openclawPeer
        );

        if (newMessages.length > 0) {
          await session.addMessages(newMessages);
        }
        await session.setMetadata({ ...meta, lastSavedIndex: event.messages.length });
      } catch (err) {
        api.logger.error(`[mux] Failed to save to Honcho: ${err}`);
      }
    });

    // ========================================================================
    // TOOL: memory_get — read local workspace files (the backup)
    // ========================================================================
    api.registerTool({
      name: "memory_get",
      label: "Read Memory File",
      description:
        "Safe snippet read from MEMORY.md or memory/*.md with optional from/lines; " +
        "use after memory_search to pull only the needed lines and keep context small.",
      parameters: Type.Object({
        path: Type.String({ description: "Relative path to file (e.g. MEMORY.md, memory/2026-02-12.md)" }),
        from: Type.Optional(Type.Number({ description: "Line to start reading from (1-indexed)" })),
        lines: Type.Optional(Type.Number({ description: "Max lines to read" })),
      }),
      async execute(_id: string, params: { path: string; from?: number; lines?: number }, _signal: any) {
        // Delegate to the builtin helper if available
        const tool = api.runtime.tools.createMemoryGetTool({
          config: api.config,
          agentSessionKey: "main",
        });
        if (tool) {
          return tool.execute(_id, params, _signal);
        }

        // Fallback: direct file read
        const fs = await import("node:fs/promises");
        const pathMod = await import("node:path");
        const workspace = api.config?.agents?.defaults?.workspace ?? process.cwd();
        const fullPath = pathMod.resolve(workspace, params.path);

        // Safety: only allow MEMORY.md and memory/ paths
        const rel = pathMod.relative(workspace, fullPath);
        if (rel.startsWith("..") || (!rel.startsWith("memory") && rel !== "MEMORY.md")) {
          return { content: [{ type: "text", text: `Access denied: ${params.path}` }] };
        }

        try {
          const raw = await fs.readFile(fullPath, "utf-8");
          const allLines = raw.split("\n");
          const start = (params.from ?? 1) - 1;
          const count = params.lines ?? allLines.length;
          const slice = allLines.slice(start, start + count).join("\n");
          return { content: [{ type: "text", text: slice, path: params.path }] };
        } catch {
          return { content: [{ type: "text", text: `File not found: ${params.path}` }] };
        }
      },
    }, { name: "memory_get" });

    // ========================================================================
    // TOOL: honcho_session
    // ========================================================================
    api.registerTool({
      name: "honcho_session",
      label: "Session History",
      description:
        "Retrieve conversation history from THIS SESSION ONLY. Does NOT access cross-session memory.\n\n" +
        "━━━ SCOPE: CURRENT SESSION ━━━\n" +
        "This tool retrieves messages and summaries from the current conversation session.\n" +
        "It does NOT know about previous sessions or long-term user knowledge.\n\n" +
        "━━━ DATA TOOL ━━━\n" +
        "Returns: Recent messages + optional summary of earlier conversation in this session\n" +
        "Cost: Low (database query only, no LLM)\nSpeed: Fast\n\n" +
        "Best for:\n- \"What did we talk about earlier?\" (in this conversation)\n" +
        "- \"What was that thing you just mentioned?\"\n- Recalling recent conversation context\n\n" +
        "NOT for:\n- \"What do you know about me?\" → Use honcho_context instead\n" +
        "- Long-term user preferences → Use honcho_profile or honcho_context",
      parameters: Type.Object({
        includeMessages: Type.Optional(Type.Boolean({ description: "Include recent messages (default: true)" })),
        includeSummary: Type.Optional(Type.Boolean({ description: "Include conversation summary (default: true)" })),
        searchQuery: Type.Optional(Type.String({ description: "Semantic search within this session" })),
        messageLimit: Type.Optional(Type.Number({ description: "Token budget for messages (default: 4000)" })),
      }),
      async execute(_id: string, params: any) {
        await ensureInitialized();
        const { includeMessages = true, includeSummary = true, searchQuery, messageLimit = 4000 } = params;
        try {
          const session = await honcho.session(params.sessionKey ?? "default");
          const context = await (session as any).context({
            summary: includeSummary,
            tokens: messageLimit,
            peerTarget: ownerPeer,
            peerPerspective: openclawPeer,
            searchQuery,
          });

          const sections: string[] = [];
          if (context.summary?.content)
            sections.push(`## Summary\n\n${context.summary.content}`);
          if (context.peerCard?.length)
            sections.push(`## Profile\n\n${context.peerCard.map((f: string) => `• ${f}`).join("\n")}`);
          if (includeMessages && context.messages?.length) {
            const lines = context.messages.map((m: any) => {
              const who = m.peerId === ownerPeer.id ? "User" : "Agent";
              return `**${who}**: ${m.content}`;
            });
            sections.push(`## Messages (${context.messages.length})\n\n${lines.join("\n\n---\n\n")}`);
          }

          return {
            content: [{
              type: "text",
              text: sections.length ? sections.join("\n\n") : "No history for this session yet.",
            }],
          };
        } catch (e: any) {
          if (e?.message?.toLowerCase().includes("not found"))
            return { content: [{ type: "text", text: "New session — no history yet." }] };
          throw e;
        }
      },
    }, { name: "honcho_session" });

    // ========================================================================
    // TOOL: honcho_profile
    // ========================================================================
    api.registerTool({
      name: "honcho_profile",
      label: "User Profile",
      description:
        "Retrieve the user's peer card — a curated list of their most important facts.\n\n" +
        "━━━ DATA TOOL ━━━\nReturns: Raw fact list\nCost: Minimal\nSpeed: Instant\n\n" +
        "Best for: Quick context, core identity, cost-efficient fact lookup.",
      parameters: Type.Object({}),
      async execute() {
        await ensureInitialized();
        const card = await ownerPeer.card().catch(() => null);
        if (!card?.length)
          return { content: [{ type: "text", text: "No profile yet. Builds over time." }] };
        return { content: [{ type: "text", text: card.map((f: string) => `• ${f}`).join("\n") }] };
      },
    }, { name: "honcho_profile" });

    // ========================================================================
    // TOOL: honcho_search
    // ========================================================================
    api.registerTool({
      name: "honcho_search",
      label: "Search Memory",
      description:
        "Semantic search over Honcho's stored observations. Returns raw memories ranked by relevance.\n\n" +
        "━━━ DATA TOOL ━━━\nReturns: Raw observations matching query\nCost: Low\nSpeed: Fast\n\n" +
        "Best for: Finding specific past context, seeing evidence before conclusions.\n" +
        "Parameters: topK (3-5 focused, 10-20 exploratory), maxDistance (0.3 strict, 0.5 balanced, 0.7 loose)",
      parameters: Type.Object({
        query: Type.String({ description: "Semantic search query" }),
        topK: Type.Optional(Type.Number({ description: "Number of results (default: 10)" })),
        maxDistance: Type.Optional(Type.Number({ description: "Semantic distance 0-1 (default: 0.5)" })),
      }),
      async execute(_id: string, params: { query: string; topK?: number; maxDistance?: number }) {
        await ensureInitialized();
        const rep = await ownerPeer.representation({
          searchQuery: params.query,
          searchTopK: params.topK ?? 10,
          searchMaxDistance: params.maxDistance ?? 0.5,
        });
        if (!rep)
          return { content: [{ type: "text", text: `No memories matching: "${params.query}"` }] };
        return { content: [{ type: "text", text: rep }] };
      },
    }, { name: "honcho_search" });

    // ========================================================================
    // TOOL: honcho_context
    // ========================================================================
    api.registerTool({
      name: "honcho_context",
      label: "Broad Context",
      description:
        "Retrieve everything Honcho knows about this user ACROSS ALL SESSIONS.\n\n" +
        "━━━ DATA TOOL ━━━\nReturns: Broad synthesized representation\nCost: Low\nSpeed: Fast\n\n" +
        "Best for: \"What do you know about me?\", holistic understanding, long-term patterns.",
      parameters: Type.Object({
        includeMostFrequent: Type.Optional(Type.Boolean({ description: "Include frequent observations (default: true)" })),
      }),
      async execute(_id: string, params: { includeMostFrequent?: boolean }) {
        await ensureInitialized();
        const rep = await ownerPeer.representation({
          includeMostFrequent: params.includeMostFrequent ?? true,
        });
        if (!rep)
          return { content: [{ type: "text", text: "No context yet. Builds over time." }] };
        return { content: [{ type: "text", text: rep }] };
      },
    }, { name: "honcho_context" });

    // ========================================================================
    // TOOL: honcho_recall — quick factual Q&A
    // ========================================================================
    api.registerTool({
      name: "honcho_recall",
      label: "Quick Recall",
      description:
        "Ask Honcho a simple factual question. Uses minimal LLM reasoning.\n\n" +
        "━━━ Q&A TOOL ━━━\nCost: ~$0.001\nSpeed: Instant\n\n" +
        "Best for: Single data points (name, timezone, preferred language).\n" +
        "Use honcho_profile for raw facts (cheaper). Use this for direct answers.",
      parameters: Type.Object({
        query: Type.String({ description: "Simple factual question" }),
      }),
      async execute(_id: string, params: { query: string }) {
        await ensureInitialized();
        return {
          content: [{
            type: "text",
            text: await openclawPeer.chat(params.query, {
              target: ownerPeer,
              reasoningLevel: "minimal",
            }),
          }],
        };
      },
    }, { name: "honcho_recall" });

    // ========================================================================
    // TOOL: honcho_analyze — complex synthesis Q&A
    // ========================================================================
    api.registerTool({
      name: "honcho_analyze",
      label: "Analyze",
      description:
        "Ask Honcho a complex question requiring synthesis. Uses medium LLM reasoning.\n\n" +
        "━━━ Q&A TOOL ━━━\nCost: ~$0.05\nSpeed: Fast\n\n" +
        "Best for: Synthesizing patterns, communication style, decision summaries.\n" +
        "Use honcho_search for raw evidence (cheaper). Use this for interpreted answers.",
      parameters: Type.Object({
        query: Type.String({ description: "Complex question requiring synthesis" }),
      }),
      async execute(_id: string, params: { query: string }) {
        await ensureInitialized();
        return {
          content: [{
            type: "text",
            text: await openclawPeer.chat(params.query, {
              target: ownerPeer,
              reasoningLevel: "medium",
            }),
          }],
        };
      },
    }, { name: "honcho_analyze" });

    // ========================================================================
    // CLI
    // ========================================================================
    api.registerCli(({ program }: any) => {
      const cmd = program.command("honcho").description("Honcho memory");

      cmd.command("status").action(async () => {
        try {
          await ensureInitialized();
          console.log(`Connected — workspace: ${cfg.workspaceId}`);
        } catch (err) {
          console.error(`Failed: ${err}`);
        }
      });

      cmd.command("ask <question>").action(async (q: string) => {
        try {
          await ensureInitialized();
          console.log(await openclawPeer.chat(q, { target: ownerPeer }) ?? "No info.");
        } catch (err) {
          console.error(`Failed: ${err}`);
        }
      });

      cmd.command("search <query>")
        .option("-k, --top-k <n>", "Results", "10")
        .option("-d, --max-distance <n>", "Distance", "0.5")
        .action(async (q: string, opts: any) => {
          try {
            await ensureInitialized();
            const rep = await ownerPeer.representation({
              searchQuery: q,
              searchTopK: parseInt(opts.topK),
              searchMaxDistance: parseFloat(opts.maxDistance),
            });
            console.log(rep ?? `No results for: "${q}"`);
          } catch (err) {
            console.error(`Failed: ${err}`);
          }
        });
    }, { commands: ["honcho"] });

    api.logger.info("[mux] Memory multiplexer loaded (honcho + local files)");
  },
};


path: extension/openclaw.plugin.json
content: {
    "id": "honcho-memory-mux",
    "kind": "memory",
    "configSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
            "apiKey": {
                "type": "string",
                "description": "Honcho API key (or set HONCHO_API_KEY env var)"
            },
            "workspaceId": {
                "type": "string",
                "default": "openclaw",
                "description": "Honcho workspace ID"
            },
            "baseUrl": {
                "type": "string",
                "default": "https://api.honcho.dev",
                "description": "Honcho API base URL"
            }
        }
    },
    "uiHints": {
        "apiKey": {
            "label": "Honcho API Key",
            "sensitive": true
        },
        "workspaceId": {
            "label": "Workspace ID",
            "placeholder": "openclaw"
        },
        "baseUrl": {
            "label": "API URL",
            "advanced": true
        }
    }
}

path: extension/package-lock.json
content: {
  "name": "openclaw-honcho-memory-multiplexer",
  "version": "0.1.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "openclaw-honcho-memory-multiplexer",
      "version": "0.1.0",
      "license": "MIT",
      "dependencies": {
        "@honcho-ai/sdk": "^1.0.0",
        "@sinclair/typebox": "^0.32.0"
      },
      "devDependencies": {
        "@types/node": "^24.0.0",
        "typescript": "^5.9.0"
      }
    },
    "node_modules/@honcho-ai/core": {
      "version": "1.8.0",
      "license": "Apache-2.0",
      "dependencies": {
        "@types/node": "^18.11.18",
        "@types/node-fetch": "^2.6.4",
        "abort-controller": "^3.0.0",
        "agentkeepalive": "^4.2.1",
        "form-data-encoder": "1.7.2",
        "formdata-node": "^4.3.2",
        "node-fetch": "^2.6.7"
      }
    },
    "node_modules/@honcho-ai/core/node_modules/@types/node": {
      "version": "18.19.130",
      "license": "MIT",
      "dependencies": {
        "undici-types": "~5.26.4"
      }
    },
    "node_modules/@honcho-ai/core/node_modules/@types/node/node_modules/undici-types": {
      "version": "5.26.5",
      "license": "MIT"
    },
    "node_modules/@honcho-ai/sdk": {
      "version": "1.6.0",
      "license": "Apache-2.0",
      "dependencies": {
        "@honcho-ai/core": "^1.6.1",
        "@types/node": "^24.0.1",
        "zod": "4.0.0"
      }
    },
    "node_modules/@sinclair/typebox": {
      "version": "0.32.35",
      "license": "MIT"
    },
    "node_modules/@types/node": {
      "version": "24.10.14",
      "license": "MIT",
      "dependencies": {
        "undici-types": "~7.16.0"
      }
    },
    "node_modules/@types/node-fetch": {
      "version": "2.6.13",
      "license": "MIT",
      "dependencies": {
        "@types/node": "*",
        "form-data": "^4.0.4"
      }
    },
    "node_modules/abort-controller": {
      "version": "3.0.0",
      "license": "MIT",
      "dependencies": {
        "event-target-shim": "^5.0.0"
      },
      "engines": {
        "node": ">=6.5"
      }
    },
    "node_modules/agentkeepalive": {
      "version": "4.6.0",
      "license": "MIT",
      "dependencies": {
        "humanize-ms": "^1.2.1"
      },
      "engines": {
        "node": ">= 8.0.0"
      }
    },
    "node_modules/asynckit": {
      "version": "0.4.0",
      "license": "MIT"
    },
    "node_modules/call-bind-apply-helpers": {
      "version": "1.0.2",
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/combined-stream": {
      "version": "1.0.8",
      "license": "MIT",
      "dependencies": {
        "delayed-stream": "~1.0.0"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/delayed-stream": {
      "version": "1.0.0",
      "license": "MIT",
      "engines": {
        "node": ">=0.4.0"
      }
    },
    "node_modules/dunder-proto": {
      "version": "1.0.1",
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.1",
        "es-errors": "^1.3.0",
        "gopd": "^1.2.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-define-property": {
      "version": "1.0.1",
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-errors": {
      "version": "1.3.0",
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-object-atoms": {
      "version": "1.1.1",
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-set-tostringtag": {
      "version": "2.1.0",
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "get-intrinsic": "^1.2.6",
        "has-tostringtag": "^1.0.2",
        "hasown": "^2.0.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/event-target-shim": {
      "version": "5.0.1",
      "license": "MIT",
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/form-data": {
      "version": "4.0.5",
      "license": "MIT",
      "dependencies": {
        "asynckit": "^0.4.0",
        "combined-stream": "^1.0.8",
        "es-set-tostringtag": "^2.1.0",
        "hasown": "^2.0.2",
        "mime-types": "^2.1.12"
      },
      "engines": {
        "node": ">= 6"
      }
    },
    "node_modules/form-data-encoder": {
      "version": "1.7.2",
      "license": "MIT"
    },
    "node_modules/formdata-node": {
      "version": "4.4.1",
      "license": "MIT",
      "dependencies": {
        "node-domexception": "1.0.0",
        "web-streams-polyfill": "4.0.0-beta.3"
      },
      "engines": {
        "node": ">= 12.20"
      }
    },
    "node_modules/function-bind": {
      "version": "1.1.2",
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-intrinsic": {
      "version": "1.3.0",
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.2",
        "es-define-property": "^1.0.1",
        "es-errors": "^1.3.0",
        "es-object-atoms": "^1.1.1",
        "function-bind": "^1.1.2",
        "get-proto": "^1.0.1",
        "gopd": "^1.2.0",
        "has-symbols": "^1.1.0",
        "hasown": "^2.0.2",
        "math-intrinsics": "^1.1.0"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-proto": {
      "version": "1.0.1",
      "license": "MIT",
      "dependencies": {
        "dunder-proto": "^1.0.1",
        "es-object-atoms": "^1.0.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/gopd": {
      "version": "1.2.0",
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/has-symbols": {
      "version": "1.1.0",
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/has-tostringtag": {
      "version": "1.0.2",
      "license": "MIT",
      "dependencies": {
        "has-symbols": "^1.0.3"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/hasown": {
      "version": "2.0.2",
      "license": "MIT",
      "dependencies": {
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/humanize-ms": {
      "version": "1.2.1",
      "license": "MIT",
      "dependencies": {
        "ms": "^2.0.0"
      }
    },
    "node_modules/math-intrinsics": {
      "version": "1.1.0",
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/mime-db": {
      "version": "1.52.0",
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/mime-types": {
      "version": "2.1.35",
      "license": "MIT",
      "dependencies": {
        "mime-db": "1.52.0"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/ms": {
      "version": "2.1.3",
      "license": "MIT"
    },
    "node_modules/node-domexception": {
      "version": "1.0.0",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/jimmywarting"
        },
        {
          "type": "github",
          "url": "https://paypal.me/jimmywarting"
        }
      ],
      "license": "MIT",
      "engines": {
        "node": ">=10.5.0"
      }
    },
    "node_modules/node-fetch": {
      "version": "2.7.0",
      "license": "MIT",
      "dependencies": {
        "whatwg-url": "^5.0.0"
      },
      "engines": {
        "node": "4.x || >=6.0.0"
      },
      "peerDependencies": {
        "encoding": "^0.1.0"
      },
      "peerDependenciesMeta": {
        "encoding": {
          "optional": true
        }
      }
    },
    "node_modules/tr46": {
      "version": "0.0.3",
      "license": "MIT"
    },
    "node_modules/typescript": {
      "version": "5.9.3",
      "dev": true,
      "license": "Apache-2.0",
      "bin": {
        "tsc": "bin/tsc",
        "tsserver": "bin/tsserver"
      },
      "engines": {
        "node": ">=14.17"
      }
    },
    "node_modules/undici-types": {
      "version": "7.16.0",
      "license": "MIT"
    },
    "node_modules/web-streams-polyfill": {
      "version": "4.0.0-beta.3",
      "license": "MIT",
      "engines": {
        "node": ">= 14"
      }
    },
    "node_modules/webidl-conversions": {
      "version": "3.0.1",
      "license": "BSD-2-Clause"
    },
    "node_modules/whatwg-url": {
      "version": "5.0.0",
      "license": "MIT",
      "dependencies": {
        "tr46": "~0.0.3",
        "webidl-conversions": "^3.0.0"
      }
    },
    "node_modules/zod": {
      "version": "4.0.0",
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/colinhacks"
      }
    }
  }
}


path: extension/package.json
content: {
    "name": "@alloralabs/honcho-memory-mux",
    "version": "0.1.0",
    "private": false,
    "description": "OpenClaw memory extension that multiplexes Honcho cloud memory with local file fallback.",
    "license": "MIT",
    "type": "module",
    "main": "./dist/index.js",
    "types": "./dist/index.d.ts",
    "exports": {
        ".": {
            "import": "./dist/index.js",
            "types": "./dist/index.d.ts"
        }
    },
    "openclaw": {
        "plugin": "./openclaw.plugin.json",
        "extensions": [
            "./dist/index.js"
        ]
    },
    "files": [
        "dist",
        "openclaw.plugin.json"
    ],
    "scripts": {
        "build": "tsc -p tsconfig.json",
        "prepublishOnly": "npm run build",
        "clawhub-publish": "clawhub publish --slug honcho-memory-mux --name 'Honcho Memory Multiplexer'"
    },
    "dependencies": {
        "@honcho-ai/sdk": "^1.0.0",
        "@sinclair/typebox": "^0.32.0"
    },
    "peerDependencies": {
        "openclaw": "*"
    },
    "devDependencies": {
        "@types/node": "^24.0.0",
        "typescript": "^5.9.0"
    }
}


path: extension/tsconfig.json
content: {
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": ["ES2022"],
    "strict": true,
    "noEmitOnError": true,
    "declaration": true,
    "sourceMap": true,
    "outDir": "dist",
    "rootDir": ".",
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["index.ts"]
}


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)