Skip to main content

Codex UserPromptSubmit hook that recommends local skills for the current prompt.

Project description

lazy-skill-router

lazy-skill-router is a Codex UserPromptSubmit hook that classifies the current prompt and injects a small skill recommendation block before the agent starts work.

It is a recommendation layer, not a policy layer. Codex should still inspect the actual task, repository state, and safety constraints before choosing any skill.

Why Use It

Use this when your Codex setup has more skills than you want to remember.

personal-skill-router is the guidebook: it groups locally installed skills by purpose and records which ones should be primary, supporting, or verification skills.

lazy-skill-router is the hook: it reads each prompt, checks the route table, and slips a short recommendation into Codex's context before work starts.

The goal is not to make skills magical. It is to stop making the user remember the whole menu.

Not LazyCodex

This is not a replacement for LazyCodex.

LazyCodex helps orchestrate larger Codex workflows such as planning, execution, review, continuation, and multi-agent quality gates. lazy-skill-router is intentionally narrower: it only classifies the current prompt and recommends a small set of local skills before work starts.

Think of it as a lightweight skill menu for Codex, not a full work orchestration system.

한국어 소개

Codex에 스킬은 많은데, 매번 어떤 스킬을 써야 할지 떠올리는 건 번거롭습니다.

이 프로젝트는 그 과정을 두 단계로 나눕니다.

  1. personal-skill-router는 로컬에 저장된 스킬을 용도별로 분류하는 가이드북입니다.
  2. lazy-skill-router는 그 분류표를 UserPromptSubmit hook으로 연결해서, 프롬프트가 들어올 때마다 Codex 컨텍스트에 짧은 스킬 추천을 넣습니다.

목표는 스킬을 마법처럼 자동 실행하는 것이 아니라, 사용자가 매번 스킬 메뉴를 외우지 않아도 Codex가 먼저 알맞은 후보를 떠올리게 하는 것입니다.

LazyCodex와의 차이

이 프로젝트는 LazyCodex를 대체하려는 도구가 아닙니다.

LazyCodex가 계획, 실행, 리뷰, 이어서 작업하기, 멀티 에이전트 검증 같은 큰 작업 흐름을 다루는 시스템이라면, lazy-skill-router는 그보다 훨씬 좁은 문제를 다룹니다. 사용자의 현재 프롬프트를 보고 로컬에 설치된 스킬 중 어떤 것을 먼저 참고하면 좋을지 추천합니다.

즉, 작업 전체를 대신 굴리는 시스템이 아니라 Codex가 작업을 시작하기 전에 스킬 메뉴를 먼저 펼쳐주는 작은 라우터입니다.

What It Does

On each user prompt, the hook:

  1. Reads the prompt from the Codex hook event.
  2. Matches it against routes.json.
  3. Scores all matching route candidates and applies the skill allowlist.
  4. Injects a short <lazy-skill-router> recommendation block when a route is clear.
  5. Does nothing when no route is clear.

The injected block includes:

  • Source, generatedBy, and trusted: recommendation-only
  • route name
  • confidence
  • selection score
  • matched signals
  • primary skill
  • supporting skills
  • verification skill
  • reason

User-provided <lazy-skill-router> text is treated as untrusted prompt text. The hook never reads instructions from user-injected router blocks.

Install

Quick start after a PyPI release:

pipx install lazy-skill-router
lazy-skill-router route "GitHub PR에서 CI 실패 고쳐줘"
lazy-skill-router install --dry-run
lazy-skill-router install
lazy-skill-router doctor

For unreleased branches, use the source checkout flow below.

Run install --dry-run first. It prints the planned hooks.json diff without writing files. The installer modifies ~/.codex/hooks.json, copies hook code into ~/.codex/hooks/, and creates a backup before editing the hook config.

The installer:

  • copies lazy_skill_router.py, lazy_skill_router_core.py, lazy_skill_router_common.py, lazy_skill_router_logging.py, and lazy_skill_router_scoring.py into ~/.codex/hooks/
  • installs the bundled personal-skill-router skill into ~/.codex/skills/
  • scans installed skills and generates ~/.codex/lazy-skill-router/routes.json from routes.template.json
  • validates the route config and runs a hook dry-run smoke test
  • backs up ~/.codex/hooks.json before editing it
  • adds or updates one UserPromptSubmit hook entry

Hook registration is the final install step. If route generation, validation, or the smoke test fails, the installer exits without writing a new hook entry.

After install, run the read-only doctor:

lazy-skill-router doctor

