leasehold 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- leasehold-0.2.0/.claude/settings.json +16 -0
- leasehold-0.2.0/.claude-plugin/marketplace.json +16 -0
- leasehold-0.2.0/.github/ISSUE_TEMPLATE/bug.yml +30 -0
- leasehold-0.2.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- leasehold-0.2.0/.github/ISSUE_TEMPLATE/feature.yml +15 -0
- leasehold-0.2.0/.github/workflows/ci.yml +60 -0
- leasehold-0.2.0/.github/workflows/pages.yml +37 -0
- leasehold-0.2.0/.github/workflows/release.yml +42 -0
- leasehold-0.2.0/.gitignore +8 -0
- leasehold-0.2.0/.importlinter +88 -0
- leasehold-0.2.0/.pre-commit-config.yaml +15 -0
- leasehold-0.2.0/AGENTS.md +40 -0
- leasehold-0.2.0/CHANGELOG.md +81 -0
- leasehold-0.2.0/CLAUDE.md +1 -0
- leasehold-0.2.0/CONTRIBUTING.md +48 -0
- leasehold-0.2.0/LICENSE +202 -0
- leasehold-0.2.0/PKG-INFO +163 -0
- leasehold-0.2.0/README.md +130 -0
- leasehold-0.2.0/SECURITY.md +52 -0
- leasehold-0.2.0/bench/job.py +120 -0
- leasehold-0.2.0/bench/probe.py +99 -0
- leasehold-0.2.0/bench/results/8cpu-16g/k4-full-d240.csv +10 -0
- leasehold-0.2.0/bench/results/8cpu-16g/k4-full-d240.md +21 -0
- leasehold-0.2.0/bench/results/8cpu-16g/k8-full-d240.csv +9 -0
- leasehold-0.2.0/bench/results/8cpu-16g/k8-full-d240.md +20 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-advisory/jobs.csv +26 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-advisory/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-advisory/run.json +14 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-advisory/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-advisory-greedy/jobs.csv +26 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-advisory-greedy/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-advisory-greedy/run.json +14 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-advisory-greedy/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-enforced/jobs.csv +26 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-enforced/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-enforced/run.json +21 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-enforced/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-enforced-greedy/jobs.csv +26 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-enforced-greedy/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-enforced-greedy/run.json +21 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-enforced-greedy/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-hardcpu-greedy/jobs.csv +26 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-hardcpu-greedy/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-hardcpu-greedy/run.json +21 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/lh-hardcpu-greedy/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/nice/jobs.csv +26 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/nice/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/nice/run.json +14 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/nice/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/slice/jobs.csv +26 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/slice/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/slice/run.json +21 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/slice/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/spec.json +163 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/static-jobs/jobs.csv +26 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/static-jobs/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/static-jobs/run.json +14 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/static-jobs/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/unmanaged/jobs.csv +26 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/unmanaged/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/unmanaged/run.json +14 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k4-d240/unmanaged/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-advisory/jobs.csv +53 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-advisory/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-advisory/run.json +14 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-advisory/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-enforced/jobs.csv +53 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-enforced/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-enforced/run.json +21 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-enforced/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-enforced-greedy/jobs.csv +53 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-enforced-greedy/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-enforced-greedy/run.json +21 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-enforced-greedy/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-hardcpu-greedy/jobs.csv +53 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-hardcpu-greedy/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-hardcpu-greedy/run.json +21 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/lh-hardcpu-greedy/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/nice/jobs.csv +53 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/nice/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/nice/run.json +14 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/nice/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/slice/jobs.csv +53 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/slice/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/slice/run.json +21 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/slice/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/spec.json +278 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/static-jobs/jobs.csv +53 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/static-jobs/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/static-jobs/run.json +14 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/static-jobs/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/unmanaged/jobs.csv +53 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/unmanaged/outer.json +13 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/unmanaged/run.json +14 -0
- leasehold-0.2.0/bench/results/8cpu-16g/raw-k8-d240/unmanaged/samples.csv.gz +0 -0
- leasehold-0.2.0/bench/run.py +1019 -0
- leasehold-0.2.0/deploy/gates.slice +9 -0
- leasehold-0.2.0/deploy/github-runner/common.sh +25 -0
- leasehold-0.2.0/deploy/github-runner/job-completed.sh +19 -0
- leasehold-0.2.0/deploy/github-runner/job-started.sh +63 -0
- leasehold-0.2.0/deploy/github-runner/runner-slice.conf +6 -0
- leasehold-0.2.0/deploy/leasehold-report.service +33 -0
- leasehold-0.2.0/deploy/leasehold-user.service +18 -0
- leasehold-0.2.0/deploy/leasehold.service +35 -0
- leasehold-0.2.0/deploy/lend.slice +13 -0
- leasehold-0.2.0/deploy/machine.toml +90 -0
- leasehold-0.2.0/deploy/runner.slice +9 -0
- leasehold-0.2.0/deploy/work.slice +9 -0
- leasehold-0.2.0/docs/bench.md +246 -0
- leasehold-0.2.0/docs/index.html +688 -0
- leasehold-0.2.0/docs/media/top.gif +0 -0
- leasehold-0.2.0/docs/media/top.png +0 -0
- leasehold-0.2.0/docs/media/ui-dark.png +0 -0
- leasehold-0.2.0/docs/media/ui-light.png +0 -0
- leasehold-0.2.0/docs/reference.md +788 -0
- leasehold-0.2.0/examples/agents/README.md +105 -0
- leasehold-0.2.0/examples/agents/machine.toml +4 -0
- leasehold-0.2.0/examples/agents/run.sh +30 -0
- leasehold-0.2.0/examples/github-runner/README.md +110 -0
- leasehold-0.2.0/examples/github-runner/ephemeral.sh +18 -0
- leasehold-0.2.0/examples/github-runner/try-hooks.sh +17 -0
- leasehold-0.2.0/examples/lend/README.md +70 -0
- leasehold-0.2.0/examples/lend/batch-job.sh +8 -0
- leasehold-0.2.0/examples/lend/machine.toml +12 -0
- leasehold-0.2.0/examples/lend/run.sh +26 -0
- leasehold-0.2.0/examples/library/README.md +42 -0
- leasehold-0.2.0/examples/library/decide.py +51 -0
- leasehold-0.2.0/examples/observed/README.md +79 -0
- leasehold-0.2.0/examples/observed/machine.toml +15 -0
- leasehold-0.2.0/examples/observed/prep_dataset.py +9 -0
- leasehold-0.2.0/examples/observed/run.sh +24 -0
- leasehold-0.2.0/examples/test-suite/Makefile +9 -0
- leasehold-0.2.0/examples/test-suite/README.md +65 -0
- leasehold-0.2.0/examples/test-suite/job.sh +8 -0
- leasehold-0.2.0/examples/test-suite/run.sh +31 -0
- leasehold-0.2.0/examples/test-suite/test_sample.py +14 -0
- leasehold-0.2.0/integrations/AGENTS.md.snippet +15 -0
- leasehold-0.2.0/integrations/claude-code/.claude-plugin/plugin.json +16 -0
- leasehold-0.2.0/integrations/claude-code/hooks/hooks.json +40 -0
- leasehold-0.2.0/integrations/claude-code/hooks/prefilter.sh +62 -0
- leasehold-0.2.0/integrations/claude-code/skills/leasehold/SKILL.md +42 -0
- leasehold-0.2.0/integrations/mcp/README.md +128 -0
- leasehold-0.2.0/integrations/mcp/claude-code/.mcp.json +9 -0
- leasehold-0.2.0/integrations/mcp/codex/config.toml +4 -0
- leasehold-0.2.0/integrations/mcp/cursor/mcp.json +9 -0
- leasehold-0.2.0/integrations/mcp/gemini/settings.json +8 -0
- leasehold-0.2.0/pyproject.toml +74 -0
- leasehold-0.2.0/scripts/gen_cli_reference.py +69 -0
- leasehold-0.2.0/scripts/screenshots.py +253 -0
- leasehold-0.2.0/src/leasehold/__init__.py +18 -0
- leasehold-0.2.0/src/leasehold/__main__.py +8 -0
- leasehold-0.2.0/src/leasehold/access.py +43 -0
- leasehold-0.2.0/src/leasehold/admission.py +373 -0
- leasehold-0.2.0/src/leasehold/agent.py +570 -0
- leasehold-0.2.0/src/leasehold/api.py +129 -0
- leasehold-0.2.0/src/leasehold/busy.py +62 -0
- leasehold-0.2.0/src/leasehold/claude_code.py +218 -0
- leasehold-0.2.0/src/leasehold/cli/__init__.py +151 -0
- leasehold-0.2.0/src/leasehold/cli/admin.py +55 -0
- leasehold-0.2.0/src/leasehold/cli/agents.py +45 -0
- leasehold-0.2.0/src/leasehold/cli/daemon.py +169 -0
- leasehold-0.2.0/src/leasehold/cli/demo.py +64 -0
- leasehold-0.2.0/src/leasehold/cli/hooks.py +190 -0
- leasehold-0.2.0/src/leasehold/cli/leases.py +170 -0
- leasehold-0.2.0/src/leasehold/cli/push.py +42 -0
- leasehold-0.2.0/src/leasehold/cli/run.py +57 -0
- leasehold-0.2.0/src/leasehold/cli/setup.py +30 -0
- leasehold-0.2.0/src/leasehold/cli/views.py +214 -0
- leasehold-0.2.0/src/leasehold/cli/watch.py +49 -0
- leasehold-0.2.0/src/leasehold/client.py +86 -0
- leasehold-0.2.0/src/leasehold/commands.py +99 -0
- leasehold-0.2.0/src/leasehold/contain.py +279 -0
- leasehold-0.2.0/src/leasehold/demo.py +228 -0
- leasehold-0.2.0/src/leasehold/discovery.py +125 -0
- leasehold-0.2.0/src/leasehold/doctor.py +314 -0
- leasehold-0.2.0/src/leasehold/events.py +139 -0
- leasehold-0.2.0/src/leasehold/exits.py +16 -0
- leasehold-0.2.0/src/leasehold/facts.py +139 -0
- leasehold-0.2.0/src/leasehold/guard.py +88 -0
- leasehold-0.2.0/src/leasehold/handoff.py +190 -0
- leasehold-0.2.0/src/leasehold/hold.py +451 -0
- leasehold-0.2.0/src/leasehold/hooknotes.py +111 -0
- leasehold-0.2.0/src/leasehold/jobs.py +117 -0
- leasehold-0.2.0/src/leasehold/kinds.py +17 -0
- leasehold-0.2.0/src/leasehold/ledger.py +833 -0
- leasehold-0.2.0/src/leasehold/lend.py +101 -0
- leasehold-0.2.0/src/leasehold/load.py +131 -0
- leasehold-0.2.0/src/leasehold/machine.py +453 -0
- leasehold-0.2.0/src/leasehold/mcp_server.py +748 -0
- leasehold-0.2.0/src/leasehold/metrics.py +91 -0
- leasehold-0.2.0/src/leasehold/model.py +79 -0
- leasehold-0.2.0/src/leasehold/observe.py +174 -0
- leasehold-0.2.0/src/leasehold/paths.py +120 -0
- leasehold-0.2.0/src/leasehold/process.py +64 -0
- leasehold-0.2.0/src/leasehold/proctable.py +84 -0
- leasehold-0.2.0/src/leasehold/programs.py +55 -0
- leasehold-0.2.0/src/leasehold/py.typed +0 -0
- leasehold-0.2.0/src/leasehold/quota.py +44 -0
- leasehold-0.2.0/src/leasehold/report.py +190 -0
- leasehold-0.2.0/src/leasehold/server.py +190 -0
- leasehold-0.2.0/src/leasehold/session.py +131 -0
- leasehold-0.2.0/src/leasehold/setup.py +196 -0
- leasehold-0.2.0/src/leasehold/shell.py +312 -0
- leasehold-0.2.0/src/leasehold/shims.py +197 -0
- leasehold-0.2.0/src/leasehold/static/ui.html +1135 -0
- leasehold-0.2.0/src/leasehold/status.py +152 -0
- leasehold-0.2.0/src/leasehold/template.py +98 -0
- leasehold-0.2.0/src/leasehold/templates/machine.toml +88 -0
- leasehold-0.2.0/src/leasehold/templates/user.toml +60 -0
- leasehold-0.2.0/src/leasehold/terminal.py +87 -0
- leasehold-0.2.0/src/leasehold/top.py +1374 -0
- leasehold-0.2.0/src/leasehold/units.py +33 -0
- leasehold-0.2.0/src/leasehold/webui.py +159 -0
- leasehold-0.2.0/tests/conftest.py +36 -0
- leasehold-0.2.0/tests/fixtures/top/events-busy.json +100 -0
- leasehold-0.2.0/tests/fixtures/top/status-busy.json +291 -0
- leasehold-0.2.0/tests/fixtures/top/status-idle.json +155 -0
- leasehold-0.2.0/tests/golden/busy-100x40-ascii.styles +40 -0
- leasehold-0.2.0/tests/golden/busy-100x40-ascii.txt +40 -0
- leasehold-0.2.0/tests/golden/busy-100x40.styles +40 -0
- leasehold-0.2.0/tests/golden/busy-100x40.txt +40 -0
- leasehold-0.2.0/tests/golden/busy-80x24.styles +24 -0
- leasehold-0.2.0/tests/golden/busy-80x24.txt +24 -0
- leasehold-0.2.0/tests/golden/idle-100x40.styles +40 -0
- leasehold-0.2.0/tests/golden/idle-100x40.txt +40 -0
- leasehold-0.2.0/tests/support.py +129 -0
- leasehold-0.2.0/tests/test_access.py +49 -0
- leasehold-0.2.0/tests/test_admission.py +501 -0
- leasehold-0.2.0/tests/test_agent.py +216 -0
- leasehold-0.2.0/tests/test_api.py +140 -0
- leasehold-0.2.0/tests/test_bench.py +48 -0
- leasehold-0.2.0/tests/test_busy.py +60 -0
- leasehold-0.2.0/tests/test_claude_code.py +240 -0
- leasehold-0.2.0/tests/test_commands.py +166 -0
- leasehold-0.2.0/tests/test_contain.py +200 -0
- leasehold-0.2.0/tests/test_daemon.py +264 -0
- leasehold-0.2.0/tests/test_demo.py +31 -0
- leasehold-0.2.0/tests/test_discovery.py +90 -0
- leasehold-0.2.0/tests/test_docs.py +46 -0
- leasehold-0.2.0/tests/test_doctor.py +145 -0
- leasehold-0.2.0/tests/test_events.py +115 -0
- leasehold-0.2.0/tests/test_examples.py +275 -0
- leasehold-0.2.0/tests/test_facts_load.py +141 -0
- leasehold-0.2.0/tests/test_guard.py +106 -0
- leasehold-0.2.0/tests/test_handoff.py +172 -0
- leasehold-0.2.0/tests/test_hold.py +249 -0
- leasehold-0.2.0/tests/test_jobs.py +155 -0
- leasehold-0.2.0/tests/test_ledger.py +555 -0
- leasehold-0.2.0/tests/test_lend.py +147 -0
- leasehold-0.2.0/tests/test_machine.py +376 -0
- leasehold-0.2.0/tests/test_mcp.py +490 -0
- leasehold-0.2.0/tests/test_model.py +96 -0
- leasehold-0.2.0/tests/test_neutral.py +112 -0
- leasehold-0.2.0/tests/test_observe.py +336 -0
- leasehold-0.2.0/tests/test_paths.py +89 -0
- leasehold-0.2.0/tests/test_prefilter.py +158 -0
- leasehold-0.2.0/tests/test_process.py +56 -0
- leasehold-0.2.0/tests/test_quota.py +77 -0
- leasehold-0.2.0/tests/test_report.py +172 -0
- leasehold-0.2.0/tests/test_runner_hooks.py +147 -0
- leasehold-0.2.0/tests/test_session.py +137 -0
- leasehold-0.2.0/tests/test_setup.py +94 -0
- leasehold-0.2.0/tests/test_shims.py +203 -0
- leasehold-0.2.0/tests/test_standalone.py +62 -0
- leasehold-0.2.0/tests/test_status.py +189 -0
- leasehold-0.2.0/tests/test_top.py +378 -0
- leasehold-0.2.0/tests/test_user_mode.py +244 -0
- leasehold-0.2.0/tests/test_webui.py +164 -0
- leasehold-0.2.0/uv.lock +942 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PostToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Write|Edit",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "f=$(jq -r '.tool_input.file_path // empty'); case \"$f\" in *.py) ;; *) exit 0 ;; esac; cd \"$CLAUDE_PROJECT_DIR\" && uv run --no-sync ruff check --fix \"$f\" 1>&2 && uv run --no-sync ruff format \"$f\" 1>&2 || exit 2",
|
|
10
|
+
"statusMessage": "ruff"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "leasehold",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Matthew Strudwick"
|
|
5
|
+
},
|
|
6
|
+
"metadata": {
|
|
7
|
+
"description": "leasehold's Claude Code plugin: heavy commands take turns on a shared machine."
|
|
8
|
+
},
|
|
9
|
+
"plugins": [
|
|
10
|
+
{
|
|
11
|
+
"name": "leasehold",
|
|
12
|
+
"source": "./integrations/claude-code",
|
|
13
|
+
"description": "Heavy Bash commands wait for a lease on a shared machine instead of piling on."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Bug
|
|
2
|
+
description: Something does not work as the docs say.
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: what
|
|
7
|
+
attributes:
|
|
8
|
+
label: What happened, and what you expected
|
|
9
|
+
validations:
|
|
10
|
+
required: true
|
|
11
|
+
- type: textarea
|
|
12
|
+
id: reproduce
|
|
13
|
+
attributes:
|
|
14
|
+
label: How to make it happen
|
|
15
|
+
description: Commands, the config (machine.toml) if it matters, and their output.
|
|
16
|
+
render: shell
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: textarea
|
|
20
|
+
id: doctor
|
|
21
|
+
attributes:
|
|
22
|
+
label: "`leasehold doctor` output"
|
|
23
|
+
render: text
|
|
24
|
+
- type: input
|
|
25
|
+
id: version
|
|
26
|
+
attributes:
|
|
27
|
+
label: "`leasehold --version`, distribution and kernel"
|
|
28
|
+
placeholder: leasehold 0.2.0, Debian 13, 6.12
|
|
29
|
+
validations:
|
|
30
|
+
required: true
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
name: Idea
|
|
2
|
+
description: Something leasehold should do, or do better.
|
|
3
|
+
labels: [enhancement]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: problem
|
|
7
|
+
attributes:
|
|
8
|
+
label: The problem
|
|
9
|
+
description: What you are trying to do on your machine, and what gets in the way today.
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: idea
|
|
14
|
+
attributes:
|
|
15
|
+
label: What would help
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
lint:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
timeout-minutes: 10
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
- uses: astral-sh/setup-uv@v10.3.0 # it publishes no moving major tag
|
|
19
|
+
- run: uv sync --python 3.12
|
|
20
|
+
- run: uv run --no-sync ruff check
|
|
21
|
+
- run: uv run --no-sync ruff format --check
|
|
22
|
+
- run: uv run --no-sync lint-imports
|
|
23
|
+
|
|
24
|
+
test:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
timeout-minutes: 20
|
|
27
|
+
strategy:
|
|
28
|
+
fail-fast: false
|
|
29
|
+
matrix:
|
|
30
|
+
python: ["3.12", "3.13", "3.14"]
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v7
|
|
33
|
+
- uses: astral-sh/setup-uv@v10.3.0 # it publishes no moving major tag
|
|
34
|
+
- run: uv sync --python ${{ matrix.python }}
|
|
35
|
+
- run: uv run --no-sync pytest -q
|
|
36
|
+
|
|
37
|
+
build:
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
timeout-minutes: 10
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v7
|
|
42
|
+
- uses: astral-sh/setup-uv@v10.3.0 # it publishes no moving major tag
|
|
43
|
+
- run: uv build
|
|
44
|
+
- name: The wheel carries its package data
|
|
45
|
+
run: |
|
|
46
|
+
python3 - <<'PY'
|
|
47
|
+
import glob, zipfile
|
|
48
|
+
names = zipfile.ZipFile(glob.glob("dist/*.whl")[0]).namelist()
|
|
49
|
+
for needed in ("leasehold/static/ui.html", "leasehold/templates/machine.toml"):
|
|
50
|
+
assert needed in names, f"{needed} is not in the wheel"
|
|
51
|
+
PY
|
|
52
|
+
- name: It installs and runs from the wheel
|
|
53
|
+
run: |
|
|
54
|
+
uv venv /tmp/try && uv pip install --python /tmp/try/bin/python dist/*.whl
|
|
55
|
+
/tmp/try/bin/leasehold --version
|
|
56
|
+
/tmp/try/bin/leasehold init --print > /dev/null
|
|
57
|
+
- uses: actions/upload-artifact@v7
|
|
58
|
+
with:
|
|
59
|
+
name: dist
|
|
60
|
+
path: dist/
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Publishes docs/ (index.html and its media) to GitHub Pages. It runs only once the repository is
|
|
2
|
+
# public: a Pages site of a private repository can still be public, and nothing is published
|
|
3
|
+
# before the repository itself is. Pages must be set to "GitHub Actions" in the settings.
|
|
4
|
+
name: pages
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [main]
|
|
9
|
+
paths: ["docs/**"]
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
concurrency:
|
|
16
|
+
group: pages
|
|
17
|
+
cancel-in-progress: true
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
deploy:
|
|
21
|
+
if: github.event.repository.visibility == 'public'
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
timeout-minutes: 10
|
|
24
|
+
permissions:
|
|
25
|
+
pages: write
|
|
26
|
+
id-token: write
|
|
27
|
+
environment:
|
|
28
|
+
name: github-pages
|
|
29
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v7
|
|
32
|
+
- uses: actions/configure-pages@v6
|
|
33
|
+
- uses: actions/upload-pages-artifact@v5
|
|
34
|
+
with:
|
|
35
|
+
path: docs
|
|
36
|
+
- id: deployment
|
|
37
|
+
uses: actions/deploy-pages@v5
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Publishes to PyPI when a GitHub release is published, with PyPI's trusted publishing (no
|
|
2
|
+
# token is stored anywhere). It needs, once: a PyPI project with this repository and workflow
|
|
3
|
+
# named as a trusted publisher, and an environment "pypi" here. Nothing else triggers it.
|
|
4
|
+
name: release
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
timeout-minutes: 10
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v7
|
|
19
|
+
- uses: astral-sh/setup-uv@v10.3.0 # it publishes no moving major tag
|
|
20
|
+
- run: uv build
|
|
21
|
+
- name: The version is the release's
|
|
22
|
+
run: |
|
|
23
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
24
|
+
ls dist/leasehold-"$tag"-py3-none-any.whl
|
|
25
|
+
- uses: actions/upload-artifact@v7
|
|
26
|
+
with:
|
|
27
|
+
name: dist
|
|
28
|
+
path: dist/
|
|
29
|
+
|
|
30
|
+
publish:
|
|
31
|
+
needs: build
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
timeout-minutes: 10
|
|
34
|
+
environment: pypi
|
|
35
|
+
permissions:
|
|
36
|
+
id-token: write
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/download-artifact@v8
|
|
39
|
+
with:
|
|
40
|
+
name: dist
|
|
41
|
+
path: dist/
|
|
42
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# leasehold's import contracts, checked by `uv run lint-imports` (pre-commit and CI).
|
|
2
|
+
#
|
|
3
|
+
# The library (model, admission, ledger, quota) is pure policy and storage: it never imports
|
|
4
|
+
# the daemon. Commands that talk to a daemon (hold, handoff, report, top, the views and the MCP
|
|
5
|
+
# server) reach it only through `leasehold.client`, never by importing the agent, the API or the
|
|
6
|
+
# ledger.
|
|
7
|
+
|
|
8
|
+
[importlinter]
|
|
9
|
+
root_package = leasehold
|
|
10
|
+
|
|
11
|
+
[importlinter:contract:layers]
|
|
12
|
+
name = leasehold layers, top first: a module may import its own layer and those below
|
|
13
|
+
type = layers
|
|
14
|
+
layers =
|
|
15
|
+
cli
|
|
16
|
+
demo | mcp_server
|
|
17
|
+
setup
|
|
18
|
+
server | doctor | template | top | claude_code | shims
|
|
19
|
+
api
|
|
20
|
+
agent
|
|
21
|
+
status | lend | guard | events
|
|
22
|
+
machine
|
|
23
|
+
quota | observe | commands
|
|
24
|
+
shell
|
|
25
|
+
ledger
|
|
26
|
+
admission
|
|
27
|
+
model | discovery
|
|
28
|
+
paths
|
|
29
|
+
containers =
|
|
30
|
+
leasehold
|
|
31
|
+
|
|
32
|
+
[importlinter:contract:clients]
|
|
33
|
+
name = commands that talk to a daemon use only the client
|
|
34
|
+
type = forbidden
|
|
35
|
+
source_modules =
|
|
36
|
+
leasehold.busy
|
|
37
|
+
leasehold.hold
|
|
38
|
+
leasehold.handoff
|
|
39
|
+
leasehold.report
|
|
40
|
+
leasehold.webui
|
|
41
|
+
leasehold.top
|
|
42
|
+
leasehold.claude_code
|
|
43
|
+
leasehold.shims
|
|
44
|
+
leasehold.mcp_server
|
|
45
|
+
forbidden_modules =
|
|
46
|
+
leasehold.agent
|
|
47
|
+
leasehold.api
|
|
48
|
+
leasehold.server
|
|
49
|
+
leasehold.ledger
|
|
50
|
+
leasehold.machine
|
|
51
|
+
leasehold.admission
|
|
52
|
+
|
|
53
|
+
[importlinter:contract:leaves]
|
|
54
|
+
name = leaf modules import nothing else of leasehold's
|
|
55
|
+
type = forbidden
|
|
56
|
+
source_modules =
|
|
57
|
+
leasehold.model
|
|
58
|
+
leasehold.contain
|
|
59
|
+
leasehold.units
|
|
60
|
+
leasehold.process
|
|
61
|
+
leasehold.kinds
|
|
62
|
+
leasehold.exits
|
|
63
|
+
leasehold.client
|
|
64
|
+
leasehold.facts
|
|
65
|
+
leasehold.access
|
|
66
|
+
leasehold.paths
|
|
67
|
+
leasehold.jobs
|
|
68
|
+
leasehold.proctable
|
|
69
|
+
leasehold.metrics
|
|
70
|
+
leasehold.shell
|
|
71
|
+
leasehold.terminal
|
|
72
|
+
leasehold.hooknotes
|
|
73
|
+
forbidden_modules =
|
|
74
|
+
leasehold.admission
|
|
75
|
+
leasehold.ledger
|
|
76
|
+
leasehold.quota
|
|
77
|
+
leasehold.machine
|
|
78
|
+
leasehold.agent
|
|
79
|
+
leasehold.api
|
|
80
|
+
leasehold.server
|
|
81
|
+
leasehold.cli
|
|
82
|
+
leasehold.hold
|
|
83
|
+
leasehold.handoff
|
|
84
|
+
leasehold.report
|
|
85
|
+
leasehold.top
|
|
86
|
+
leasehold.status
|
|
87
|
+
leasehold.lend
|
|
88
|
+
leasehold.load
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# `uv tool install pre-commit && pre-commit install` runs these on every commit.
|
|
2
|
+
repos:
|
|
3
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
4
|
+
rev: v0.17.0
|
|
5
|
+
hooks:
|
|
6
|
+
- id: ruff-check
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
- repo: local
|
|
9
|
+
hooks:
|
|
10
|
+
- id: import-contracts
|
|
11
|
+
name: import contracts (.importlinter)
|
|
12
|
+
entry: uv run --no-sync lint-imports
|
|
13
|
+
language: system
|
|
14
|
+
pass_filenames: false
|
|
15
|
+
files: ^src/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Working on leasehold
|
|
2
|
+
|
|
3
|
+
Notes for anyone changing this repository, people and coding agents alike. `CLAUDE.md` points
|
|
4
|
+
here.
|
|
5
|
+
|
|
6
|
+
## Checks
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
uv sync
|
|
10
|
+
uv run pytest -q # about a minute; daemon tests use real unix sockets under /tmp
|
|
11
|
+
uv run ruff check && uv run ruff format --check
|
|
12
|
+
uv run lint-imports # the layer contracts in .importlinter
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`pre-commit install` runs ruff and the import contracts on every commit.
|
|
16
|
+
|
|
17
|
+
## Shape of the code
|
|
18
|
+
|
|
19
|
+
- The library is pure: `model` (pools), `admission` (the decision), `ledger` (SQLite),
|
|
20
|
+
`quota`. It never imports the daemon. `.importlinter` enforces the layers; fix an upward
|
|
21
|
+
import rather than listing it as an exception.
|
|
22
|
+
- The daemon is `machine` (config) → `agent` (requests, ticks) → `api` (routes, no sockets) →
|
|
23
|
+
`server` (the unix socket). `status` builds the status object as a pure function.
|
|
24
|
+
- `paths` says where each mode (system, user) keeps things; `discovery` is how a client finds
|
|
25
|
+
a daemon; `doctor` checks a setup; `template` renders the config `init` writes.
|
|
26
|
+
- `cli` is a package: one module per group of commands, each with a `register(sub)` and
|
|
27
|
+
handlers `(args, ctx) -> exit code`. A new command goes in the module of its group, or a new
|
|
28
|
+
module listed in `cli/__init__.py`.
|
|
29
|
+
- Anything that talks to a daemon (`hold`, `handoff`, `report`, the views) does so through
|
|
30
|
+
`leasehold.client` only.
|
|
31
|
+
- One module, one job. A new behaviour that does not belong to an existing module's docstring
|
|
32
|
+
gets its own module.
|
|
33
|
+
|
|
34
|
+
## Conventions
|
|
35
|
+
|
|
36
|
+
- Comments and docstrings state what is guaranteed, once, in plain words. No task history.
|
|
37
|
+
- New behaviour comes with a test that would fail without it. Daemon behaviour is tested
|
|
38
|
+
against a real daemon on a temporary socket (`tests/support.py`).
|
|
39
|
+
- Every change to a public name, config key, CLI flag, exit code, status field or error code is
|
|
40
|
+
listed in `CHANGELOG.md` under the next release.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to leasehold. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
4
|
+
and versions follow [Semantic Versioning](https://semver.org/) as described under "Stability" in
|
|
5
|
+
the README: before 1.0, a minor release may break the public API, and every break is listed
|
|
6
|
+
here.
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.2.0] - first public release
|
|
11
|
+
|
|
12
|
+
leasehold gives the heavy commands on one Linux machine (coding agents' builds and test runs, CI
|
|
13
|
+
jobs, people's work) a lease on a share of the machine before they start, sizes them to it, and,
|
|
14
|
+
where systemd allows it, holds them to it while they run.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
**For coding agents**
|
|
19
|
+
|
|
20
|
+
- `leasehold setup` (and `--remove`): a config sized for the machine, a systemd user unit
|
|
21
|
+
running your own daemon, the PATH shims, the agents found on PATH, and a containment and
|
|
22
|
+
`doctor` check, in one step.
|
|
23
|
+
- `leasehold run -- AGENT`: the agent as one session (`LEASEHOLD_SESSION`), the PATH shims first
|
|
24
|
+
on its PATH and, when contained, a slice of its own whose leftovers are stopped when it exits
|
|
25
|
+
(`--leave-running`, `--session`, `--quiet`, `--no-contain`).
|
|
26
|
+
- PATH shims (`leasehold shims install|list|remove`) for the programs the `[commands]` rules
|
|
27
|
+
name: a matching command runs under a lease, anything else passes straight through, and a
|
|
28
|
+
command an agent hook has leased already runs in that lease.
|
|
29
|
+
- The Claude Code plugin: a hook that takes a lease around a heavy Bash command, with a shell
|
|
30
|
+
pre-filter so ordinary commands start no Python, and a skill that tells the agent what a wait,
|
|
31
|
+
a refusal and the exit codes mean.
|
|
32
|
+
- `[commands]` rules (built in, extendable): which programs and arguments are heavy, their
|
|
33
|
+
weight and memory. A busy machine refuses after 20 s (`wait_s`, `when_busy = "deny"`), and the
|
|
34
|
+
refusal names what holds the pool and the place in line. `leasehold commands --test` and
|
|
35
|
+
`--programs`.
|
|
36
|
+
- `leasehold mcp` (experimental): MCP tools to read the machine's state and wait for a lease.
|
|
37
|
+
|
|
38
|
+
**Admission**
|
|
39
|
+
|
|
40
|
+
- Weighted leases on slots, renewed by their holder and expiring when it dies. Pools with
|
|
41
|
+
reserved and maximum slots and a priority; lease kinds `work`, `external`, `lend` and
|
|
42
|
+
`observed`. Turns go round by Unix user, then by session within a user; no backfill.
|
|
43
|
+
- Lending (experimental): when nothing waits and pressure stall information is quiet, `lend`
|
|
44
|
+
leases use spare slots, and are revoked the moment other work queues or pressure rises.
|
|
45
|
+
- The memory guard (`[guard]`): while memory pressure or available memory is past its
|
|
46
|
+
threshold, every pool admits only its reserved slots. On for a user's daemon.
|
|
47
|
+
- `[[observe]]` rules (experimental): a known program started outside leasehold counts as a
|
|
48
|
+
lease while it runs.
|
|
49
|
+
|
|
50
|
+
**Running a command**
|
|
51
|
+
|
|
52
|
+
- `leasehold hold -- CMD`: wait for a lease, run the command in its own process group (the
|
|
53
|
+
terminal's foreground when run from one), renew the lease, and release it when the command
|
|
54
|
+
ends. With no `--weight`, the command is weighed by the `[commands]` rules. The command learns
|
|
55
|
+
its size from `LEASEHOLD_JOBS` and the job-count variables cargo, make, CMake, pytest-xdist,
|
|
56
|
+
Go and others read; `--jobserver` adds a GNU make jobserver.
|
|
57
|
+
- Containment: where the caller runs in the user's own session and the systemd user manager is
|
|
58
|
+
delegated cpu and memory, `hold` and the shims start the command in a user scope with
|
|
59
|
+
`CPUWeight` 100 per slot and, with `--memory` or a rule's `memory`, `MemoryHigh`. `[contain]
|
|
60
|
+
mode` (`auto`, `off`), `hard_cpu`; `LEASEHOLD_CONTAIN=off`. Elsewhere leases are advisory.
|
|
61
|
+
- `hold --if-unavailable run|fail`: with no daemon, run the command without a lease (the
|
|
62
|
+
default) or exit 69.
|
|
63
|
+
- Exit codes: 69 no daemon, 75 busy (gave up waiting), 76 revoked, 77 refused, 127 not started.
|
|
64
|
+
|
|
65
|
+
**The daemon and views**
|
|
66
|
+
|
|
67
|
+
- `leasehold serve`: your own daemon (`--user`, no root) or the machine's (`--system`, a
|
|
68
|
+
`leasehold` group on its socket). One unix socket, a SQLite ledger, an HTTP API with a
|
|
69
|
+
versioned status object (schema 2).
|
|
70
|
+
- `leasehold init` writes a commented config sized for the machine: two pools for a user's
|
|
71
|
+
daemon, four for the machine's.
|
|
72
|
+
- `leasehold top` (terminal), `leasehold ui` (browser, experimental), `status`, `leases`,
|
|
73
|
+
`metrics` (Prometheus, experimental), `report` (a webhook, experimental) and `demo`, which
|
|
74
|
+
runs a made-up busy machine.
|
|
75
|
+
- `leasehold doctor`: the config, socket and its path length and permissions, the daemon, PSI,
|
|
76
|
+
cgroups, the ledger and containment, each with a fix.
|
|
77
|
+
- `bench/`: a seeded benchmark of several agents starting heavy jobs on one machine, run
|
|
78
|
+
unmanaged, under `nice`, in a low-weight systemd slice, with a fixed job count, and through
|
|
79
|
+
`leasehold hold` advisory and enforced (`bench/run.py`); method and results in `docs/bench.md`.
|
|
80
|
+
- The admission policy as a library (`leasehold.model`, `admission`, `ledger`, `quota`,
|
|
81
|
+
`client`), with no daemon.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Issues and pull requests are welcome. For anything larger than a fix, open an issue first, so
|
|
4
|
+
we can agree on the shape before you write it.
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
git clone https://github.com/chalkworth/leasehold && cd leasehold
|
|
10
|
+
uv sync # Python 3.12+, the dev tools included
|
|
11
|
+
uv tool install pre-commit && pre-commit install # ruff and the import contracts on each commit
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Checks
|
|
15
|
+
|
|
16
|
+
Everything CI runs:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
uv run ruff check
|
|
20
|
+
uv run ruff format --check
|
|
21
|
+
uv run lint-imports # which module may import which (.importlinter)
|
|
22
|
+
uv run pytest -q # about two minutes; Linux only
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Tests start real daemons on real unix sockets in temporary directories. A few need tools:
|
|
26
|
+
`make` 4.4 for the jobserver test, which is skipped without it.
|
|
27
|
+
|
|
28
|
+
## How the code is laid out
|
|
29
|
+
|
|
30
|
+
`AGENTS.md` describes the layers and the rules each module keeps. In short:
|
|
31
|
+
|
|
32
|
+
- The library (`model`, `admission`, `ledger`, `quota`) is pure policy and storage, and never
|
|
33
|
+
imports the daemon.
|
|
34
|
+
- Everything that talks to a daemon (`hold`, the views, the agent integrations) goes through
|
|
35
|
+
`leasehold.client`.
|
|
36
|
+
- The core depends on the standard library and pydantic only. Anything else is an optional
|
|
37
|
+
extra (`mcp`).
|
|
38
|
+
|
|
39
|
+
## Changes that others depend on
|
|
40
|
+
|
|
41
|
+
The command line and its exit codes, the config file, the HTTP API and its `status` schema,
|
|
42
|
+
`--json` output and the library modules are public. A change to any of them gets a row in
|
|
43
|
+
`CHANGELOG.md`, saying what to do instead.
|
|
44
|
+
|
|
45
|
+
## Licence
|
|
46
|
+
|
|
47
|
+
By contributing, you agree that your contribution is licensed under the Apache License 2.0,
|
|
48
|
+
the licence of this project.
|