From 393d376008c2c4fafcb3078d66bbea0215631cb5 Mon Sep 17 00:00:00 2001 From: Claudio Date: Thu, 18 Jun 2026 17:15:44 +0200 Subject: [PATCH] feat(memory): Added mempalace as memory provider Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Claudio --- CLAUDE.md | 127 ++++++++++++++++++++++++ Containerfile | 16 ++- README.md | 61 ++++++++++++ conf/.claude/context.d/CLAUDE-base.md | 6 +- conf/.claude/context.d/CLAUDE-memory.md | 38 +++++++ conf/.claude/settings.json | 4 + conf/.mempalace/config.json | 3 + renovate.json | 13 +++ 8 files changed, 262 insertions(+), 6 deletions(-) create mode 100644 CLAUDE.md create mode 100644 conf/.claude/context.d/CLAUDE-memory.md create mode 100644 conf/.mempalace/config.json diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8eb13c2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,127 @@ +# Claudio — CLAUDE.md + +## Project Overview + +**Claudio** is an OCI container image that packages Claude Code CLI into a portable, CI/CD-friendly container. It uses Google Vertex AI as the Claude API backend by default and bundles skills/plugins from the [claudio-skills](https://github.com/aipcc-cicd/claudio-skills) marketplace. + +Primary use cases: +- Running Claude Code in GitLab CI pipelines via a reusable `.claudio` job template +- Providing a portable AI development assistant that can be mounted into any working directory + +## Key Files + +| File | Purpose | +|---|---| +| `Containerfile` | Multi-stage OCI image build (preparer → final) | +| `entrypoint.sh` | Container startup: configures git identity, SSH signing key, gcloud auth, starts MemPalace if `MEMPAL_DIR` is set | +| `Makefile` | Build, tag, push, and release automation | +| `conf/.claude.json` | Claude client config template baked into the image | +| `scripts/generate-plugin-configs.sh` | Registers claudio-skills as Claude plugins at build time | +| `integrations/gitlab-ci/claudio.yml` | Generated GitLab CI reusable job template | +| `.github/workflows/build.yml` | Multi-arch (amd64 + arm64) CI build pipeline | +| `.github/workflows/push-pr-images.yml` | Publishes PR images to GHCR | +| `renovate.json` | Automated dependency updates (Claude Code, gcloud SDK versions) | + +## Build & Run + +### Build the image + +```bash +make oci-build # native arch, defaults +CONTAINER_MANAGER=docker make oci-build # use Docker instead of Podman + +# Pin to a specific claudio-skills ref +CS_REF_TYPE=tag CS_REF=v0.1.0 make oci-build +CS_REF_TYPE=branch CS_REF=main make oci-build +CS_REF_TYPE=pr CS_REF=9 make oci-build + +# Custom image repo/tag +IMAGE_REPO=ghcr.io/myorg/claudio IMAGE_TAG=latest make oci-build +``` + +### Run locally + +```bash +podman run -it --rm --userns=keep-id \ + -v ${HOME}/.config/gcloud:/home/claudio/.config/gcloud \ + -v ${PWD}:/home/claudio/workdir \ + -e ANTHROPIC_VERTEX_PROJECT_ID="my-gcp-project" \ + quay.io/aipcc-cicd/claudio:latest +``` + +## Architecture + +- **Base images**: `ubi10` (preparer) → `ubi10/python-312-minimal` (final) +- **Key installed tools**: Claude Code CLI, Google Cloud SDK, Podman, Skopeo, git, jq +- **Runs as**: non-root user `claudio` (uid 1000) +- **Multi-arch**: amd64 and arm64 manifests published to GHCR (dev/PRs) and Quay (releases) + +## Dependencies & Versions (managed by Renovate) + +- Claude Code CLI: pinned in `Containerfile` (`ARG CLAUDE_CODE_VERSION`) +- Google Cloud SDK: pinned in `Containerfile` (`ARG GCLOUD_SDK_VERSION`) +- claudio-skills: resolved at build time via `CS_REF`/`CS_REF_TYPE`; cache invalidated automatically via `CS_CACHE_KEY` (resolved commit SHA) + +## Release Process + +1. Bump `VERSION` in `Makefile` +2. Tag the commit: `make tag` (creates `v` git tag) +3. Push the tag — the `build.yml` workflow publishes to Quay and GHCR + +Current version: `0.6.0-dev` + +## CI/CD + +- **PRs**: builds both arches, uploads tar artifacts, `push-pr-images.yml` loads and pushes to GHCR with PR tag +- **`main` push**: builds + pushes `latest` multi-arch manifest to GHCR +- **Version tags**: builds + pushes versioned multi-arch manifest to Quay (`quay.io/aipcc-cicd/claudio`) + +## GitLab CI Integration + +Users include the reusable template and extend `.claudio`: + +```yaml +include: + - project: 'aipcc-cicd/claudio' + file: 'integrations/gitlab-ci/claudio.yml' + +my-job: + extends: .claudio + variables: + CLAUDIO_PROMPT: "Review this MR and suggest improvements" +``` + +## Memory (MemPalace) + +Persistent memory is provided by [MemPalace](https://github.com/MemPalace/mempalace) and is **opt-in** via the `MEMPAL_ENABLED` env var. + +The palace is stored at a **fixed path inside the container**: `/home/claudio/.mempalace/palace`. Mount a persistent volume there to retain memory across sessions. + +| Variable | Default | Purpose | +|---|---|---| +| `MEMPAL_ENABLED` | `"false"` (disabled) | Enable flag for MemPalace hooks. Set to `true` to activate. The palace is always at `/home/claudio/.mempalace/palace` — mount a volume there to persist memory across runs. | +| `MEMPAL_SAVE_INTERVAL` | `5` | Number of conversation exchanges between automatic memory saves on shutdown (passed to `mempal_save_hook.sh`). Lower values save more frequently; higher values reduce overhead. | + +Two hooks are always registered in `conf/.claude/settings.json` but short-circuit with `exit 0` when `MEMPAL_ENABLED` is unset: + +- **PreCompact** → `mempal_precompact_hook.sh` — saves context before compaction +- **Stop** → `mempal_save_hook.sh` — auto-saves every N exchanges on shutdown + +Hook scripts are downloaded at build time from the `v${MEMPALACE_V}` tag of the upstream repo, keeping them in sync with the installed pip package version. Renovate bumps `MEMPALACE_V` in the Containerfile and the hook URLs update automatically. + +### Local usage with memory + +```bash +podman run -it --rm --userns=keep-id \ + -v ${HOME}/.config/gcloud:/home/claudio/.config/gcloud \ + -v ${PWD}:/home/claudio/workdir \ + -v ${HOME}/.local/share/claudio-memory:/home/claudio/.mempalace/palace \ + -e ANTHROPIC_VERTEX_PROJECT_ID="..." \ + -e MEMPAL_ENABLED=true \ + -e MEMPAL_SAVE_INTERVAL=5 \ + quay.io/aipcc-cicd/claudio:v0.6.0 +``` + +## License + +Apache 2.0 diff --git a/Containerfile b/Containerfile index e89a9bb..8195053 100644 --- a/Containerfile +++ b/Containerfile @@ -56,7 +56,10 @@ ENV HOME /home/claudio ENV PATH="${HOME}/.local/bin:${PATH}" # Base for claudio image -RUN microdnf install -y skopeo podman unzip gzip git jq; \ +# pyopenssl is pulled in transitively by the UBI10 base image and is not a direct +# dependency. CVE-2026-27459 (CRITICAL) affects <26.0.0; pin the fixed version. +RUN microdnf install -y skopeo podman unzip gzip git jq && microdnf clean all; \ + pip install --no-cache-dir "pyopenssl>=26.0.0"; \ useradd claudio # Claude @@ -93,6 +96,17 @@ RUN echo "cs-cache-key: ${CS_CACHE_KEY}" \ claude plugin install --scope user claudio-plugin; \ pt-manager.sh + +# MemPalace +ENV MEMPALACE_V 3.4.1 +ENV MEMPAL_SAVE_INTERVAL 5 +RUN pip install --no-cache-dir MemPalace==${MEMPALACE_V}; \ + claude plugin marketplace add MemPalace/mempalace@v${MEMPALACE_V};\ + claude plugin install --scope user mempalace; \ + grep -q '^SAVE_INTERVAL=[0-9]' ${HOME}/.claude/plugins/marketplaces/mempalace/hooks/mempal_save_hook.sh || \ + { echo 'ERROR: SAVE_INTERVAL pattern not found in hook script'; exit 1; }; \ + sed -i 's/^SAVE_INTERVAL=[0-9]\+/SAVE_INTERVAL="${MEMPAL_SAVE_INTERVAL:-15}"/' ${HOME}/.claude/plugins/marketplaces/mempalace/hooks/mempal_save_hook.sh; + # Claudio RUN chown -R claudio:0 ${HOME}; \ chmod -R ug+rwx ${HOME} diff --git a/README.md b/README.md index dc77088..bfdbe2a 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,67 @@ cd ~/my-project claudio ``` +## Memory + +Claudio bundles [MemPalace](https://github.com/MemPalace/mempalace) for persistent memory across sessions. Memory is **opt-in** — it is disabled by default and only activates when `MEMPAL_ENABLED` is set. + +The palace is stored at a **fixed path inside the container**: `/home/claudio/.mempalace/palace`. Mount a persistent volume there to retain memory across runs. + +Two Claude Code hooks activate automatically: + +| Hook | Event | Purpose | +|---|---|---| +| `mempal_precompact_hook.sh` | `PreCompact` | Saves context before conversation compaction | +| `mempal_save_hook.sh` | `Stop` | Auto-saves memory every N exchanges on shutdown | + +| Variable | Default | Purpose | +|---|---|---| +| `MEMPAL_SAVE_INTERVAL` | `5` | Number of conversation exchanges between automatic memory saves. Lower values save more frequently; higher values reduce overhead. | + +### Enabling memory locally + +Mount a host directory at the palace path and set `MEMPAL_ENABLED`: + +```bash +podman run -it --rm --userns=keep-id \ + -v ${HOME}/.config/gcloud:/home/claudio/.config/gcloud \ + -v ${PWD}:/home/claudio/workdir \ + -v ${HOME}/.local/share/claudio-memory:/home/claudio/.mempalace/palace \ + -e ANTHROPIC_VERTEX_PROJECT_ID="${ANTHROPIC_VERTEX_PROJECT_ID}" \ + -e ANTHROPIC_VERTEX_PROJECT_QUOTA="${ANTHROPIC_VERTEX_PROJECT_QUOTA}" \ + -e MEMPAL_ENABLED=true \ + -e MEMPAL_SAVE_INTERVAL=5 \ + quay.io/aipcc-cicd/claudio:v0.6.0 +``` + +Add it to your wrapper script and `.env` file to make it permanent: + +```bash +# in ~/.config/claudio/.env +MEMPAL_SAVE_INTERVAL=5 +``` + +```bash +# in the wrapper script, add the volume mount and env vars + -v ${HOME}/.local/share/claudio-memory:/home/claudio/.mempalace/palace \ + -e MEMPAL_ENABLED="${MEMPAL_ENABLED:-false}" \ + -e MEMPAL_SAVE_INTERVAL="${MEMPAL_SAVE_INTERVAL:-5}" \ +``` + +### Enabling memory in GitLab CI + +Mount a persistent volume at the palace path and pass `MEMPAL_ENABLED`: + +```yaml +my-claudio-job: + extends: .claudio + variables: + CLAUDIO_PROMPT: "Analyze the latest MRs and report to Slack" + MEMPAL_ENABLED: "true" + volumes: + - claudio-memory:/home/claudio/.mempalace/palace +``` + ## One-time Prompt ```bash diff --git a/conf/.claude/context.d/CLAUDE-base.md b/conf/.claude/context.d/CLAUDE-base.md index 0cd4f44..e5c3863 100644 --- a/conf/.claude/context.d/CLAUDE-base.md +++ b/conf/.claude/context.d/CLAUDE-base.md @@ -1,9 +1,4 @@ # Sequential Thinking Memory -- To inspect container images or container registries you can use skopeo -- To interact with k8s / OpenShift Cluster you can use kubectl -- To create Konflux production releases you can use your konflux-release skill -- To interact with slack you can use your slack mcp server -- To interact with gitlab you can use your gitlab skill - Before giving a final answer, summarize reasoning and list assumptions. - When coding, first outline the approach → then show the code → then explain potential pitfalls. - Carry forward context from earlier turns unless I override it. @@ -11,3 +6,4 @@ - Propose at least one alternative path or trade-off if relevant. - When unsure, ask clarifying questions rather than guessing. - Regularly checkpoint: summarize progress and agreed decisions before moving on. +- When running scripts under /home/claudio/claudio-skills/, always use absolute paths (never `cd ... && ./script`) so the Bash permission rule matches. \ No newline at end of file diff --git a/conf/.claude/context.d/CLAUDE-memory.md b/conf/.claude/context.d/CLAUDE-memory.md new file mode 100644 index 0000000..bf0ec3d --- /dev/null +++ b/conf/.claude/context.d/CLAUDE-memory.md @@ -0,0 +1,38 @@ +# Memory Protocol — MemPalace + +MemPalace is the sole memory store. Never write to Claude Code's native auto-memory (`.md` files). + +## On Wake-Up + +**MANDATORY: On the first user message of every new conversation, before responding to anything else, run steps 1–3 below. Do not wait to be asked. Do not send any text response until all steps are complete.** + +⚠ MemPalace tools are deferred — their schemas are NOT loaded at startup. +Before calling any `mempalace_*` tool you MUST first run: + +``` +ToolSearch(query: "select:mcp__plugin_mempalace_mempalace__mempalace_status,mcp__plugin_mempalace_mempalace__mempalace_diary_read") +``` + +Then immediately call both tools **in parallel**: + +1. `mempalace_status` — palace overview +2. `mempalace_diary_read` (agent_name: "session-hook", last_n: 5) — recent session checkpoints (auto-saved by plugin) +3. `mempalace_search` (query: "") — before answering about past work + +## After Completing Work (MANDATORY) + +After completing any of the following, call `mempalace_add_drawer` immediately — do not wait until end of session: + +- Jira operations: cards created, updated, transitioned, or linked (save keys + summaries) +- Decisions made (architecture, approach, naming) +- Config or settings changes (CLAUDE.md, settings.json, hooks) +- Scripts or tools created or modified + +Save the **outcome** (what was done, keys created, values set) — not the process. The stop hook saves the transcript; this saves the facts. + +## Rules + +- Search before answering about past work — never guess. +- **Before generating any artifact** (document, plan, summary, config, script) search MemPalace first. If a prior version exists, retrieve and present it — do not regenerate from inference. +- If a fact changes: `kg_invalidate` then `kg_add`. +- Checkpoints are raw message extracts written by the plugin automatically under `agent_name: "session-hook"`. They capture what was said but not curated findings. diff --git a/conf/.claude/settings.json b/conf/.claude/settings.json index c8c7629..98ccc32 100644 --- a/conf/.claude/settings.json +++ b/conf/.claude/settings.json @@ -2,6 +2,10 @@ "model": "claude-sonnet-4-6", "permissions": { "allow": [ + "mcp__plugin_mempalace_mempalace__mempalace_status", + "mcp__plugin_mempalace_mempalace__mempalace_diary_read", + "mcp__plugin_mempalace_mempalace__mempalace_search", + "mcp__plugin_mempalace_mempalace__mempalace_add_drawer" ], "deny": [], "ask": [] diff --git a/conf/.mempalace/config.json b/conf/.mempalace/config.json new file mode 100644 index 0000000..0a5b2e1 --- /dev/null +++ b/conf/.mempalace/config.json @@ -0,0 +1,3 @@ +{ + "palace_path": "/home/claudio/.mempalace/palace" +} \ No newline at end of file diff --git a/renovate.json b/renovate.json index 5e19999..3740ddb 100644 --- a/renovate.json +++ b/renovate.json @@ -39,6 +39,19 @@ "datasourceTemplate": "github-releases", "depNameTemplate": "aipcc-cicd/claudio-skills", "autoReplaceStringTemplate": "CS_REF ?= {{newValue}}" + }, + { + "customType": "regex", + "description": "Update MemPalace version", + "managerFilePatterns": [ + "Containerfile" + ], + "matchStrings": [ + "ENV\\s+MEMPALACE_V\\s+(?\\d+\\.\\d+\\.\\d+)" + ], + "datasourceTemplate": "pypi", + "depNameTemplate": "mempalace", + "autoReplaceStringTemplate": "ENV MEMPALACE_V {{newValue}}" } ], "packageRules": []