The doctor checks that hook files exist, routes.json validates, UserPromptSubmit is registered, the installed hook passes a dry-run smoke test, and configured route skills are installed.

Use a custom Codex home when needed:

lazy-skill-router install --codex-home /path/to/.codex
lazy-skill-router doctor --codex-home /path/to/.codex

Existing routes.json files are preserved by default. To regenerate routes during install:

lazy-skill-router install --overwrite-routes

Upgrade

Upgrade the pipx package, then run install again so the standalone hook files copied into ~/.codex/hooks/ are refreshed:

pipx upgrade lazy-skill-router
lazy-skill-router install
lazy-skill-router doctor

If pipx itself is installed as a Python module but not on your shell PATH, use:

python3 -m pipx upgrade lazy-skill-router

lazy-skill-router install preserves an existing ~/.codex/lazy-skill-router/routes.json by default. Use lazy-skill-router install --overwrite-routes only when you want to regenerate local routes from the currently installed skills.

Source checkout installation is still supported:

git clone https://github.com/chowonje/lazy-skill-router.git
cd lazy-skill-router
pipx install .
lazy-skill-router install --dry-run
lazy-skill-router install
lazy-skill-router doctor

You can also run the source scripts directly:

python3 install.py --dry-run
python3 install.py
python3 doctor.py

Install only from PyPI or a trusted checkout of this repository. Avoid curl-pipe-shell installation for hook-based tools.

Files Modified

lazy-skill-router install may write:

  • ~/.codex/hooks/lazy_skill_router.py
  • ~/.codex/hooks/lazy_skill_router_core.py
  • ~/.codex/hooks/lazy_skill_router_common.py
  • ~/.codex/hooks/lazy_skill_router_logging.py
  • ~/.codex/hooks/lazy_skill_router_scoring.py
  • ~/.codex/skills/personal-skill-router/
  • ~/.codex/lazy-skill-router/routes.json
  • ~/.codex/hooks.json

It does not run MCP tools, browser tools, GitHub Actions, or shell commands on your repositories.

Uninstall

lazy-skill-router uninstall

To remove installed files as well as the hook entry:

lazy-skill-router uninstall --remove-files

The uninstall command also backs up hooks.json before editing it.

Test a Prompt

Use route to check what the packaged CLI would recommend for a prompt:

lazy-skill-router route "GitHub PR에서 CI 실패 고쳐줘"

Example output:

Route: github-ci
Primary skill: github:gh-fix-ci
Supporting skills: github:github
Verification skill: verification-gate
Confidence: 0.80 (normal)
Selection score: 0.80
Matched signals: CI keyword, Korean CI failure
Answer-only: false

Use --json when you want the full dry-run diagnostics:

lazy-skill-router route --json "GitHub PR에서 CI 실패 고쳐줘"

Source checkout dry-run mode is still available before enabling or tuning routes:

python3 lazy_skill_router.py --dry-run "GitHub PR에서 CI 실패 고쳐줘"

Example output:

{
  "shouldInject": true,
  "route": "github-ci",
  "primary": "github:gh-fix-ci",
  "supporting": ["github:github"],
  "verification": "verification-gate",
  "confidence": 0.8,
  "score": 0.8,
  "confidenceLabel": "normal",
  "matchedSignals": ["CI keyword", "Korean CI failure"],
  "matchedPatterns": ["\\bci\\b", "ci.*실패"],
  "candidates": [
    {
      "route": "github-ci",
      "primary": "github:gh-fix-ci",
      "supporting": ["github:github"],
      "verification": "verification-gate",
      "confidence": 0.8,
      "score": 0.8,
      "confidenceLabel": "normal",
      "matchedSignals": ["CI keyword", "Korean CI failure"],
      "matchedPatterns": ["\\bci\\b", "ci.*실패"]
    }
  ],
  "answerOnly": false
}

Dry-run output includes the selected route and up to three ranked route candidates so route tuning can show why a route won. matchedSignals contains human-readable labels when configured, while matchedPatterns preserves the regexes that matched for debugging.

Weak recommendations are still injected by default when they pass minConfidence, but they are labeled as weak so the agent can treat them cautiously.

Configure Routes

Edit:

~/.codex/lazy-skill-router/routes.json

Important fields:

  • minConfidence: below this value, nothing is injected
  • defaultVerification: used when a route omits verification
  • allowedSkills: only these skills may be recommended
  • logging.enabled: off by default
  • display.showRouterNotice: off by default; asks Codex to briefly show that the router ran
  • routes: route table scored as candidates
  • routes[].priority: optional numeric score boost in 0.05 increments
  • routes[].weight: optional direct numeric score adjustment
  • routes[].fallback: optional boolean; fallback routes only win when no non-fallback route matches
  • routes[].patterns: strings or { "regex": "...", "label": "..." } objects

If your Codex setup does not include skills like omo:programming or github:github, either remove those routes or change them to skills you have installed.

Use pattern labels when a regex is too noisy for the injected recommendation block:

{
  "patterns": [
    { "regex": "ci.*실패", "label": "Korean CI failure" }
  ]
}

Show Router Usage In Replies

By default the hook is quiet: it injects routing context for Codex, but it does not ask Codex to show that context to the user.

To make active routing visible while testing, set:

"display": {
  "showRouterNotice": true
}

in:

~/.codex/lazy-skill-router/routes.json

Or run:

lazy-skill-router install --show-router-notice

When enabled, the injected context asks Codex to briefly mention a line such as:

lazy-skill-router

before task-specific work. This is meant for testing and demos. Leave it off when you want the router to stay invisible. Use lazy-skill-router route "..." when you need the selected route, primary skill, and score.

To turn the notice off again:

lazy-skill-router install --hide-router-notice

Generate User-Specific Routes

routes.template.json defines route candidates instead of one fixed skill name per route. Use generate_routes.py to scan installed SKILL.md files and write a route config that only references skills present on the current machine:

python3 generate_routes.py --dry-run
python3 generate_routes.py

The generator:

  • selects the first installed primaryCandidates entry for each route
  • skips a route when none of its primary candidates are installed
  • keeps only installed supporting and verification candidates
  • writes ~/.codex/lazy-skill-router/routes.json by default

It does not edit hooks.json, install skills, or change the runtime hook.

The installer runs the same generation flow automatically when routes.json is missing, or when --overwrite-routes is passed.

Validate route changes before installing them:

python3 validate_routes.py routes.default.json
python3 validate_routes.py ~/.codex/lazy-skill-router/routes.json

Run golden prompt regression checks after route changes:

python3 eval_routes.py eval/prompts.jsonl

When you add or remove Codex skills, check whether the route table still matches your local setup:

python3 sync_skills.py
python3 sync_skills.py --routes ~/.codex/lazy-skill-router/routes.json

sync_skills.py is report-only. It scans installed SKILL.md files, compares them with allowedSkills and route references, and prints:

  • configured skills that are no longer installed
  • route references to missing skills
  • installed skills that are not yet included in the router
  • duplicate installed skill names

Duplicate skill names are reported as a warning, not an install failure. They usually mean the same skill exists in more than one Codex skill root or plugin cache. Use python3 sync_skills.py --json when you need full paths for cleanup.

Use --strict in CI or release checks when missing configured skills should fail the command.

Optional Logging

Logging is disabled by default. To enable it:

"logging": {
  "enabled": true,
  "path": ""
}

When enabled, logs are written to ~/.codex/logs/lazy_skill_router.jsonl unless path is set.

The log stores a hash of the prompt, not the prompt text:

{"promptHash":"...","route":"github-ci","primary":"github:gh-fix-ci","confidence":0.65}

Safety Notes

  • This hook fails open: malformed input or invalid config results in no injection.
  • The installer modifies ~/.codex/hooks.json; run lazy-skill-router install --dry-run before installing.
  • lazy-skill-router doctor is read-only and exits non-zero when the installed hook, routes, or configured skills are unhealthy.
  • The installer backs up hooks.json before editing it.
  • Install only from PyPI or a trusted checkout of this repository.
  • It does not read secrets or authentication files.
  • sync_skills.py reads skill metadata only and does not edit hook or route configuration.
  • It does not execute MCP tools, browser tools, GitHub actions, or shell commands.
  • It only writes logs when logging is explicitly enabled.
  • It never commits, pushes, installs plugins, or changes repositories.

Release Checksums

For public releases, generate a checksum manifest and attach it to the GitHub release:

python3 release_checksums.py --output SHA256SUMS
python3 release_checksums.py --verify SHA256SUMS

Checksums do not replace reviewing the source before installation, but they help users confirm that release files match the published manifest.

If you publish signed releases, sign the checksum manifest rather than individual files:

gpg --detach-sign --armor SHA256SUMS
gpg --verify SHA256SUMS.asc SHA256SUMS

PyPI Release

