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.
Files changed (77) hide show
  1. hugpy_ops-0.2.2/LICENSE +41 -0
  2. hugpy_ops-0.2.2/PKG-INFO +63 -0
  3. hugpy_ops-0.2.2/README.md +26 -0
  4. hugpy_ops-0.2.2/deploy/SENTINEL.md +142 -0
  5. hugpy_ops-0.2.2/deploy/bin/hugpy-chaos +14 -0
  6. hugpy_ops-0.2.2/deploy/bin/hugpy-todo-keeper +14 -0
  7. hugpy_ops-0.2.2/deploy/hugpy-sentinel.service +76 -0
  8. hugpy_ops-0.2.2/deploy/hugpy-sentinel.timer +17 -0
  9. hugpy_ops-0.2.2/deploy/hugpy-todo-keeper.service +78 -0
  10. hugpy_ops-0.2.2/docs/DRIFT.md +196 -0
  11. hugpy_ops-0.2.2/docs/README.md +9 -0
  12. hugpy_ops-0.2.2/docs/ops-reliability.md +218 -0
  13. hugpy_ops-0.2.2/pyproject.toml +89 -0
  14. hugpy_ops-0.2.2/setup.cfg +4 -0
  15. hugpy_ops-0.2.2/src/hugpy_ops/__init__.py +54 -0
  16. hugpy_ops-0.2.2/src/hugpy_ops/admission.py +819 -0
  17. hugpy_ops-0.2.2/src/hugpy_ops/chaos/SCHEMA.md +70 -0
  18. hugpy_ops-0.2.2/src/hugpy_ops/chaos/__init__.py +24 -0
  19. hugpy_ops-0.2.2/src/hugpy_ops/chaos/__main__.py +7 -0
  20. hugpy_ops-0.2.2/src/hugpy_ops/chaos/alloc.py +97 -0
  21. hugpy_ops-0.2.2/src/hugpy_ops/chaos/assortment.py +261 -0
  22. hugpy_ops-0.2.2/src/hugpy_ops/chaos/client.py +308 -0
  23. hugpy_ops-0.2.2/src/hugpy_ops/chaos/observations.py +14 -0
  24. hugpy_ops-0.2.2/src/hugpy_ops/chaos/observe.py +223 -0
  25. hugpy_ops-0.2.2/src/hugpy_ops/chaos/runner.py +399 -0
  26. hugpy_ops-0.2.2/src/hugpy_ops/chaos/schema.py +300 -0
  27. hugpy_ops-0.2.2/src/hugpy_ops/chaos/sweep.py +956 -0
  28. hugpy_ops-0.2.2/src/hugpy_ops/drift.py +1004 -0
  29. hugpy_ops-0.2.2/src/hugpy_ops/drift_units/hugpy-drift-check.service +26 -0
  30. hugpy_ops-0.2.2/src/hugpy_ops/drift_units/hugpy-drift-check.timer +16 -0
  31. hugpy_ops-0.2.2/src/hugpy_ops/keeper.py +403 -0
  32. hugpy_ops-0.2.2/src/hugpy_ops/model_archive.py +232 -0
  33. hugpy_ops-0.2.2/src/hugpy_ops/model_audit.py +2103 -0
  34. hugpy_ops-0.2.2/src/hugpy_ops/model_manifest_backfill.py +290 -0
  35. hugpy_ops-0.2.2/src/hugpy_ops/provisioner.py +967 -0
  36. hugpy_ops-0.2.2/src/hugpy_ops/py.typed +0 -0
  37. hugpy_ops-0.2.2/src/hugpy_ops/resweep.py +512 -0
  38. hugpy_ops-0.2.2/src/hugpy_ops/sentinel/__init__.py +14 -0
  39. hugpy_ops-0.2.2/src/hugpy_ops/sentinel/__main__.py +93 -0
  40. hugpy_ops-0.2.2/src/hugpy_ops/sentinel/cases.py +156 -0
  41. hugpy_ops-0.2.2/src/hugpy_ops/sentinel/checks.py +330 -0
  42. hugpy_ops-0.2.2/src/hugpy_ops/sentinel/remedies.py +158 -0
  43. hugpy_ops-0.2.2/src/hugpy_ops/sentinel/runner.py +282 -0
  44. hugpy_ops-0.2.2/src/hugpy_ops/sentinel/settings.py +112 -0
  45. hugpy_ops-0.2.2/src/hugpy_ops/todo_keeper.py +388 -0
  46. hugpy_ops-0.2.2/src/hugpy_ops/todo_keeper_daemon.py +431 -0
  47. hugpy_ops-0.2.2/src/hugpy_ops/versions.py +33 -0
  48. hugpy_ops-0.2.2/src/hugpy_ops.egg-info/PKG-INFO +63 -0
  49. hugpy_ops-0.2.2/src/hugpy_ops.egg-info/SOURCES.txt +75 -0
  50. hugpy_ops-0.2.2/src/hugpy_ops.egg-info/dependency_links.txt +1 -0
  51. hugpy_ops-0.2.2/src/hugpy_ops.egg-info/entry_points.txt +13 -0
  52. hugpy_ops-0.2.2/src/hugpy_ops.egg-info/requires.txt +11 -0
  53. hugpy_ops-0.2.2/src/hugpy_ops.egg-info/scm_file_list.json +71 -0
  54. hugpy_ops-0.2.2/src/hugpy_ops.egg-info/scm_version.json +8 -0
  55. hugpy_ops-0.2.2/src/hugpy_ops.egg-info/top_level.txt +1 -0
  56. hugpy_ops-0.2.2/tests/chaos_fakes.py +176 -0
  57. hugpy_ops-0.2.2/tests/conftest.py +40 -0
  58. hugpy_ops-0.2.2/tests/test_admission.py +354 -0
  59. hugpy_ops-0.2.2/tests/test_alloc_modes.py +64 -0
  60. hugpy_ops-0.2.2/tests/test_chaos_alloc_restore.py +63 -0
  61. hugpy_ops-0.2.2/tests/test_chaos_assortment.py +75 -0
  62. hugpy_ops-0.2.2/tests/test_chaos_backoff.py +101 -0
  63. hugpy_ops-0.2.2/tests/test_chaos_schema.py +134 -0
  64. hugpy_ops-0.2.2/tests/test_chaos_stream_ceiling.py +72 -0
  65. hugpy_ops-0.2.2/tests/test_chaos_sweep.py +120 -0
  66. hugpy_ops-0.2.2/tests/test_cli_help.py +104 -0
  67. hugpy_ops-0.2.2/tests/test_drift.py +625 -0
  68. hugpy_ops-0.2.2/tests/test_import_policy.py +107 -0
  69. hugpy_ops-0.2.2/tests/test_keeper.py +106 -0
  70. hugpy_ops-0.2.2/tests/test_model_audit.py +377 -0
  71. hugpy_ops-0.2.2/tests/test_model_manifest_backfill.py +93 -0
  72. hugpy_ops-0.2.2/tests/test_provisioner.py +616 -0
  73. hugpy_ops-0.2.2/tests/test_provisioner_registries.py +189 -0
  74. hugpy_ops-0.2.2/tests/test_resweep.py +170 -0
  75. hugpy_ops-0.2.2/tests/test_sentinel.py +447 -0
  76. hugpy_ops-0.2.2/tests/test_sentinel_http.py +150 -0
  77. hugpy_ops-0.2.2/tests/test_todo_keeper.py +454 -0
@@ -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
@@ -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