Skip to content

Commit f2138f7

Browse files
committed
feat: add granular feature switches
1 parent 1d5a872 commit f2138f7

18 files changed

Lines changed: 417 additions & 24 deletions

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ csep recall --recent
141141

142142
`session-start``session-stop``session-archive``session-ingest` 主要给 hook 和底层排障使用。完整列表见 [docs/getting-started.md](docs/getting-started.md)
143143

144+
三条主线可在 `~/.codex-self-evolution/config.toml` 里独立关闭:`[stable_memory] enabled = false` 关闭启动记忆注入,`[session_recall] enabled = false` 关闭 recall policy / 查询 / Stop 归档,`[session_reflection] enabled = false` 关闭后台 reflection worker。需要更细时,还可以用 `session_recall.session_start_policy``session_recall.manual_query``session_recall.stop_hook_archive``session_reflection.trigger.memory_review``session_reflection.trigger.skill_review` 分别控制子能力。
145+
144146
## 性能和运行方式
145147

146148
`SessionStart` / `Stop` 前台只做轻量文件读写、归档触发和状态判断;模型 reflection 在后台运行,不阻塞 Codex 正常退出。

docs/getting-started.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ csep config validate
5555
```toml
5656
schema_version = 2
5757

58+
[stable_memory]
59+
enabled = true
60+
5861
[session_reflection]
5962
enabled = true
6063
backend = "codex-app-server"
@@ -68,6 +71,8 @@ max_concurrent_jobs = 1
6871

6972
[session_reflection.trigger]
7073
enabled = true
74+
memory_review = true
75+
skill_review = true
7176
memory_stop_interval = 3
7277
memory_context_chars = 16000
7378
skill_tool_call_interval = 15
@@ -77,6 +82,8 @@ active_job_stale_seconds = 1800
7782

7883
[session_recall]
7984
enabled = true
85+
session_start_policy = true
86+
manual_query = true
8087
stop_hook_archive = true
8188

8289
[log]
@@ -85,6 +92,20 @@ retention_days = 14
8592

8693
默认的 `sandbox = "danger-full-access"``approval_policy = "never"` 是为了让后台 reflection 能无阻塞完成写入和 receipt 落盘。它的信任边界不在 child 自述,而在父进程的 receipt validation:路径、hash、job identity、memory root 和 `csep-reflect-*` skill namespace 都会被校验。低信任或共享环境可以调低这两个值,但后台 reflection 可能因此需要人工确认或无法完成。
8794

95+
三条主线都可以独立关闭:
96+
97+
- `[stable_memory] enabled = false``SessionStart` 不读取或注入 `MEMORY.md`
98+
- `[session_recall] enabled = false``SessionStart` 不注入 recall policy,`csep recall` 返回空结果,`Stop` hook 不归档 transcript。
99+
- `[session_reflection] enabled = false``Stop` hook 不再创建后台 reflection job。
100+
101+
也可以只关子能力:
102+
103+
- `session_recall.session_start_policy = false`:保留 recall 数据库和手动查询,但启动时不注入 recall policy。
104+
- `session_recall.manual_query = false`:保留 Stop 归档,但 `csep recall` 不读库。
105+
- `session_recall.stop_hook_archive = false`:保留手动查询已有库,但 Stop hook 不再归档新 transcript。
106+
- `session_reflection.trigger.memory_review = false`:trigger 不再因为 memory counter / memory keyword 创建 memory review。
107+
- `session_reflection.trigger.skill_review = false`:trigger 不再因为 tool counter / skill keyword 创建 skill review。
108+
88109
## 3. 检查 Codex Plugin Hooks
89110

90111
`csep setup` 会写入或更新 `~/.codex/config.toml`

docs/session-reflection.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ Parent 校验:
122122
核心配置:
123123

124124
```toml
125+
[stable_memory]
126+
enabled = true
127+
125128
[session_reflection]
126129
enabled = true
127130
backend = "codex-app-server"
@@ -135,6 +138,8 @@ max_concurrent_jobs = 1
135138

136139
[session_reflection.trigger]
137140
enabled = true
141+
memory_review = true
142+
skill_review = true
138143
memory_stop_interval = 3
139144
memory_context_chars = 16000
140145
skill_tool_call_interval = 15