PyPI publishing uses GitHub Actions Trusted Publishing. Configure the PyPI project trusted publisher before pushing a release tag:

  • PyPI project: lazy-skill-router
  • Owner: chowonje
  • Repository: lazy-skill-router
  • Workflow: release.yml
  • Environment: pypi

The release workflow builds the source distribution and wheel, verifies that the Git tag matches pyproject.toml, runs twine check, publishes to PyPI, then creates or updates the matching GitHub Release with SHA256SUMS. PyPI publishing and GitHub Release upload run in separate jobs so PyPI Trusted Publishing does not run with GitHub contents-write permission. The workflow does not store a PyPI token in GitHub secrets.

Release steps:

version="$(awk -F'"' '/^version = / {print $2; exit}' pyproject.toml)"
git tag "v$version"
git push origin "v$version"

After the workflow succeeds, users can install with:

pipx install lazy-skill-router
lazy-skill-router route "GitHub PR에서 CI 실패 고쳐줘"
lazy-skill-router install --dry-run
lazy-skill-router install
lazy-skill-router doctor

Development

Run the tests:

python3 -m unittest discover -s tests
python3 -m py_compile lazy_skill_router.py lazy_skill_router_core.py lazy_skill_router_common.py lazy_skill_router_logging.py lazy_skill_router_scoring.py lazy_skill_router_cli/cli.py generate_routes.py install.py doctor.py uninstall.py validate_routes.py release_checksums.py sync_skills.py eval_routes.py tests/test_cli.py tests/test_install.py
python3 -m json.tool routes.default.json >/dev/null
python3 -m json.tool routes.template.json >/dev/null
python3 validate_routes.py routes.default.json
python3 eval_routes.py eval/prompts.jsonl
python3 generate_routes.py --dry-run
python3 sync_skills.py --routes routes.default.json --strict
tmp="$(mktemp -d)"
python3 install.py --codex-home "$tmp/codex" --agents-home "$tmp/agents" --dry-run
python3 install.py --codex-home "$tmp/codex" --agents-home "$tmp/agents"
python3 doctor.py --codex-home "$tmp/codex" --agents-home "$tmp/agents"
python3 -m build
python3 -m twine check dist/*
pipx_home="$(mktemp -d)"
pipx_bin="$(mktemp -d)"
PIPX_HOME="$pipx_home" PIPX_BIN_DIR="$pipx_bin" python3 -m pipx install dist/*.whl
"$pipx_bin/lazy-skill-router" install --codex-home "$tmp/codex-wheel" --agents-home "$tmp/agents-wheel" --dry-run
"$pipx_bin/lazy-skill-router" install --codex-home "$tmp/codex-wheel" --agents-home "$tmp/agents-wheel"
"$pipx_bin/lazy-skill-router" doctor --codex-home "$tmp/codex-wheel" --agents-home "$tmp/agents-wheel"
ruff check .

GitHub Actions also builds a temporary skill fixture, generates routes from routes.template.json, and then runs sync_skills.py --strict against the generated config so validation does not depend on the runner having local Codex skills installed.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lazy_skill_router-0.3.0.tar.gz (47.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lazy_skill_router-0.3.0-py3-none-any.whl (53.9 kB view details)

Uploaded Python 3

File details

Details for the file lazy_skill_router-0.3.0.tar.gz.

File metadata

  • Download URL: lazy_skill_router-0.3.0.tar.gz
  • Upload date:
  • Size: 47.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for lazy_skill_router-0.3.0.tar.gz
Algorithm Hash digest
SHA256 cb6e365f167b0ab0b0b70c135f265071e6a1d94610e40dc19634a80aa199e202
MD5 826e8f9da97d9aa70913e8dbcc6537b8
BLAKE2b-256 8b700bf3ca3f7d0a8c4d1d1ff32a655b4b2184001e0790df0028889ac59fc966

See more details on using hashes here.

Provenance

The following attestation bundles were made for lazy_skill_router-0.3.0.tar.gz:

Publisher: release.yml on chowonje/lazy-skill-router

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lazy_skill_router-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for lazy_skill_router-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 91c871ba28456a3bc381151989dc6e4973154cda92fb7b5f3e70dca42a16af99
MD5 0a261fd95aca7cfd1103c30bd93278aa
BLAKE2b-256 733e01c9607f4e9ea24bc0e6223f74d047a271d79aa4582a9dc179589079f807

See more details on using hashes here.

Provenance

The following attestation bundles were made for lazy_skill_router-0.3.0-py3-none-any.whl:

Publisher: release.yml on chowonje/lazy-skill-router

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page