Skip to content

Commit 24b5a25

Browse files
authored
Merge pull request #72 from Yif-Yang/feat/plugin-feature-sync
feat: sync all 4 runtime plugins with full engine surface + fix #52 #58 #62
2 parents 0b5b9a4 + 0d648b2 commit 24b5a25

12 files changed

Lines changed: 489 additions & 16 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env bash
2+
# SkillOpt-Sleep shared runner — used by all platform plugins (Claude Code,
3+
# Codex, Copilot). Resolves the repo root (which contains the skillopt_sleep
4+
# package), picks a Python >= 3.10, and execs the engine CLI.
5+
#
6+
# Usage: run-sleep.sh <run|dry-run|status|adopt|harvest|...> [args...]
7+
set -euo pipefail
8+
9+
# This script lives at <repo>/plugins/run-sleep.sh, so the repo root (which
10+
# holds skillopt_sleep/) is one level up. CLAUDE_PLUGIN_ROOT (if set by Claude
11+
# Code) points at the plugin dir; the engine is then two levels above it.
12+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13+
if [ -d "$SCRIPT_DIR/../skillopt_sleep" ]; then
14+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
15+
elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -d "$CLAUDE_PLUGIN_ROOT/../../skillopt_sleep" ]; then
16+
REPO_ROOT="$(cd "$CLAUDE_PLUGIN_ROOT/../.." && pwd)"
17+
elif [ -n "${SKILLOPT_SLEEP_REPO:-}" ] && [ -d "$SKILLOPT_SLEEP_REPO/skillopt_sleep" ]; then
18+
REPO_ROOT="$SKILLOPT_SLEEP_REPO"
19+
else
20+
# last resort: search upward from CWD
21+
d="$PWD"
22+
while [ "$d" != "/" ]; do
23+
[ -d "$d/skillopt_sleep" ] && { REPO_ROOT="$d"; break; }
24+
d="$(dirname "$d")"
25+
done
26+
fi
27+
if [ -z "${REPO_ROOT:-}" ]; then
28+
echo "[sleep] ERROR: could not locate the skillopt_sleep package. Set SKILLOPT_SLEEP_REPO to the repo root." >&2
29+
exit 1
30+
fi
31+
32+
PY=""
33+
for cand in python3.12 python3.11 python3.10 python3; do
34+
if command -v "$cand" >/dev/null 2>&1; then
35+
ver="$("$cand" -c 'import sys; print("%d%d" % sys.version_info[:2])' 2>/dev/null || echo 0)"
36+
if [ "${ver:-0}" -ge 310 ]; then PY="$cand"; break; fi
37+
fi
38+
done
39+
if [ -z "$PY" ]; then
40+
echo "[sleep] ERROR: need Python >= 3.10 (found none)." >&2
41+
exit 1
42+
fi
43+
44+
if [ "$#" -eq 0 ]; then set -- status; fi
45+
cd "$REPO_ROOT"
46+
exec "$PY" -m skillopt_sleep "$@"
Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
11
#!/usr/bin/env bash
2-
# Claude Code plugin runner — thin wrapper over the shared runner so all three
3-
# platform plugins share one engine launcher. The shared runner lives at
4-
# <repo>/plugins/run-sleep.sh and handles repo-root + interpreter resolution.
2+
# Claude Code plugin runner — thin wrapper over the shared runner so all
3+
# platform plugins share one engine launcher.
4+
#
5+
# After marketplace install the plugin is isolated in a cache directory and
6+
# the repo-relative path no longer works. We try four locations:
7+
# 1. Co-located run-sleep.sh (bundled copy — works in marketplace cache)
8+
# 2. Repo-relative ../../run-sleep.sh (dev checkout)
9+
# 3. CLAUDE_PLUGIN_ROOT/../run-sleep.sh (plugin env variable)
10+
# 4. SKILLOPT_SLEEP_REPO/plugins/run-sleep.sh (explicit env)
511
set -euo pipefail
6-
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # <repo>/plugins/claude-code/scripts
7-
SHARED="$(cd "$HERE/../.." && pwd)/run-sleep.sh" # <repo>/plugins/run-sleep.sh
8-
if [ ! -f "$SHARED" ] && [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
12+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13+
14+
SHARED=""
15+
if [ -f "$HERE/run-sleep.sh" ]; then
16+
SHARED="$HERE/run-sleep.sh"
17+
elif [ -f "$(cd "$HERE/../.." 2>/dev/null && pwd)/run-sleep.sh" ]; then
18+
SHARED="$(cd "$HERE/../.." && pwd)/run-sleep.sh"
19+
elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -f "$(cd "$CLAUDE_PLUGIN_ROOT/.." 2>/dev/null && pwd)/run-sleep.sh" ]; then
920
SHARED="$(cd "$CLAUDE_PLUGIN_ROOT/.." && pwd)/run-sleep.sh"
21+
elif [ -n "${SKILLOPT_SLEEP_REPO:-}" ] && [ -f "$SKILLOPT_SLEEP_REPO/plugins/run-sleep.sh" ]; then
22+
SHARED="$SKILLOPT_SLEEP_REPO/plugins/run-sleep.sh"
23+
fi
24+
25+
if [ -z "$SHARED" ]; then
26+
echo "[sleep] ERROR: cannot locate run-sleep.sh." >&2
27+
echo "[sleep] Set SKILLOPT_SLEEP_REPO to the SkillOpt repo root, or pip install skillopt." >&2
28+
exit 1
1029
fi
1130
exec bash "$SHARED" "$@"

