# Self Curator controller / autonomy receipt

Date: 2026-05-02

## Changed truth

Self Curator now has an autonomy controller for **unattended apply / 事後報備制**.

Within the current low-risk envelope, the controller may apply first, then report what changed. CK/operator can keep the mutation or use the apply receipt to roll it back.

The autonomy loop is:

```text
review -> plan -> policy -> checkpoint -> apply -> verify -> report -> rollback if verify fails or operator requests
```

## Controller scope

The scheduled controller now mutates the skill body itself. Current low-risk mutation family:

```md
## Curator lifecycle

- Status: `promote_to_review|refresh`
- Reason: `<reason_code>`
- Rollback: use the apply receipt generated by `openclaw-mem self-curator`.
```

It skips files that already have `## Curator lifecycle` to avoid weekly timestamp churn. The policy also allows checkpointed archive-to-`skills/.archive/` for malformed/very-short skill files, while broader freeform body rewrites remain outside scheduled mode.

## CLI / wrapper

Supported controller modes today: `dry_run` and `unattended_apply`. `auto_low_risk` is intentionally not exposed until a later policy upgrade.

Manual controller:

```bash
uv run openclaw-mem self-curator controller \
  --skill-root /root/.openclaw/workspace/skills \
  --workspace-root /root/.openclaw/workspace \
  --out-root /root/.openclaw/workspace/.state/self-curator/controller-runs \
  --mode unattended_apply \
  --max-mutations 5 \
  --json
```

Cron wrapper:

```bash
python3 /root/.openclaw/workspace/openclaw-mem/tools/self_curator_controller.py --mode unattended_apply --cron-output
```

Stable repo cron job script:

```text
/root/.openclaw/workspace/openclaw-mem/tools/cron-runner/jobs/self_curator_weekly.sh
```

Installed cron-runner script path:

```text
/root/.openclaw/workspace/tools/cron-runner/jobs/self_curator_weekly.sh
```

## Artifacts per run

```text
/root/.openclaw/workspace/.state/self-curator/controller-runs/<run-id>/
├── review.json
├── plan.json
├── policy.json
├── REPORT.md
├── controller-receipt.json
└── apply/
    ├── apply-receipt.json
    └── apply.diff
```

If verification fails, rollback is executed and a rollback receipt is emitted.

## Verification receipts

```text
uv run -- python -m py_compile openclaw_mem/self_curator.py openclaw_mem/cli.py tools/self_curator_controller.py
uv run -- python -m unittest tests.test_self_curator -q
```

Result:

```text
Ran 15 tests
OK
```

Wrapper smoke with synthetic workspace:

- `python3 tools/self_curator_controller.py ... --mode unattended_apply --cron-output`
- emitted controller receipt
- wrote `## Curator lifecycle` into fixture `SKILL.md` body
- verify returned `ok=true`

## Cron posture

Self Curator must run via a separate cron job. It is not heartbeat. Heartbeat remains health/alert only.

Recommended cadence: weekly, unattended apply mode. Cron output is `NO_REPLY` on no-op and `NEEDS_CK` with report/rollback paths when files changed.

## Rollback

Use the apply receipt from the controller run:

```bash
uv run openclaw-mem self-curator rollback --receipt <apply-receipt.json> --json
```

## Topology delta

Changed once cron is installed: one dedicated Self Curator weekly cron job.
No gateway config or heartbeat topology changes.

## Final closure update

QA attempts:

- Full controller QA timed out; no pass verdict was accepted from the timeout.
- Narrow QA timed out; no pass verdict was accepted from the timeout.
- Ultra-short diff QA returned must-fix findings for operator surface clarity; fixes applied:
  - renamed mode to `unattended_apply` everywhere;
  - removed exposed `auto_low_risk` mode until a later policy upgrade;
  - made README/skill cron commands absolute and explicit;
  - verified repo wrapper and installed cron-runner script use `/root/.openclaw/workspace` as workspace root and `/root/.openclaw/workspace/skills` as skill root.

Final verification:

```text
uv run -- python -m py_compile openclaw_mem/self_curator.py openclaw_mem/cli.py tools/self_curator_controller.py
uv run -- python -m unittest tests.test_self_curator -q
```

Result:

```text
Ran 16 tests
OK
```

Controller output smoke:

- dry run: `NO_REPLY`
- first unattended apply on fixture: `NEEDS_CK ... changed 1 file(s) ... report=... rollback=...`
- repeat unattended apply on same fixture: `NO_REPLY` because existing `## Curator lifecycle` section is skipped to avoid churn.

Live canary:

- Ran `python3 /root/.openclaw/workspace/openclaw-mem/tools/self_curator_controller.py --mode unattended_apply --cron-output`
- Result: created 5 real skill lifecycle sidecars under `/root/.openclaw/workspace/skills/*/.curator-lifecycle.json` before the body-mutating policy upgrade. These are historical canary artifacts; the scheduled policy now mutates `SKILL.md` body sections instead.
- Report: `/root/.openclaw/workspace/.state/self-curator/controller-runs/controller-20260502-123759/REPORT.md`
- Rollback receipt path: `/root/.openclaw/workspace/.state/self-curator/controller-runs/controller-20260502-123759/apply/apply-receipt.json`

Cron install readback:

```text
42 10 * * 6 /root/.openclaw/workspace/tools/cron-runner/jobs/self_curator_weekly.sh
```

Supercronic was preflight-tested with `-test`, then restarted to load the updated crontab:

```text
old_pid=1363
new_pid=11730
```

## Body-mutating policy upgrade

CK requested that unattended apply modify the skill body itself, not only detached metadata.

Implemented current scheduled behavior:

- controller appends a bounded `## Curator lifecycle` section into `SKILL.md` body;
- policy admits only the exact lifecycle-section append regex;
- rollback-containing freeform append is rejected by policy;
- very-short/malformed skills may be archived to `skills/.archive/<name>/SKILL.md` with rollback;
- repeat runs skip files with existing `## Curator lifecycle` to avoid churn.

Final QA verdict: OK to live canary / commit. Remaining must-fix: none for this gate.

Verification:

```text
uv run -- python -m py_compile openclaw_mem/self_curator.py openclaw_mem/cli.py tools/self_curator_controller.py
uv run -- python -m unittest tests.test_self_curator -q
```

Result:

```text
Ran 19 tests
OK
```

Synthetic smoke:

- first unattended apply changed fixture `SKILL.md` body with `## Curator lifecycle`;
- repeat run returned `NO_REPLY`;
- rollback restored original file hash.

Live body-mutation canary:

```text
python3 /root/.openclaw/workspace/openclaw-mem/tools/self_curator_controller.py --mode unattended_apply --cron-output
```

Result:

```text
NEEDS_CK: Self Curator unattended_apply changed 5 file(s)
```

Report:

```text
/root/.openclaw/workspace/.state/self-curator/controller-runs/controller-20260502-132349/REPORT.md
```

Rollback receipt:

```text
/root/.openclaw/workspace/.state/self-curator/controller-runs/controller-20260502-132349/apply/apply-receipt.json
```

Changed real skill bodies:

- `/root/.openclaw/workspace/skills/ai-humanizer/SKILL.md`
- `/root/.openclaw/workspace/skills/ck-software-engineering-ops/SKILL.md`
- `/root/.openclaw/workspace/skills/claude-review/SKILL.md`
- `/root/.openclaw/workspace/skills/cli-contract-design/SKILL.md`
- `/root/.openclaw/workspace/skills/codex-cli-standalone/SKILL.md`

Cron readback remains:

```text
42 10 * * 6 /root/.openclaw/workspace/tools/cron-runner/jobs/self_curator_weekly.sh
```

## 五氣朝元 closure sweep

Closure sweep after `self-curator-body-v1` checked the active self-curator surfaces for stale metadata-only / sidecar-only / frontmatter-only wording.

Amended active surfaces:

- `README.md`: current advanced-lab summary now names Self Curator as an engine and says scheduled lane mutates `SKILL.md` body sections with rollback receipts.
- `docs/core-vs-advanced-labs.md`: changed Self Curator row from review-only sidecar/no-mutation to checkpointed lifecycle review/apply with body-section mutation and gated memory/dream/authority surfaces.

Left historical surfaces unchanged:

- `docs/2026-05-02_self-curator-v0-receipt.md` remains accurate for v0 review-only sidecar history.
- `docs/2026-05-02_self-curator-controller-receipt.md` explicitly labels the earlier `.curator-lifecycle.json` canary as historical before the body-mutating policy upgrade.
- Unrelated `auto_low_risk` references belong to the optimize-assist controller family, not the self-curator controller exposed modes.

Verifier:

```text
uv run -- python -m unittest tests.test_self_curator -q
```

Result:

```text
Ran 19 tests
OK
```

Topology: unchanged after this sweep. Existing cron remains:

```text
42 10 * * 6 /root/.openclaw/workspace/tools/cron-runner/jobs/self_curator_weekly.sh
```
