Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ User-facing MeTTa skills the agent invokes. Each page follows the template **Sig
- [reference-channels.md](./reference-channels.md) — IRC, Telegram, Slack, Mattermost, WebSocket, and websearch adapters plus the channel contract
- [reference-python-bridges.md](./reference-python-bridges.md) — `lib_llm_ext.py`, `src/helper.py`, `src/skills.pl`
- [reference-memory-portability.md](./reference-memory-portability.md) — Operator backup, restore, and archive-transfer workflow
- [reference-memory-migration.md](./reference-memory-migration.md) — Automatic migration from OmegaClaw Docker volumes and interrupted-copy recovery

### Plugin API

Expand Down
75 changes: 75 additions & 0 deletions docs/reference-memory-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Reference - Memory Migration

## Recovering an interrupted memory migration

When upgrading from OmegaClaw, the launcher copies `omegaclaw-memory` to
`omega-memory`. Keep the terminal open and do not interrupt the launcher or
Docker until it reports `Memory copied`. Keep `omegaclaw-memory` until you have
checked that the agent remembers its previous data.

If the copy is interrupted, follow the appropriate path below before importing
memory or starting the agent again. Use the same image tag and launcher options
as in your original start command.

### Retry the copy from OmegaClaw

Use this path only if `omega-memory` contains no imported memory or new agent
data that you need to keep. Retrying replaces this volume with a fresh copy of
`omegaclaw-memory`.

1. List the containers holding the destination volume:

```sh
docker ps -a --filter volume=omega-memory --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'
```

2. If the launcher reports `volume is in use` or names a leftover container,
stop and remove the identified container. Replace `CONTAINER_ID` with its
actual ID; do not remove unrelated containers or the source volume:

```sh
docker rm -f CONTAINER_ID
```

This also applies to stopped containers. Copy containers started by the
v0.1.20 launcher have no migration label and need to be removed manually.
The updated launcher removes labelled copy containers and the `omega`
container automatically when retrying.

3. Repeat your original `scripts/omega start ...` command, without
`--memory-import` or the `--only-history` and `--only-vector` flags. Wait for
`Memory copied`, then check the agent's memory.

### Keep memory imported or used after the interruption

Do not retry the copy or run another import before preserving the current
`omega-memory` volume. Stop any remaining copy containers identified above and
stop the `omega` agent if it is running. Back up the destination volume before
changing migration state.

The v0.1.20 launcher could leave `.migration-started` in `omegaclaw-memory`
after a memory import. In particular, an `--only-vector` import can leave
`history.metta` unchanged. The updated launcher's history check cannot detect
this case: a normal start can delete the imported vectors and copy the old
memory over them.

If the import completed successfully and you want to keep the current memory,
clear only the stale migration marker before starting again:

```sh
docker volume inspect omegaclaw-memory omega-memory
docker run --rm --entrypoint rm --volume omegaclaw-memory:/from \
singularitynet/omega:v0.1.20 -f /from/.migration-started
```

Run the second command only if both volumes exist. Then repeat your normal
start command without `--memory-import` or the `--only-history` and
`--only-vector` flags. Clearing the marker keeps the current destination volume;
it does not finish a partial copy. If you are unsure whether
the import or copy completed, preserve both volumes and ask for help before
continuing.

If imported memory has already been overwritten, the old OmegaClaw volume
cannot recover that import. Restore it from the original import archive or a
backup of `omega-memory`.

87 changes: 74 additions & 13 deletions scripts/omega
Original file line number Diff line number Diff line change
Expand Up @@ -863,36 +863,95 @@ options() {
fi
}

remove_partly_copied_memory() {
local volume="$1"
local container="$2"
local label="$3"
local leftover
local holder
local holders=""

if ! docker volume inspect "${volume}" >/dev/null 2>&1; then
return 0
fi
for leftover in $(docker ps -aq --filter "label=${label}" --filter "volume=${volume}" 2>/dev/null); do
docker rm -f "${leftover}" >/dev/null 2>&1 || true
done
for holder in $(docker ps -a --filter "volume=${volume}" --format '{{.Names}}' 2>/dev/null); do
if [ "${holder}" != "${container}" ]; then
holders="${holders:+${holders} }${holder}"
fi
done
if [ -n "${holders}" ]; then
echo "Could not remove the partly copied volume ${volume}, it is used by containers: ${holders}. Remove them with 'docker rm -f ${holders}' and start again" >&2
return 1
fi
if docker container inspect "${container}" >/dev/null 2>&1 \
&& docker rm -f "${container}" >/dev/null; then
echo "Removed container ${container}, it used the partly copied volume ${volume}"
fi
if ! docker volume rm "${volume}" >/dev/null; then
echo "Could not remove the partly copied volume ${volume}" >&2
return 1
fi
}