plugins/claude-code/skills/skillopt-sleep/SKILL.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,53 @@ Prefer the `/skillopt-sleep` command. Under the hood it calls the bundled runner
5454
- Add `--backend claude` or `--backend codex` to spend the user's real budget for genuine improvement.
5555
- Scope defaults to the invoked project; `--scope all` harvests every project.
5656

57+
### Scheduling
58+
59+
```bash
60+
"${CLAUDE_PLUGIN_ROOT}/scripts/sleep.sh" schedule --project "$(pwd)" --hour 3 --minute 17
61+
"${CLAUDE_PLUGIN_ROOT}/scripts/sleep.sh" unschedule --project "$(pwd)"
62+
```
63+
64+
Installs a nightly cron entry. `unschedule --all` removes every managed entry.
65+
66+
## All CLI flags
67+
68+
| Flag | Default | Description |
69+
|------|---------|-------------|
70+
| `--project PATH` | cwd | Project directory to evolve |
71+
| `--scope all\|invoked` | invoked | Harvest scope |
72+
| `--backend mock\|claude\|codex\|copilot` | mock | Replay backend (mock = no API spend) |
73+
| `--model NAME` | backend default | Override the model used for replay |
74+
| `--source claude\|codex\|auto` | claude | Transcript source |
75+
| `--lookback-hours N` | 72 | Harvest window |
76+
| `--max-sessions N` | unlimited | Cap harvested sessions |
77+
| `--max-tasks N` | 40 | Cap mined tasks |
78+
| `--target-skill-path PATH` | auto | Explicit SKILL.md to evolve |
79+
| `--tasks-file PATH` || Reviewed TaskRecord JSON (skip harvest) |
80+
| `--progress` | off | Print phase progress to stderr |
81+
| `--auto-adopt` | off | Auto-adopt if gate passes |
82+
| `--edit-budget N` | 4 | Max bounded edits per night |
83+
| `--json` | off | Machine-readable JSON output |
84+
85+
## Config keys (`~/.skillopt-sleep/config.json`)
86+
87+
Beyond the CLI flags, advanced behavior is controlled via config:
88+
89+
- **`preferences`** — free-text house rules injected into the optimizer's reflect step (e.g. "Always use async/await", "Answers in `\boxed{}`").
90+
- **`gate_mode`**`on` (default, validation-gated) or `off` (greedy, accept all edits).
91+
- **`gate_metric`**`hard`, `soft`, or `mixed` (default). Controls how the held-out gate scores.
92+
- **`dream_rollouts`** — >1 enables multi-rollout contrastive reflection per task.
93+
- **`recall_k`** — >0 recalls K similar past tasks into the dream (long-term memory).
94+
- **`evolve_memory`** / **`evolve_skill`** — independently toggle CLAUDE.md vs SKILL.md consolidation.
95+
96+
## Memory consolidation
97+
98+
The sleep cycle can consolidate both:
99+
- **SKILL.md** — the managed skill file (bounded edits: add/delete/replace)
100+
- **CLAUDE.md** — the project memory (same bounded edits)
101+
102+
Both are gated by the same held-out validation score. Set `evolve_memory: false` to consolidate only skills, or `evolve_skill: false` for only memory.
103+
57104
## Hard rules
58105

