Retires the Moonshot/Kimi subscription in favour of the already-paid ChatGPT plan. Both CLI wrappers now run `codex exec`; the kimi-agent container is gone. adolf-llm + hindsight-llm: - runKimi -> runCodex (`codex exec --json --skip-git-repo-check`), resume via `codex exec resume <thread_id>`. - MCP moves from a per-session .mcp.json (a workaround for Kimi having no --mcp-config-file flag) to a $CODEX_HOME/config.toml generated once at startup from shared-mcp.json. Field translation is load-bearing: bearerTokenEnvVar -> bearer_token_env_var, enabledTools -> enabled_tools. - approval_policy="never" + sandbox_mode required, or unattended turns block on an approval prompt nobody can answer. kimi-agent removed. It was the ONLY large-tier deployment behind LiteLLM, so deleting it outright would have silently degraded every large-tier request to the local 4B model via the existing fallbacks. tier-large, the auto_router complex-reasoning route and their fallbacks now point at the codex-backed adolf-llm wrapper (model_name: codex-agent). Three environment blockers fixed along the way: - OpenAI geo-blocks this host (403 unsupported_country_region_territory). Both containers now egress via the host xray proxy, with NO_PROXY keeping MCP and *.alogins.net traffic off the tunnel. - node:22-slim ships no system CA store; the Rust codex binary validates TLS against it, so every HTTPS call failed with a generic transport error while Node's own fetch worked. ca-certificates added to both images. - `codex exec resume` rejects -C/--cd (plain `codex exec` accepts it), which broke follow-up turns while first turns succeeded. Known regression: Kimi's managed-usage API has no Codex equivalent, so the /usage route returns 501 and there is no quota probe for the codex model. The two quota plugins degrade quietly to no output. Also: stop tracking cognee.env (live LLM + JWT secrets) and gitignore it. The secrets remain in earlier history and should be rotated. Verified live: plain turn, SSE streaming, session resume, MCP tool call, bearer-token MCP call, and completions through both LiteLLM routes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Y5QPagv4iun1ghpwM96Ff
152 lines
6.2 KiB
Bash
Executable File
152 lines
6.2 KiB
Bash
Executable File
#!/bin/bash
|
|
# Migration script for kb#219 — move Adolf's runtime state off the named
|
|
# Docker volume (openai_adolf-state) onto a host bind mount at
|
|
# /mnt/ssd/dbs/adolf/state, matching the convention every other Agap
|
|
# service already follows (hindsight, litellm, qdrant, langfuse, ...).
|
|
#
|
|
# SAFETY MODEL:
|
|
# - COPY ONLY. Never touches or deletes the source volume. The volume
|
|
# stays intact and usable as a rollback source until a human explicitly
|
|
# removes it (see rollback section in the compose-diff writeup /
|
|
# kb#219 report), long after this script has run and the container has
|
|
# been soak-tested on the new mount.
|
|
# - Dry-run by default. Pass --apply to actually copy.
|
|
# - Idempotent. Safe to re-run; re-copying onto an already-populated
|
|
# destination just refreshes it (cp -a overwrite-in-place). It will
|
|
# NOT delete files at the destination that were removed from the
|
|
# source between runs -- if that matters, wipe the dest dir yourself
|
|
# before re-running.
|
|
# - Verifies file counts + a sha256 manifest diff between source and
|
|
# destination before declaring success. Non-zero exit if they disagree.
|
|
# - Uses only `docker run` (alvis is in the `docker` group -- no `sudo`
|
|
# needed for container operations) to read the volume; never reads
|
|
# /var/lib/docker/volumes directly (root-only, 0700).
|
|
# - Does NOT create /mnt/ssd/dbs/adolf itself. That directory tree is
|
|
# root-owned (/mnt/ssd/dbs is 0755 root:root, same as every other
|
|
# service dir under it) and must be created + chowned by a human with
|
|
# sudo first -- see the paste-ready root block in the kb#219 report.
|
|
# This script aborts early with a clear message if the destination
|
|
# parent doesn't exist or isn't writable.
|
|
#
|
|
# USAGE:
|
|
# ./migrate-adolf-state.sh # dry run (default), prints plan
|
|
# ./migrate-adolf-state.sh --apply # actually copies + verifies
|
|
# ./migrate-adolf-state.sh --apply --dest /path/to/scratch --volume some-test-volume
|
|
# # point at a throwaway volume/dest for a trial run
|
|
#
|
|
# This script is NOT executed against live state as part of kb#219 prep.
|
|
# It has been dry-run tested and trial-run tested against a throwaway
|
|
# volume with a handful of files (see kb#219 report for the transcript).
|
|
|
|
set -euo pipefail
|
|
|
|
SRC_VOLUME="openai_adolf-state"
|
|
DEST_DIR="/mnt/ssd/dbs/adolf/state"
|
|
APPLY=0
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--apply) APPLY=1; shift ;;
|
|
--dest) DEST_DIR="$2"; shift 2 ;;
|
|
--volume) SRC_VOLUME="$2"; shift 2 ;;
|
|
-h|--help)
|
|
grep '^#' "$0" | sed 's/^#//'
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown argument: $1" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
echo "== kb#219 adolf-state migration =="
|
|
echo "Source volume : $SRC_VOLUME"
|
|
echo "Dest dir : $DEST_DIR"
|
|
echo "Mode : $([ "$APPLY" -eq 1 ] && echo APPLY || echo DRY-RUN)"
|
|
echo
|
|
|
|
# --- 0. sanity: source volume exists ---
|
|
if ! docker volume inspect "$SRC_VOLUME" >/dev/null 2>&1; then
|
|
echo "ERROR: source volume '$SRC_VOLUME' does not exist." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# --- 1. sanity: destination parent exists and is writable ---
|
|
DEST_PARENT="$(dirname "$DEST_DIR")"
|
|
if [ ! -d "$DEST_PARENT" ]; then
|
|
cat >&2 <<EOF
|
|
ERROR: $DEST_PARENT does not exist.
|
|
|
|
/mnt/ssd/dbs is root-owned; this directory must be created by a human
|
|
with sudo before this script can run. See the paste-ready root block in
|
|
the kb#219 report (creates /mnt/ssd/dbs/adolf/{state,config,personas},
|
|
chowned 1000:1000 to match the adolf container's node user).
|
|
EOF
|
|
exit 1
|
|
fi
|
|
if [ ! -w "$DEST_PARENT" ]; then
|
|
echo "ERROR: $DEST_PARENT exists but is not writable by $(whoami). Check ownership (should be chowned to your uid, or 1000:1000)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$DEST_DIR"
|
|
|
|
# --- 2. source manifest (counts + sha256, computed inside a container) ---
|
|
echo "-- Computing source manifest (read-only mount of $SRC_VOLUME) --"
|
|
SRC_COUNT=$(docker run --rm -v "$SRC_VOLUME":/from:ro alpine sh -c "find /from -type f | wc -l")
|
|
echo "Source file count: $SRC_COUNT"
|
|
|
|
if [ "$APPLY" -eq 0 ]; then
|
|
echo
|
|
echo "[DRY RUN] Would copy $SRC_COUNT files from volume '$SRC_VOLUME' into $DEST_DIR,"
|
|
echo "[DRY RUN] then verify file count + sha256 manifest match."
|
|
echo "[DRY RUN] Re-run with --apply to actually copy."
|
|
exit 0
|
|
fi
|
|
|
|
# --- 3. copy (tar stream preserves ownership/perms across the boundary) ---
|
|
echo "-- Copying (tar stream, preserves perms/ownership) --"
|
|
docker run --rm \
|
|
-v "$SRC_VOLUME":/from:ro \
|
|
-v "$DEST_DIR":/to \
|
|
alpine sh -c "cd /from && tar cf - . | (cd /to && tar xf -)"
|
|
|
|
# --- 4. verify: file count ---
|
|
DEST_COUNT=$(docker run --rm -v "$DEST_DIR":/to:ro alpine sh -c "find /to -type f | wc -l")
|
|
echo "Dest file count: $DEST_COUNT"
|
|
if [ "$SRC_COUNT" != "$DEST_COUNT" ]; then
|
|
echo "ERROR: file count mismatch (source=$SRC_COUNT dest=$DEST_COUNT). NOT declaring success." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# --- 5. verify: sha256 manifest diff ---
|
|
echo "-- Verifying sha256 manifests match --"
|
|
SRC_MANIFEST=$(mktemp)
|
|
DEST_MANIFEST=$(mktemp)
|
|
trap 'rm -f "$SRC_MANIFEST" "$DEST_MANIFEST"' EXIT
|
|
|
|
docker run --rm -v "$SRC_VOLUME":/from:ro alpine sh -c \
|
|
"cd /from && find . -type f -exec sha256sum {} \; | sort -k2" > "$SRC_MANIFEST"
|
|
docker run --rm -v "$DEST_DIR":/to:ro alpine sh -c \
|
|
"cd /to && find . -type f -exec sha256sum {} \; | sort -k2" > "$DEST_MANIFEST"
|
|
|
|
if diff -u "$SRC_MANIFEST" "$DEST_MANIFEST" > /tmp/adolf-state-migration.diff; then
|
|
echo "OK: manifests match byte-for-byte ($SRC_COUNT files)."
|
|
else
|
|
echo "ERROR: manifest mismatch, see /tmp/adolf-state-migration.diff" >&2
|
|
cat /tmp/adolf-state-migration.diff >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo
|
|
echo "== Migration copy verified OK =="
|
|
echo "Source volume '$SRC_VOLUME' left untouched (not deleted, not modified)."
|
|
echo "Next steps (NOT done by this script -- human-supervised, see kb#219 report):"
|
|
echo " 1. Apply the docker-compose.yml bind-mount diff for the 'adolf' service."
|
|
echo " 2. docker compose -f ai/docker-compose.yml config -q # validate"
|
|
echo " 3. docker compose -f ai/docker-compose.yml up -d adolf # recreates container on new mount"
|
|
echo " 4. Verify: docker inspect adolf shows /mnt/ssd/dbs/adolf/state, not the volume;"
|
|
echo " Matrix session survives (no re-login), memory/config/persona intact."
|
|
echo " 5. Only after a soak period: docker volume rm $SRC_VOLUME"
|