Skip to content

Commit 00d07bc

Browse files
committed
Merge pull request #48 from Kirchberg/codex/codex-desktop-harvest
Add Codex Desktop transcript harvesting
2 parents e8c3e10 + 31715a8 commit 00d07bc

12 files changed

Lines changed: 479 additions & 59 deletions

File tree

‎README.md‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Dreams** (offline consolidation; review-then-adopt), and the **agent sleep**
7070
idea (short-term experience → long-term competence). One "night":
7171

7272
```
73-
harvest session transcripts → mine recurring tasks → replay offline
73+
harvest Claude Code / Codex Desktop transcripts → mine recurring tasks → replay offline
7474
→ consolidate (reflect → bounded edit → GATE on real held-out tasks)
7575
→ stage proposal → (you) adopt
7676
```
@@ -99,6 +99,11 @@ positive, and the gate blocks regressions
9999
100100
Deterministic proof (no API key): `python -m skillopt_sleep.experiments.run_experiment --persona researcher --assert-improves`.
101101

102+
For local sleep cycles, transcript source and replay backend are separate knobs:
103+
use `--source claude` for Claude Code transcripts, `--source codex` for Codex
104+
Desktop archived sessions under `~/.codex/archived_sessions`, and
105+
`--backend codex` only when you want the replay/optimizer to spend Codex budget.
106+
102107
---
103108

104109
## Extensibility & WebUI

‎docs/sleep/CONTROLLABLE_DREAMING.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@ The sleep engine is no longer a single fixed pipeline. It is a controllable
44
offline "dream / imagination" loop the user steers. This documents the knobs
55
added in the four-stage refactor and how they map to the user's design.
66

7+
## Transcript sources
8+
9+
Sleep separates the source of past sessions from the backend used to replay and
10+
optimize tasks:
11+
12+
```bash
13+
python -m skillopt_sleep dry-run --project "$(pwd)" --source claude --backend mock
14+
python -m skillopt_sleep dry-run --project "$(pwd)" --source codex --backend mock
15+
python -m skillopt_sleep run --project "$(pwd)" --source codex --backend codex
16+
```
17+
18+
`--source claude` reads Claude Code transcripts from `~/.claude/projects`.
19+
`--source codex` reads Codex Desktop archives from
20+
`~/.codex/archived_sessions`. `--source auto` tries Codex archives first, then
21+
falls back to Claude Code transcripts. Use `--codex-home /path/to/.codex` or
22+
`--claude-home /path/to/.claude` to point at non-default homes.
23+
724
## The mental model
825

926
> Sleep = an offline imagination rollout. Re-run the user's real

‎plugins/codex/README.md‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,18 @@ Use the skillopt-sleep skill to adopt the latest staged proposal.
4848
Or call the engine directly:
4949

5050
```bash
51-
python -m skillopt_sleep run --project "$(pwd)" --backend codex
51+
python -m skillopt_sleep dry-run --project "$(pwd)" --source codex --backend mock
52+
python -m skillopt_sleep run --project "$(pwd)" --source codex --backend codex
5253
```
5354

54-
Default backend is `mock` (no API spend). `--backend codex` uses your Codex
55-
budget for real improvement. All the controllable knobs (`--gate on|off`,
56-
`--rollouts-k`, `--budget-tokens`, `--preferences`, optimizer/target split) work
57-
identically — see [`../../docs/sleep/CONTROLLABLE_DREAMING.md`](../../docs/sleep/CONTROLLABLE_DREAMING.md).
55+
`--source codex` reads Codex Desktop archived sessions from
56+
`~/.codex/archived_sessions`. Use `--codex-home /path/to/.codex` to point at a
57+
different Codex home, or `--source auto` to try Codex archives first and fall
58+
back to Claude Code transcripts. Default backend is `mock` (no API spend).
59+
`--backend codex` uses your Codex budget for real improvement. All the
60+
controllable knobs (`--gate on|off`, `--rollouts-k`, `--budget-tokens`,
61+
`--preferences`, optimizer/target split) work identically — see
62+
[`../../docs/sleep/CONTROLLABLE_DREAMING.md`](../../docs/sleep/CONTROLLABLE_DREAMING.md).
5863

5964
## Notes / status
6065

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,20 @@ finds the engine and a Python >= 3.10 automatically.
4444
```bash
4545
# point at the repo if it isn't auto-detected from CWD:
4646
export SKILLOPT_SLEEP_REPO=/path/to/SkillOpt-Sleep
47-
4847
bash "$SKILLOPT_SLEEP_REPO/plugins/run-sleep.sh" status --project "$(pwd)"
4948
bash "$SKILLOPT_SLEEP_REPO/plugins/run-sleep.sh" harvest --project "$(pwd)"
5049
bash "$SKILLOPT_SLEEP_REPO/plugins/run-sleep.sh" dry-run --project "$(pwd)" --backend mock
5150
bash "$SKILLOPT_SLEEP_REPO/plugins/run-sleep.sh" run --project "$(pwd)" --backend codex
51+
bash "$SKILLOPT_SLEEP_REPO/plugins/run-sleep.sh" run --project "$(pwd)" --source codex # harvest from Codex Desktop
5252
bash "$SKILLOPT_SLEEP_REPO/plugins/run-sleep.sh" adopt --project "$(pwd)"
5353
```
5454

5555
Actions are `status`, `harvest`, `dry-run`, `run`, and `adopt`.
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.
59+
- `--source codex` reads Codex Desktop archived sessions from `~/.codex/archived_sessions`;
60+
use `--codex-home /path/to/.codex` if the archive lives elsewhere.
5961
- Keep `dry-run --backend mock` as the first smoke check unless the user
6062
explicitly asked for a real optimization run.
6163

‎skillopt_sleep/__main__.py‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
Common flags:
1010
--project PATH project to evolve (default: cwd)
1111
--scope all|invoked harvest scope (default: invoked)
12-
--backend mock|anthropic
12+
--backend mock|claude|codex
13+
--source claude|codex|auto
1314
--model NAME
1415
--lookback-hours N
1516
--auto-adopt
@@ -25,10 +26,11 @@
2526

2627
from skillopt_sleep.config import load_config
2728
from skillopt_sleep.cycle import run_sleep_cycle
28-
from skillopt_sleep.harvest import harvest
29+
from skillopt_sleep.harvest_sources import harvest_for_config
2930
from skillopt_sleep.mine import mine
31+
from skillopt_sleep.staging import adopt as adopt_staging
32+
from skillopt_sleep.staging import latest_staging
3033
from skillopt_sleep.state import SleepState
31-
from skillopt_sleep.staging import latest_staging, adopt as adopt_staging
3234

3335

3436
def _add_common(p: argparse.ArgumentParser) -> None:
@@ -38,6 +40,9 @@ def _add_common(p: argparse.ArgumentParser) -> None:
3840
p.add_argument("--model", default="")
3941
p.add_argument("--codex-path", default="", help="path to the real @openai/codex binary")
4042
p.add_argument("--claude-home", default="", help="override ~/.claude (also isolates state)")
43+
p.add_argument("--codex-home", default="", help="override ~/.codex for archived session harvest")
44+
p.add_argument("--source", default="", choices=["", "claude", "codex", "auto"],
45+
help="session transcript source")
4146
p.add_argument("--lookback-hours", type=int, default=0)
4247
p.add_argument("--edit-budget", type=int, default=0)
4348
p.add_argument("--auto-adopt", action="store_true")
@@ -59,6 +64,10 @@ def _cfg_from_args(args) -> Any:
5964
overrides["codex_path"] = os.path.abspath(args.codex_path)
6065
if getattr(args, "claude_home", ""):
6166
overrides["claude_home"] = os.path.abspath(args.claude_home)
67+
if getattr(args, "codex_home", ""):
68+
overrides["codex_home"] = os.path.abspath(args.codex_home)
69+
if getattr(args, "source", ""):
70+
overrides["transcript_source"] = args.source
6271
if getattr(args, "lookback_hours", 0):
6372
overrides["lookback_hours"] = args.lookback_hours
6473
if getattr(args, "edit_budget", 0):
@@ -143,12 +152,7 @@ def cmd_adopt(args) -> int:
143152

144153
def cmd_harvest(args) -> int:
145154
cfg = _cfg_from_args(args)
146-
digests = harvest(
147-
cfg.transcripts_dir,
148-
scope=cfg.get("projects", "invoked"),
149-
invoked_project=cfg.get("invoked_project", ""),
150-
limit=cfg.get("max_tasks_per_night", 40) * 3,
151-
)
155+
digests = harvest_for_config(cfg, limit=cfg.get("max_tasks_per_night", 40) * 3)
152156
tasks = mine(digests, max_tasks=cfg.get("max_tasks_per_night", 40),
153157
holdout_fraction=cfg.get("holdout_fraction", 0.34), seed=cfg.get("seed", 42))
154158
if args.json:

‎skillopt_sleep/config.py‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,19 @@
1313

1414
import json
1515
import os
16-
from dataclasses import dataclass, field, asdict
17-
from typing import Any, Dict, List, Optional
18-
16+
from dataclasses import dataclass, field
17+
from typing import Any, Dict, Optional
1918

2019
HOME_STATE_DIR = os.path.expanduser("~/.skillopt-sleep")
2120
CLAUDE_HOME = os.path.expanduser("~/.claude")
21+
CODEX_HOME = os.path.expanduser("~/.codex")
2222

2323

2424
DEFAULTS: Dict[str, Any] = {
2525
# ── scope ──────────────────────────────────────────────────────────────
2626
"claude_home": CLAUDE_HOME,
27+
"codex_home": CODEX_HOME,
28+
"transcript_source": "claude", # "claude" | "codex" | "auto"
2729
"projects": "invoked", # "invoked" | "all" | [list of abs paths]
2830
"invoked_project": "", # filled at runtime (cwd) when projects == "invoked"
2931
"lookback_hours": 72, # harvest window when no prior sleep recorded
@@ -94,6 +96,10 @@ def state_path(self) -> str:
9496
def transcripts_dir(self) -> str:
9597
return os.path.join(self.data["claude_home"], "projects")
9698

99+
@property
100+
def codex_archived_sessions_dir(self) -> str:
101+
return os.path.join(self.data["codex_home"], "archived_sessions")
102+
97103
@property
98104
def history_path(self) -> str:
99105
return os.path.join(self.data["claude_home"], "history.jsonl")

‎skillopt_sleep/cycle.py‎

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@
1010
from __future__ import annotations
1111

1212
import os
13-
import time
1413
from dataclasses import dataclass
15-
from typing import Any, Dict, List, Optional
14+
from typing import List, Optional
1615

1716
from skillopt_sleep.backend import get_backend
1817
from skillopt_sleep.config import SleepConfig, load_config
1918
from skillopt_sleep.consolidate import consolidate
20-
from skillopt_sleep.harvest import harvest
19+
from skillopt_sleep.harvest_sources import harvest_for_config
2120
from skillopt_sleep.memory import ensure_skill_scaffold
2221
from skillopt_sleep.mine import mine
22+
from skillopt_sleep.staging import adopt as adopt_staging
23+
from skillopt_sleep.staging import write_staging
2324
from skillopt_sleep.state import SleepState, _now_iso
24-
from skillopt_sleep.staging import write_staging, adopt as adopt_staging
2525
from skillopt_sleep.types import SessionDigest, SleepReport, TaskRecord
2626

2727

@@ -117,10 +117,8 @@ def run_sleep_cycle(
117117
n_sessions = 0
118118
else:
119119
since = state.last_harvest_for(project)
120-
digests = harvest(
121-
cfg.transcripts_dir,
122-
scope=cfg.get("projects", "invoked"),
123-
invoked_project=cfg.get("invoked_project", ""),
120+
digests = harvest_for_config(
121+
cfg,
124122
since_iso=since,
125123
limit=cfg.get("max_tasks_per_night", 40) * 3,
126124
)
@@ -151,7 +149,7 @@ def run_sleep_cycle(
151149
if not skill:
152150
skill = ensure_skill_scaffold(
153151
"", name=cfg.get("managed_skill_name", "skillopt-sleep-learned"),
154-
description="Preferences and procedures learned from past Claude Code sessions.",
152+
description="Preferences and procedures learned from past local agent sessions.",
155153
)
156154

157155
report = SleepReport(

0 commit comments

Comments
 (0)