small gleam coding and persistent agent daemon with a detachable cli and a canvas ui albedo.sh

Idle maintenance releases unowned kernels even when snapshot variables were skipped #163

Closed okami.mom
Closed opened by okami.mom

Summary #

Automatic release of an idle kernel without a loaded session actor ignores variables that the snapshot could not save, and can destroy the only copy of those values.

Reviewed commit: 154c6911d6a491aeeecacf26a06b6ae8c4bc70e1.

Code path #

In src/albedo/daemon/maintenance.gleam:175–195, release_unowned calls session_namespace.save_state and calls runtime.forget_session for any Ok(_) result.

However, Ok(Saved(...)) can contain a nonempty missed list. src/albedo/harness/extensions/python/kernel.gleam:238–325 maps the Python snapshot's skipped and failed entries into Saved.missed without making that a top-level error.

The actor-owned automatic release path already handles this correctly: src/albedo/daemon/session.gleam:2489 retains the kernel on Released(_, Ok(python.Saved(missed: [_, ..], ..)), None).

Failure scenario #

  1. A session retains an unserializable value, such as a generator, without any running cells/jobs or other resources keeping it non-quiescent.
  2. Restart the daemon, preserving its detached kernel.
  3. Leave the session unopened so the reattached kernel has no session actor.
  4. Once idle maintenance releases it, the snapshot succeeds with the value listed as skipped, but maintenance stops the kernel anyway.
  5. The next restore cannot recover that value.

Validation #

The snapshot behavior was reproduced using the actual Python functions, without starting a daemon:

import os
import tempfile
from albedo_state import save_state, load_state

with tempfile.TemporaryDirectory() as home:
    path = os.path.join(home, 'session.state')
    result = save_state(path, {'g': (i for i in range(3)), 'plain': 42}, {}, set())
    restored = {}
    load_state(path, restored, {})
    print(result.get('error'))  # None
    print(result['skipped'])   # g: TypeError: cannot pickle 'generator' object
    print('g' in restored)     # False

Run from the checkout with PYTHONPATH=priv/python. The complete daemon-restart/maintenance scenario is source-traced, not end-to-end reproduced.

Expected behavior #

Automatic release should retain unowned kernels whose snapshot reports missed names, just as actor-owned automatic release does. A regression scenario should cover restart, reattachment without opening the session, and an idle sweep with an unserializable variable.

Fixed on main (up to 0e4b0c30): idle maintenance now keeps a kernel whose snapshot missed names (owned sweep, unowned/reattached, and unload alike), via one rule, session_namespace.may_release. It's released lossy only under memory pressure (unowned kernels now count toward the budget) or once the session has idled past 24 h; the restore notice names what was lost. A kept kernel that runs nothing no longer rewrites its state file each sweep (measured: 0 bytes/h idle, was ~2.9 MB/h for a tiny namespace). Tests in test/e2e/idle_reap_test.py fail on the old code.

Sign up or Sign in to add to the discussion.