migrate_omegaclaw_memory() {
local old_container="omegaclaw"
local old_volume="omegaclaw-memory"
local new_volume="omega-memory"
local new_container="omega"
local migration_label="omega.memory-migration"
local memory_path="/PeTTa/repos/Omega/memory"
local marker=".migrated-to-omega"
local started_marker=".migration-started"
local stopped_old_container=0
local old_state
local new_state

if [ -n "${memory_import_file}" ] \
|| ! docker volume inspect "${old_volume}" >/dev/null 2>&1; then
if ! docker volume inspect "${old_volume}" >/dev/null 2>&1; then
return 0
fi
if ! old_state=$(docker run --rm --entrypoint sh --volume "${old_volume}:/from:ro" "${image}" \
if ! old_state=$(docker run --rm --label "${migration_label}" --entrypoint sh --volume "${old_volume}:/from:ro" "${image}" \
-c "if [ -e /from/${marker} ]; then echo migrated; elif [ -e /from/${started_marker} ]; then echo interrupted; fi"); then
echo "Could not read the migration markers in ${old_volume}" >&2
return 1
fi
if [ -n "${memory_import_file}" ]; then
if [ "${old_state}" = "interrupted" ]; then
echo "The last copy from ${old_volume} did not finish, the memory import replaces it"
remove_partly_copied_memory "${new_volume}" "${new_container}" "${migration_label}" || return 1
if ! docker run --rm --label "${migration_label}" --entrypoint sh --volume "${old_volume}:/from" "${image}" \
-c "rm -f /from/${started_marker}"; then
echo "Could not clear the migration marker in ${old_volume}" >&2
return 1
fi
fi
return 0
fi
case "${old_state}" in
migrated)
return 0
;;
interrupted)
echo "The last copy from ${old_volume} did not finish, copying again"
if docker volume inspect "${new_volume}" >/dev/null 2>&1 \
&& ! docker volume rm "${new_volume}" >/dev/null; then
echo "Could not remove the partly copied volume ${new_volume}" >&2
return 1
echo "WARNING: Retrying the migration can replace omega-memory. If you imported memory or used the agent after the interruption, preserve it before retrying." >&2
echo "Recovery instructions: https://github.com/singnet/Omega/blob/patch-to-v0.1.20/docs/reference-memory-migration.md#recovering-an-interrupted-memory-migration" >&2
if docker volume inspect "${new_volume}" >/dev/null 2>&1; then
if ! new_state=$(docker run --rm --label "${migration_label}" --entrypoint sh \
--volume "${old_volume}:/from:ro" --volume "${new_volume}:/to:ro" "${image}" \
-c 'if [ -e /to/history.metta ]; then n=$(wc -c < /to/history.metta); if [ ! -e /from/history.metta ] || [ "$n" -gt "$(wc -c < /from/history.metta)" ] || ! head -c "$n" /from/history.metta | cmp -s - /to/history.metta; then echo kept; fi; fi'); then
echo "Could not compare ${new_volume} with ${old_volume}" >&2
return 1
fi
if [ "${new_state}" = "kept" ]; then
echo "${new_volume} holds memory that is not a partial copy of ${old_volume}, for example an imported archive, so it was left as it was" >&2
echo "To keep it, clear the marker: docker run --rm --entrypoint rm --volume ${old_volume}:/from ${image} /from/${started_marker}" >&2
echo "To copy ${old_volume} again instead, remove it: docker rm -f ${new_container}; docker volume rm ${new_volume}" >&2
return 1
fi
fi
echo "The last copy from ${old_volume} did not finish, copying again"
remove_partly_copied_memory "${new_volume}" "${new_container}" "${migration_label}" || return 1

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we check whether memory was successfully imported after the interrupted migration, or preserve the existing volume before deleting it? A stale .migration-started marker could otherwise cause a normal start to discard valid imported memory. I see this is documented under Known limits - is this behavior intentional within the scope of OMEGA-492, or should we handle it here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we check whether memory was successfully imported after the interrupted migration, or preserve the existing volume before deleting it?