src/codex_self_evolution/config_file.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,24 @@
1111
from .config import DEFAULT_LOCK_STALE_SECONDS, REFLECT_SKILL_PREFIX
1212

1313

14+
@dataclass
15+
class StableMemoryConfig:
16+
"""SessionStart stable memory injection configuration."""
17+
18+
# Whether SessionStart reads and injects project MEMORY.md.
19+
enabled: bool = True
20+
21+
1422
@dataclass
1523
class SessionReflectionTriggerConfig:
1624
"""Deterministic Stop-hook trigger policy configuration."""
1725

1826
# Whether Stop-hook trigger evaluation is active.
1927
enabled: bool = True
28+
# Whether trigger policy may request memory review.
29+
memory_review: bool = True
30+
# Whether trigger policy may request skill review.
31+
skill_review: bool = True
2032
# Number of Stop events between memory-reflection nudges.
2133
memory_stop_interval: int = 3
2234
# Maximum transcript context sent to the memory-reflection decision path.
@@ -63,6 +75,10 @@ class SessionRecallConfig:
6375

6476
# Whether focused recall and archive storage are enabled.
6577
enabled: bool = True
78+
# Whether SessionStart injects the recall policy pointer.
79+
session_start_policy: bool = True
80+
# Whether manual `csep recall` queries may read the recall store.
81+
manual_query: bool = True
6682
# Whether the Stop hook archives transcripts into session recall.
6783
stop_hook_archive: bool = True
6884

@@ -81,6 +97,8 @@ class PluginConfig:
8197

8298
# Configuration schema version supported by this loader.
8399
schema_version: int = 2
100+
# Stable Memory startup injection configuration.
101+
stable_memory: StableMemoryConfig = field(default_factory=StableMemoryConfig)
84102
# Session reflection worker and trigger configuration.
85103
session_reflection: SessionReflectionConfig = field(default_factory=SessionReflectionConfig)
86104
# Session recall archive and recall configuration.
@@ -116,13 +134,16 @@ class LoadResult:
116134

117135
_KNOWN_TOP_LEVEL_KEYS = {
118136
"schema_version",
137+
"stable_memory",
119138
"session_reflection",
120139
"session_recall",
121140
"log",
122141
}
123142

