diff --git a/changelog/current.md b/changelog/current.md index 177d7684..b81d6ab2 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -4,3 +4,6 @@ Record image-affecting changes to `manager/`, `worker/`, `openclaw-base/` here b --- +### Features + +- **feat(mem0): add Mem0 long-term memory support for the Manager and OpenClaw Workers** — HiClaw now supports Mem0 in Platform mode, so the Manager and OpenClaw Workers can retain long-term memory across sessions. Each OpenClaw Worker keeps its memory isolated under its own worker identity. CoPaw Workers are not supported yet. diff --git a/install/hiclaw-install.sh b/install/hiclaw-install.sh index 9f810c2b..95259be0 100644 --- a/install/hiclaw-install.sh +++ b/install/hiclaw-install.sh @@ -2290,6 +2290,15 @@ HICLAW_DATA_DIR=${HICLAW_DATA_DIR:-hiclaw-data} HICLAW_WORKSPACE_DIR=${HICLAW_WORKSPACE_DIR:-} # Host directory sharing HICLAW_HOST_SHARE_DIR=${HICLAW_HOST_SHARE_DIR:-} + +# mem0 Plugin (long-term memory for Manager and OpenClaw Workers) +# Get API key from: https://app.mem0.ai +HICLAW_MEM0_ENABLED=${HICLAW_MEM0_ENABLED:-false} +HICLAW_MEM0_API_KEY=${HICLAW_MEM0_API_KEY:-} +HICLAW_MEM0_USER_ID=${HICLAW_MEM0_USER_ID:-} +HICLAW_MEM0_ORG_ID=${HICLAW_MEM0_ORG_ID:-} +HICLAW_MEM0_PROJECT_ID=${HICLAW_MEM0_PROJECT_ID:-} +HICLAW_MEM0_ENABLE_GRAPH=${HICLAW_MEM0_ENABLE_GRAPH:-false} EOF chmod 600 "${ENV_FILE}" diff --git a/manager/Dockerfile b/manager/Dockerfile index f33eccc8..3a8abd7b 100644 --- a/manager/Dockerfile +++ b/manager/Dockerfile @@ -103,6 +103,28 @@ RUN cp /opt/hiclaw/scripts/systemctl-shim.sh /usr/local/bin/systemctl COPY agent/ /opt/hiclaw/agent/ ARG BUILTIN_VERSION=latest + +# ---- Optional: mem0 Plugin (long-term memory for agents) ---- +# Set MEM0_PLUGIN_ENABLED=1 at build time to bundle the plugin. +# Runtime configuration is injected via HICLAW_MEM0_* env vars. +ENV OPENCLAW_MEM0_PLUGIN_DIR="/opt/openclaw/extensions/openclaw-mem0" +ARG MEM0_PLUGIN_ENABLED=0 +RUN if [ "${MEM0_PLUGIN_ENABLED}" = "1" ]; then \ + echo "Installing @mem0/openclaw-mem0 plugin..." && \ + tmp_dir="$(mktemp -d)" && \ + cd "${tmp_dir}" && \ + pack_name="$(npm pack @mem0/openclaw-mem0 --silent)" && \ + rm -rf "${OPENCLAW_MEM0_PLUGIN_DIR}" && \ + mkdir -p "${OPENCLAW_MEM0_PLUGIN_DIR}" && \ + tar -xzf "${pack_name}" -C "${tmp_dir}" && \ + cp -rf "${tmp_dir}/package/." "${OPENCLAW_MEM0_PLUGIN_DIR}/" && \ + cd "${OPENCLAW_MEM0_PLUGIN_DIR}" && \ + npm install --omit=dev --ignore-scripts && \ + test -f "${OPENCLAW_MEM0_PLUGIN_DIR}/openclaw.plugin.json" && \ + rm -rf "${tmp_dir}" && \ + echo "mem0 plugin installed successfully"; \ + fi + RUN echo "${BUILTIN_VERSION}" > /opt/hiclaw/agent/.builtin-version \ && find /opt/hiclaw/agent/skills/*/scripts -name '*.sh' -exec chmod +x {} + 2>/dev/null || true \ && find /opt/hiclaw/agent/worker-skills/*/scripts -name '*.sh' -exec chmod +x {} + 2>/dev/null || true \ diff --git a/manager/Dockerfile.aliyun b/manager/Dockerfile.aliyun index 1da2d9d6..12cff851 100644 --- a/manager/Dockerfile.aliyun +++ b/manager/Dockerfile.aliyun @@ -53,6 +53,27 @@ RUN tmp_dir="$(mktemp -d)" && \ npm install --omit=dev --ignore-scripts && \ rm -rf "${tmp_dir}" +# ---- Optional: mem0 Plugin (long-term memory for agents) ---- +# Set MEM0_PLUGIN_ENABLED=1 at build time to bundle the plugin. +# Runtime configuration is injected via HICLAW_MEM0_* env vars. +ENV OPENCLAW_MEM0_PLUGIN_DIR="/opt/openclaw/extensions/openclaw-mem0" +ARG MEM0_PLUGIN_ENABLED=0 +RUN if [ "${MEM0_PLUGIN_ENABLED}" = "1" ]; then \ + echo "Installing @mem0/openclaw-mem0 plugin..." && \ + tmp_dir="$(mktemp -d)" && \ + cd "${tmp_dir}" && \ + pack_name="$(npm pack @mem0/openclaw-mem0 --silent)" && \ + rm -rf "${OPENCLAW_MEM0_PLUGIN_DIR}" && \ + mkdir -p "${OPENCLAW_MEM0_PLUGIN_DIR}" && \ + tar -xzf "${pack_name}" -C "${tmp_dir}" && \ + cp -rf "${tmp_dir}/package/." "${OPENCLAW_MEM0_PLUGIN_DIR}/" && \ + cd "${OPENCLAW_MEM0_PLUGIN_DIR}" && \ + npm install --omit=dev --ignore-scripts && \ + test -f "${OPENCLAW_MEM0_PLUGIN_DIR}/openclaw.plugin.json" && \ + rm -rf "${tmp_dir}" && \ + echo "mem0 plugin installed successfully"; \ + fi + # Ensure openclaw binary is on PATH ENV PATH="/opt/openclaw/packages/clawdbot/node_modules/.bin:${PATH}" diff --git a/manager/agent/skills/worker-management/scripts/generate-worker-config.sh b/manager/agent/skills/worker-management/scripts/generate-worker-config.sh index 5f5d15de..a58b1ea1 100644 --- a/manager/agent/skills/worker-management/scripts/generate-worker-config.sh +++ b/manager/agent/skills/worker-management/scripts/generate-worker-config.sh @@ -145,6 +145,54 @@ fi log "Generated ${OUTPUT_DIR}/openclaw.json (model=${MODEL_NAME}, ctx=${CTX}, max=${MAX})" +# ============================================================ +# Optional: inject openclaw-mem0 long-term memory config +# +# Manager runtime config uses HICLAW_MEM0_* env vars; Workers receive a +# per-worker entry in openclaw.json so they do not need mem0 runtime envs. +# ============================================================ +_mem0_enabled_lc="$(echo "${HICLAW_MEM0_ENABLED:-false}" | tr '[:upper:]' '[:lower:]')" +if [ "${_mem0_enabled_lc}" = "true" ]; then + _mem0_plugin_name="openclaw-mem0" + _mem0_plugin_dir="${OPENCLAW_MEM0_PLUGIN_DIR:-/opt/openclaw/extensions/${_mem0_plugin_name}}" + _mem0_enable_graph_lc="$(echo "${HICLAW_MEM0_ENABLE_GRAPH:-false}" | tr '[:upper:]' '[:lower:]')" + + if [ -z "${HICLAW_MEM0_API_KEY:-}" ]; then + log "WARNING: HICLAW_MEM0_ENABLED=true but HICLAW_MEM0_API_KEY is not set; skipping mem0 config for worker ${WORKER_NAME}" + else + jq --arg pluginName "${_mem0_plugin_name}" \ + --arg pluginDir "${_mem0_plugin_dir}" \ + --arg apiKey "${HICLAW_MEM0_API_KEY}" \ + --arg userId "${WORKER_NAME}" \ + --arg orgId "${HICLAW_MEM0_ORG_ID:-}" \ + --arg projectId "${HICLAW_MEM0_PROJECT_ID:-}" \ + --arg enableGraphRaw "${_mem0_enable_graph_lc}" \ + ' + .plugins = (.plugins // {}) + | .plugins.load = (.plugins.load // {}) + | .plugins.entries = (.plugins.entries // {}) + | if (.plugins.allow | type) != "array" then .plugins.allow = [] else . end + | if (.plugins.allow | index($pluginName)) == null then .plugins.allow += [$pluginName] else . end + | if (.plugins.load.paths | type) != "array" then .plugins.load.paths = [] else . end + | if (.plugins.load.paths | index($pluginDir)) == null then .plugins.load.paths += [$pluginDir] else . end + | .plugins.entries[$pluginName] = { + "enabled": true, + "config": ( + { + "apiKey": $apiKey, + "userId": $userId + } + + (if $orgId != "" then {"orgId": $orgId} else {} end) + + (if $projectId != "" then {"projectId": $projectId} else {} end) + + (if $enableGraphRaw == "true" then {"enableGraph": true} else {} end) + ) + } + ' "${OUTPUT_DIR}/openclaw.json" > "${OUTPUT_DIR}/openclaw.json.mem0-tmp" && \ + mv "${OUTPUT_DIR}/openclaw.json.mem0-tmp" "${OUTPUT_DIR}/openclaw.json" + log "mem0 config injected into Worker ${WORKER_NAME} openclaw.json (userId=${WORKER_NAME}, enableGraph=${_mem0_enable_graph_lc})" + fi +fi + # ============================================================ # Optional: inject openclaw-cms-plugin observability config # diff --git a/manager/scripts/init/start-manager-agent.sh b/manager/scripts/init/start-manager-agent.sh index 4ade2694..113f5c52 100755 --- a/manager/scripts/init/start-manager-agent.sh +++ b/manager/scripts/init/start-manager-agent.sh @@ -600,6 +600,63 @@ if [ "${HICLAW_RUNTIME}" = "aliyun" ]; then log "Cloud overlay applied" fi +# mem0 Plugin Configuration (Platform mode only) +# Injects openclaw-mem0 plugin config into openclaw.json when enabled. +# Requires: HICLAW_MEM0_ENABLED=true, HICLAW_MEM0_API_KEY +# Optional: HICLAW_MEM0_USER_ID, HICLAW_MEM0_ORG_ID, HICLAW_MEM0_PROJECT_ID, HICLAW_MEM0_ENABLE_GRAPH +# ============================================================ +if [ "${HICLAW_MEM0_ENABLED:-false}" = "true" ]; then + MEM0_PLUGIN_NAME="openclaw-mem0" + MEM0_PLUGIN_DIR="${OPENCLAW_MEM0_PLUGIN_DIR:-/opt/openclaw/extensions/${MEM0_PLUGIN_NAME}}" + MEM0_PLUGIN_MANIFEST="${MEM0_PLUGIN_DIR}/openclaw.plugin.json" + if [ -z "${HICLAW_MEM0_API_KEY:-}" ]; then + log "WARNING: HICLAW_MEM0_ENABLED=true but HICLAW_MEM0_API_KEY is not set, skipping mem0 plugin" + elif [ ! -f "${MEM0_PLUGIN_MANIFEST}" ]; then + log "WARNING: mem0 plugin manifest not found at ${MEM0_PLUGIN_MANIFEST}, skipping mem0 config. Verify the image was built with MEM0_PLUGIN_ENABLED=1." + else + log "Configuring mem0 plugin (Platform mode)..." + _mem0_user_id="${HICLAW_MEM0_USER_ID:-${HICLAW_ADMIN_USER:-admin}}" + _mem0_org_id="${HICLAW_MEM0_ORG_ID:-}" + _mem0_project_id="${HICLAW_MEM0_PROJECT_ID:-}" + _mem0_enable_graph="$(echo "${HICLAW_MEM0_ENABLE_GRAPH:-false}" | tr '[:upper:]' '[:lower:]')" + + # Build mem0 plugin config JSON + _mem0_config=$(jq -n \ + --arg apiKey "${HICLAW_MEM0_API_KEY}" \ + --arg userId "${_mem0_user_id}" \ + --arg orgId "${_mem0_org_id}" \ + --arg projectId "${_mem0_project_id}" \ + --arg enableGraphRaw "${_mem0_enable_graph}" \ + '{ + "enabled": true, + "config": { + "apiKey": $apiKey, + "userId": $userId + } + (if $orgId != "" then {"orgId": $orgId} else {} end) + + (if $projectId != "" then {"projectId": $projectId} else {} end) + + (if $enableGraphRaw == "true" then {"enableGraph": true} else {} end) + }') + + # Inject into openclaw.json (idempotent merge) + jq --arg pluginName "${MEM0_PLUGIN_NAME}" \ + --arg pluginDir "${MEM0_PLUGIN_DIR}" \ + --argjson mem0 "${_mem0_config}" ' + .plugins = (.plugins // {}) + | .plugins.load = (.plugins.load // {}) + | .plugins.entries = (.plugins.entries // {}) + | if (.plugins.allow | type) != "array" then .plugins.allow = [] else . end + | if (.plugins.allow | index($pluginName)) == null then .plugins.allow += [$pluginName] else . end + | if (.plugins.load.paths | type) != "array" then .plugins.load.paths = [] else . end + | if (.plugins.load.paths | index($pluginDir)) == null then .plugins.load.paths += [$pluginDir] else . end + | .plugins.entries[$pluginName] = $mem0 + ' /root/manager-workspace/openclaw.json > /tmp/openclaw-mem0.json && \ + mv /tmp/openclaw-mem0.json /root/manager-workspace/openclaw.json + + log "mem0 plugin configured (userId: ${_mem0_user_id})" + fi +fi +unset MEM0_PLUGIN_NAME MEM0_PLUGIN_DIR MEM0_PLUGIN_MANIFEST + # ============================================================ # Optional: enable openclaw-cms-plugin observability # Config is applied at runtime so secrets stay out of image layers. diff --git a/worker/Dockerfile b/worker/Dockerfile index 905e09b4..3340d03b 100644 --- a/worker/Dockerfile +++ b/worker/Dockerfile @@ -8,6 +8,7 @@ # HIGRESS_REGISTRY - base image registry (default: cn-hangzhou) # OPENCLAW_BASE_IMAGE - openclaw-base image (default: hiclaw/openclaw-base:latest) # OPENCLAW_CMS_PLUGIN_URL - observability plugin tarball URL (bundled unconditionally) +# MEM0_PLUGIN_ENABLED - set to 1 to bundle @mem0/openclaw-mem0 at build time # ============================================================ ARG HIGRESS_REGISTRY=higress-registry.cn-hangzhou.cr.aliyuncs.com @@ -48,6 +49,27 @@ RUN tmp_dir="$(mktemp -d)" && \ # which correctly resolves to /.openclaw/openclaw.json ENV PATH="/opt/openclaw/packages/clawdbot/node_modules/.bin:${PATH}" +# ---- Optional: mem0 Plugin (long-term memory for agents) ---- +# Set MEM0_PLUGIN_ENABLED=1 at build time to bundle the plugin. +# Runtime configuration is injected via HICLAW_MEM0_* env vars. +ENV OPENCLAW_MEM0_PLUGIN_DIR="/opt/openclaw/extensions/openclaw-mem0" +ARG MEM0_PLUGIN_ENABLED=0 +RUN if [ "${MEM0_PLUGIN_ENABLED}" = "1" ]; then \ + echo "Installing @mem0/openclaw-mem0 plugin..." && \ + tmp_dir="$(mktemp -d)" && \ + cd "${tmp_dir}" && \ + pack_name="$(npm pack @mem0/openclaw-mem0 --silent)" && \ + rm -rf "${OPENCLAW_MEM0_PLUGIN_DIR}" && \ + mkdir -p "${OPENCLAW_MEM0_PLUGIN_DIR}" && \ + tar -xzf "${pack_name}" -C "${tmp_dir}" && \ + cp -rf "${tmp_dir}/package/." "${OPENCLAW_MEM0_PLUGIN_DIR}/" && \ + cd "${OPENCLAW_MEM0_PLUGIN_DIR}" && \ + npm install --omit=dev --ignore-scripts && \ + test -f "${OPENCLAW_MEM0_PLUGIN_DIR}/openclaw.plugin.json" && \ + rm -rf "${tmp_dir}" && \ + echo "mem0 plugin installed successfully"; \ + fi + # Shared environment bootstrap and credential management COPY --from=shared . /opt/hiclaw/scripts/lib/