59106
- **Never** hand-edit the user's `CLAUDE.md` / `SKILL.md` as part of this skill.

plugins/codex/skills/skillopt-sleep/SKILL.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ bash "$SKILLOPT_SLEEP_REPO/plugins/run-sleep.sh" run --project "$(pwd)" --source
5252
bash "$SKILLOPT_SLEEP_REPO/plugins/run-sleep.sh" adopt --project "$(pwd)"
5353
```
5454

55-
Actions are `status`, `harvest`, `dry-run`, `run`, and `adopt`.
55+
Actions are `status`, `harvest`, `dry-run`, `run`, `adopt`, `schedule`, and `unschedule`.
5656

5757
- Default backend is `mock`, which is deterministic and spends no API budget.
5858
- `--backend codex` uses the user's Codex budget for real improvement.
@@ -61,6 +61,43 @@ Actions are `status`, `harvest`, `dry-run`, `run`, and `adopt`.
6161
- Keep `dry-run --backend mock` as the first smoke check unless the user
6262
explicitly asked for a real optimization run.
6363

64+
### Scheduling
65+
66+
```bash
67+
bash "$SKILLOPT_SLEEP_REPO/plugins/run-sleep.sh" schedule --project "$(pwd)" --hour 3 --minute 17
68+
bash "$SKILLOPT_SLEEP_REPO/plugins/run-sleep.sh" unschedule --project "$(pwd)"
69+
```
70+
71+
Installs a nightly cron entry. `unschedule --all` removes every managed entry.
72+
73+
### All backends
74+
75+
- `--backend mock` — deterministic, no API spend (default)
76+
- `--backend claude` — uses the Claude CLI
77+
- `--backend codex` — uses the Codex CLI
78+
- `--backend copilot` — uses the GitHub Copilot CLI
79+
80+
### Additional flags
81+
82+
| Flag | Description |
83+
|------|-------------|
84+
| `--auto-adopt` | Auto-adopt if the gate passes (default: stage only) |
85+
| `--edit-budget N` | Max bounded edits per night (default: 4) |
86+
| `--lookback-hours N` | Harvest window in hours (default: 72) |
87+
| `--json` | Machine-readable JSON output |
88+
89+
### Config keys (`~/.skillopt-sleep/config.json`)
90+
91+
- **`preferences`** — free-text house rules for the optimizer
92+
- **`gate_mode`**`on` (validation-gated, default) or `off` (greedy)
93+
- **`gate_metric`**`hard` | `soft` | `mixed` (default)
94+
- **`dream_rollouts`** — >1 for multi-rollout contrastive reflection
95+
- **`recall_k`** — >0 recalls similar past tasks from the archive
96+
97+
### Memory consolidation
98+
99+
The sleep cycle consolidates both **memory** (AGENTS.md / CLAUDE.md) and **skills** (SKILL.md) by default. Each is independently toggleable via `evolve_memory` / `evolve_skill` config keys. Both are gated by the same held-out validation score.
100+
64101
## Steps
65102

66103
1. Run the requested action; capture stdout.

plugins/copilot/copilot-instructions.snippet.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,24 @@ my preferences", or "make the agent improve from past usage", use the MCP tools:
1919
- `sleep_run` — full cycle, stages a reviewed proposal (nothing live changes)
2020
- `sleep_adopt` — apply the staged proposal (backs up first)
2121
- `sleep_harvest` — list mined recurring tasks
22+
- `sleep_schedule` — install a nightly cron entry (set `hour`/`minute`)
23+
- `sleep_unschedule` — remove the nightly cron entry
24+
25+
### Key parameters (pass as MCP tool arguments)
26+
27+
- `backend``mock` (default, free), `claude`, `codex`, or `copilot`
28+
- `source``claude`, `codex`, or `auto` (where to read transcripts)
29+
- `target_skill_path` — explicit SKILL.md to evolve
30+
- `tasks_file` — pre-built TaskRecord JSON (skip harvest)
31+
- `max_tasks` / `max_sessions` — cap workload
32+
- `auto_adopt` — auto-adopt if the gate passes
33+
- `json` — machine-readable output for programmatic use
34+
35+
### Advanced config (`~/.skillopt-sleep/config.json`)
36+
37+
- `preferences` — free-text house rules for the optimizer
38+
- `gate_mode``on` (default) or `off`; `dream_rollouts` — >1 for more signal
39+
- `evolve_memory` / `evolve_skill` — toggle which docs consolidate
2240

2341
Always show the user the held-out baseline → candidate score and the proposed
2442
edits before suggesting `sleep_adopt`. Never hand-edit the user's memory/skill

plugins/copilot/mcp_server.py

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,48 @@
3838
"description": "Apply the latest staged proposal to CLAUDE.md/SKILL.md (backs up first)."},
3939
{"name": "sleep_harvest", "action": "harvest",
4040
"description": "Debug: list the recurring tasks mined from recent sessions."},
41+
{"name": "sleep_schedule", "action": "schedule",
42+
"description": "Install a nightly cron entry to run the sleep cycle automatically."},
43+
{"name": "sleep_unschedule", "action": "unschedule",
44+
"description": "Remove the nightly cron entry for a project."},
4145
]
4246
_BY_NAME = {t["name"]: t for t in TOOLS}
4347

4448
_TOOL_SCHEMA = {
4549
"type": "object",
4650
"properties": {
47-
"project": {"type": "string", "description": "Project dir to evolve (default: cwd)."},
51+
"project": {"type": "string",
52+
"description": "Project dir to evolve (default: cwd)."},
4853
"backend": {"type": "string", "enum": ["mock", "claude", "codex", "copilot"],
4954
"description": "mock = no API spend (default); claude/codex/copilot = real."},
50-
"scope": {"type": "string", "enum": ["invoked", "all"]},
55+
"scope": {"type": "string", "enum": ["invoked", "all"],
56+
"description": "Harvest scope (default: invoked project only)."},
57+
"source": {"type": "string", "enum": ["claude", "codex", "auto"],
58+
"description": "Transcript source (default: claude)."},
59+
"model": {"type": "string",
60+
"description": "Backend-specific model override."},
61+
"tasks_file": {"type": "string",
62+
"description": "Path to reviewed TaskRecord JSON (skips harvest)."},
63+
"target_skill_path": {"type": "string",
64+
"description": "Explicit SKILL.md path to evolve/stage/adopt."},
65+
"progress": {"type": "boolean",
66+
"description": "Print phase progress to stderr."},
67+
"max_sessions": {"type": "integer",
68+
"description": "Cap harvested sessions per run."},
69+
"max_tasks": {"type": "integer",
70+
"description": "Cap mined tasks per run."},
71+
"lookback_hours": {"type": "integer",
72+
"description": "Harvest window in hours (default: 72)."},
73+
"auto_adopt": {"type": "boolean",
74+
"description": "Auto-adopt if gate passes (default: false)."},
75+
"json": {"type": "boolean",
76+
"description": "Return machine-readable JSON output."},
77+
"edit_budget": {"type": "integer",
78+
"description": "Max bounded edits per night (default: 4)."},
79+
"hour": {"type": "integer",
80+
"description": "Hour for schedule (0-23, default: 3)."},
81+
"minute": {"type": "integer",
82+
"description": "Minute for schedule (0-59, default: 17)."},
5183
},
5284
"additionalProperties": False,
5385
}
@@ -56,15 +88,35 @@
5688
def _run_engine(action: str, args: dict) -> str:
5789
py = sys.executable or "python3"
5890
cmd = [py, "-m", "skillopt_sleep", action]
59-
if args.get("project"):
60-
cmd += ["--project", str(args["project"])]
61-
if args.get("backend"):
62-
cmd += ["--backend", str(args["backend"])]
63-
if args.get("scope"):
64-
cmd += ["--scope", str(args["scope"])]
91+
# String-valued flags
92+
for flag, key in [
93+
("--project", "project"), ("--backend", "backend"),
94+
("--scope", "scope"), ("--source", "source"),
95+
("--model", "model"), ("--tasks-file", "tasks_file"),
96+
("--target-skill-path", "target_skill_path"),
97+
]:
98+
val = args.get(key)
99+
if val:
100+
cmd += [flag, str(val)]
101+
# Integer-valued flags
102+
for flag, key in [
103+
("--max-sessions", "max_sessions"), ("--max-tasks", "max_tasks"),
104+
("--lookback-hours", "lookback_hours"), ("--edit-budget", "edit_budget"),
105+
("--hour", "hour"), ("--minute", "minute"),
106+
]:
107+
val = args.get(key)
108+
if val is not None:
109+
cmd += [flag, str(int(val))]
110+
# Boolean flags
111+
for flag, key in [
112+
("--progress", "progress"), ("--auto-adopt", "auto_adopt"),
113+
("--json", "json"),
114+
]:
115+
if args.get(key):
116+
cmd.append(flag)
65117
try:
66118
proc = subprocess.run(cmd, cwd=REPO_ROOT, capture_output=True, text=True, timeout=3600)
67-
except Exception as e: # noqa: BLE001
119+
except Exception as e:
68120
return f"[error] failed to run engine: {e}"
69121
out = (proc.stdout or "").strip()
70122
err = (proc.stderr or "").strip()

plugins/openclaw/SKILL.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,39 @@ python3 run_sleep.py --dry-run
5252
python3 run_sleep.py --tasks tests/research-cron-tasks.json
5353
```
5454