Done in 7263f93. The copy goes entry by entry, so a partial copy leaves history.metta empty, missing, or a prefix of the old file. Before redoing the copy, start compares the two files. If omega-memory holds anything else, such as an imported archive, start stops, leaves the volume and the omega container as they are, and prints two commands: one clears the marker and keeps the memory, the other removes omega-memory so the copy runs again.

On singularitynet/omega:v0.1.20 both commands work, and a partly copied history is still copied again. The new cases are in d4f1da4.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that covers the case where history has changed. What about --only-vector, where imported vector memory may change while history.metta stays the same?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timur-ashkenov Only for installs that went through v0.1.20 first. On this branch an import after an interrupted copy removes the partial volume and clears .migration-started (scripts/omega#L907-L918), so the history check never runs over imported memory. The v0.1.20 launcher kept the marker on import. An interrupted copy and an --only-vector import on v0.1.20, followed by a plain start with this launcher, would still remove the imported vectors. I'd cover that with the warning rather than extend the check.

;;
*)
if docker volume inspect "${new_volume}" >/dev/null 2>&1; then
Expand All @@ -902,6 +961,8 @@ migrate_omegaclaw_memory() {
esac

echo "Copying memory from volume ${old_volume} to ${new_volume}"
echo "WARNING: Keep this terminal open and do not interrupt the launcher or Docker until 'Memory copied' appears. Keep ${old_volume} until you have checked the migrated memory." >&2
echo "Recovery instructions: https://github.com/singnet/Omega/blob/patch-to-v0.1.20/docs/reference-memory-migration.md#recovering-an-interrupted-memory-migration" >&2
if [ "$(docker inspect -f '{{.State.Running}}' "${old_container}" 2>/dev/null)" = "true" ]; then
if ! docker stop "${old_container}" >/dev/null; then
echo "Could not stop container ${old_container}, memory was not copied" >&2
Expand All @@ -911,15 +972,15 @@ migrate_omegaclaw_memory() {
echo "Stopped container ${old_container}"
fi

if docker run --rm --entrypoint sh --volume "${old_volume}:/from" "${image}" \
if docker run --rm --label "${migration_label}" --entrypoint sh --volume "${old_volume}:/from" "${image}" \
-c "date -u > /from/${started_marker}" \
&& docker volume create "${new_volume}" >/dev/null \
&& docker run --rm --entrypoint sh --volume "${new_volume}:${memory_path}" "${image}" \
&& docker run --rm --label "${migration_label}" --entrypoint sh --volume "${new_volume}:${memory_path}" "${image}" \
-c "test -f ${memory_path}/prompt.txt" \
&& docker run --rm --entrypoint sh \
&& docker run --rm --label "${migration_label}" --entrypoint sh \
--volume "${old_volume}:/from:ro" --volume "${new_volume}:/to" "${image}" \
-c 'list=$(mktemp) && cd /from && find . -mindepth 1 -maxdepth 1 ! -name "prompt*.txt" ! -name tg_prompt.txt ! -name .migration-started > "$list" && while IFS= read -r entry; do rm -rf "/to/$entry" && cp -a "$entry" /to/ || exit 1; done < "$list"' \
&& docker run --rm --entrypoint sh --volume "${old_volume}:/from" "${image}" \
&& docker run --rm --label "${migration_label}" --entrypoint sh --volume "${old_volume}:/from" "${image}" \
-c "date -u > /from/${marker}"; then
echo "Memory copied. Carrying memory over is experimental, so keep the volume ${old_volume} until you have checked that the agent remembers what it knew. Then remove the old installation with 'docker rm ${old_container}' and 'docker volume rm ${old_volume}'"
return 0
Expand All @@ -928,7 +989,7 @@ migrate_omegaclaw_memory() {
echo "Could not copy memory from ${old_volume}, the old installation is left as it was" >&2
if docker volume rm "${new_volume}" >/dev/null 2>&1 \
|| ! docker volume inspect "${new_volume}" >/dev/null 2>&1; then
docker run --rm --entrypoint sh --volume "${old_volume}:/from" "${image}" \
docker run --rm --label "${migration_label}" --entrypoint sh --volume "${old_volume}:/from" "${image}" \
-c "rm -f /from/${started_marker}" >/dev/null 2>&1 || true
else
echo "Could not remove ${new_volume}, the next start will copy the memory again" >&2
Expand Down
Loading
Loading