feat(sleep): add handoff backend — session-executed model calls, no API subprocess (#125)

Adds --backend handoff: the engine runs all deterministic stages and
outsources attempt/judge/reflect to prompt/answer files an interactive
agent session fills between runs (exit 3 = pending batch, re-run to
resume). Deterministic replay + the prompt-hash answer cache make resume
stateless; sentinel detection aborts any call built from unanswered
output so placeholders never reach scores or staging. Session digests
and mined tasks are pinned per night (secret-redacted) so the sessions
answering prompts cannot shift the task set, and LLM mining is routed
through the same handoff files. Ships a /skillopt-sleep-handoff Claude
Code command that answers each prompt in a fresh-context subagent to
protect the held-out gate.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
dimitarvdenev
2026-07-12 18:23:53 +02:00
committed by GitHub
parent 7df49656d1
commit b309723baa
10 changed files with 745 additions and 6 deletions
+5 -2
View File
@@ -14,7 +14,7 @@ import sys
from dataclasses import dataclass
from typing import List, Optional
from skillopt_sleep.backend import get_backend
from skillopt_sleep.backend import Backend, get_backend
from skillopt_sleep.config import SleepConfig, load_config
from skillopt_sleep.dream import dream_consolidate
from skillopt_sleep.harvest_sources import harvest_for_config
@@ -94,6 +94,7 @@ def run_sleep_cycle(
seed_tasks: Optional[List[TaskRecord]] = None,
dry_run: bool = False,
clock: Optional[float] = None,
backend: Optional[Backend] = None,
) -> CycleOutcome:
"""Run one full sleep cycle and return the outcome.
@@ -104,6 +105,8 @@ def run_sleep_cycle(
inject a known persona instead of harvesting ~/.claude).
dry_run : harvest+mine+replay but DO NOT stage/adopt (report only).
clock : fixed epoch seconds for deterministic timestamps in tests.
backend : optional pre-built Backend; the handoff driver passes one so
it can inspect the backend's pending calls after the run.
"""
cfg = cfg or load_config()
state = SleepState.load(cfg.state_path)
@@ -111,7 +114,7 @@ def run_sleep_cycle(
project = _project_paths(cfg)
started = _now_iso(clock)
backend = get_backend(
backend = backend or get_backend(
cfg.get("backend", "mock"),
model=cfg.get("model", ""),
codex_path=cfg.get("codex_path", ""),