55+
## Scheduling
56+
57+
```bash
58+
python3 slash_sleep.py schedule --hour 3 --minute 17
59+
python3 slash_sleep.py unschedule
60+
python3 slash_sleep.py unschedule --all
61+
```
62+
63+
Installs a nightly cron entry using the shared SkillOpt-Sleep scheduler. This is an alternative to the external `run_sleep_cron.sh` script.
64+
65+
## Alternative backends
66+
67+
While OpenClaw defaults to `openclaw-deepseek` (DeepSeek V4 Pro + Ollama), the shared engine also supports:
68+
- `--backend mock` — deterministic, no API spend (for testing)
69+
- `--backend claude` — uses the Claude CLI
70+
- `--backend codex` — uses the Codex CLI
71+
- `--backend copilot` — uses the GitHub Copilot CLI
72+
73+
These can be used via the engine directly (`python -m skillopt_sleep`).
74+
75+
## Shared-engine flags
76+
77+
When invoking the engine directly, all standard flags are available:
78+
- `--source codex` / `--source auto` — harvest from Codex Desktop sessions
79+
- `--tasks-file PATH` — use a pre-built task set
80+
- `--target-skill-path PATH` — explicit SKILL.md target
81+
- `--max-tasks N` / `--max-sessions N` — cap workload
82+
- `--progress` — print phase progress
83+
- `--json` — machine-readable output
84+
- `--auto-adopt` — auto-adopt if gate passes
85+
86+
Config keys: `preferences`, `gate_mode`, `gate_metric`, `dream_rollouts`, `recall_k`, `evolve_memory`, `evolve_skill`.
87+
5588
## Config (config.json)
5689

5790
Key knobs:

0 commit comments

Comments
 (0)