124143
_KNOWN_PATHS = {
125144
"schema_version",
145+
"stable_memory",
146+
"stable_memory.enabled",
126147
"session_reflection",
127148
"session_reflection.enabled",
128149
"session_reflection.backend",
@@ -135,6 +156,8 @@ class LoadResult:
135156
"session_reflection.max_concurrent_jobs",
136157
"session_reflection.trigger",
137158
"session_reflection.trigger.enabled",
159+
"session_reflection.trigger.memory_review",
160+
"session_reflection.trigger.skill_review",
138161
"session_reflection.trigger.memory_stop_interval",
139162
"session_reflection.trigger.memory_context_chars",
140163
"session_reflection.trigger.skill_tool_call_interval",
@@ -143,6 +166,8 @@ class LoadResult:
143166
"session_reflection.trigger.active_job_stale_seconds",
144167
"session_recall",
145168
"session_recall.enabled",
169+
"session_recall.session_start_policy",
170+
"session_recall.manual_query",
146171
"session_recall.stop_hook_archive",
147172
"log",
148173
"log.retention_days",
@@ -199,6 +224,15 @@ def load_config(
199224
sources = _default_sources(config)
200225
sources["schema_version"] = "config.toml" if "schema_version" in raw_toml else "default"
201226

227+
stable_memory_toml = _table(raw_toml, "stable_memory", warnings)
228+
_apply_bool(
229+
config.stable_memory,
230+
"enabled",
231+
stable_memory_toml,
232+
"stable_memory.enabled",
233+
sources,
234+
)
235+
202236
reflection_toml = _table(raw_toml, "session_reflection", warnings)
203237
_apply_bool(
204238
config.session_reflection,
@@ -278,6 +312,20 @@ def load_config(
278312
"session_reflection.trigger.enabled",
279313
sources,
280314
)
315+
_apply_bool(
316+
config.session_reflection.trigger,
317+
"memory_review",
318+
trigger_toml,
319+
"session_reflection.trigger.memory_review",
320+
sources,
321+
)
322+
_apply_bool(
323+
config.session_reflection.trigger,
324+
"skill_review",
325+
trigger_toml,
326+
"session_reflection.trigger.skill_review",
327+
sources,
328+
)
281329
for field_name in (
282330
"memory_stop_interval",
283331
"memory_context_chars",
@@ -352,6 +400,20 @@ def load_config(
352400
"session_recall.enabled",
353401
sources,
354402
)
403+
_apply_bool(
404+
config.session_recall,
405+
"session_start_policy",
406+
recall_toml,
407+
"session_recall.session_start_policy",
408+
sources,
409+
)
410+
_apply_bool(
411+
config.session_recall,
412+
"manual_query",
413+
recall_toml,
414+
"session_recall.manual_query",
415+
sources,
416+
)
355417
_apply_bool(
356418
config.session_recall,
357419
"stop_hook_archive",

src/codex_self_evolution/config_file_template.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
99
schema_version = 2
1010
11+
[stable_memory]
12+
enabled = true
13+
1114
[session_reflection]
1215
enabled = true
1316
backend = "codex-app-server"
@@ -21,6 +24,8 @@
2124
2225
[session_reflection.trigger]
2326
enabled = true
27+
memory_review = true
28+
skill_review = true
2429
memory_stop_interval = 3
2530
memory_context_chars = 16000
2631
skill_tool_call_interval = 15
@@ -30,6 +35,8 @@
3035
3136
[session_recall]
3237
enabled = true
38+
session_start_policy = true
39+
manual_query = true
3340
stop_hook_archive = true
3441
3542
[log]

src/codex_self_evolution/diagnostics.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from . import __version__
2323
from .config import PROJECTS_SUBDIR, SESSION_REFLECTION_SUBDIR, get_home_dir
24+
from .config_file import ConfigError, config_to_dict, load_config
2425
from .session_reflection.runner import session_reflection_status
2526
from .session_recall.archive import default_db_path
2627
from .session_recall.store import SessionRecallStore
@@ -41,9 +42,11 @@ def collect_status(
4142
) -> dict[str, Any]:
4243
"""Assemble the retained diagnostic snapshot without mutating state."""
4344
home_dir = Path(home).expanduser().resolve() if home else get_home_dir()
45+
config_status = _check_config(home_dir)
4446
return {
4547
"timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
4648
"home": str(home_dir),
49+
"config": config_status,
4750
"plugin_hooks": _check_plugin_hook_bundle(),
4851
"stable_memory": _check_stable_memory(home_dir),
4952
"session_reflection": session_reflection_status(home=home_dir),
@@ -53,6 +56,46 @@ def collect_status(
5356
}
5457

5558

59+
def _check_config(home_dir: Path) -> dict[str, Any]:
60+
"""Return resolved feature-switch status without leaking sensitive values."""
61+
try:
62+
loaded = load_config(home=home_dir)
63+
except ConfigError as exc:
64+
return {
65+
"status": "parse_error",
66+
"error": str(exc),
67+
"feature_switches": None,
68+
}
69+
resolved = config_to_dict(loaded.config)
70+
return {
71+
"status": "ok" if not loaded.warnings else "warnings",
72+
"config_path": str(loaded.config_path),
73+
"config_exists": loaded.config_exists,
74+
"feature_switches": {
75+
"stable_memory": bool(resolved["stable_memory"]["enabled"]),
76+
"session_recall": bool(resolved["session_recall"]["enabled"]),
77+
"session_reflection": bool(resolved["session_reflection"]["enabled"]),
78+
},
79+
"sub_switches": {
80+
"session_recall.session_start_policy": bool(
81+
resolved["session_recall"]["session_start_policy"]
82+
),
83+
"session_recall.manual_query": bool(resolved["session_recall"]["manual_query"]),
84+
"session_recall.stop_hook_archive": bool(resolved["session_recall"]["stop_hook_archive"]),
85+
"session_reflection.trigger.enabled": bool(
86+
resolved["session_reflection"]["trigger"]["enabled"]
87+
),
88+
"session_reflection.trigger.memory_review": bool(
89+
resolved["session_reflection"]["trigger"]["memory_review"]
90+
),
91+
"session_reflection.trigger.skill_review": bool(
92+
resolved["session_reflection"]["trigger"]["skill_review"]
93+
),
94+
},
95+
"warnings": loaded.warnings,
96+
}
97+
98+
5699
def _check_plugin_hook_bundle(plugin_root: Path | None = None) -> dict[str, Any]:
57100
root = plugin_root or _default_plugin_root()
58101
manifest_path = root / ".codex-plugin" / "plugin.json"

src/codex_self_evolution/hooks/session_start.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,47 @@
44
from typing import Any
55

66
from ..config import PACKAGE_ROOT, build_paths
7+
from ..config_file import load_config
78
from ..storage import ensure_runtime_dirs, load_stable_memory, repo_fingerprint
89

910

1011
def session_start(cwd: str | Path | None = None, state_dir: str | Path | None = None) -> dict:
1112
paths = build_paths(repo_root=cwd, state_dir=state_dir)
12-
ensure_runtime_dirs(paths)
13-
policy = (PACKAGE_ROOT / "session_recall" / "policy.md").read_text(encoding="utf-8")
14-
memory_text = load_stable_memory(paths)
15-
combined_prefix = "\n\n".join(
16-
section
17-
for section in [
13+
config = load_config(home=Path(state_dir).expanduser().resolve() if state_dir else None).config
14+
stable_memory_enabled = config.stable_memory.enabled
15+
session_recall_enabled = config.session_recall.enabled
16+
if stable_memory_enabled:
17+
ensure_runtime_dirs(paths)
18+
memory_text = load_stable_memory(paths)
19+
else:
20+
memory_text = ""
21+
policy = ""
22+
if session_recall_enabled and config.session_recall.session_start_policy:
23+
policy = (PACKAGE_ROOT / "session_recall" / "policy.md").read_text(encoding="utf-8")
24+
stable_background_sections = []
25+
if stable_memory_enabled:
26+
stable_background_sections = [
1827
"# Stable Background",
1928
"## MEMORY.md\n" + (memory_text or "_No entries yet._\n"),
2029
]
21-
if section
30+
combined_prefix = "\n\n".join(
31+
section for section in stable_background_sections if section
2232
)
2333
return {
2434
"hook": "SessionStart",
2535
"cwd": str(paths.repo_root),
2636
"repo_fingerprint": repo_fingerprint(paths.repo_root),
2737
"state_dir": str(paths.state_dir),
2838
"stable_background": {
39+
"enabled": stable_memory_enabled,
2940
"current_memory_md": memory_text,
3041
"memory_path": str(paths.memory_dir / "MEMORY.md"),
3142
"memory_refs_dir": str(paths.memory_refs_dir),
3243
"legacy_user_md_ignored": (paths.memory_dir / "USER.md").exists(),
3344
"combined_prefix": combined_prefix,
3445
},
3546
"recall": {
47+
"enabled": session_recall_enabled,
3648
"policy": policy,
3749
"skill": {
3850
"skill_id": "csep-session-recall",

src/codex_self_evolution/session_recall/workflow.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def build_focused_recall(
3030
) -> dict[str, Any]:
3131
"""Build model-readable recall from the session_recall SQLite/FTS store only."""
3232
scope = "global" if global_scope else "repo"
33-
config = load_config().config
34-
if not config.session_recall.enabled:
33+
config = load_config(home=Path(state_dir).expanduser().resolve() if state_dir else None).config
34+
if not config.session_recall.enabled or not config.session_recall.manual_query:
3535
return _empty_payload(query=query, scope=scope)
3636

3737
db_path = default_db_path(state_dir=state_dir)

src/codex_self_evolution/session_reflection/state.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ def create_job_from_payload(
112112
if decision_provided:
113113
job.update(
114114
{
115-
"review_memory": True,
116-
"review_skills": True,
115+
"review_memory": bool(decision.get("review_memory", True)),
116+
"review_skills": bool(decision.get("review_skills", True)),
117117
"trigger_reasons": list(decision.get("trigger_reasons") or []),
118118
"skill_generation_mode": skill_generation_mode,
119119
"covered_byte_offset": int(covered_byte_offset),

0 commit comments

Comments
 (0)