Skip to content

Commit b309723

Browse files
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>
1 parent 7df4965 commit b309723

10 files changed

Lines changed: 745 additions & 6 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ logs/
2424
external/
2525
# SkillOpt-Sleep runtime state (staging proposals, config, diagnostics, cron logs)
2626
.skillopt-sleep/
27+
# SkillOpt-Sleep handoff-backend round data (prompts/answers derived from transcripts)
28+
.skillopt-sleep-handoff/
29+
.skillopt-sleep-handoff.night*.done/
2730

2831
/BabyVision/
2932
/MMRB/

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ All notable changes to SkillOpt are documented here. This project adheres to
44
[Semantic Versioning](https://semver.org/) and the format is based on
55
[Keep a Changelog](https://keepachangelog.com/).
66

7+
## [Unreleased]
8+
9+
### Added
10+
- **Handoff backend** (`--backend handoff`) for SkillOpt-Sleep — runs the
11+
sleep cycle with no model subprocess or API key: the engine writes each
12+
pending model call to `PROMPTS.md`/`pending.json` (exit code 3) and the
13+
user's own agent session answers into `answers/<id>.md`; re-running the
14+
same command resumes statelessly from the answers (typically 3–6 rounds
15+
per night). Mined tasks are pinned per night so answering sessions cannot
16+
shift the task set. Ships a `/skillopt-sleep-handoff` Claude Code command
17+
that automates the loop with fresh-context subagents to protect the
18+
held-out gate.
19+
720
## [0.2.0] — 2026-07-02
821

922
The headline of this release is **SkillOpt-Sleep**: a nightly offline

plugins/README.md

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ sleep** idea (short-term experience → long-term competence).
2222

2323
| Platform | Folder | Mechanism | Status |
2424
|---|---|---|---|
25-
| **Claude Code** | [`claude-code/`](claude-code) | `.claude-plugin` + `/skillopt-sleep` command + skill + hooks | full, installable |
25+
| **Claude Code** | [`claude-code/`](claude-code) | `.claude-plugin` + `/skillopt-sleep` + `/skillopt-sleep-handoff` commands + skill + hooks | full, installable |
2626
| **Codex** | [`codex/`](codex) | user-level `skillopt-sleep` skill + shared runner | full |
2727
| **Copilot** | [`copilot/`](copilot) | MCP server (`sleep_*` tools) + `copilot-instructions` | full (MCP) |
2828
| **Devin** | [`devin/`](devin) | MCP server (`sleep_*` tools) + Devin ATIF-v1.7 harvest + `.devin/rules` | full (MCP) |
@@ -149,6 +149,47 @@ The reward can weight not just correctness but **cost and speed**, so a skill ca
149149
learn to be cheaper and faster, not only more accurate. *What it does for you:*
150150
"answer directly instead of opening five files" becomes a learned habit.
151151

152+
### `--backend handoff` — session-executed calls (no API subprocess)
153+
154+
For subscription seats and environments where the engine shouldn't spawn
155+
`claude -p` / API calls itself. The engine still runs every deterministic
156+
stage (harvest → mine → replay scoring → gate → stage), but each model call
157+
(attempt / judge / reflect) is written to a prompt file that **your own agent
158+
session answers between engine runs**:
159+
160+
```bash
161+
python -m skillopt_sleep run --backend handoff --project "$(pwd)"
162+
# exit 3 => .skillopt-sleep-handoff/PROMPTS.md + pending.json were written
163+
# answer each prompt (each in a FRESH context) into answers/<id>.md
164+
# re-run the same command => it resumes from the answers and either
165+
# finishes (exit 0) or stages the next prompt batch (exit 3)
166+
```
167+
168+
A typical night converges in 3–6 rounds: baseline attempts → reflect →
169+
candidate re-scoring per accepted edit. Resume is stateless — replay is
170+
deterministic and answers are cached by prompt hash, so re-running skips
171+
everything already answered. Mined tasks are pinned to
172+
`.skillopt-sleep-handoff/tasks.json` on the first round, so the sessions that
173+
answer the prompts can't shift the task set and invalidate earlier answers.
174+
On a completed real run the handoff directory is archived to
175+
`.skillopt-sleep-handoff.night<N>.done`.
176+
177+
On Claude Code, `/skillopt-sleep-handoff run` drives the whole loop for you,
178+
answering each prompt in an isolated fresh-context subagent.
179+
180+
**Integrity rule:** answer every prompt in a fresh context (a subagent with no
181+
conversation history). Answering from a session that has already seen the
182+
mined tasks and their references contaminates the held-out gate and fakes the
183+
improvement score.
184+
185+
*What it does for you:* the sleep cycle runs entirely on your interactive
186+
session's subscription budget — no API key, no headless subprocess — while the
187+
gate, splits, and staging discipline stay in the engine.
188+
189+
Limitations: `--rollouts-k > 1` gives no contrastive spread (identical prompt
190+
→ identical answer file), and tool-loop tasks fall back to the single-shot
191+
`TOOL_CALL:` marker convention.
192+
152193
### `schedule` / `unschedule` — set it and forget it
153194

154195
Built-in nightly scheduling (no manual cron):
@@ -176,7 +217,7 @@ schedule, if you trust it).
176217

177218
| Flag | Default | Meaning |
178219
|---|---|---|
179-
| `--backend mock\|claude\|codex\|copilot` | `mock` | who runs/optimizes (mock = free) |
220+
| `--backend mock\|claude\|codex\|copilot\|handoff` | `mock` | who runs/optimizes (mock = free; handoff = your own session answers) |
180221
| `--preferences "..."` || your house rules, as a prior |
181222
| `--gate on\|off` | `on` | strict held-out gate vs. greedy |
182223
| `--rollouts-k K` | `1` | multi-rollout contrastive reflection |

plugins/claude-code/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ they shell out to the CLIs you already have.
6060
/skillopt-sleep run # full cycle: stages a reviewed proposal (still no live edits)
6161
/skillopt-sleep status # see history + the latest staged proposal
6262
/skillopt-sleep adopt # apply the staged proposal to CLAUDE.md / SKILL.md (with backup)
63+
64+
/skillopt-sleep-handoff run # same cycle, but THIS session answers the model calls
65+
# (no claude -p subprocess, no API key — subscription-friendly)
6366
```
6467

6568
Or call the engine directly (Python ≥ 3.10):
@@ -74,6 +77,26 @@ Default backend is **`mock`** — deterministic, no API spend — so you can try
7477
plumbing for free. Switch to `--backend claude` or `--backend codex` for genuine
7578
improvement on your own budget.
7679

80+
### Handoff mode (session answers the model calls)
81+
82+
`--backend handoff` runs the cycle without any model subprocess: the engine
83+
executes the deterministic stages and writes every model call it needs to
84+
`.skillopt-sleep-handoff/PROMPTS.md` + `pending.json` (exit code 3). You (or
85+
the `/skillopt-sleep-handoff` command, which automates the loop with isolated
86+
fresh-context subagents) write each raw answer to `answers/<id>.md` and re-run
87+
the same command; it resumes from the answers and either finishes or stages
88+
the next batch. Typically 3–6 rounds per night.
89+
90+
```bash
91+
python -m skillopt_sleep run --backend handoff --project "$(pwd)"
92+
# ... answer .skillopt-sleep-handoff/PROMPTS.md into answers/<id>.md ...
93+
python -m skillopt_sleep run --backend handoff --project "$(pwd)" # resume
94+
```
95+
96+
Answer every prompt in a **fresh context** — a session that has already seen
97+
the mined tasks and their references would contaminate the held-out gate.
98+
Details: [the plugins README](../README.md#--backend-handoff--session-executed-calls-no-api-subprocess).
99+
77100
## Does it actually improve? (real models, public benchmark)
78101

79102
SkillOpt-Sleep is validated against [gbrain-evals](https://github.com/garrytan/gbrain-evals)'
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
description: Run the SkillOpt-Sleep cycle with the handoff backend — no API subprocess; this session answers the engine's model calls via prompt/answer files, in isolated fresh-context subagents
3+
argument-hint: "[run | dry-run] [--preferences \"...\"] (default: run)"
4+
allowed-tools: Bash, Read, Write, Task
5+
---
6+
7+
# /skillopt-sleep-handoff — session-executed sleep cycle
8+
9+
You are driving **SkillOpt-Sleep in handoff mode**: the Python engine runs
10+
every deterministic stage (harvest → mine → replay scoring → gate → stage)
11+
and outsources each model call (attempt / judge / reflect) to YOU via
12+
prompt files. No `claude -p` subprocess, no API key — the model work runs
13+
on this session's budget, but each prompt MUST be answered in a fresh,
14+
isolated context so the validation gate stays honest.
15+
16+
## Requested action: $ARGUMENTS
17+
18+
(If `$ARGUMENTS` is empty, treat it as `run`.)
19+
20+
## The loop
21+
22+
Repeat until the engine exits 0 (done) — at most 8 rounds:
23+
24+
1. **Run the engine** via the bundled runner:
25+
26+
```bash
27+
"${CLAUDE_PLUGIN_ROOT}/scripts/sleep.sh" <action> --backend handoff --project "$(pwd)" --scope invoked
28+
```
29+
30+
- exit 0 → the night is complete; go to "Finish" below.
31+
- exit 3 → pending model calls; continue with step 2.
32+
- anything else → stop and show the user the error output.
33+
34+
2. **Read the batch**: `Read` `.skillopt-sleep-handoff/pending.json` in the
35+
project. Each entry has `id`, `prompt`, `max_tokens`, `answer_file`.
36+
37+
3. **Answer each prompt in ISOLATION** — this is the integrity rule:
38+
- For each entry, launch a subagent (Task tool) whose ENTIRE input is
39+
the `prompt` text verbatim. Add nothing: no summary of this session,
40+
no mention of SkillOpt, no other prompts from the batch.
41+
- Take the subagent's reply and `Write` the raw answer text (no
42+
commentary, no code fences) to the entry's `answer_file`.
43+
- NEVER answer from this session's own context — you have seen the
44+
mined tasks and their references, so inline answers would contaminate
45+
the held-out gate and fake the improvement score.
46+
47+
4. **Re-run the same engine command** — it resumes from the answers
48+
directory and either finishes or stages the next batch.
49+
50+
## Finish
51+
52+
- `Read` the `report.md` in the staging dir the engine printed and show
53+
the user: held-out baseline → candidate score, the gate decision, the
54+
proposed edits, and where the proposal is staged.
55+
- Tell the user nothing live changed; offer `/skillopt-sleep adopt`.
56+
- The engine archives `.skillopt-sleep-handoff/` on a completed real run;
57+
do not delete it yourself.
58+
59+
## Safety reminders
60+
61+
- **Never** edit `CLAUDE.md` or `SKILL.md` yourself — only `adopt` does
62+
that, with a backup.
63+
- Mined tasks are pinned to `.skillopt-sleep-handoff/tasks.json` on round
64+
one, so sessions created while answering prompts cannot shift the task
65+
set. Do not edit that file.
66+
- If a batch looks like it contains secrets or content the user would not
67+
want re-processed, stop and ask before answering.

0 commit comments

Comments
 (0)