"Harness Wave 3 — auto-bump applier for low-risk refinement proposals"
Summary
Wave 3 of the continuous-improvement harness: when a refinement_proposal@1 is approved (via cyberos-cuo proposal apply <id>), this task's applier reads the proposal's ## Suggested change diff, classifies it as minor / major / safety per the target skill's human_fine_tune.review_required policy, and either:
- Minor +
on_minor_bump: false→ applies the diff automatically, bumps the skill'smetadata.versionpatch number, writesmemory_refinement_entryaux row + CHANGELOG entry. - Major OR
on_*: true→ queues under<proposals_root>/pending_approval/<stripe>-<ts>.md, requires explicitcyberos-cuo proposal approve <id>from Stephen before applying.
This is the first wave where the harness can mutate the codebase. Heavily gated by classification, version bumps, and audit emission.
Problem
After TASK-CUO-201, the harness queues good proposals into docs/proposals/open/. But every proposal requires Stephen to manually edit the skill file, bump the version, run tests, and commit. For boring proposals (typo fix in a description, wording polish on a rule rationale), this is a tax on attention with no real risk. For risky proposals (new rule_id, removal of an existing rule, contract version bump), the manual gate is essential.
Without automatic application of low-risk changes, the harness becomes a backlog rather than an active improvement loop.
Proposed Solution
§1 Normative requirements
- MUST ship
cuo/core/proposal_applier.pywithapply_proposal(proposal_path) -> ApplyResultthat reads the proposal, classifies the diff, and either applies-or-queues. - MUST ship
cuo/core/version_bump.pywithbump(skill_path, level: "patch" | "minor" | "major") -> strthat rewrites themetadata.versionfield in SKILL.md frontmatter and returns the new version. - MUST classify diffs into 4 buckets:
cosmetic(description wording, comments, formatting only) → patch bump, auto-apply.rule_addition(new SEC/QA/TRACE rule entry) → minor bump, major review-required class → queue.rule_removal(deletes a rule from RUBRIC) → major bump, queue.contract_field_change(SKILL.mdcyberos-templateor contracttemplate.mdfield shape change) → major bump, queue.
- MUST honour the target skill's
human_fine_tune.review_requiredflags. AnyTrueflag matching the proposal's bucket forces queue regardless of bucket default. - MUST write proposals that auto-apply to
<proposals_root>/applied/<stripe>-<ts>.mdafter the apply; queued ones to<proposals_root>/pending_approval/<stripe>-<ts>.md. - MUST emit
cuo.proposal_appliedmemory aux row per auto-apply; payload{stripe_id, skill_path, bump_level, old_version, new_version, diff_summary}. Emitcuo.proposal_queuedfor queued ones. - MUST add CLI subcommands:
cyberos-cuo proposal apply <stripe>— runs the applier (auto-apply OR queue depending on classification).cyberos-cuo proposal approve <stripe>— moves apending_approval/proposal toapplied/AND runs the apply (this is the explicit HITL gate).cyberos-cuo proposal classify <stripe>— dry-run; shows the bucket + bump level + would-be action.
- MUST run the skill's acceptance/TRIGGER_TESTS.md tests against the new version BEFORE writing the applied state — if any test fails, abort + queue + emit
cuo.proposal_apply_failed. - MUST append a
CHANGELOG.mdentry per applied proposal with the format:### YYYY-MM-DD — [SKILL] <skill_name> v<old> → v<new>, followed by the diff summary. - MUST NOT apply ANY proposal whose
## Risk classbody section equals"safety"automatically —on_safety_change: trueis the protocol's defence-in-depth and overrides bucket classification.
§2 Bump-level table
| diff bucket | default bump | review_required override |
|---|---|---|
cosmetic (description / comment) | patch | none |
wording_polish (rule rationale prose) | patch | none |
threshold_tune (numeric threshold in self_audit block) | minor | on_minor_bump: true → queue |
rule_addition | minor | on_rubric_rule_added: true → queue |
rule_removal | major | always queue |
contract_field_change | major | always queue |
safety_class (anything marked ## Risk class: safety in proposal body) | major | always queue, NEVER auto |
Alternatives Considered
- All proposals require HITL — defeats the purpose of the harness for boring fixes.
- All proposals auto-apply — unacceptable risk; rule removal could destabilise audits silently.
- Slack-based approval inline — out of scope; CLI-based HITL is good enough for Stephen's solo workflow today.
Success Metrics
| metric | baseline | target | deadline |
|---|---|---|---|
| % of proposals auto-applied (vs. queued) | 0% | 30–60% | 30 days post-ship |
| % auto-applied proposals reverted by Stephen within 24h (false-positive auto-apply) | n/a | < 5% | continuous |
| Median time from proposal-created to applied (for auto-bucket) | n/a | < 5 min | continuous |
Scope
In scope: classifier, applier, version-bumper, test-gate, CLI, audit row emission.
Out of scope
- Workflow chain edits (Wave 4 — TASK-CUO-203)
- Multi-skill atomic apply (transactional cross-file changes)
- Cross-tenant proposal propagation
Dependencies
- TASK-CUO-201 — proposals must exist before they can be applied.
AI Risk Assessment
Data Sources
The applier reads two trusted sources only: the proposal markdown file itself (authored by an LLM under operator review per TASK-CUO-201), and the target SKILL.md / RUBRIC.md files (operator-authored). No external network calls. No untrusted user input enters the applier.
Human Oversight
major / safety class diffs ALWAYS queue under pending_approval/ and never apply without an explicit cyberos-cuo proposal approve <stripe> command — that command IS the human oversight gate. minor class diffs honour each skill's per-skill human_fine_tune.review_required flag set; if the target skill declares on_minor_bump: true, the operator's review remains required even though the diff bucket would otherwise auto-apply. The pre-apply TRIGGER_TESTS gate (§1 #8) is a second oversight layer — even auto-approved diffs revert if the skill's own acceptance tests fail post-bump.
Failure Modes
(a) False-positive auto-apply — classifier mis-categorises a rule_addition as cosmetic and the diff lands automatically. Mitigation: the success-metrics %-reverted-within-24h target (< 5%) is monitored; if breached, the classifier is downgraded to "always queue" until the regression is found. (b) Test-gate flake — TRIGGER_TESTS sporadically fails post-bump and the apply aborts; the operator sees cuo.proposal_apply_failed rows and can manually re-apply. (c) Concurrent apply — two operators apply different proposals to the same skill simultaneously; the version_bump is keyed on the SKILL.md's current metadata.version string, so the second apply fails its byte-for-byte precondition check (analogous to TASK-MEMORY-118's put_if). (d) Partial CHANGELOG — if the apply succeeds but CHANGELOG.md write fails, the audit row is emitted but no human-readable trace; mitigation: idempotent cyberos-cuo proposal verify <stripe> reconciles.
AI Authorship Disclosure
- Tools used: Anthropic Claude.
- Scope: §1 normative clauses, §2 bump-level table, §4 ACs, §5 named test entries, alternatives, AI Risk Assessment.
- Human review: Stephen Cheng reviewed; the safety-class never-auto rule (§1 #10) is explicitly operator-mandated.
§4 Acceptance Criteria
- A proposal classified
cosmeticagainst a skill withon_minor_bump: falseauto-applies; SKILL.mdmetadata.versionpatch increments by 1. (traces_to: §1 #3, #4) - A proposal classified
rule_additionwithon_rubric_rule_added: truemoves topending_approval/; SKILL.md is unchanged untilcyberos-cuo proposal approveruns. (traces_to: §1 #4, #7) - A proposal with
## Risk class: safetyin body NEVER auto-applies regardless of classifier bucket; goes straight topending_approval/. (traces_to: §1 #10) - Pre-apply test gate runs acceptance/TRIGGER_TESTS.md against the new skill version; if any TRIGGER_TEST fails, the apply aborts cleanly + emits
cuo.proposal_apply_failed. (traces_to: §1 #8) - Each auto-applied proposal emits exactly one
cuo.proposal_appliedaux row + appends oneCHANGELOG.mdentry. (traces_to: §1 #6, #9) - Each queued proposal emits exactly one
cuo.proposal_queuedaux row. (traces_to: §1 #6) cyberos-cuo proposal classify <id>is read-only — produces the classifier output WITHOUT mutating any file. (traces_to: §1 #7)- Version-bump pathing:
cosmetic/wording_polish→ patch;threshold_tune/rule_addition→ minor;rule_removal/contract_field_change→ major. (traces_to: §2) cyberos-cuo proposal approve <id>after a queue: the file moves frompending_approval/→applied/AND the diff applies; both happen in a single transaction (one fails → both revert). (traces_to: §1 #7)- After an apply, running
cyberos-cuo proposal list --status appliedincludes the stripe with its new SKILL.md version footer. (traces_to: §1 #5)
§5 Verification
modules/cuo/tests/test_proposal_applier.py::test_cosmetic_auto_applies(AC #1, #8)modules/cuo/tests/test_proposal_applier.py::test_rule_addition_queues(AC #2)modules/cuo/tests/test_proposal_applier.py::test_safety_class_never_auto(AC #3)modules/cuo/tests/test_proposal_applier.py::test_test_gate_blocks_bad_apply(AC #4)modules/cuo/tests/test_proposal_applier.py::test_audit_rows_emitted(AC #5, #6)modules/cuo/tests/test_proposal_applier.py::test_classify_is_read_only(AC #7)modules/cuo/tests/test_proposal_applier.py::test_bump_levels(AC #8)modules/cuo/tests/test_proposal_applier.py::test_approve_transactional(AC #9)modules/cuo/tests/test_proposal_applier.py::test_post_apply_list(AC #10)