ooda-config — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ooda-config (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
View, modify, and validate config.json without editing JSON by hand. All writes: back up to config.json.bak, then re-parse to confirm valid JSON.
Read config.safety.halt_file (default agent/safety/HALT). If that path exists: HALT: <reason>. ooda-config aborted. — stop.
If config.json is missing and subcommand is not validate: config.json not found. Run /ooda-setup to create one. — stop.
Parse the user's invocation to one of: show | level | domain add | domain remove | domain list | safety | validate | lens review | lens reset | action {list|approve|defer|reject|prioritize} | mode/season | rotation {show|reset} | context show | rollback {cycle} | auto-merge {on|off}. Unknown subcommand → print the Usage block and exit.
Print a compact summary: schema_version, project fields, domain list with weight/skill/enabled status, current progressive_complexity level and name, and key safety values (halt_file presence, confidence_threshold, max_prs_per_cycle).
[DANGER] Level 3 enables AUTONOMOUS mode:
- The agent autonomously writes code and opens PRs (no prompt).
- By DEFAULT those are DRAFT PRs you review and merge — nothing reaches
main without your click. (You stay in command.)
- Auto-merge is a SEPARATE opt-in: only if you ALSO run
`/ooda-config auto-merge on` (safety.enable_auto_merge=true) will it merge
LOW-RISK PRs by itself — non-protected paths, <= auto_merge_max_files /
auto_merge_max_lines, tests green — with a post-merge health check that
auto-reverts + HALTs on failure. It stays OFF until you flip it.
- Only the HALT file or the daily cost cap can stop a running cycle.
Type "enable autonomous" to confirm (anything else cancels):Only accept the exact phrase enable autonomous. Any other response: Cancelled.
progressive_complexity.current_level = N and implementation.enabled = (N == 3).Level changed: <old> → <N> ("<name>").Ask the user in sequence for: weight (float, default 1.0), primary_skill (must start with "/"), state_file path (default agent/state/<name>.json).
Back up config.json. Insert into config.domains:
{ "weight": <w>, "state_file": "<path>", "primary_skill": "<skill>",
"chain": [], "branch_prefix": "auto/<name>/", "fallback": true,
"enabled": true, "status": "active" }(status is the field evolve's filters treat as authoritative — a domain without it would never be selected. enabled is kept for back-compat; evolve treats a domain with enabled: true and NO status field as "active".) Create empty state file if absent ({ "schema_version": "1.0.0", "domain": "<name>", "last_run": null }). Append primary_skill to safety.skill_allowlist if not already present. Write + validate. Print confirmation showing state file status and allowlist change.
Look up config.domains.<name>. Not found or already disabled → print message and exit. Back up. Set enabled: false (soft-delete — data retained). Write + validate. Print: Domain disabled: <name> (re-enable by setting enabled: true)
Print a table: name, status (enabled/disabled), weight, primary_skill, state_file. Footer: Total: <N> domains, <enabled_count> enabled. If config.domains is empty: No domains configured.
Print the full safety section as a labeled key-value block: halt_file (with presence indicator), confidence_threshold, min_cycle_interval_minutes, max_prs_per_cycle, max_files_per_pr, max_lines_per_pr, first_cycle_observe_only, protected_paths list, and skill_allowlist.
The full check list (1–27) is defined ONCE in "Step G — validate (extended)" below — that section is canonical. (An earlier 17-check copy of this list lived here and drifted from the extended one; never maintain two lists.)
Show the lens snapshot for the named domain.
config.domains. Not found → Domain not found: <name> and exit.agent/state/<name>/lens.json (or lens_file field if present in config).No lens data for <name>. Run a cycle first. and exit.Wipe the lens snapshot for the named domain, forcing a fresh start on the next cycle.
config.domains. Not found → Domain not found: <name> and exit.No lens file found for <name>. Nothing to reset. and exit.Reset lens for <name>? This clears all accumulated evidence. (yes/no)Any answer other than "yes" → Cancelled. and exit.
lens.json.bak in the same directory before overwriting.Print: Backed up: <lens_path>.bak
must exist — a reset file missing focus_items/learned_thresholds/ discovered_signals breaks the next lens update): { "schema_version": "1.0.0", "domain": "<name>", "reset_at": "<ISO timestamp>", "focus_items": [], "learned_thresholds": [], "discovered_signals": [], "deprecated_items": [], "evidence": [] }
Lens reset: <name> (evidence cleared, backup saved, next cycle starts fresh)Print pending actions from agent/state/evolve/action_queue.json:
[Action Queue] {pending_count} pending, {completed_count} completed
| # | ID | Title | RICE | Age | Status |
|---|--------|------------------------------|-------|------|---------|
| 1 | a15-1 | Update context.json names | 50400 | 3.2d | pending |
| 2 | a17-1 | Pipeline FTS index | 25200 | 3.0d | approved |Show effective_rice (with decay applied) and age since extracted_at.
Set action status to "approved". Approved items are prioritized over pending items in dev-cycle selection regardless of RICE score.
1. Find action by id in action_queue.pending
2. If not found: "Action not found: {id}" — exit
3. Set status = "approved", approved_at = now
4. Boost effective_rice by 20% (item.effective_rice *= 1.2)
5. Print "Approved: {title} (effective RICE: {new_rice})"Defer an action for N days (default 7). Deferred items are excluded from dev-cycle selection until their defer period expires.
1. Find action by id
2. Set status = "deferred", deferred_until = now + N days
3. Print "Deferred: {title} until {date} ({N} days)"Move action to completed with status "rejected_by_human".
1. Find action by id
2. Move to completed array with status = "rejected_by_human", reason = reason
3. Print "Rejected: {title} — {reason or 'no reason given'}"Move action to top of queue by setting effective_rice above current highest.
1. Find action by id
2. Set effective_rice = max(all pending effective_rice) + 1.0
3. Re-sort pending by effective_rice descending
4. Print "Prioritized: {title} → top of queue (RICE: {new_rice})"season {name})Switch season/phase mode. Mode overrides domain weights, disables selected domains for the season, and may bump signal bonuses. Changes take effect on the NEXT cycle (they are read in-memory at Step 1-A; disk config.json is updated so /ooda-status shows the current mode).
1. Read config.season_modes.modes
2. If modes not configured: "No season modes defined. Add season_modes to config.json." — exit
3. If {name} not in modes: "Unknown mode: {name}. Available: {list}" — exit
4. Back up config.json
5. Set config.season_modes.current_mode = {name}
6. Set config.season_modes.enabled = true (in case it was false)
7. Print applied overrides:
- weight_overrides: {domain: multiplier}
- disabled_domains: [...]
- signal_bonuses: {signal: value}
8. Write + validate JSON
9. Print "Season mode changed: {old} → {name}"List all defined modes with a one-line summary.
1. Read config.season_modes.modes
2. For each mode: print "- {name}: {len(weight_overrides)} weights, {len(disabled_domains)} disabled, {len(signal_bonuses)} signal bonuses"
3. Print: "Current: {current_mode}" (or "(season_modes disabled)" if enabled=false)Print the full active-mode overrides applied to the current scoring table.
1. Read config.season_modes
2. If not enabled: print "Season modes disabled." — exit
3. For the current_mode:
- Print "Mode: {current_mode}"
- Print weight_overrides with the previous weights for comparison:
" service_health: config=2.0 -> mode=1.0 (effective)"
- Print disabled_domains: " disabled: [competitors]"
- Print signal_bonuses: " signal bonuses: {health_alert_bonus: +5.0}"Show the rotation list and current cursor for a domain.
1. Look up domain in config.domains
2. If no `rotation` field: "Domain '{name}' has no rotation list." — exit
3. Read cursor from agent/state/{domain}/rotation_cursor.json (missing = cursor 0)
4. Print the rotation list with an arrow at the current cursor position:
[thumbnail-editor, dashboard -> stats, billing]
5. Print: "Next focus_item: {list[cursor]}"Reset the rotation cursor to 0 for a domain.
1. Look up domain in config.domains
2. If no `rotation` field: "Domain '{name}' has no rotation list." — exit
3. Back up agent/state/{domain}/rotation_cursor.json to .bak (if present)
4. Write {"cursor": 0, "last_updated": now, "focus_item": rotation_list[0]}
5. Print: "Rotation cursor reset: {domain} -> 0 ({first_focus_item})"Print the current active_context config + the loaded blob contents.
1. Read config.active_context
2. If no active_context.path: "No active_context configured." — exit
3. Try to read the file at that path:
- If missing: "active_context file missing: {path}"
- If malformed JSON: "active_context file is not valid JSON"
- If OK: print path, mtime age, refresh_skill (if any), refresh_interval_hours,
and the blob contents (pretty-printed, cap at 80 lines)Revert the repo + state to a recorded checkpoint. Checkpoints are written by evolve 4-C2 when safety.enable_rollback (or safety.enable_auto_merge) is on.
1. Read agent/state/evolve/checkpoints.json. If missing/empty:
"No checkpoints recorded. Set safety.enable_rollback=true to enable." — exit.
2. Find the checkpoint where checkpoint.cycle == {cycle}.
If not found: "No checkpoint for cycle {cycle}. Available: {cycles}" — exit.
3. Confirm (typed phrase): print
"Roll back to cycle {cycle} (commit {sha})? This reverts every commit since
then and restores state. Type 'rollback {cycle}' to confirm:"
Accept ONLY the exact phrase; anything else: "Cancelled." — exit.
4. Re-check the HALT file.
5. Revert the repo. CAUTION: `git revert {sha}..HEAD` reverts each commit in the
range and **fails on any merge commit in that range** (it needs `-m`, and a
mixed range can't take a single `-m`) — leaving main HALF-reverted. This is
safe only on a linear history; auto-merge uses `--squash` precisely to keep it
linear. If the range may contain `--merge` merge commits, prefer `--hard`.
# default (linear history, e.g. squash-merged auto-merges):
git revert --no-edit {checkpoint.commit_sha}..HEAD
git push origin HEAD
# robust fallback / operator `--hard` (destructive — warn first):
git reset --hard {checkpoint.commit_sha} && git push --force origin HEAD
6. Restore confidence.json and action_queue.json pending items from
checkpoint.state_snapshot.
7. Create the HALT file: "Manual rollback to cycle {cycle} ({sha}). Delete to resume."
8. Print "Rolled back to cycle {cycle} ({sha}). HALT created — review, then
delete the HALT file to resume."Toggle the autonomous low-risk auto-merge opt-in (config.safety.enable_auto_merge, default off). This is the SINGLE switch that lets evolve merge PRs on its own.
1. Parse on|off. Unknown → print Usage and exit.
2. Turning ON:
- Require progressive_complexity.current_level == 3, else:
"Auto-merge needs Level 3. Run /ooda-config level 3 first." — exit.
- Print the gates and require a typed phrase:
"[DANGER] Auto-merge will let the agent merge LOW-RISK PRs without your click:
non-protected paths, <= {auto_merge_max_files} files / {auto_merge_max_lines}
lines, tests green. A post-merge health check auto-reverts + HALTs on
failure. Larger/protected changes still wait for you.
Type 'enable auto-merge' to confirm:" (accept only the exact phrase)
3. Back up config.json. Set safety.enable_auto_merge = (on ? true : false).
Write + validate JSON.
4. Print "Auto-merge: {enabled|disabled}." When enabled, also print the active
low-risk thresholds so the operator sees the blast-radius bound.Run checks in order; print [PASS] or [FAIL] <reason> for each:
schema_version field presentproject.name, project.locale, project.timezone all presentsafety.halt_file, safety.confidence_threshold, safety.max_prs_per_cycle presentconfidence_threshold in [0.0, 1.0]; max_prs_per_cycle >= 1; min_cycle_interval_minutes >= 1health_check_timeout_seconds is a number in [2, 30]test_timeout_seconds is a number >= 10deploy_monitor_timeout_seconds is a number >= 60deploy_health_wait_seconds is a number >= 5deploy_workflow_inputs is a plain object (not array, not null)safety.lock_timeout_minutes is a number >= 1weight, primary_skill, state_file, and status(or legacy enabled — warn [WARN] domain {name}: no status field; evolve treats enabled:true as status:"active" instead of failing)
status == "active", or legacy enabled: true)primary_skill is in safety.skill_allowlist
progressive_complexity.current_level is 0–3$ENV_VAR form)fallback: true (confidence gate escape)branch_prefix values across domainssignals.health_alert_bonus should be <= 2× max domain weight (monopoly risk)memory.contrarian_check_interval is in range [1, 100]memory.action_queue_decay_days > 0 and action_queue_decay_amount in (0, 1]implementation.enabled, progressive_complexity.current_level must be 3memory.working_memory_size >= 5 (minimum for pattern detection)saturation block present, warn_threshold < boost_threshold < halt_thresholdsafety.enable_auto_merge is true, progressive_complexity.current_level must be 3safety.auto_merge_max_files <= safety.max_files_per_pr and safety.auto_merge_max_lines <= safety.max_lines_per_pr (the low-risk bar must be tighter than the hard PR cap)eval.enabled is true, eval.model is a non-empty string and eval.grade_on is a non-empty list containing none of futile/error/observe (never spend an eval call on a no-value cycle)Final: Validation: <N> passed, <M> failed On any failure append: Run /ooda-config show to review your settings.
/ooda-config show Print current config summary
/ooda-config level {0-3} Change progressive complexity level
/ooda-config domain add {name} Add a new domain (interactive)
/ooda-config domain remove {name} Disable a domain (soft-delete)
/ooda-config domain list List all domains with status
/ooda-config safety Show full safety settings
/ooda-config validate Validate config.json structure and values
/ooda-config lens review {domain} Show lens.json for a domain with evidence trails
/ooda-config lens reset {domain} Wipe lens.json for a domain (start fresh)
/ooda-config action list List pending actions with RICE and age
/ooda-config action approve {id} Approve action for priority execution
/ooda-config action defer {id} [N] Defer action for N days (default 7)
/ooda-config action reject {id} Reject action with optional reason
/ooda-config action prioritize {id} Move action to top of queue
/ooda-config mode {name} Switch season/phase mode (alias: `season {name}`)
/ooda-config season list List available season modes
/ooda-config season show Show current mode overrides in detail
/ooda-config rotation show {d} Show rotation list + cursor for a domain
/ooda-config rotation reset {d} Reset rotation cursor to 0
/ooda-config context show Show active_context path + blob contents
/ooda-config auto-merge {on|off} Toggle low-risk autonomous merge (Level 3, opt-in)
/ooda-config rollback {cycle} Revert repo + state to a recorded checkpoint~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.