Skip to content

Add SkillLoop setup/status controller UX#9

Merged
lamenting-hawthorn merged 6 commits into
mainfrom
feat/proposal-provenance-and-skill-packages
Jun 15, 2026
Merged

Add SkillLoop setup/status controller UX#9
lamenting-hawthorn merged 6 commits into
mainfrom
feat/proposal-provenance-and-skill-packages

Conversation

@lamenting-hawthorn

@lamenting-hawthorn lamenting-hawthorn commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • add setup command for Hermes-backed policy creation and optional first controller tick
  • add status command with trace/evaluation/proposal/run/dataset summary
  • persist controller run reports in SQLite and expose controller run/history/show commands
  • document new CLI flows and add tests

Verification

  • python -m pytest -q
  • python -m compileall skillloop tests -q
  • git diff --check
  • smoke-tested setup/status/controller history/show with a temporary Hermes-style state.db

Summary by CodeRabbit

  • New Features

    • New CLI commands: setup (with optional start/auto-export), status, and controller run/history/show with ID/prefix lookup.
    • Controller runs are now persisted and retrievable.
  • Documentation

    • Added detailed Loop Engineering skills guides (pre-loop-checklist, goal-loop, cron-job-workflows).
    • Updated CLI and README command references; removed/trimmed some roadmap docs.
  • Tests

    • Added CLI and store/controller tests covering setup, status, controller history, persistence, and lookup.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3a2cc8ca-a791-426c-8c50-5da9511f34a4

📥 Commits

Reviewing files that changed from the base of the PR and between f7bf18c and 1e5bfc9.

📒 Files selected for processing (5)
  • references/hermes-skills/goal-loop/SKILL.md
  • skillloop/cli.py
  • skillloop/store.py
  • tests/test_cli.py
  • tests/test_store.py
✅ Files skipped from review due to trivial changes (1)
  • references/hermes-skills/goal-loop/SKILL.md

📝 Walkthrough

Walkthrough

Adds controller-run persistence and store APIs, extends the skillloop CLI with setup/status/controller commands and policy helpers, adds tests asserting persistence and prefix lookup behavior, and introduces extensive loop-engineering documentation and three Hermes skills (pre-loop-checklist, cron-job-workflows, goal-loop).

Changes

Controller CLI and Persistence Implementation

Layer / File(s) Summary
Controller run persistence and store APIs
skillloop/store.py, skillloop/controller.py
Create controller_runs table and add save_controller_run, list_controller_runs, get_controller_run; controller reports are persisted to DB and filesystem.
CLI commands and policy helpers
skillloop/cli.py
Add policy path/load helpers; implement cmd_setup, cmd_status; add skillloop controller run/history/show handlers wired to controller_tick.
Test coverage for CLI and store behavior
tests/test_cli.py, tests/test_controller.py, tests/test_store.py
Add CLI integration tests for setup/status/controller-history and unit tests for controller-run contract validation and prefix lookup escaping; extend controller tick test to assert persisted runs.
Loop-engineering analysis and skills README
docs/analysis/loop-engineering-analysis.md, references/hermes-skills/README.md
Add loop-engineering analysis and a skills README describing implemented Hermes skills and installation instructions.
Cron Job Workflows skill reference
references/hermes-skills/cron-job-workflows/SKILL.md
Add comprehensive cron-mode guidance: restrictions, write_file workaround, STATE.md persistence template, delivery rules, efficiency metrics, watchdog pattern, red flags, pitfalls, and verification checklist.
Goal Loop skill pattern
references/hermes-skills/goal-loop/SKILL.md
Add /goal cron-loop pattern doc covering prerequisites, maker/checker flow, prompt templates, state handling, self-cancellation, and monitoring guidance.
Pre-Loop Checklist skill framework
references/hermes-skills/pre-loop-checklist/SKILL.md
Add SKILL.md with metadata, Tier 1/2 decision frameworks, verdict rules (BUILD/FIX/SKIP), and guidance for loop adoption.
Repo docs and .gitignore updates
.gitignore, README.md, docs/cli.md
Ignore .worktrees/ and docs/HANDOFF.md; document setup and controller CLI commands and options in README and CLI docs.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 The controller stores each run with care,
CLI setup sings and policies pair,
Skills for loops line guides so neat,
Cron, goal, pre-check — the loop is complete. 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main changes: adding setup/status and controller user experience features to SkillLoop, which is the central theme across all modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/proposal-provenance-and-skill-packages

Comment @coderabbitai help to get the list of available commands and usage tips.

@lamenting-hawthorn
lamenting-hawthorn force-pushed the feat/proposal-provenance-and-skill-packages branch from a4fc05c to f7bf18c Compare June 13, 2026 21:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
skillloop/cli.py (2)

456-456: 💤 Low value

Validate that limit is positive when provided.

Line 456 uses args.limit without validation. In SQLite, a negative LIMIT is treated as "no limit," and zero returns no rows. This may confuse users. Consider validating limit > 0 or documenting the behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skillloop/cli.py` at line 456, Validate args.limit before calling
store.list_controller_runs: ensure that if args.limit is not None it is an
integer > 0 and reject or normalize invalid values (e.g., raise a user-facing
error or ignore/convert zero/negative to None) so you never pass zero or
negative to store.list_controller_runs; update the CLI handling where runs =
store.list_controller_runs(limit=args.limit) is invoked to perform this check
and return a clear error message or fallback behavior when args.limit <= 0.

112-118: ⚡ Quick win

Handle JSON decode errors when loading the dataset manifest.

Line 113 loads the manifest JSON without error handling. If the file is corrupt, the user will see a raw json.JSONDecodeError traceback. Consider wrapping in a try/except for a friendlier message.

🛡️ Proposed fix: add error handling
     dataset_stats = None
     if dataset_manifest.exists():
-        manifest = json.loads(dataset_manifest.read_text(encoding="utf-8"))
-        dataset_stats = {
-            "manifest": str(dataset_manifest),
-            "records": manifest.get("records", 0),
-            "estimated_tokens": manifest.get("estimated_tokens", 0),
-        }
+        try:
+            manifest = json.loads(dataset_manifest.read_text(encoding="utf-8"))
+            dataset_stats = {
+                "manifest": str(dataset_manifest),
+                "records": manifest.get("records", 0),
+                "estimated_tokens": manifest.get("estimated_tokens", 0),
+            }
+        except (json.JSONDecodeError, OSError) as exc:
+            dataset_stats = {"error": f"Failed to load manifest: {exc}"}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skillloop/cli.py` around lines 112 - 118, Wrap the JSON load of
dataset_manifest (the line manifest =
json.loads(dataset_manifest.read_text(...)) and the subsequent population of
dataset_stats) in a try/except that catches json.JSONDecodeError; on error, emit
a clear, user-friendly message (e.g., using the existing logger or a click.echo)
indicating the manifest is corrupt and include the filename, then set
dataset_stats to a safe default (manifest path plus records and estimated_tokens
= 0) or bail out gracefully as appropriate. Ensure you import
json.JSONDecodeError if needed and do not let the raw traceback propagate to the
user.
skillloop/store.py (1)

187-196: ⚡ Quick win

Consider validating required keys or documenting the report dict contract.

save_controller_run is a public method accepting a dict. Line 189 assumes report["id"] exists (will raise KeyError if missing), while Line 194 uses .get() with an empty-string fallback for started_at. If started_at is missing, inserting "" may mask a caller bug rather than fail fast.

🛡️ Proposed fix: validate required keys
 def save_controller_run(self, report: dict) -> str:
     self.init()
+    if "id" not in report or not report["id"]:
+        raise ValueError("report must have non-empty 'id'")
+    if "started_at" not in report:
+        raise ValueError("report must have 'started_at'")
     run_id = str(report["id"])
     payload = json.dumps(report, ensure_ascii=False)
     with self._connect() as conn:
         conn.execute(
             "INSERT OR REPLACE INTO controller_runs (id, started_at, finished_at, payload) VALUES (?, ?, ?, ?)",
-            (run_id, str(report.get("started_at") or ""), report.get("finished_at"), payload),
+            (run_id, str(report["started_at"]), report.get("finished_at"), payload),
         )
     return run_id
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skillloop/store.py` around lines 187 - 196, The save_controller_run method
assumes required keys in the report dict (uses report["id"] and falls back to ""
for started_at), so add explicit validation at the top of save_controller_run:
verify 'id' exists and is non-empty (raise ValueError with a clear message if
missing) and decide whether 'started_at' is required—if it is, validate it
similarly instead of defaulting to "" (or if optional, keep using
report.get('started_at') but persist None rather than an empty string); keep the
rest of the logic (self.init(), payload = json.dumps(...), and the DB insert via
self._connect()) unchanged and convert id/started_at to strings only after
validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@references/hermes-skills/goal-loop/SKILL.md`:
- Around line 241-244: The example in SKILL.md sets workdir to a host-specific
path (workdir='/Users/raghav/myproject'); change that to a neutral, non-personal
path such as a repo-relative or placeholder (for example workdir='./myproject'
or workdir='${REPO_ROOT}/myproject' or workdir='<your-project-path>') so readers
can copy/paste without editing a personal machine path; update the line that
assigns workdir in the given example block to use the chosen neutral
placeholder.
- Around line 176-178: The current guidance tells the skill to find its own cron
entry by name/prompt text and then call cronjob(action='remove') which can
delete the wrong job; instead, capture and persist the exact job_id returned
when scheduling the job (the response from cronjob(action='create' or schedule
API)), then after reporting success call cronjob(action='remove',
job_id=THE_JOB_ID) with that stored job_id to deterministically self-cancel;
update any places that instruct searching by name/prompt (the lines using
cronjob(action='list') and fuzzy matching) to use the persisted job_id approach
instead.

In `@skillloop/cli.py`:
- Line 88: Validate args.max_sessions is a positive integer before passing it
into the constructor: check args.max_sessions > 0 (or use an argparse
type/validator) and if not, raise a clear error (e.g.,
argparse.ArgumentTypeError or print an error and exit) so the assignment
max_sessions=args.max_sessions never receives zero or negative values; update
the validation near where max_sessions=args.max_sessions is set (referencing
args.max_sessions and the code that constructs the object receiving
max_sessions).
- Around line 90-92: Validate args.min_score before assigning it to
policy.dataset, policy.evaluation.min_score, and when constructing
LoopCondition; ensure it falls within the expected range (e.g., 0–100) and raise
or exit with a clear error message if out of range. Specifically, add a guard
near the top of the CLI handling that checks args.min_score and normalizes or
rejects invalid values, then only set policy.dataset = DatasetPolicy(...),
policy.evaluation.min_score = args.min_score, and policy.evaluation.condition =
LoopCondition(score_gte=args.min_score) after the value passes validation.

In `@skillloop/store.py`:
- Around line 209-224: In get_controller_run, escape '%' '_' and backslash in
the run_id before using it in the LIKE pattern to avoid unintended wildcard
matches: replace '\' with '\\', '%' with '\%', and '_' with '\_' to produce
escaped_id, then use conn.execute with the pattern f"{escaped_id}%" and include
an ESCAPE '\' clause in the SQL (e.g., "WHERE id LIKE ? ESCAPE '\'") so the
prefix search treats run_id literally; keep the exact error handling and JSON
loading unchanged.

---

Nitpick comments:
In `@skillloop/cli.py`:
- Line 456: Validate args.limit before calling store.list_controller_runs:
ensure that if args.limit is not None it is an integer > 0 and reject or
normalize invalid values (e.g., raise a user-facing error or ignore/convert
zero/negative to None) so you never pass zero or negative to
store.list_controller_runs; update the CLI handling where runs =
store.list_controller_runs(limit=args.limit) is invoked to perform this check
and return a clear error message or fallback behavior when args.limit <= 0.
- Around line 112-118: Wrap the JSON load of dataset_manifest (the line manifest
= json.loads(dataset_manifest.read_text(...)) and the subsequent population of
dataset_stats) in a try/except that catches json.JSONDecodeError; on error, emit
a clear, user-friendly message (e.g., using the existing logger or a click.echo)
indicating the manifest is corrupt and include the filename, then set
dataset_stats to a safe default (manifest path plus records and estimated_tokens
= 0) or bail out gracefully as appropriate. Ensure you import
json.JSONDecodeError if needed and do not let the raw traceback propagate to the
user.

In `@skillloop/store.py`:
- Around line 187-196: The save_controller_run method assumes required keys in
the report dict (uses report["id"] and falls back to "" for started_at), so add
explicit validation at the top of save_controller_run: verify 'id' exists and is
non-empty (raise ValueError with a clear message if missing) and decide whether
'started_at' is required—if it is, validate it similarly instead of defaulting
to "" (or if optional, keep using report.get('started_at') but persist None
rather than an empty string); keep the rest of the logic (self.init(), payload =
json.dumps(...), and the DB insert via self._connect()) unchanged and convert
id/started_at to strings only after validation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5a27e520-13f7-4581-bedb-70ae8bac30df

📥 Commits

Reviewing files that changed from the base of the PR and between f7ba1b2 and f7bf18c.

📒 Files selected for processing (15)
  • .gitignore
  • README.md
  • docs/analysis/loop-engineering-analysis.md
  • docs/cli.md
  • docs/next-steps.md
  • docs/training-integrations.md
  • references/hermes-skills/README.md
  • references/hermes-skills/cron-job-workflows/SKILL.md
  • references/hermes-skills/goal-loop/SKILL.md
  • references/hermes-skills/pre-loop-checklist/SKILL.md
  • skillloop/cli.py
  • skillloop/controller.py
  • skillloop/store.py
  • tests/test_cli.py
  • tests/test_controller.py
💤 Files with no reviewable changes (2)
  • docs/next-steps.md
  • docs/training-integrations.md

Comment thread references/hermes-skills/goal-loop/SKILL.md Outdated
Comment thread references/hermes-skills/goal-loop/SKILL.md Outdated
Comment thread skillloop/cli.py
Comment thread skillloop/cli.py
Comment thread skillloop/store.py
@lamenting-hawthorn
lamenting-hawthorn merged commit aba5b60 into main Jun 15, 2026
3 checks passed
@lamenting-hawthorn
lamenting-hawthorn deleted the feat/proposal-provenance-and-skill-packages branch June 15, 2026 07:53
@lamenting-hawthorn
lamenting-hawthorn restored the feat/proposal-provenance-and-skill-packages branch June 15, 2026 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant