hugpy-ops 0.2.2__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.
- hugpy_ops-0.2.2/LICENSE +41 -0
- hugpy_ops-0.2.2/PKG-INFO +63 -0
- hugpy_ops-0.2.2/README.md +26 -0
- hugpy_ops-0.2.2/deploy/SENTINEL.md +142 -0
- hugpy_ops-0.2.2/deploy/bin/hugpy-chaos +14 -0
- hugpy_ops-0.2.2/deploy/bin/hugpy-todo-keeper +14 -0
- hugpy_ops-0.2.2/deploy/hugpy-sentinel.service +76 -0
- hugpy_ops-0.2.2/deploy/hugpy-sentinel.timer +17 -0
- hugpy_ops-0.2.2/deploy/hugpy-todo-keeper.service +78 -0
- hugpy_ops-0.2.2/docs/DRIFT.md +196 -0
- hugpy_ops-0.2.2/docs/README.md +9 -0
- hugpy_ops-0.2.2/docs/ops-reliability.md +218 -0
- hugpy_ops-0.2.2/pyproject.toml +89 -0
- hugpy_ops-0.2.2/setup.cfg +4 -0
- hugpy_ops-0.2.2/src/hugpy_ops/__init__.py +54 -0
- hugpy_ops-0.2.2/src/hugpy_ops/admission.py +819 -0
- hugpy_ops-0.2.2/src/hugpy_ops/chaos/SCHEMA.md +70 -0
- hugpy_ops-0.2.2/src/hugpy_ops/chaos/__init__.py +24 -0
- hugpy_ops-0.2.2/src/hugpy_ops/chaos/__main__.py +7 -0
- hugpy_ops-0.2.2/src/hugpy_ops/chaos/alloc.py +97 -0
- hugpy_ops-0.2.2/src/hugpy_ops/chaos/assortment.py +261 -0
- hugpy_ops-0.2.2/src/hugpy_ops/chaos/client.py +308 -0
- hugpy_ops-0.2.2/src/hugpy_ops/chaos/observations.py +14 -0
- hugpy_ops-0.2.2/src/hugpy_ops/chaos/observe.py +223 -0
- hugpy_ops-0.2.2/src/hugpy_ops/chaos/runner.py +399 -0
- hugpy_ops-0.2.2/src/hugpy_ops/chaos/schema.py +300 -0
- hugpy_ops-0.2.2/src/hugpy_ops/chaos/sweep.py +956 -0
- hugpy_ops-0.2.2/src/hugpy_ops/drift.py +1004 -0
- hugpy_ops-0.2.2/src/hugpy_ops/drift_units/hugpy-drift-check.service +26 -0
- hugpy_ops-0.2.2/src/hugpy_ops/drift_units/hugpy-drift-check.timer +16 -0
- hugpy_ops-0.2.2/src/hugpy_ops/keeper.py +403 -0
- hugpy_ops-0.2.2/src/hugpy_ops/model_archive.py +232 -0
- hugpy_ops-0.2.2/src/hugpy_ops/model_audit.py +2103 -0
- hugpy_ops-0.2.2/src/hugpy_ops/model_manifest_backfill.py +290 -0
- hugpy_ops-0.2.2/src/hugpy_ops/provisioner.py +967 -0
- hugpy_ops-0.2.2/src/hugpy_ops/py.typed +0 -0
- hugpy_ops-0.2.2/src/hugpy_ops/resweep.py +512 -0
- hugpy_ops-0.2.2/src/hugpy_ops/sentinel/__init__.py +14 -0
- hugpy_ops-0.2.2/src/hugpy_ops/sentinel/__main__.py +93 -0
- hugpy_ops-0.2.2/src/hugpy_ops/sentinel/cases.py +156 -0
- hugpy_ops-0.2.2/src/hugpy_ops/sentinel/checks.py +330 -0
- hugpy_ops-0.2.2/src/hugpy_ops/sentinel/remedies.py +158 -0
- hugpy_ops-0.2.2/src/hugpy_ops/sentinel/runner.py +282 -0
- hugpy_ops-0.2.2/src/hugpy_ops/sentinel/settings.py +112 -0
- hugpy_ops-0.2.2/src/hugpy_ops/todo_keeper.py +388 -0
- hugpy_ops-0.2.2/src/hugpy_ops/todo_keeper_daemon.py +431 -0
- hugpy_ops-0.2.2/src/hugpy_ops/versions.py +33 -0
- hugpy_ops-0.2.2/src/hugpy_ops.egg-info/PKG-INFO +63 -0
- hugpy_ops-0.2.2/src/hugpy_ops.egg-info/SOURCES.txt +75 -0
- hugpy_ops-0.2.2/src/hugpy_ops.egg-info/dependency_links.txt +1 -0
- hugpy_ops-0.2.2/src/hugpy_ops.egg-info/entry_points.txt +13 -0
- hugpy_ops-0.2.2/src/hugpy_ops.egg-info/requires.txt +11 -0
- hugpy_ops-0.2.2/src/hugpy_ops.egg-info/scm_file_list.json +71 -0
- hugpy_ops-0.2.2/src/hugpy_ops.egg-info/scm_version.json +8 -0
- hugpy_ops-0.2.2/src/hugpy_ops.egg-info/top_level.txt +1 -0
- hugpy_ops-0.2.2/tests/chaos_fakes.py +176 -0
- hugpy_ops-0.2.2/tests/conftest.py +40 -0
- hugpy_ops-0.2.2/tests/test_admission.py +354 -0
- hugpy_ops-0.2.2/tests/test_alloc_modes.py +64 -0
- hugpy_ops-0.2.2/tests/test_chaos_alloc_restore.py +63 -0
- hugpy_ops-0.2.2/tests/test_chaos_assortment.py +75 -0
- hugpy_ops-0.2.2/tests/test_chaos_backoff.py +101 -0
- hugpy_ops-0.2.2/tests/test_chaos_schema.py +134 -0
- hugpy_ops-0.2.2/tests/test_chaos_stream_ceiling.py +72 -0
- hugpy_ops-0.2.2/tests/test_chaos_sweep.py +120 -0
- hugpy_ops-0.2.2/tests/test_cli_help.py +104 -0
- hugpy_ops-0.2.2/tests/test_drift.py +625 -0
- hugpy_ops-0.2.2/tests/test_import_policy.py +107 -0
- hugpy_ops-0.2.2/tests/test_keeper.py +106 -0
- hugpy_ops-0.2.2/tests/test_model_audit.py +377 -0
- hugpy_ops-0.2.2/tests/test_model_manifest_backfill.py +93 -0
- hugpy_ops-0.2.2/tests/test_provisioner.py +616 -0
- hugpy_ops-0.2.2/tests/test_provisioner_registries.py +189 -0
- hugpy_ops-0.2.2/tests/test_resweep.py +170 -0
- hugpy_ops-0.2.2/tests/test_sentinel.py +447 -0
- hugpy_ops-0.2.2/tests/test_sentinel_http.py +150 -0
- hugpy_ops-0.2.2/tests/test_todo_keeper.py +454 -0
hugpy_ops-0.2.2/LICENSE
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
hugpy — Source-Available License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 putkoff (hugpy.ai). All rights reserved.
|
|
4
|
+
|
|
5
|
+
Permission is granted, free of charge, to use this software ("hugpy") for
|
|
6
|
+
personal and non-commercial purposes, and for time-limited commercial
|
|
7
|
+
evaluation, subject to the following conditions:
|
|
8
|
+
|
|
9
|
+
1. Non-commercial use means use by an individual for personal purposes, or
|
|
10
|
+
use by a non-profit or educational institution for its own internal
|
|
11
|
+
purposes. Any use by, for, or on behalf of a for-profit business or in
|
|
12
|
+
connection with revenue-generating activity is commercial use — including
|
|
13
|
+
internal business use, use in producing goods or services, and use on
|
|
14
|
+
paid engagements.
|
|
15
|
+
|
|
16
|
+
2. Commercial use requires a commercial license from the copyright holder.
|
|
17
|
+
Exception: a business may evaluate the software internally for up to
|
|
18
|
+
thirty (30) days free of charge; continued use after that requires a
|
|
19
|
+
commercial license.
|
|
20
|
+
|
|
21
|
+
3. Redistribution of this software, in source or binary form, modified or
|
|
22
|
+
unmodified, is not permitted without prior written permission from the
|
|
23
|
+
copyright holder. Downloading the software from an official distribution
|
|
24
|
+
channel (PyPI, npm, hugpy.ai) is not redistribution.
|
|
25
|
+
|
|
26
|
+
4. Modification for personal use or internal evaluation is permitted;
|
|
27
|
+
distribution of modified versions is not.
|
|
28
|
+
|
|
29
|
+
5. This notice must be retained in all copies or substantial portions of
|
|
30
|
+
the software.
|
|
31
|
+
|
|
32
|
+
6. Any use outside these terms automatically terminates this license.
|
|
33
|
+
|
|
34
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
35
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
36
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
37
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING
|
|
38
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
39
|
+
IN THE SOFTWARE.
|
|
40
|
+
|
|
41
|
+
For commercial licensing or redistribution permission: https://hugpy.ai
|
hugpy_ops-0.2.2/PKG-INFO
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hugpy-ops
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Hugpy operations: sentinel, chaos runner, keeper REPL, todo keeper and provisioner for declared-but-missing model weights
|
|
5
|
+
Author-email: putkoff <support@hugpy.ai>
|
|
6
|
+
License-Expression: LicenseRef-Proprietary
|
|
7
|
+
Project-URL: Homepage, https://hugpy.ai
|
|
8
|
+
Project-URL: Documentation, https://github.com/hugpy/hugpy/blob/main/py/operations/hugpy_ops/README.md
|
|
9
|
+
Project-URL: Repository, https://github.com/hugpy/hugpy
|
|
10
|
+
Project-URL: Source, https://github.com/hugpy/hugpy/tree/main/py/operations/hugpy_ops
|
|
11
|
+
Project-URL: Issues, https://github.com/hugpy/hugpy/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/hugpy/hugpy/releases
|
|
13
|
+
Project-URL: Architecture, https://github.com/hugpy/hugpy/blob/main/PARTITION.md
|
|
14
|
+
Keywords: hugpy,llm,self-hosted,operations,sentinel,chaos-testing,provisioning
|
|
15
|
+
Classifier: Development Status :: 3 - Alpha
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Environment :: Console
|
|
22
|
+
Classifier: Topic :: System :: Monitoring
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: hugpy-platform
|
|
27
|
+
Requires-Dist: hugpy-control
|
|
28
|
+
Requires-Dist: hugpy-storage
|
|
29
|
+
Requires-Dist: hugpy-engine
|
|
30
|
+
Requires-Dist: hugpy-video
|
|
31
|
+
Requires-Dist: hugpy-fleet
|
|
32
|
+
Requires-Dist: hugpy-curation
|
|
33
|
+
Provides-Extra: test
|
|
34
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
35
|
+
Requires-Dist: pytest-timeout; extra == "test"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# hugpy-ops
|
|
39
|
+
|
|
40
|
+
`hugpy_ops` — the operational consumers of the Hugpy public APIs, extracted
|
|
41
|
+
from `abstract_hugpy_dev` as part of the partition (`py/partition.toml`,
|
|
42
|
+
`PARTITION.md`). Nothing in this package is imported by normal server
|
|
43
|
+
startup; every tool is its own console script:
|
|
44
|
+
|
|
45
|
+
| Script | Module | What it does |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `hugpy-sentinel` | `hugpy_ops.sentinel` | one detect -> case -> diagnose pass over central's HTTP read surfaces (`run-once`, `status`, `record-scorecard`) |
|
|
48
|
+
| `hugpy-chaos` | `hugpy_ops.chaos` | chaos-and-learn exerciser (`hugpy-chaos`) and the k7 offload speed-cliff sweep (`hugpy-chaos sweep`) |
|
|
49
|
+
| `hugpy-keeper` | `hugpy_ops.keeper` | stationary terminal REPL in which a served model keeps a machine or an LXD instance |
|
|
50
|
+
| `hugpy-todo-keeper` | `hugpy_ops.todo_keeper_daemon` | the enrolling todo-keeper agent node (`todo.add` / `todo.tidy`) |
|
|
51
|
+
| `hugpy-provisioner` | `hugpy_ops.provisioner` | declared-but-missing weights across the engine, studio and comfy registries -> `hugpy_storage` download queue |
|
|
52
|
+
|
|
53
|
+
Allowed ecosystem dependencies: `hugpy_platform`, `hugpy_control`,
|
|
54
|
+
`hugpy_storage`, `hugpy_engine`, `hugpy_video`, `hugpy_fleet`,
|
|
55
|
+
`hugpy_curation`. The central URL and every state root come from
|
|
56
|
+
`hugpy_platform` (`central_base_url`, `hugpy_state_dir`, `config_dir`,
|
|
57
|
+
`models_root`).
|
|
58
|
+
|
|
59
|
+
Deploy units and the operator notes live in `deploy/`; mechanics in `docs/`.
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
cd py/operations/hugpy_ops && ../../../.venv/bin/python -m pytest -q --timeout=120
|
|
63
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# hugpy-ops
|
|
2
|
+
|
|
3
|
+
`hugpy_ops` — the operational consumers of the Hugpy public APIs, extracted
|
|
4
|
+
from `abstract_hugpy_dev` as part of the partition (`py/partition.toml`,
|
|
5
|
+
`PARTITION.md`). Nothing in this package is imported by normal server
|
|
6
|
+
startup; every tool is its own console script:
|
|
7
|
+
|
|
8
|
+
| Script | Module | What it does |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| `hugpy-sentinel` | `hugpy_ops.sentinel` | one detect -> case -> diagnose pass over central's HTTP read surfaces (`run-once`, `status`, `record-scorecard`) |
|
|
11
|
+
| `hugpy-chaos` | `hugpy_ops.chaos` | chaos-and-learn exerciser (`hugpy-chaos`) and the k7 offload speed-cliff sweep (`hugpy-chaos sweep`) |
|
|
12
|
+
| `hugpy-keeper` | `hugpy_ops.keeper` | stationary terminal REPL in which a served model keeps a machine or an LXD instance |
|
|
13
|
+
| `hugpy-todo-keeper` | `hugpy_ops.todo_keeper_daemon` | the enrolling todo-keeper agent node (`todo.add` / `todo.tidy`) |
|
|
14
|
+
| `hugpy-provisioner` | `hugpy_ops.provisioner` | declared-but-missing weights across the engine, studio and comfy registries -> `hugpy_storage` download queue |
|
|
15
|
+
|
|
16
|
+
Allowed ecosystem dependencies: `hugpy_platform`, `hugpy_control`,
|
|
17
|
+
`hugpy_storage`, `hugpy_engine`, `hugpy_video`, `hugpy_fleet`,
|
|
18
|
+
`hugpy_curation`. The central URL and every state root come from
|
|
19
|
+
`hugpy_platform` (`central_base_url`, `hugpy_state_dir`, `config_dir`,
|
|
20
|
+
`models_root`).
|
|
21
|
+
|
|
22
|
+
Deploy units and the operator notes live in `deploy/`; mechanics in `docs/`.
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
cd py/operations/hugpy_ops && ../../../.venv/bin/python -m pytest -q --timeout=120
|
|
26
|
+
```
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# hugpy-sentinel — bound-exceeded watcher (k95)
|
|
2
|
+
|
|
3
|
+
Operator directive (2026-08-06): *"the hugpy-agent needs to be deployed under
|
|
4
|
+
suspected hangs and edge cases where expected bounds are exceeded — to
|
|
5
|
+
document, remedy, fix or other."* The sentinel formalizes the loop the keeper
|
|
6
|
+
ran by hand on the 2026-08-06 vision wedge (repeated worker 500s → diagnose
|
|
7
|
+
stale projector-less seat → evict → verify → document).
|
|
8
|
+
|
|
9
|
+
## What one pass does (`hugpy-sentinel run-once`)
|
|
10
|
+
|
|
11
|
+
1. **Detect** — four read surfaces of central, over HTTP (never imports
|
|
12
|
+
central's internals): `GET /llm/jobs?live=0` (stalled beyond grace,
|
|
13
|
+
expired-without-real-terminal), `GET /llm/workers` (offline/unreachable,
|
|
14
|
+
`version_ok: false` — the tri-state `null` is not an anomaly),
|
|
15
|
+
`GET /oracle/capabilities` (eligible→ineligible transition vs. the last
|
|
16
|
+
snapshot), plus a local scorecard history (`record-scorecard` feeds it
|
|
17
|
+
from observed `POST /oracle/route` responses; N consecutive
|
|
18
|
+
`hard_pass=false` for one (capability, model) is an anomaly).
|
|
19
|
+
2. **Case** — one OPEN case per fingerprint in a SQLite store under
|
|
20
|
+
`$HUGPY_SENTINEL_DIR` (partial unique index; re-detection touches
|
|
21
|
+
`last_seen`, never re-spawns).
|
|
22
|
+
3. **Diagnose** — for each NEWLY opened case, spawn ONE bounded
|
|
23
|
+
`hugpy-agent case <brief> --case-dir <dir>` subprocess (default 900 s
|
|
24
|
+
timeout). The agent runs under a pinned DOCUMENT-ONLY policy profile
|
|
25
|
+
(readonly + case-dir-jailed `fs_write` + `http_fetch`; shell/spawn
|
|
26
|
+
hard-denied) and must return a markdown case report, stored as
|
|
27
|
+
`report.md` in the case dir; a one-liner lands in `cases.md`.
|
|
28
|
+
4. States: `open → agent_running → documented | escalated`; `remedied` and
|
|
29
|
+
`closed` are operator moves (`status` lists them all).
|
|
30
|
+
|
|
31
|
+
## Remedies are OFF by default
|
|
32
|
+
|
|
33
|
+
`hugpy_ops/sentinel/remedies.py` is the complete whitelist (worker
|
|
34
|
+
`POST /models/unload`, `POST /slots/<id>/unload|relaunch`, central
|
|
35
|
+
`POST /llm/chat/cancel/<id>` — all reversible). Execution raises
|
|
36
|
+
`RemediesDisabled` unless `HUGPY_SENTINEL_REMEDIES=1` is set, and the prod
|
|
37
|
+
worker **ae** is excluded structurally (`PROD_EXCLUDED_WORKERS`, enforced in
|
|
38
|
+
both eligibility and execute) — ae cases are document+escalate only, always.
|
|
39
|
+
|
|
40
|
+
**Exception — downloads are ON by default (k97, operator ruling 2026-08-06:
|
|
41
|
+
"autonomous downloads should be greenlit — especially for ComfyUI").** The
|
|
42
|
+
`enqueue_download` remedy rides its OWN gate, `HUGPY_SENTINEL_DOWNLOADS`
|
|
43
|
+
(default ON; set `0` to turn it off), separate from the remedies gate above.
|
|
44
|
+
Each pass runs the k97 provisioner scan (`hugpy_ops/provisioner.py`
|
|
45
|
+
— declared-but-missing weights across the comfy/studio/tasks registries,
|
|
46
|
+
read through `hugpy_engine`, `hugpy_video.intel.studio` and `hugpy_storage`) and
|
|
47
|
+
opens one info-severity `weight_missing` case per missing weight. Those cases
|
|
48
|
+
take a deterministic FAST PATH (no agent spawn): the sentinel POSTs
|
|
49
|
+
`{central}/llm/repos/download` — the console's own add-models enqueue, so the
|
|
50
|
+
job lands on the hugpy-downloader-dev queue with dedupe/progress/cancel —
|
|
51
|
+
then documents the case as `remedied`. A want whose source can't be proven
|
|
52
|
+
from its registry row (no HF hub id) is documented UNRESOLVED, never guessed.
|
|
53
|
+
The ae exclusion is irrelevant here by construction: downloads land on
|
|
54
|
+
central's shared storage, never on a worker. Dry-run the same scan by hand:
|
|
55
|
+
`hugpy-provisioner` (add `--apply` to enqueue).
|
|
56
|
+
|
|
57
|
+
## Install (operator; the keeper only writes these files)
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
pip install hugpy-ops # provides the hugpy-sentinel console script
|
|
61
|
+
sudo cp <hugpy_ops>/deploy/hugpy-sentinel.{service,timer} /etc/systemd/system/
|
|
62
|
+
# edit ExecStart's venv path per box, then
|
|
63
|
+
sudo systemctl daemon-reload
|
|
64
|
+
sudo systemctl enable --now hugpy-sentinel.timer
|
|
65
|
+
systemctl list-timers hugpy-sentinel*; journalctl -u hugpy-sentinel -f
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Later, to arm remedies (deliberate, after reviewing documented cases): add
|
|
69
|
+
`Environment=HUGPY_SENTINEL_REMEDIES=1` to the service, `daemon-reload`,
|
|
70
|
+
done — nothing else changes.
|
|
71
|
+
|
|
72
|
+
## Brain ladder + pilot light (k96, operator ruling 2026-08-06)
|
|
73
|
+
|
|
74
|
+
The 2026-08-06 shakedown aborted its case runs because the agent's single
|
|
75
|
+
configured brain (Qwen3-Coder-Next) was mid-reload. The fix is an ordered
|
|
76
|
+
**brain ladder**: `HUGPY_AGENT_BRAINS` (csv, best-first) on the sentinel unit
|
|
77
|
+
— spawned case agents inherit it. A run starts on the first entry that is
|
|
78
|
+
**warm** on a fleet worker (`GET /llm/workers` allocations) and walks DOWN one
|
|
79
|
+
entry per capacity-class / permanent-verdict refusal (forward-only). Every
|
|
80
|
+
brain chat carries `"no_makeroom": true`, so a cold brain load can NEVER evict
|
|
81
|
+
a fleet model — it takes genuinely free room or is refused fast (which the
|
|
82
|
+
ladder walks). A run answered below ladder position 1 stamps
|
|
83
|
+
`[answered by <model> (ladder position N of M)]` onto its report, so a
|
|
84
|
+
pilot-light case report is visibly reduced-depth.
|
|
85
|
+
|
|
86
|
+
**The pilot light** is the LAST ladder entry by convention: a model small
|
|
87
|
+
enough that a cold load is cheap, so the sentinel always has *some* brain even
|
|
88
|
+
when the fleet is wedged. Keep it warm with the existing keep-warm star (no
|
|
89
|
+
new pinning machinery — reconcile reseats a star every beat, even after a
|
|
90
|
+
pressure eviction):
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
# pin the pilot light on a worker (operator-gated route); example: computron
|
|
94
|
+
curl -X POST http://127.0.0.1:7002/api/llm/workers/<worker_id>/boot-prewarm \
|
|
95
|
+
-H 'Content-Type: application/json' \
|
|
96
|
+
-d '{"model_key": "Qwen~Qwen2.5-3B-Instruct-GGUF"}'
|
|
97
|
+
# verify: the star map, and the model's allocation on /llm/workers
|
|
98
|
+
curl http://127.0.0.1:7002/api/llm/workers/boot-prewarm
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Recommended ladder for THIS fleet (ae 3090 + computron 4060-8GB + op,
|
|
102
|
+
2026-08-06 — warm today: Qwen3-Coder-Next on the ae slot, Qwen2.5-VL-3B,
|
|
103
|
+
small LFM/3B models):
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
HUGPY_AGENT_BRAINS=Qwen~Qwen3-Coder-Next-GGUF,Qwen~Qwen2.5-3B-Instruct-GGUF
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Coder stays the quality brain; the 3B instruct is the pilot light — star it on
|
|
110
|
+
computron (8 GB holds a 3B GGUF comfortably beside the VL model) so ae's slot
|
|
111
|
+
churn can never leave case runs brainless. (VL-3B is deliberately NOT on the
|
|
112
|
+
ladder: it is a vision model holding the vision seat.)
|
|
113
|
+
|
|
114
|
+
The sentinel watches the floor itself: `pilot_light_not_resident` (severity
|
|
115
|
+
warn) fires when the pilot light (from `HUGPY_SENTINEL_PILOT_LIGHT`, else the
|
|
116
|
+
last `HUGPY_AGENT_BRAINS` entry) has no healthy allocation on any worker, and
|
|
117
|
+
the case evidence carries the exact boot-prewarm POST to fix it.
|
|
118
|
+
|
|
119
|
+
## Knobs (env; all read by `hugpy_ops/sentinel/settings.py`)
|
|
120
|
+
|
|
121
|
+
| var | default | meaning |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `HUGPY_SENTINEL_CENTRAL` | `central_base_url()` → `http://127.0.0.1:7002` | central base |
|
|
124
|
+
| `HUGPY_SENTINEL_DIR` | `<HUGPY_HOME>/state/sentinel` (`hugpy_platform.hugpy_state_dir()`) | case db, case dirs, `cases.md`, history |
|
|
125
|
+
| `HUGPY_SENTINEL_REMEDIES` | unset (OFF) | THE remedy gate |
|
|
126
|
+
| `HUGPY_SENTINEL_DOWNLOADS` | unset (ON) | k97 downloads gate — `enqueue_download` only; `0` disables |
|
|
127
|
+
| `HUGPY_PROVISION_FLOOR_GB` | 500 | provisioner free-space floor on the destination volume |
|
|
128
|
+
| `HUGPY_SENTINEL_STALLED_GRACE_S` | 120 | stall must persist this long past central's 90 s label |
|
|
129
|
+
| `HUGPY_SENTINEL_HARD_FAIL_STREAK` | 3 | consecutive `hard_pass=false` to case |
|
|
130
|
+
| `HUGPY_SENTINEL_AGENT` | `hugpy-agent` | absolute path in the unit |
|
|
131
|
+
| `HUGPY_SENTINEL_AGENT_TIMEOUT_S` | 900 | one case run's wall clock |
|
|
132
|
+
| `HUGPY_SENTINEL_AGENT_MAX_STEPS` | 20 | agent loop bound |
|
|
133
|
+
| `HUGPY_AGENT_BRAINS` | unset | k96 brain ladder, inherited by case agents (csv, best-first; last = pilot light) |
|
|
134
|
+
| `HUGPY_SENTINEL_PILOT_LIGHT` | last `HUGPY_AGENT_BRAINS` entry | override for the pilot-light-not-resident check; empty ladder + unset = check off |
|
|
135
|
+
|
|
136
|
+
A worker `version_ok: false` case additionally carries the `hugpy-fleet` /
|
|
137
|
+
`hugpy-server` distribution versions installed beside the sentinel
|
|
138
|
+
(`hugpy_ops.versions`, resolved lazily through `importlib.metadata`).
|
|
139
|
+
|
|
140
|
+
Tests: `python -m pytest tests/test_sentinel.py tests/test_sentinel_http.py -q`
|
|
141
|
+
in `py/operations/hugpy_ops` and `python -m unittest test_case_cli` in
|
|
142
|
+
`py/inference/hugpy_agent/tests` (the pinned agent-side profile).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""SUPERSEDED by the `hugpy-chaos` console script (hugpy-ops distribution).
|
|
3
|
+
|
|
4
|
+
Kept only for operators who run the checkout without installing it:
|
|
5
|
+
<venv>/bin/python deploy/bin/hugpy-chaos --dry-run
|
|
6
|
+
With `pip install -e py/operations/hugpy_ops` the console script covers this
|
|
7
|
+
file completely; prefer `hugpy-chaos`.
|
|
8
|
+
"""
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from hugpy_ops.chaos.runner import main
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
sys.exit(main())
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""SUPERSEDED by the `hugpy-todo-keeper` console script (hugpy-ops distribution).
|
|
3
|
+
|
|
4
|
+
Kept only for operators who run the checkout without installing it:
|
|
5
|
+
<venv>/bin/python deploy/bin/hugpy-todo-keeper
|
|
6
|
+
With `pip install -e py/operations/hugpy_ops` the console script covers this
|
|
7
|
+
file completely; prefer `hugpy-todo-keeper` (the unit's ExecStart uses it).
|
|
8
|
+
"""
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from hugpy_ops.todo_keeper_daemon import main
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
sys.exit(main())
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# hugpy-sentinel.service — one sentinel pass (k95): detect bound-exceeded
|
|
2
|
+
# conditions on central's read surfaces, open deduplicated cases, spawn ONE
|
|
3
|
+
# bounded document-only `hugpy-agent case` run per NEW case, exit.
|
|
4
|
+
#
|
|
5
|
+
# NOT INSTALLED/ENABLED BY THIS FILE. The keeper writes it; the OPERATOR
|
|
6
|
+
# installs after review (a checker that spawns agent runs — and could one
|
|
7
|
+
# day apply remedies — is an operator decision):
|
|
8
|
+
#
|
|
9
|
+
# sudo cp <hugpy_ops>/deploy/hugpy-sentinel.{service,timer} /etc/systemd/system/
|
|
10
|
+
# sudo systemctl daemon-reload
|
|
11
|
+
# sudo systemctl enable --now hugpy-sentinel.timer
|
|
12
|
+
# systemctl list-timers hugpy-sentinel*; journalctl -u hugpy-sentinel -f
|
|
13
|
+
#
|
|
14
|
+
# The sentinel is the `hugpy-sentinel` console script of the hugpy-ops
|
|
15
|
+
# distribution (`pip install hugpy-ops`); adjust the venv path below per box.
|
|
16
|
+
#
|
|
17
|
+
# REMEDIES ARE OFF BY DEFAULT and stay off until the operator adds
|
|
18
|
+
# Environment=HUGPY_SENTINEL_REMEDIES=1 below — until then every case is
|
|
19
|
+
# document+escalate only. The prod worker "ae" is excluded in code
|
|
20
|
+
# (hugpy_ops/sentinel/remedies.py PROD_EXCLUDED_WORKERS), not by this file.
|
|
21
|
+
|
|
22
|
+
[Unit]
|
|
23
|
+
Description=hugpy sentinel: one detect -> case -> diagnose pass
|
|
24
|
+
After=network-online.target hugpy-api-dev.service
|
|
25
|
+
Wants=network-online.target
|
|
26
|
+
|
|
27
|
+
[Service]
|
|
28
|
+
Type=oneshot
|
|
29
|
+
User=ubuntu
|
|
30
|
+
WorkingDirectory=/home/ubuntu
|
|
31
|
+
ExecStartPre=/usr/bin/test -x /home/ubuntu/hugpy/venv/bin/hugpy-sentinel
|
|
32
|
+
ExecStart=/home/ubuntu/hugpy/venv/bin/hugpy-sentinel run-once
|
|
33
|
+
# One pass polls four HTTP GETs and possibly runs one diagnosis agent per new
|
|
34
|
+
# case (each internally capped at HUGPY_SENTINEL_AGENT_TIMEOUT_S, default
|
|
35
|
+
# 900s). 30m is the outer backstop for a pass that opens several cases.
|
|
36
|
+
TimeoutStartSec=30m
|
|
37
|
+
Nice=10
|
|
38
|
+
|
|
39
|
+
Environment=PYTHONUNBUFFERED=1
|
|
40
|
+
# Central base URL: HUGPY_SENTINEL_CENTRAL overrides; else the platform
|
|
41
|
+
# resolver (HUGPY_BASE_URL and its legacy aliases), else loopback central.
|
|
42
|
+
Environment=HUGPY_SENTINEL_CENTRAL=http://127.0.0.1:7002
|
|
43
|
+
# Case db, per-case dirs, cases.md ledger, scorecard history — durable, and
|
|
44
|
+
# outside any checkout. Default without this line: <HUGPY_HOME>/state/sentinel.
|
|
45
|
+
Environment=HUGPY_SENTINEL_DIR=/home/ubuntu/.local/state/hugpy-sentinel
|
|
46
|
+
# The one-shot diagnosis agent. Pin the absolute path (PATH inside systemd
|
|
47
|
+
# is minimal); adjust per box.
|
|
48
|
+
Environment=HUGPY_SENTINEL_AGENT=/home/ubuntu/.local/bin/hugpy-agent
|
|
49
|
+
# The agent talks to the fleet through central too.
|
|
50
|
+
Environment=HUGPY_BASE=http://127.0.0.1:7002
|
|
51
|
+
# k96 BRAIN LADDER for spawned case agents (inherited env; csv, best-first).
|
|
52
|
+
# The run starts on the first WARM entry and walks down one entry per
|
|
53
|
+
# capacity/permanent-verdict refusal; the LAST entry is the PILOT LIGHT — keep
|
|
54
|
+
# it small (cheap cold load) and pinned warm (see deploy/SENTINEL.md "Pilot
|
|
55
|
+
# light"). The sentinel also derives its pilot-light-not-resident check from
|
|
56
|
+
# this line (override with HUGPY_SENTINEL_PILOT_LIGHT). Recommended for THIS
|
|
57
|
+
# fleet (ae 3090 + computron 4060-8GB + op; 2026-08-06):
|
|
58
|
+
# Environment=HUGPY_AGENT_BRAINS=Qwen~Qwen3-Coder-Next-GGUF,Qwen~Qwen2.5-3B-Instruct-GGUF
|
|
59
|
+
# Remedy gate — LEAVE UNSET until the operator deliberately enables:
|
|
60
|
+
# Environment=HUGPY_SENTINEL_REMEDIES=1
|
|
61
|
+
|
|
62
|
+
# Hardening: network + its own state dir, nothing else writable.
|
|
63
|
+
NoNewPrivileges=true
|
|
64
|
+
PrivateTmp=true
|
|
65
|
+
ProtectSystem=strict
|
|
66
|
+
ProtectHome=read-only
|
|
67
|
+
ReadWritePaths=/home/ubuntu/.local/state/hugpy-sentinel
|
|
68
|
+
# The platform's per-user dirs (HUGPY_HOME, default ~/.hugpy) are created on
|
|
69
|
+
# first resolve; grant them so a ProtectHome box does not die at import.
|
|
70
|
+
ReadWritePaths=/home/ubuntu/.hugpy
|
|
71
|
+
ProtectKernelTunables=true
|
|
72
|
+
ProtectControlGroups=true
|
|
73
|
+
RestrictSUIDSGID=true
|
|
74
|
+
|
|
75
|
+
[Install]
|
|
76
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# hugpy-sentinel.timer — 10-minute cadence for the sentinel's run-once pass.
|
|
2
|
+
# sudo systemctl enable --now hugpy-sentinel.timer
|
|
3
|
+
[Unit]
|
|
4
|
+
Description=hugpy sentinel: periodic bound-exceeded sweep
|
|
5
|
+
|
|
6
|
+
[Timer]
|
|
7
|
+
# Every 10 minutes, first run 2m after boot. A pass that finds nothing is
|
|
8
|
+
# four HTTP GETs; a pass that opens a case runs one bounded agent.
|
|
9
|
+
OnBootSec=2m
|
|
10
|
+
OnUnitActiveSec=10m
|
|
11
|
+
# Jitter so several boxes running a sentinel don't all poll central at once.
|
|
12
|
+
RandomizedDelaySec=60
|
|
13
|
+
Persistent=true
|
|
14
|
+
Unit=hugpy-sentinel.service
|
|
15
|
+
|
|
16
|
+
[Install]
|
|
17
|
+
WantedBy=timers.target
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# hugpy todo-keeper agent node — the standing P3 agent node that answers the
|
|
2
|
+
# console's ☑ todo drawer assists (todo.add / todo.tidy) as durable dispatched
|
|
3
|
+
# tasks instead of stateless one-shot completions.
|
|
4
|
+
#
|
|
5
|
+
# NOT INSTALLED/ENABLED BY THIS FILE. The keeper writes it; the OPERATOR installs
|
|
6
|
+
# it after review (deliberate — a standing daemon that enrolls a node and holds a
|
|
7
|
+
# credential is an operator decision, not a subagent's):
|
|
8
|
+
#
|
|
9
|
+
# sudo cp <hugpy_ops>/deploy/hugpy-todo-keeper.service /etc/systemd/system/
|
|
10
|
+
# sudo systemctl daemon-reload
|
|
11
|
+
# sudo systemctl enable --now hugpy-todo-keeper
|
|
12
|
+
# systemctl status hugpy-todo-keeper; journalctl -u hugpy-todo-keeper -f
|
|
13
|
+
#
|
|
14
|
+
# The daemon is the `hugpy-todo-keeper` console script of the hugpy-ops
|
|
15
|
+
# distribution (`pip install hugpy-ops`); adjust the venv path below per box.
|
|
16
|
+
#
|
|
17
|
+
# RESTART SURVIVAL: enrollment happens ONCE; the node id + one-time agt_ token
|
|
18
|
+
# persist to ~/.config/hugpy/todo-keeper.json (0600, outside any checkout), so a
|
|
19
|
+
# reboot resumes the SAME node rather than orphaning it. Restart=always + a
|
|
20
|
+
# 10s backoff covers a central that is briefly down.
|
|
21
|
+
|
|
22
|
+
[Unit]
|
|
23
|
+
Description=hugpy todo-keeper agent node (P3 /agent node; answers todo.add + todo.tidy)
|
|
24
|
+
After=network.target hugpy-api-dev.service
|
|
25
|
+
Wants=hugpy-api-dev.service
|
|
26
|
+
StartLimitIntervalSec=0
|
|
27
|
+
|
|
28
|
+
[Service]
|
|
29
|
+
Type=simple
|
|
30
|
+
User=ubuntu
|
|
31
|
+
WorkingDirectory=/home/ubuntu
|
|
32
|
+
ExecStartPre=/usr/bin/test -x /home/ubuntu/hugpy/venv/bin/hugpy-todo-keeper
|
|
33
|
+
ExecStart=/home/ubuntu/hugpy/venv/bin/hugpy-todo-keeper
|
|
34
|
+
Restart=always
|
|
35
|
+
RestartSec=10
|
|
36
|
+
|
|
37
|
+
Environment=PYTHONUNBUFFERED=1
|
|
38
|
+
# Central base URL — HUGPY_BASE_URL is canonical; the legacy HUGPY_CENTRAL
|
|
39
|
+
# alias is still honoured by hugpy_platform.central_base_url.
|
|
40
|
+
Environment=HUGPY_BASE_URL=http://127.0.0.1:7002
|
|
41
|
+
Environment=HUGPY_TODO_KEEPER_MODEL=Qwen2.5-3B-Instruct-GGUF
|
|
42
|
+
# The token/cursor state file. MUST be on durable storage under $HOME — /tmp and
|
|
43
|
+
# XDG_RUNTIME_DIR are cleared on boot, which would orphan the node every reboot.
|
|
44
|
+
Environment=HUGPY_TODO_KEEPER_STATE=/home/ubuntu/.config/hugpy/todo-keeper.json
|
|
45
|
+
# REQUIRED — /agent/register is PERMANENTLY key-gated (operator ruling 2026-07-16:
|
|
46
|
+
# agent keys are "a separate api category entirely… gated permanently"). The gate
|
|
47
|
+
# ignores the sitewide api_key_required toggle AND HUGPY_AGENT_OPEN, because
|
|
48
|
+
# register MINTS credentials and is publicly reachable. Enrolling bare 401s.
|
|
49
|
+
#
|
|
50
|
+
# Sourced from the share, NOT written here: d-env/env holds CONSOLE_API=<key>, so
|
|
51
|
+
# the secret never enters git (the repo tracks this unit; it must stay clean).
|
|
52
|
+
# EnvironmentFile is optional-prefixed (-) so a missing file doesn't block boot —
|
|
53
|
+
# enrollment then fails loudly with an explicit 401 message instead, which is the
|
|
54
|
+
# honest failure. Once enrolled the node uses its own agt_ token; the console key
|
|
55
|
+
# is only needed for the one-time bootstrap (and any re-enroll).
|
|
56
|
+
# ⚠️ systemd does NOT shell-expand: `Environment=HUGPY_API_KEY=${CONSOLE_API}` passes the
|
|
57
|
+
# LITERAL string "${CONSOLE_API}" (verified 2026-07-16 via `systemctl show -p Environment`),
|
|
58
|
+
# and the node would 401 on enroll. EnvironmentFile already puts CONSOLE_API in the
|
|
59
|
+
# environment under its own name — so the daemon reads CONSOLE_API directly and no
|
|
60
|
+
# expansion is needed. Don't "fix" this back into an Environment= line.
|
|
61
|
+
EnvironmentFile=-/srv/share/projects/hugpy/d-env/env
|
|
62
|
+
|
|
63
|
+
# Hardening: this daemon needs the network, its state file, and nothing else.
|
|
64
|
+
NoNewPrivileges=true
|
|
65
|
+
PrivateTmp=true
|
|
66
|
+
ProtectSystem=strict
|
|
67
|
+
ProtectHome=read-only
|
|
68
|
+
# ...except its own state directory, which it must write (the token + cursor).
|
|
69
|
+
ReadWritePaths=/home/ubuntu/.config/hugpy
|
|
70
|
+
# The platform's per-user dirs (HUGPY_HOME, default ~/.hugpy) are created on
|
|
71
|
+
# first resolve; grant them rather than loosening ProtectHome.
|
|
72
|
+
ReadWritePaths=/home/ubuntu/.hugpy
|
|
73
|
+
ProtectKernelTunables=true
|
|
74
|
+
ProtectControlGroups=true
|
|
75
|
+
RestrictSUIDSGID=true
|
|
76
|
+
|
|
77
|
+
[Install]
|
|
78
|
+
WantedBy=multi-user.target
|