Skip to content

skillopt-sleep: harvest checkpoint is keyed by project, so a second --source in the same project skips its unseen sessions #312

Open

Description

What happens

skillopt-sleep run stores one harvest checkpoint per project, whatever --source the run used. cycle.py reads state.last_harvest_for(project) before harvesting and calls state.set_last_harvest(project, started) after every stateful run (both the no-tasks branch and the normal end). So when one project is fed from several transcript sources, one night per source, every source after the first only sees sessions newer than the first run's start: its older, never-harvested sessions are skipped for good. Each source is a separate store with its own timestamps, so a single shared cursor cannot be right for all of them.

Repro (mock backend, isolated state, no model calls)

Version: main at 343db22, installed with uv tool install "skillopt @ git+https://github.com/microsoft/SkillOpt", Python 3.14, macOS. --claude-home points the state at a scratch dir; P is an empty scratch project.

T=$(mktemp -d); mkdir -p $T/.claude $T/proj
common="--project $T/proj --scope all --backend mock --claude-home $T/.claude --progress"
skillopt-sleep dry-run $common --source codex   # harvest done: sessions=60
skillopt-sleep run     $common --source cursor  # stateful; sets last_harvest[$T/proj]
skillopt-sleep dry-run $common --source codex   # harvest done: sessions=0

(Our Codex sessions are CLI rollouts, so we pass --codex-home pointing at a dir whose archived_sessions/ symlinks ~/.codex/sessions/**/rollout-*.jsonl; see #282.)

The second codex dry-run harvests nothing although no codex session was ever harvested: it inherited the cursor run's checkpoint.

Expected

Each source keeps its own checkpoint.

Suggested fix

Key last_harvest by f"{project}::{transcript_source}" and fall back to the bare project key when reading existing state, or accept several --source values in one run so one checkpoint covers them together.

Workaround we use

A wrapper keeps one cursor per source in its own file and writes it into state.json before each per-source run.

Activity

  1. Yif-Yang commented on Oct 18, 2026

    @Yif-Yang
    Contributor

    #322 demonstrates the intended source-cursor improvement: separate stateful sources no longer advance each other's new checkpoints, and the documented legacy fallback remains intact. It needs a focused integration rebase to preserve #317's unmeasured-budget state alongside source-qualified cursors. A separate inherited empty-handoff snapshot can still hide a later source's unseen sessions; that case also fails on main. This issue remains open for the remaining handoff/source-switch scope, using a non-closing reference or an explicit scoped follow-up rather than treating every source path as resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions