All commands accept the global --path argument. The path selects the project root where SkillLoop stores local state.
skillloop --path . <command>Equivalent module form:
python -m skillloop --path . <command>python -m skillloop.cli also works (backward-compatible entry point).
The CLI is split into focused modules: skillloop/interfaces/cli/ holds one
module per command group (parsing + presentation only), skillloop/application/
holds use-case orchestration and typed request objects, and skillloop/cli.py
is a thin re-export preserving the skillloop.cli:main console-script entry
point. All command syntax and flags are backward-compatible.
SkillLoop deploys as a project-local sidecar. The current one-time setup path is:
pipx install git+https://github.com/lamenting-hawthorn/skillloop.git
skillloop --path /path/to/project setup --connect hermes --start --auto-exportThat creates .skillloop/policy.json, reads Hermes sessions from
~/.hermes/state.db, runs one controller tick, stores local state in
.skillloop/, and writes a dataset manifest when auto-export is enabled.
setup --start is a one-shot controller run. It does not install, bootstrap, or
start a background service.
For recurring controller ticks on macOS:
skillloop --path /path/to/project service install --kind launchd --interval-seconds 3600
skillloop --path /path/to/project service statusFor recurring controller ticks on Linux:
skillloop --path /path/to/project service install --kind systemd --interval-seconds 3600
skillloop --path /path/to/project service statusservice install writes the platform service file (launchd plist or systemd
unit) and .skillloop/service.json metadata, then prints the exact OS command
to load it. It does not silently load the service. Both implementations live
behind the ServiceManager port in skillloop/infrastructure/services/.
Current deployment scope:
- macOS launchd plist generation is implemented.
- Linux systemd user-service generation is implemented.
- No cloud service is required.
- SkillLoop does not mutate Hermes memory, skills, config, cron jobs, tools, or model state.
- GitHub, wheel, and editable installs expose the same CLI.
Initializes local SkillLoop state.
skillloop --path . initCreates:
.skillloop/skillloop.db
Runs read-only package, Python, project, SQLite, policy, output-boundary, and
connector checks. Use --json for automation; failed required checks return a
non-zero exit code.
Configures SkillLoop as a local sidecar for Hermes by writing .skillloop/policy.json with read-only hermes-db ingestion. With --start, it immediately runs one controller tick.
skillloop --path . setup --connect hermes --start
skillloop --path . setup --connect hermes --db-path ~/.hermes/state.db --max-sessions 20 --min-score 70 --auto-export --dataset-out data/sft.jsonl --startImportant behavior:
- validates that
--db-pathexists - validates positive
--max-sessions - validates
--min-scorein0..100 - writes a conservative
.skillloop/policy.json - reads Hermes
state.dbonly; it does not mutate Hermes --auto-exportenables controller-managed SFT export by writingdataset.auto_update: true- controller-managed export includes only traces whose latest evaluation passes the configured evaluation condition and dataset score gate
Shows configured policy path, trace/evaluation/proposal counts, dataset stats, and the latest controller run.
skillloop --path . status
skillloop --path . status --jsonIf a dataset manifest exists but cannot be decoded, status reports a manifest error instead of throwing a raw traceback.
Ingests a generic JSONL trace.
skillloop --path . ingest generic examples/traces/simple_trace.jsonlEach line should contain a message-like object. Unknown fields are tolerated. Common secret patterns are redacted.
Ingests Hermes-like JSON exports.
skillloop --path . ingest hermes path/to/export.jsonThe adapter normalizes known message and tool-call fields into the internal schema.
Ingests directly from a Hermes state.db file using read-only SQLite access.
skillloop --path . ingest hermes-db --latest
skillloop --path . ingest hermes-db --session-id <session-id>
skillloop --path . ingest hermes-db --db-path ~/.hermes/state.db --latestBy default, --db-path is ~/.hermes/state.db.
Lists stored traces.
skillloop --path . traces listShows a specific trace or the latest trace.
skillloop --path . traces show latest
skillloop --path . traces show <trace-id-or-prefix>Evaluates a trace and stores an Evaluation record.
skillloop --path . eval latest
skillloop --path . eval <trace-id-or-prefix>
skillloop --path . eval latest --evaluator rubricCurrent evaluators are deterministic and local. Use benchmark to compare evaluator behavior across stored traces.
Creates memory and skill proposals from a trace.
skillloop --path . distill latest
skillloop --path . distill <trace-id-or-prefix>Distillation writes proposals to the review queue. It does not mutate Hermes or global agent state.
Lists proposals.
skillloop --path . review list
skillloop --path . review list --verbose
skillloop --path . review list --all
skillloop --path . review list --status approvedApproves a proposal by full ID or unique prefix.
skillloop --path . review approve <proposal-id-or-prefix>Rejects a proposal by full ID or unique prefix.
skillloop --path . review reject <proposal-id-or-prefix>Writes approved proposals into project-local approved export files.
skillloop --path . apply
skillloop --path . apply --out-dir .skillloop/approvedWrites to:
.skillloop/approved/memory/*.md
.skillloop/approved/skill/*.md
This is an export boundary, not live runtime mutation.
Exports supervised fine-tuning records.
skillloop --path . export sft --out data/sft.jsonl
skillloop --path . export sft --out data/sft.jsonl --min-score 70
skillloop --path . export sft --out data/sft.jsonl --splits train=0.8,validation=0.1,test=0.1
skillloop --path . export sft --out data/sft.jsonl --manifest-out data/manifest.jsonUse --min-score N to export only traces with at least one stored evaluation score greater than or equal to N. Traces without evaluations are skipped when this gate is active.
Exports always write a dataset manifest. By default the manifest path is <out>.manifest.json; pass --manifest-out to choose a path.
The manifest includes output file paths, export metadata, split-level record/token stats, trace/evaluation/proposal provenance summaries, and evaluator counts.
Use --splits to write deterministic split files. For example --splits train=0.8,validation=0.1,test=0.1 writes data/sft.train.jsonl, data/sft.validation.jsonl, and data/sft.test.jsonl.
Record shape:
{"messages": [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}], "metadata": {"trace_id": "...", "evaluation_id": "..."}}Exports preference records when chosen/rejected data is available.
skillloop --path . export dpo --out data/dpo.jsonl
skillloop --path . export dpo --out data/dpo.jsonl --min-score 70
skillloop --path . export dpo --out data/dpo.jsonl --splits train=0.9,test=0.1DPO export is conservative in v1 and only exports explicit preference pairs already present in trace metadata.
Record shape:
{"prompt": "...", "chosen": "...", "rejected": "...", "metadata": {"trace_id": "...", "evaluation_id": "..."}}Replays stored traces through evaluator versions and writes a report comparing scores, deltas, tags, and evidence counts.
skillloop --path . benchmark
skillloop --path . benchmark --baseline rubric_legacy --candidates rubric --out data/benchmark.json
skillloop --path . benchmark --trace-id latest --out data/latest-benchmark.jsonUse this before trusting an evaluator change for dataset export or training data readiness.
Report shape:
{"baseline":"rubric_legacy","candidates":["rubric"],"summary":{"traces":1,"average_delta":{"rubric":0}},"cases":[{"trace_id":"...","scores":{"rubric_legacy":70,"rubric":75}}]}Generates training configuration artifacts for Unsloth, TRL, or Axolotl from a dataset manifest. This command does not run training.
skillloop --path . training-config trl --dataset-manifest data/sft.jsonl.manifest.json --base-model NousResearch/Meta-Llama-3.1-8B --output-dir runs/trl-sft --config-dir configs/trl
skillloop --path . training-config unsloth --dataset-manifest data/sft.jsonl.manifest.json --base-model unsloth/llama-3-8b --output-dir runs/unsloth-sft --config-dir configs/unsloth
skillloop --path . training-config axolotl --dataset-manifest data/sft.jsonl.manifest.json --base-model NousResearch/Meta-Llama-3.1-8B --output-dir runs/axolotl-sft --config-dir configs/axolotlGenerated files:
configs/trl/trl_sft_config.json
configs/unsloth/unsloth_config.json
configs/unsloth/unsloth_sft_skeleton.py
configs/axolotl/axolotl_config.yml
Generated configs include explicit no-auto-run safety metadata.
Runs one local outer-loop evaluation/distillation pass.
skillloop --path . loop run
skillloop --path . loop run --min-score 80
skillloop --path . loop run --require-tag success_signal --forbid-tag tool_failure
skillloop --path . loop run --condition '{"score_gte":80,"forbidden_tags":["tool_failure"]}'Writes and executes a project-local loop schedule. This is a local scheduling primitive, not an installed OS background service.
skillloop --path . loop schedule --interval daily --min-score 70
skillloop --path . loop status
skillloop --path . loop tick
skillloop --path . loop tick --forceSupported intervals:
hourlydailyweekly
Runs the autonomous controller once using .skillloop/policy.json if present, and inspects prior run reports stored in SQLite.
skillloop --path . controller run
skillloop --path . controller history
skillloop --path . controller history --limit 5
skillloop --path . controller show <run-id-or-prefix>Controller run reports are also mirrored as JSON under .skillloop/controller_runs/ for easy inspection. If policy has dataset.auto_update: true (or legacy dataset.enabled: true), controller ticks update the configured dataset using only traces whose latest evaluation passes the evaluation condition and dataset score gate.
Installs, inspects, and removes the project-local background controller service metadata. On macOS, service install writes a launchd plist that runs skillloop --path <project-root> controller run on an interval. It prints the exact launchctl bootstrap / bootout commands instead of silently loading the service.
skillloop --path . service install --kind launchd --interval-seconds 3600
skillloop --path . service status
skillloop --path . service status --json
skillloop --path . service uninstallGenerated files:
~/Library/LaunchAgents/com.skillloop.controller.<hash>.plist
.skillloop/service.json
.skillloop/service.out.log
.skillloop/service.err.log
Use --launch-agents-dir to write the plist somewhere else for tests or dry-run inspection.
tmp=$(mktemp -d)
cp -R examples "$tmp/"
python -m skillloop.cli --path "$tmp" init
python -m skillloop.cli --path "$tmp" ingest generic "$tmp/examples/traces/simple_trace.jsonl"
python -m skillloop.cli --path "$tmp" traces list
python -m skillloop.cli --path "$tmp" eval latest
python -m skillloop.cli --path "$tmp" distill latest
python -m skillloop.cli --path "$tmp" review list --verbose
python -m skillloop.cli --path "$tmp" export sft --out "$tmp/sft.jsonl" --min-score 70
python -m skillloop.cli --path "$tmp" benchmark --out "$tmp/benchmark.json"
test -s "$tmp/sft.jsonl"
test -s "$tmp/sft.jsonl.manifest.json"
test -s "$tmp/benchmark.json"
echo "$tmp"The final echo prints the temporary directory so the artifacts can be inspected manually.