Gemini CLI commited on
Commit ·
60539ea
1
Parent(s): 41fa3a1
Update MeshScale controller bundle
Browse files- MESHSCALE.md +249 -0
- MESHSCALE_CLAUDE_DEEP_AUDIT_2026-06-19.md +75 -0
- MESHSCALE_CLAUDE_INGEST_PAYLOAD_2026-06-19.md +68 -0
- MESHSCALE_CLAUDE_ORIENTATION_NOTE_2026-06-19.md +41 -0
- MESHSCALE_CLAUDE_RETURN_PAYLOAD_2026-06-19.md +57 -0
- MESHSCALE_PRIMARY_COORDINATION_REPORT_2026-06-19.md +259 -0
- MESHSCALE_SITREP_TRAJECTORY_FOR_CLAUDE_2026-06-19.md +199 -0
- key_farm/__init__.py +2 -1
- key_farm/__pycache__/__init__.cpython-310.pyc +0 -0
- key_farm/__pycache__/amalgam_bridge.cpython-310.pyc +0 -0
- key_farm/__pycache__/cli.cpython-310.pyc +0 -0
- key_farm/__pycache__/contracts.cpython-310.pyc +0 -0
- key_farm/__pycache__/controller.cpython-310.pyc +0 -0
- key_farm/__pycache__/evaluator.cpython-310.pyc +0 -0
- key_farm/__pycache__/faculty.cpython-310.pyc +0 -0
- key_farm/__pycache__/regulation.cpython-310.pyc +0 -0
- key_farm/__pycache__/scaler.cpython-310.pyc +0 -0
- key_farm/__pycache__/serialization.cpython-310.pyc +0 -0
- key_farm/__pycache__/store.cpython-310.pyc +0 -0
- key_farm/__pycache__/symbiotic_trace.cpython-310.pyc +0 -0
- key_farm/controller.py +29 -1
- meshscale_space/__pycache__/__init__.cpython-310.pyc +0 -0
- meshscale_space/__pycache__/app.cpython-310.pyc +0 -0
- meshscale_space/__pycache__/os_routes.cpython-310.pyc +0 -0
- meshscale_worker_space/Dockerfile +18 -0
- meshscale_worker_space/README.md +51 -0
- meshscale_worker_space/__init__.py +1 -0
- meshscale_worker_space/app.py +189 -0
- meshscale_worker_space/requirements.txt +5 -0
MESHSCALE.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MeshScale
|
| 2 |
+
|
| 3 |
+
MeshScale is the CPU-only worker mesh for Hugging Face Spaces. KEY is the
|
| 4 |
+
first client, but the farm surface is generic enough for other CPU task
|
| 5 |
+
handlers.
|
| 6 |
+
|
| 7 |
+
The important boundary: KEY owns the NEAT generation. Speciation, selection,
|
| 8 |
+
crossover, mutation, champion compilation, and continuity stay centralized.
|
| 9 |
+
CPU workers only evaluate fitness shards and return `node_id -> fitness`.
|
| 10 |
+
Workers follow KEY's existing `Pulse`/breath rhythm. The bucket stores pulse
|
| 11 |
+
receipts and regulation intent; it is not a second runtime authority.
|
| 12 |
+
|
| 13 |
+
## Why CPU Spaces
|
| 14 |
+
|
| 15 |
+
KEY's current shape is NEAT-heavy. HF lists CPU Upgrade Spaces as 8 vCPU / 32 GB
|
| 16 |
+
at `$0.03/hr`, while A100 large is `$2.50/hr`. That makes CPU fan-out attractive
|
| 17 |
+
when the workload is many independent node evaluations.
|
| 18 |
+
|
| 19 |
+
## Resource Refraction
|
| 20 |
+
|
| 21 |
+
The farm is not an unbounded chain. It is a controlled expansion tree.
|
| 22 |
+
|
| 23 |
+
1. Controller submits a generation into the shared farm root.
|
| 24 |
+
2. Worker Spaces claim shard files.
|
| 25 |
+
3. If a shard is too large, a worker refracts it into child shards.
|
| 26 |
+
4. Workers emit expansion requests when backlog exceeds active capacity.
|
| 27 |
+
5. A scaler reads expansion requests and starts more CPU worker Spaces up to
|
| 28 |
+
policy limits.
|
| 29 |
+
6. Controller merges finished fitness values back into the live population.
|
| 30 |
+
|
| 31 |
+
Policy limits:
|
| 32 |
+
|
| 33 |
+
- max workers
|
| 34 |
+
- max refraction depth
|
| 35 |
+
- target shard size
|
| 36 |
+
- hourly budget
|
| 37 |
+
- pulse TTL
|
| 38 |
+
- controller pulse TTL
|
| 39 |
+
- claim TTL
|
| 40 |
+
|
| 41 |
+
This gives the "one Space can summon more Spaces" behavior without letting
|
| 42 |
+
workers recursively spend money without a ledger.
|
| 43 |
+
|
| 44 |
+
## Breath Regulation
|
| 45 |
+
|
| 46 |
+
KEY publishes a controller pulse while the run is alive:
|
| 47 |
+
|
| 48 |
+
```text
|
| 49 |
+
/data/farm/pulses/controller.json
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
Each worker publishes its own pulse receipt:
|
| 53 |
+
|
| 54 |
+
```text
|
| 55 |
+
/data/farm/pulses/workers/<worker-id>.json
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
Regulation intent lives here:
|
| 59 |
+
|
| 60 |
+
```text
|
| 61 |
+
/data/farm/regulation/intent.json
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
Modes:
|
| 65 |
+
|
| 66 |
+
- `run`: workers may claim shards.
|
| 67 |
+
- `drain`: workers stop claiming beyond `target_workers`.
|
| 68 |
+
- `shutdown`: workers finish their current tick and exit.
|
| 69 |
+
|
| 70 |
+
If the KEY controller pulse expires, workers exit by policy. A scaler can then
|
| 71 |
+
pause those CPU Spaces through the HF API so upgraded CPU billing does not keep
|
| 72 |
+
running after the KEY Space stops.
|
| 73 |
+
|
| 74 |
+
## Bucket Layout
|
| 75 |
+
|
| 76 |
+
Use the Hugging Face bucket as a read-write volume mounted at `/data`.
|
| 77 |
+
|
| 78 |
+
```text
|
| 79 |
+
/data/farm/
|
| 80 |
+
events.jsonl
|
| 81 |
+
expansion_requests/
|
| 82 |
+
pulses/
|
| 83 |
+
controller.json
|
| 84 |
+
workers/
|
| 85 |
+
regulation/
|
| 86 |
+
intent.json
|
| 87 |
+
runs/
|
| 88 |
+
run_<id>/
|
| 89 |
+
run.json
|
| 90 |
+
generations/
|
| 91 |
+
0/
|
| 92 |
+
jobs/
|
| 93 |
+
pending/
|
| 94 |
+
claimed/
|
| 95 |
+
done/
|
| 96 |
+
failed/
|
| 97 |
+
refracted/
|
| 98 |
+
results/
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
## Local Smoke
|
| 102 |
+
|
| 103 |
+
```bash
|
| 104 |
+
python -m meshscale.cli simulate --nodes 32 --workers 4 --shard-size 8 --clean
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
This uses local threads and a temp farm directory. It does not touch the live
|
| 108 |
+
TUI worker.
|
| 109 |
+
|
| 110 |
+
## Worker Space Command
|
| 111 |
+
|
| 112 |
+
For a CPU worker Space with the bucket mounted at `/data`:
|
| 113 |
+
|
| 114 |
+
```bash
|
| 115 |
+
python -m key_farm.worker --root /data/farm --poll-seconds 2
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
Useful environment:
|
| 119 |
+
|
| 120 |
+
```text
|
| 121 |
+
KEY_FARM_ROOT=/data/farm
|
| 122 |
+
KEY_FARM_WORKER_ID=worker-space-001
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
The repository also includes a Docker Space template at:
|
| 126 |
+
|
| 127 |
+
```text
|
| 128 |
+
meshscale_worker_space/
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
That template runs a small FastAPI health surface while a background thread
|
| 132 |
+
executes the worker loop. The important endpoints are:
|
| 133 |
+
|
| 134 |
+
```text
|
| 135 |
+
/health
|
| 136 |
+
/state
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
Publish regulation manually if needed:
|
| 140 |
+
|
| 141 |
+
```bash
|
| 142 |
+
python -m meshscale.cli regulate --root /data/farm --mode drain --target-workers 4
|
| 143 |
+
python -m meshscale.cli regulate --root /data/farm --mode shutdown --reason key_space_shutdown
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
## Scaler
|
| 147 |
+
|
| 148 |
+
MeshScale scaling is optional and policy bounded. The scaler reads
|
| 149 |
+
`/data/farm/expansion_requests/*.json`, starts CPU worker Spaces from a prepared
|
| 150 |
+
worker template, and pauses known workers when KEY stops pulsing or publishes
|
| 151 |
+
`shutdown` regulation.
|
| 152 |
+
|
| 153 |
+
Dry-run is the default:
|
| 154 |
+
|
| 155 |
+
```bash
|
| 156 |
+
python -m meshscale.cli scale-once \
|
| 157 |
+
--root /data/farm \
|
| 158 |
+
--template-space tostido/meshscale-worker-template \
|
| 159 |
+
--namespace tostido \
|
| 160 |
+
--worker-prefix meshscale-worker
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
To actually call the Hugging Face API, add `--apply` and provide `HF_TOKEN`.
|
| 164 |
+
The scaler still honors hard caps:
|
| 165 |
+
|
| 166 |
+
```bash
|
| 167 |
+
python -m meshscale.cli scale-once \
|
| 168 |
+
--root /data/farm \
|
| 169 |
+
--template-space tostido/meshscale-worker-template \
|
| 170 |
+
--namespace tostido \
|
| 171 |
+
--worker-prefix meshscale-worker \
|
| 172 |
+
--max-workers 24 \
|
| 173 |
+
--max-starts-per-tick 2 \
|
| 174 |
+
--start-cooldown-seconds 120 \
|
| 175 |
+
--budget-hourly-usd 0.72 \
|
| 176 |
+
--apply
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
The current implementation uses `duplicate_space`, `request_space_hardware`,
|
| 180 |
+
`restart_space`, and `pause_space` from `huggingface_hub`. If Hugging Face
|
| 181 |
+
exposes a stable replicas method in the installed hub client, that can become a
|
| 182 |
+
cleaner backend without changing the bucket queue contract.
|
| 183 |
+
|
| 184 |
+
## Faculty Surface
|
| 185 |
+
|
| 186 |
+
MeshScale exposes its own CPU-farm faculty manifest:
|
| 187 |
+
|
| 188 |
+
```bash
|
| 189 |
+
python -m meshscale.cli faculty
|
| 190 |
+
```
|
| 191 |
+
|
| 192 |
+
It can also extract only the faculty/capability contract from the Cocoon
|
| 193 |
+
Authority substrate without importing graph links, followup edges, or route
|
| 194 |
+
associations:
|
| 195 |
+
|
| 196 |
+
```bash
|
| 197 |
+
python -m meshscale.cli faculty --cocoon-capabilities /path/to/cocoon_capabilities.py
|
| 198 |
+
```
|
| 199 |
+
|
| 200 |
+
That keeps MeshScale plugged into the OS substrate vocabulary without copying
|
| 201 |
+
the authority topology.
|
| 202 |
+
|
| 203 |
+
## Symbiotic Trace
|
| 204 |
+
|
| 205 |
+
MeshScale traces its own CPU traffic, pulse health, shard states, worker
|
| 206 |
+
pressure, and expansion requests:
|
| 207 |
+
|
| 208 |
+
```bash
|
| 209 |
+
python -m meshscale.cli symbiotic-trace --root /data/farm
|
| 210 |
+
```
|
| 211 |
+
|
| 212 |
+
When `cascade-lattice` is installed, the trace is interpreted through
|
| 213 |
+
`SymbioticAdapter` and observed through a Cascade monitor. Without Cascade, the
|
| 214 |
+
numeric traffic report still works.
|
| 215 |
+
|
| 216 |
+
## Capacity Planning
|
| 217 |
+
|
| 218 |
+
```bash
|
| 219 |
+
python -m key_farm.cli plan --pending-jobs 120 --active-workers 4 --budget-hourly-usd 0.72
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
At `$0.03/hr`, a `$0.72/hr` ceiling permits 24 CPU Upgrade workers.
|
| 223 |
+
|
| 224 |
+
## HF Setup
|
| 225 |
+
|
| 226 |
+
The clean HF topology is:
|
| 227 |
+
|
| 228 |
+
- `tostido/Ouroboros`: controller and TUI.
|
| 229 |
+
- `tostido/Ouroboros-worker-template`: lightweight CPU worker Space.
|
| 230 |
+
- `tostido/Ouroboros-storage`: mounted bucket at `/data`.
|
| 231 |
+
|
| 232 |
+
HF bucket docs describe buckets as mutable S3-like storage for logs,
|
| 233 |
+
checkpoints, and intermediate artifacts. HF Space volume docs show bucket
|
| 234 |
+
volumes mounted read-write into Spaces, which is exactly what the farm needs.
|
| 235 |
+
The scaler should only start workers from expansion requests and should pause
|
| 236 |
+
workers after `shutdown` intent or controller pulse expiry.
|
| 237 |
+
|
| 238 |
+
## Next Wire-In
|
| 239 |
+
|
| 240 |
+
The package is intentionally standalone first. The next integration point is
|
| 241 |
+
`PopulationManager.evaluate()`:
|
| 242 |
+
|
| 243 |
+
1. If `config["cpu_farm"]["enabled"]` is false, keep current local evaluation.
|
| 244 |
+
2. If enabled, submit the population to `FarmController`.
|
| 245 |
+
3. Wait for every node fitness for that generation.
|
| 246 |
+
4. Set `node.fitness` from returned results.
|
| 247 |
+
5. Continue normal speciation and reproduction locally.
|
| 248 |
+
|
| 249 |
+
That preserves NEAT semantics while scaling the expensive evaluation step.
|
MESHSCALE_CLAUDE_DEEP_AUDIT_2026-06-19.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MeshScale - Claude Deep Audit - 2026-06-19
|
| 2 |
+
|
| 3 |
+
Extends `MESHSCALE_CLAUDE_RETURN_PAYLOAD_2026-06-19.md` (data-plane pass) with a full source audit. Role unchanged: independent corroboration; Codex remains primary; apply nothing on agreement alone, verify each claim against the cited source + live state.
|
| 4 |
+
|
| 5 |
+
## Coverage (what I actually read)
|
| 6 |
+
|
| 7 |
+
Full: key_farm/{contracts,serialization,store,evaluator,worker,controller,regulation,scaler,symbiotic_trace,amalgam_bridge}.py; meshscale_space/{app.py,os_routes.py}; meshscale_worker_space/app.py; KEY config.json (cpu_farm block); population.py farm-integration (304-453); run.py signatures.
|
| 8 |
+
Not audited (lower risk / out of farm-correctness scope): key_farm/{cli,faculty}.py, meshscale_space/static/{app.js,index.html}, meshscale/cli.py, the non-farm bulk of population.py and run.py. No live Space/bucket calls (Codex's runtime lane).
|
| 9 |
+
|
| 10 |
+
## Confirmations (system behaves as claimed)
|
| 11 |
+
|
| 12 |
+
- **V1** Data-plane externalization correct end-to-end (see return payload C1-C4). Reconfirmed.
|
| 13 |
+
- **V2** Farm IS wired into KEY: `PopulationManager.evaluate()` -> `_evaluate_with_cpu_farm()` -> `FarmController.submit_generation`/`collect_generation`, gated by `config.cpu_farm.enabled=true` (population.py:441,318-385). The MESHSCALE.md "Next Wire-In" is done.
|
| 14 |
+
- **V3** Graceful degradation: farm exception -> `fallback_to_local` -> KEY evaluates the generation locally (population.py:386-391; config:320). Farm problems slow KEY, they do not break it.
|
| 15 |
+
- **V4** Live config genuinely yields 1 node/job: `cpu_farm.target_batch_size=1`, `min_batch_size=1` (config.json:309-310) -> batch=max(1,1)=1 -> 32 jobs for pop 32. Reconciles the report's "32 huge JSON files."
|
| 16 |
+
- **V5** Cooling/pause present (scaler.py:116-145): pause on shutdown or controller-lost; `cooling_idle_jobs` cancels active Jobs when pending=0 and claimed=0.
|
| 17 |
+
- **V6** Jobs path is coherent: `_start_jobs` passes explicit `KEY_FARM_TARGET_BATCH_SIZE=1`, mounts the same `tostido/Ouroboros-storage` bucket at `/data`, and passes HF_TOKEN via `secrets=` not plaintext env (scaler.py:686-710).
|
| 18 |
+
- **V7** Claim mutual-exclusion via atomic `rename` with `FileNotFoundError` race handling; result is persisted before the claim is released (store.py:220-244, 246-277) -> idempotent and crash-tolerant.
|
| 19 |
+
- **V8** Trace has a 2 MB per-file read guard (symbiotic_trace.py:15,79) so it will not load a 211 MB file into memory.
|
| 20 |
+
|
| 21 |
+
## HIGH
|
| 22 |
+
|
| 23 |
+
- **H1 - Distributed pickle version-coupling (the #1 future-proof risk; explains Q1/Q2).** Payloads are pickled genome objects; the worker does `from run import create_fitness_function` and `unpack_node` -> `pickle.loads` (evaluator.py:18-20,64; serialization.py:42-71). KEY (controller) and the worker-template therefore must deploy in lockstep with identical genome/class definitions and `run.py`. Any skew -> `pickle.loads` raises or silently builds a wrong object -> node errors (then H2). This is exactly why "did KEY rebuild / did worker-template rebuild" (Q1/Q2) must be a coordinated pair, never one-sided. Fix direction: stamp payloads with a genome/schema version and have the worker reject a mismatch explicitly (clear error) instead of failing deep in unpickle; deploy KEY + worker-template atomically.
|
| 24 |
+
|
| 25 |
+
- **H2 - One bad/slow node stalls the whole generation 900 s, then full LOCAL recompute.** `collect_generation` is all-or-nothing on `expected_node_ids` (controller.py:228-232); a node whose fitness raises never enters `fitness_by_id` (evaluator.py:73-81 record error; controller.result_map skips errors) -> waits the full `timeout_seconds=900` -> `TimeoutError` -> caught -> fallback re-evaluates the ENTIRE generation locally, discarding completed farm work (population.py:373-391). Worst case per affected generation ~900 s wall + full local compute; across a 420-gen run a recurring bad genome class is very expensive. Fix direction: treat error-results as completed with a sentinel/worst fitness so the generation closes; or accept partial farm results and locally evaluate only the missing node_ids; or shorten the farm timeout well below a full-generation local cost.
|
| 26 |
+
|
| 27 |
+
## MEDIUM
|
| 28 |
+
|
| 29 |
+
- **M2 - `objects/` grows unbounded, no GC.** No deletion anywhere in store.py; objects are content-addressed at `root/objects` and `_cpu_farm_run_id` persists across all generations (population.py:355), so genomes differ each gen and the shared paid bucket grows monotonically. Fix: GC objects unreferenced by a live run, or TTL reaper, before long unattended runs.
|
| 30 |
+
|
| 31 |
+
- **M3 - Dashboard/trace are not run-scoped (Q8, broadened).** `_job_counts` and `_result_stats` glob `runs/*/generations/*/...` across ALL runs (symbiotic_trace.py:65-66,139), so `large_files`, `large_file_count`, `failed`, and `node_errors` are all-time aggregates. A fresh run's dashboard shows the old 211 MB files as `large_files` and stays `equilibrium="faulted"` from historical failures (meshscale_space/app.py:284-285). The run_id is already in each path, so scoping is straightforward. Fix: filter trace/dashboard to the current (or last-N) run_id.
|
| 32 |
+
|
| 33 |
+
- **M4 - Ungated mutation endpoints on the controller Space.** `POST /api/regulate` writes regulation (drain/shutdown) with no auth (meshscale_space/app.py:480-499). On a public Space anyone can drain/shutdown the farm (a shutdown then triggers the scaler's job-cancel on next tick). Scaling apply is double-gated (env `MESHSCALE_ENABLE_APPLY` + payload `apply`), but regulate is not. Fix: shared-secret header on mutation endpoints, or keep the controller Space private.
|
| 34 |
+
|
| 35 |
+
- **M5 - No cumulative spend ceiling / run kill-switch.** `budget_hourly_usd` caps CONCURRENT workers (a rate via `effective_max_workers`), not total spend. A controller that stays alive with perpetual backlog bills at the budget rate indefinitely; only per-Job `timeout=3600s` and idle-cooling bound it. Fix: add a max-total-spend or max-run-wallclock guard that publishes `shutdown`.
|
| 36 |
+
|
| 37 |
+
- **M6 - Two divergent worker policy sources.** Worker-Space autostart builds `FarmPolicy` from env with `target_batch_size` default 24 (meshscale_worker_space/app.py:67), while the live Jobs path passes 1. Refraction/split and TTLs use the worker's own policy, not the job's `config`. Low impact while live mode is Jobs, latent otherwise. Fix: derive split/TTL policy from `job.config["cpu_farm"]`, or pin worker env to match KEY.
|
| 38 |
+
|
| 39 |
+
- **M1 - Long synchronous eval breaks pulse/claim liveness (Q7).** Worker writes a pulse only at claim/idle (worker.py:64-80), then runs `evaluate_job` synchronously. A node eval exceeding `pulse_ttl=120s` makes the worker look inactive to `active_worker_count`/scaler; exceeding `claim_ttl=900s` gets the claim requeued (store.py:406-423) -> duplicate eval. No heartbeat during evaluation. Fix: emit pulses during long evals (background ticker / per-node), and keep `claim_ttl` > max expected node time.
|
| 40 |
+
|
| 41 |
+
## LOW
|
| 42 |
+
|
| 43 |
+
- **L1** Object write lacks the `PermissionError` retry the JSON writer has (store.py:166-171 vs 96-106); a contended bucket replace can fail submit. Reuse the atomic-retry wrapper for objects.
|
| 44 |
+
- **L2** `externalize_payloads` is not pinned in config (config.cpu_farm has no such key); it relies on the dataclass default `True`. A default flip silently disables the fix. Set it explicitly.
|
| 45 |
+
- **L3** Pulse liveness compares file mtime to the reader's local clock (store.py:333,346); cross-Space clock skew or bucket mtime semantics can cause false pulse-loss/alive. Tolerate skew or use embedded timestamps.
|
| 46 |
+
- **L4** Externalization threshold is measured on base64 length (~33% > raw), so the effective on-disk trigger is ~196 KB (store.py:159). Document.
|
| 47 |
+
- **L5** Scaler reads controller TTL from a fresh `FarmPolicy()` default (180), ignoring any configured TTL (scaler.py:111-113). Minor.
|
| 48 |
+
- **L6** `_meshscale_jobs` matches its Jobs by label or name-prefix (scaler.py:635-648); Jobs from an older naming scheme could be missed by cooling -> orphan billing. Minor.
|
| 49 |
+
- **L7** `read_regulation` returns `drain` on a corrupt/torn file (store.py:388-395) - safe direction, but torn reads on a non-atomic FS cause transient false drain.
|
| 50 |
+
|
| 51 |
+
## Correction to my own return payload (evidence-first)
|
| 52 |
+
|
| 53 |
+
The return payload implied `min_batch_size=4` floors the batch. Live `config.json` sets `cpu_farm.min_batch_size=1`, so the live batch is genuinely 1. The "floored at 4" detail only applies if `min_batch_size` is left at the dataclass default (4). Q6 tuning therefore works as expected with the live config; the per-node-externalization caution (return payload I1) still applies when raising the batch.
|
| 54 |
+
|
| 55 |
+
## Cross-cutting theme: atomicity on the bucket
|
| 56 |
+
|
| 57 |
+
Multiple mechanisms (claim via `rename`, `_write_json_atomic` via temp+`replace`, object `replace`, pulse-mtime liveness) assume POSIX rename/replace atomicity and trustworthy mtime. The report itself cautions against assuming HF replica/bucket semantics. The 6x `PermissionError` retry in `_write_json_atomic` is evidence this FS is already contended. None of this is necessarily wrong on a true volume mount, but it is the substrate to watch: if `/data` is ever S3-like rather than a POSIX volume, claims can double and writes can tear. Recommend a one-time runtime check that rename is atomic on the live mount.
|
| 58 |
+
|
| 59 |
+
## Open questions - final
|
| 60 |
+
|
| 61 |
+
- Q1/Q2 (KEY/worker-template rebuilt): must be a coordinated pair - see H1 (pickle lockstep). The requirement is now explained, the runtime fact is still Codex's to confirm.
|
| 62 |
+
- Q3/Q4 (file refs + objects under /data/farm/objects): yes (return payload).
|
| 63 |
+
- Q5 (generation time drop): expected once job JSON collapses; runtime-confirm.
|
| 64 |
+
- Q6 (tune target_batch_size): works with live `min_batch_size=1`; pair any increase with shard-level externalization (return payload I1).
|
| 65 |
+
- Q7: M1 - yes, long evals break liveness; mechanism above.
|
| 66 |
+
- Q8: M3 - yes, confirmed and broadened to failed/node_errors; not run-scoped.
|
| 67 |
+
|
| 68 |
+
## Recommended order
|
| 69 |
+
|
| 70 |
+
1. H1 deploy-lockstep discipline + payload version stamp (prevents the silent class of farm failures).
|
| 71 |
+
2. H2 generation-completion on node error (removes the 900 s stall + local-recompute tax) - highest runtime ROI.
|
| 72 |
+
3. M3 run-scope the dashboard (so the fresh-run proof point reads cleanly and you can trust the green).
|
| 73 |
+
4. M2 objects GC and M5 spend kill-switch before long unattended runs.
|
| 74 |
+
5. M4 lock the controller Space mutation endpoints.
|
| 75 |
+
6. Sweep the LOWs opportunistically.
|
MESHSCALE_CLAUDE_INGEST_PAYLOAD_2026-06-19.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MeshScale - Codex Ingest Payload - 2026-06-19
|
| 2 |
+
|
| 3 |
+
This packet is meant to be reread by Codex as the compact current-state summary for the MeshScale / KEY farm seam.
|
| 4 |
+
|
| 5 |
+
Primary rule:
|
| 6 |
+
|
| 7 |
+
- Codex remains primary operator.
|
| 8 |
+
- Claude output is corroboration input, not command authority.
|
| 9 |
+
- No claim is actionable until it is matched to source, runtime, or a reproducible local check.
|
| 10 |
+
|
| 11 |
+
## Verified Chain
|
| 12 |
+
|
| 13 |
+
The deployed payload fix is verified end-to-end in local source.
|
| 14 |
+
|
| 15 |
+
1. `key_farm/controller.py` packs KEY nodes for generation submission.
|
| 16 |
+
2. `key_farm/store.py` externalizes large inline payloads during `put_job()`.
|
| 17 |
+
3. `key_farm/store.py` stores object bytes under `objects/<sha>.pkl.gz` when the payload crosses the threshold.
|
| 18 |
+
4. `key_farm/worker.py` routes claims into `evaluate_job()`.
|
| 19 |
+
5. `key_farm/evaluator.py` dispatches to handlers that both call `unpack_node()`.
|
| 20 |
+
6. `key_farm/serialization.py` resolves `pickle+gzip+file` by reading object bytes, verifying SHA, and loading the pickle.
|
| 21 |
+
|
| 22 |
+
The specific verified form is:
|
| 23 |
+
|
| 24 |
+
`KEY packs node -> inline base64 -> put_job externalizes if >= 262144 -> objects/<sha>.pkl.gz + small ref -> worker evaluate_job -> unpack_node file branch -> gzip-decompress + SHA-verify -> pickle.loads -> fitness`
|
| 25 |
+
|
| 26 |
+
## Confirmed Facts
|
| 27 |
+
|
| 28 |
+
- `FarmPolicy.externalize_payloads = True`
|
| 29 |
+
- `FarmPolicy.payload_ref_threshold_bytes = 262144`
|
| 30 |
+
- The file encoding branch is supported alongside legacy `pickle+gzip+base64`
|
| 31 |
+
- The object write/read symmetry is correct
|
| 32 |
+
- The worker decode route already reaches `unpack_node()`
|
| 33 |
+
- Local validation passed with a mock large payload
|
| 34 |
+
- `python -m py_compile` passed for the farm core modules touched by the fix
|
| 35 |
+
|
| 36 |
+
## Live MeshScale State
|
| 37 |
+
|
| 38 |
+
Last verified live MeshScale status:
|
| 39 |
+
|
| 40 |
+
- Pending jobs: `0`
|
| 41 |
+
- Claimed jobs: `0`
|
| 42 |
+
- Active Hugging Face Jobs: `0`
|
| 43 |
+
- Active worker pulses: `0`
|
| 44 |
+
- Controller pulse alive: `false`
|
| 45 |
+
- Burn rate: `$0.00/h`
|
| 46 |
+
- Autoscaler alive: `true`
|
| 47 |
+
- Autoscaler last reason: `pause`
|
| 48 |
+
|
| 49 |
+
Interpretation:
|
| 50 |
+
|
| 51 |
+
MeshScale is currently idle, not billing, and waiting for a fresh KEY controller pulse or a new run.
|
| 52 |
+
|
| 53 |
+
## Current Risk Notes
|
| 54 |
+
|
| 55 |
+
- Existing 211 MB job files are historical artifacts from the old inline payload format.
|
| 56 |
+
- A fresh KEY run is the proof point for the new format.
|
| 57 |
+
- If KEY and the worker template are not rebuilt together, pickled genome/class skew can still fail runtime decoding.
|
| 58 |
+
- `objects/` cleanup is still an open long-run cost issue.
|
| 59 |
+
- Batch sizes above 1 should be revisited only after the new payload format is confirmed in a fresh run.
|
| 60 |
+
|
| 61 |
+
## What To Check Next
|
| 62 |
+
|
| 63 |
+
1. Confirm KEY rebuild picked up the payload externalization code.
|
| 64 |
+
2. Confirm worker-template rebuild picked up the same farm code.
|
| 65 |
+
3. Start a small fresh run.
|
| 66 |
+
4. Verify new job JSON files are small and that `objects/` receives the large payload bytes.
|
| 67 |
+
5. Check that generation wall time drops materially compared to the historical 211 MB format.
|
| 68 |
+
|
MESHSCALE_CLAUDE_ORIENTATION_NOTE_2026-06-19.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MeshScale - Claude Orientation Note - 2026-06-19
|
| 2 |
+
|
| 3 |
+
This note is for future Claude sessions joining the MeshScale / KEY workflow.
|
| 4 |
+
|
| 5 |
+
## Role
|
| 6 |
+
|
| 7 |
+
- Codex stays primary operator.
|
| 8 |
+
- Claude is the independent corroboration pass.
|
| 9 |
+
- A Claude claim is only actionable after source, runtime, or local repro verification.
|
| 10 |
+
|
| 11 |
+
## Current Live Assumptions
|
| 12 |
+
|
| 13 |
+
- KEY uses the CPU-farm path for node evaluation.
|
| 14 |
+
- MeshScale is the worker substrate, not the controller authority.
|
| 15 |
+
- Hugging Face Jobs are the current worker mechanism.
|
| 16 |
+
- Replicas are not the active compute path for this workload.
|
| 17 |
+
- Live MeshScale was last verified idle, with `0` pending jobs, `0` claimed jobs, `0` active Jobs, and `$0.00/h` burn.
|
| 18 |
+
|
| 19 |
+
## What Was Verified
|
| 20 |
+
|
| 21 |
+
- Large node payloads externalize out of job JSON into `objects/<sha>.pkl.gz`.
|
| 22 |
+
- The worker route still reaches `unpack_node()`.
|
| 23 |
+
- The new file encoding branch is backward compatible with the old inline encoding.
|
| 24 |
+
- The end-to-end pack -> externalize -> claim -> decode -> fitness path is coherent in local source.
|
| 25 |
+
|
| 26 |
+
## What To Recheck First On Return
|
| 27 |
+
|
| 28 |
+
1. Read `MESHSCALE_PRIMARY_COORDINATION_REPORT_2026-06-19.md`.
|
| 29 |
+
2. Read `MESHSCALE_CLAUDE_RETURN_PAYLOAD_2026-06-19.md`.
|
| 30 |
+
3. Confirm the live MeshScale endpoint still shows idle or active state correctly.
|
| 31 |
+
4. Verify whether a fresh KEY run is using the new file-reference payload format.
|
| 32 |
+
5. Treat old 211 MB artifacts as historical until a fresh run shows otherwise.
|
| 33 |
+
|
| 34 |
+
## Do Not Lose
|
| 35 |
+
|
| 36 |
+
- Codex remains the operator.
|
| 37 |
+
- Evidence beats agreement.
|
| 38 |
+
- Live runtime beats archive tone.
|
| 39 |
+
- Generated champion files stay untouched.
|
| 40 |
+
- `objects/` growth still needs long-run attention.
|
| 41 |
+
|
MESHSCALE_CLAUDE_RETURN_PAYLOAD_2026-06-19.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MeshScale - Claude Return Payload - 2026-06-19
|
| 2 |
+
|
| 3 |
+
Role: independent corroboration pass. Codex remains primary operator. This is a proposal packet per `MESHSCALE_PRIMARY_COORDINATION_REPORT_2026-06-19.md`: claims first, each tied to a source surface and a verification status. Apply nothing on conceptual agreement alone.
|
| 4 |
+
|
| 5 |
+
## Verification Scope
|
| 6 |
+
|
| 7 |
+
I verified the payload fix end-to-end in local source only:
|
| 8 |
+
|
| 9 |
+
- `key_farm/contracts.py`
|
| 10 |
+
- `key_farm/serialization.py`
|
| 11 |
+
- `key_farm/store.py` (1-190)
|
| 12 |
+
- `key_farm/worker.py` (delegation)
|
| 13 |
+
- `key_farm/evaluator.py` (unpack path)
|
| 14 |
+
|
| 15 |
+
I did not read `controller.py`, `scaler.py`, `symbiotic_trace.py`, `meshscale_space/app.py`, `population.py`, `KEY worker.py`, or `config.json`. Claims touching those surfaces are unverified in this packet. I did not hit live Spaces or the bucket; that stays Codex's runtime lane.
|
| 16 |
+
|
| 17 |
+
## Confirmed
|
| 18 |
+
|
| 19 |
+
- `C1` - Deployed payload fix is present and correct, end-to-end.
|
| 20 |
+
- Policy: `FarmPolicy.externalize_payloads=True`, `payload_ref_threshold_bytes=262144` (`contracts.py`).
|
| 21 |
+
- Submit path: `put_job` -> `_externalize_job_payloads` -> `_externalize_node_payload` (`store.py`). Inline base64 is decoded back to `gzip(pickle)` bytes and written to `objects/<sha>.pkl.gz`; job JSON keeps `{encoding: "pickle+gzip+file", payload: "objects/<sha>.pkl.gz", sha256}`.
|
| 22 |
+
- Decode path: `worker.py` -> `evaluator.evaluate_job` -> `unpack_node`. `unpack_node` FILE branch does `gzip.decompress(file)` -> raw pickle -> SHA check vs `node.sha256` -> `pickle.loads` (`serialization.py`).
|
| 23 |
+
- Write/read symmetry holds: sha is computed over the uncompressed pickle on both sides. No silent corruption. The 211 MB inline-JSON failure mode is genuinely addressed.
|
| 24 |
+
|
| 25 |
+
- `C2` - Backward compatible. `unpack_node` still handles legacy `pickle+gzip+base64`. SHA verification is retained on both encodings. Old inline jobs and not-yet-rebuilt workers should not break on the new code.
|
| 26 |
+
|
| 27 |
+
- `C3` - Object store is farm-root-global, content-addressed, and deduped. `objects_dir = root/objects`; `if not object_path.exists()` skips rewrite. References resolve to `/data/farm/objects/` via `_farm_root()`, matching the expected evidence location.
|
| 28 |
+
|
| 29 |
+
- `C4` - Externalization is on-by-default. `_externalize_job_payloads` reads policy from `job.config["cpu_farm"]` via `FarmPolicy.from_dict`; `from_dict(None)` returns defaults with `externalize_payloads=True`. So a fresh run externalizes any inline node at or above threshold even if KEY passes no `cpu_farm` block. The only ways it does not apply are: node already non-inline, `len(payload) < 262144`, or policy explicitly sets `externalize_payloads=false`.
|
| 30 |
+
|
| 31 |
+
## New Issues
|
| 32 |
+
|
| 33 |
+
- `I1 [MEDIUM]` Per-node threshold regresses under batch growth. Externalization decides per node (`len(node.payload) < threshold`). Live `target_batch_size=1` is fine. But the dataclass default is `target_batch_size=24`, and if batch size rises a shard full of sub-threshold nodes re-bloats the JSON with zero externalization. Fix direction: externalize at the shard level when serialized job size exceeds a threshold, not only per node.
|
| 34 |
+
|
| 35 |
+
- `I2 [MEDIUM]` `objects/` never gets garbage-collected. No retention exists anywhere in `store.py`. Genomes differ each generation, so dedupe does not bound growth. The shared paid bucket will grow monotonically unless a sweep or TTL reaper is added.
|
| 36 |
+
|
| 37 |
+
- `I3 [LOW]` Object-write resiliency is asymmetric. `_write_json_atomic` retries `PermissionError` 6x, but the object write path does not share that retry behavior. A contended object replace can fail submit.
|
| 38 |
+
|
| 39 |
+
- `I4 [LOW/NOTE]` The threshold is measured on encoded length, so the effective on-disk trigger is about 196 KB, not 256 KB raw bytes.
|
| 40 |
+
|
| 41 |
+
- `I5 [LOW]` Relative object refs assume every process shares `KEY_FARM_ROOT`. That is true in the standard `/data/farm` topology, but tests or alternate mounts can mis-resolve refs unless they match the same root.
|
| 42 |
+
|
| 43 |
+
## Open Questions
|
| 44 |
+
|
| 45 |
+
- `Q3/Q4` yes: fresh runs should produce `pickle+gzip+file` refs and `objects/<sha>.pkl.gz`.
|
| 46 |
+
- `Q6` answered by `I1`: batch growth reintroduces bloat unless externalization moves to the shard level.
|
| 47 |
+
- `Q7/Q8` unknown-until-checked in this packet: I did not read worker pulse cadence or the dashboard.
|
| 48 |
+
- `Q1/Q2/Q5` are deploy/runtime facts in Codex's lane.
|
| 49 |
+
|
| 50 |
+
## Honest Scope
|
| 51 |
+
|
| 52 |
+
I verified the data-plane fix only. I did not read `scaler.py`, the dashboard, controller, or population, so the autoscaler section of the broader report remains unverified by me in this packet.
|
| 53 |
+
|
| 54 |
+
## Artifact
|
| 55 |
+
|
| 56 |
+
This file is the ingestible payload for Codex. The deeper audit belongs in `MESHSCALE_CLAUDE_DEEP_AUDIT_2026-06-19.md`.
|
| 57 |
+
|
MESHSCALE_PRIMARY_COORDINATION_REPORT_2026-06-19.md
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MeshScale Primary Coordination Report - 2026-06-19
|
| 2 |
+
|
| 3 |
+
This is not a handoff. Codex remains the primary operator. Claude or any other model output should be treated as an incoming proposal packet that must be verified against local source, live Hugging Face Space state, and bucket evidence before any change is applied.
|
| 4 |
+
|
| 5 |
+
## Operating Rule
|
| 6 |
+
|
| 7 |
+
Incoming analysis should be sequenced through this order:
|
| 8 |
+
|
| 9 |
+
1. Identify the claim.
|
| 10 |
+
2. Locate the source surface it affects.
|
| 11 |
+
3. Verify against local code.
|
| 12 |
+
4. Verify against live MeshScale, KEY, worker-template, or bucket state when the claim is runtime-related.
|
| 13 |
+
5. Apply the narrowest source change that satisfies the verified problem.
|
| 14 |
+
6. Re-test locally where possible.
|
| 15 |
+
7. Deploy narrowly to the relevant Hugging Face Space or Spaces.
|
| 16 |
+
8. Re-check live state after rebuild.
|
| 17 |
+
|
| 18 |
+
Do not treat conceptual agreement as implementation proof.
|
| 19 |
+
|
| 20 |
+
## Claude Corroboration Lane
|
| 21 |
+
|
| 22 |
+
Claude's role in this sequence is independent corroboration, not command authority.
|
| 23 |
+
|
| 24 |
+
Claude output should be parsed into:
|
| 25 |
+
|
| 26 |
+
- claims
|
| 27 |
+
- evidence cited
|
| 28 |
+
- affected source surfaces
|
| 29 |
+
- affected runtime surfaces
|
| 30 |
+
- proposed edits
|
| 31 |
+
- risks or unresolved seams
|
| 32 |
+
|
| 33 |
+
Codex then verifies each item against the active machinery before action. A Claude claim can become actionable only after it is matched to at least one concrete source file, runtime endpoint, bucket artifact, or reproducible local check. If Claude's reasoning is sound but evidence is absent, the next action is a read or probe, not a patch.
|
| 34 |
+
|
| 35 |
+
Claude can veto weak reasoning by exposing an unverified seam. Claude cannot override live evidence, source truth, operator constraints, or the no-generated-champion-files boundary.
|
| 36 |
+
|
| 37 |
+
## Claude Corroboration Receipts
|
| 38 |
+
|
| 39 |
+
### Receipt 001 - Payload Object Encoding Symmetry
|
| 40 |
+
|
| 41 |
+
Claude claim:
|
| 42 |
+
|
| 43 |
+
The deployed object-reference payload fix is correct only if `store.py` writes object bytes in the exact form `serialization._decode_file_payload()` reads them. A mismatch would cause worker-side decode failures.
|
| 44 |
+
|
| 45 |
+
Codex verification:
|
| 46 |
+
|
| 47 |
+
Confirmed against local source.
|
| 48 |
+
|
| 49 |
+
- `key_farm/store.py` decodes the inline payload with `base64.b64decode(node.payload.encode("ascii"))` and writes those bytes to `objects/<sha>.pkl.gz`.
|
| 50 |
+
- `key_farm/serialization.py` resolves the file reference and calls `gzip.decompress(path.read_bytes())`.
|
| 51 |
+
- The original inline format is `base64(gzip(pickle_bytes))`, so stripping base64 and storing the gzip bytes is exactly what the file decoder expects.
|
| 52 |
+
|
| 53 |
+
Status:
|
| 54 |
+
|
| 55 |
+
Confirmed. No patch required for this seam.
|
| 56 |
+
|
| 57 |
+
### Receipt 002 - Worker Decode Route
|
| 58 |
+
|
| 59 |
+
Claude claim:
|
| 60 |
+
|
| 61 |
+
The worker must route file-encoded nodes through `unpack_node()`, otherwise the new object-reference payload format would never reach the actual decode path.
|
| 62 |
+
|
| 63 |
+
Codex verification:
|
| 64 |
+
|
| 65 |
+
Confirmed against local source.
|
| 66 |
+
|
| 67 |
+
- `key_farm/worker.py` calls `evaluate_job(job, worker_id)` when a shard is ready for execution.
|
| 68 |
+
- `key_farm/evaluator.py` dispatches that job to `evaluate_key_fitness()` or `evaluate_python_callable()` through `get_handler()`.
|
| 69 |
+
- Both handlers call `unpack_node(node_payload)` before using the node object.
|
| 70 |
+
|
| 71 |
+
Status:
|
| 72 |
+
|
| 73 |
+
Confirmed. The worker execution path already uses the shared unpacker, so the new `pickle+gzip+file` format is reachable without any extra worker-side routing patch.
|
| 74 |
+
|
| 75 |
+
### Receipt 003 - End-To-End Payload Round Trip
|
| 76 |
+
|
| 77 |
+
Claude claim:
|
| 78 |
+
|
| 79 |
+
The full round trip is now verified in local source:
|
| 80 |
+
|
| 81 |
+
`KEY packs node -> inline base64 -> put_job externalizes if >= 262144 -> objects/<sha>.pkl.gz + small ref -> worker evaluate_job -> unpack_node file branch -> gzip-decompress + SHA-verify -> pickle.loads -> fitness`
|
| 82 |
+
|
| 83 |
+
Codex verification:
|
| 84 |
+
|
| 85 |
+
Confirmed against the active source chain.
|
| 86 |
+
|
| 87 |
+
- `key_farm/controller.py` calls `pack_node()` when submitting KEY fitness generations.
|
| 88 |
+
- `key_farm/store.py` externalizes large inline payloads during `put_job()`.
|
| 89 |
+
- `key_farm/worker.py` routes claims into `evaluate_job()`.
|
| 90 |
+
- `key_farm/evaluator.py` reaches `unpack_node()` for both supported handlers.
|
| 91 |
+
- `key_farm/serialization.py` resolves `pickle+gzip+file` by reading object bytes, verifying SHA, and loading the pickle.
|
| 92 |
+
|
| 93 |
+
Status:
|
| 94 |
+
|
| 95 |
+
Confirmed. The deployed fix is real and coherent end-to-end on the local source path.
|
| 96 |
+
|
| 97 |
+
## Active Resources
|
| 98 |
+
|
| 99 |
+
- KEY Space: `tostido/Ouroboros`
|
| 100 |
+
- MeshScale controller Space: `tostido/meshscale`
|
| 101 |
+
- MeshScale worker template Space: `tostido/meshscale-worker-template`
|
| 102 |
+
- Shared bucket: `tostido/Ouroboros-storage`
|
| 103 |
+
- Local repo root: `D:\End-Game\ended_game\KEY-literal`
|
| 104 |
+
|
| 105 |
+
## Hard Constraints
|
| 106 |
+
|
| 107 |
+
- Do not edit generated champion files.
|
| 108 |
+
- Do not revert unrelated local changes.
|
| 109 |
+
- Do not full-folder upload unless specifically required.
|
| 110 |
+
- Prefer narrow `HfApi.upload_file` deploys for shared farm code.
|
| 111 |
+
- Treat the bucket as shared runtime state, not ordinary disposable repo content.
|
| 112 |
+
- Do not assume Hugging Face replica semantics apply to background compute. This system is currently using Hugging Face Jobs for worker execution.
|
| 113 |
+
|
| 114 |
+
## Current Verified Live State
|
| 115 |
+
|
| 116 |
+
Live MeshScale check after resuming showed:
|
| 117 |
+
|
| 118 |
+
- Pending jobs: `0`
|
| 119 |
+
- Claimed jobs: `0`
|
| 120 |
+
- Active worker pulses: `0`
|
| 121 |
+
- Active Hugging Face Jobs: `0`
|
| 122 |
+
- Controller pulse alive: `false`
|
| 123 |
+
- Burn rate: `$0.00/h`
|
| 124 |
+
- Autoscaler alive: `true`
|
| 125 |
+
- Autoscaler last reason: `pause`
|
| 126 |
+
- Live farm policy includes:
|
| 127 |
+
- `target_batch_size: 1`
|
| 128 |
+
- `max_workers: 48`
|
| 129 |
+
- `budget_hourly_usd: 1.44`
|
| 130 |
+
- `externalize_payloads: true`
|
| 131 |
+
- `payload_ref_threshold_bytes: 262144`
|
| 132 |
+
|
| 133 |
+
Interpretation:
|
| 134 |
+
|
| 135 |
+
MeshScale is not currently billing for active Jobs. It is idle because KEY is not publishing a fresh live controller pulse into the farm.
|
| 136 |
+
|
| 137 |
+
## Confirmed Problem
|
| 138 |
+
|
| 139 |
+
The farm worked functionally, but the previous payload data plane was too heavy.
|
| 140 |
+
|
| 141 |
+
Observed historical run state:
|
| 142 |
+
|
| 143 |
+
- Run id: `run_250aa8b0f80d4d64`
|
| 144 |
+
- Completed farm generations visible: `0` and `1`
|
| 145 |
+
- Completed shards: `64`
|
| 146 |
+
- Node results: `64`
|
| 147 |
+
- Node errors: `0`
|
| 148 |
+
- Typical done job file size: about `211 MB`
|
| 149 |
+
- Large job files remain in old run data under `/data/farm/runs/.../jobs/done`
|
| 150 |
+
|
| 151 |
+
The old design embedded serialized node payloads directly inside every job JSON. With `target_batch_size=1` and population `32`, one generation created 32 huge JSON files. State transitions then copied or rewrote those heavy payloads through bucket storage. This made MeshScale appear slow even while Jobs were processing.
|
| 152 |
+
|
| 153 |
+
## Deployed Fix
|
| 154 |
+
|
| 155 |
+
The shared farm code now externalizes large serialized node payloads into object files and leaves small references in job JSON.
|
| 156 |
+
|
| 157 |
+
Uploaded narrowly to:
|
| 158 |
+
|
| 159 |
+
- `tostido/meshscale`
|
| 160 |
+
- `tostido/meshscale-worker-template`
|
| 161 |
+
- `tostido/Ouroboros`
|
| 162 |
+
|
| 163 |
+
Uploaded files:
|
| 164 |
+
|
| 165 |
+
- `key_farm/contracts.py`
|
| 166 |
+
- `key_farm/serialization.py`
|
| 167 |
+
- `key_farm/store.py`
|
| 168 |
+
- `key_farm/scaler.py`
|
| 169 |
+
|
| 170 |
+
Behavior added:
|
| 171 |
+
|
| 172 |
+
- `FarmPolicy.externalize_payloads = True`
|
| 173 |
+
- `FarmPolicy.payload_ref_threshold_bytes = 262144`
|
| 174 |
+
- Large inline payloads become objects under `objects/<sha>.pkl.gz`
|
| 175 |
+
- Job JSON stores `encoding: pickle+gzip+file`
|
| 176 |
+
- Worker-side unpacking still supports old `pickle+gzip+base64`
|
| 177 |
+
- SHA verification remains in place
|
| 178 |
+
|
| 179 |
+
Local validation:
|
| 180 |
+
|
| 181 |
+
- A mock 1 MB payload produced a `642` byte job file.
|
| 182 |
+
- The referenced payload unpacked correctly.
|
| 183 |
+
- `python -m py_compile` passed for:
|
| 184 |
+
- `key_farm/contracts.py`
|
| 185 |
+
- `key_farm/serialization.py`
|
| 186 |
+
- `key_farm/store.py`
|
| 187 |
+
- `key_farm/scaler.py`
|
| 188 |
+
- `key_farm/controller.py`
|
| 189 |
+
- `key_farm/worker.py`
|
| 190 |
+
|
| 191 |
+
## Autoscaler Cooling Fix
|
| 192 |
+
|
| 193 |
+
The scaler has an idle-cooling path for Jobs mode:
|
| 194 |
+
|
| 195 |
+
- If controller is alive, pending count is `0`, claimed count is `0`, and active Jobs exist, cancel Jobs and report `cooling_idle_jobs`.
|
| 196 |
+
- If controller pulse is lost and `pause_on_controller_lost` is true, cancel Jobs through the pause path.
|
| 197 |
+
|
| 198 |
+
Live status after deploy showed the pause path had already brought active Jobs to `0`.
|
| 199 |
+
|
| 200 |
+
## Important Caveat
|
| 201 |
+
|
| 202 |
+
Existing giant job files are historical run artifacts. They do not prove the new code failed unless they appear in a new run submitted after KEY has rebuilt with the new farm code.
|
| 203 |
+
|
| 204 |
+
The next real validation is a fresh KEY generation submitted after rebuild. Expected evidence:
|
| 205 |
+
|
| 206 |
+
- New pending/claimed/done job JSON files should be small.
|
| 207 |
+
- New object files should appear under `/data/farm/objects/`.
|
| 208 |
+
- Node results should still populate normally.
|
| 209 |
+
- MeshScale should scale Jobs while pending exists, then return to zero active Jobs when the queue drains.
|
| 210 |
+
|
| 211 |
+
## Verification Targets For Incoming Payloads
|
| 212 |
+
|
| 213 |
+
Any proposal from Claude should be checked against these local surfaces:
|
| 214 |
+
|
| 215 |
+
- `key_farm/contracts.py`
|
| 216 |
+
- `key_farm/serialization.py`
|
| 217 |
+
- `key_farm/store.py`
|
| 218 |
+
- `key_farm/controller.py`
|
| 219 |
+
- `key_farm/worker.py`
|
| 220 |
+
- `key_farm/scaler.py`
|
| 221 |
+
- `key_farm/symbiotic_trace.py`
|
| 222 |
+
- `meshscale_space/app.py`
|
| 223 |
+
- `meshscale_space/static/app.js`
|
| 224 |
+
- KEY `config.json`
|
| 225 |
+
- KEY `population.py`
|
| 226 |
+
- KEY `worker.py`
|
| 227 |
+
|
| 228 |
+
Runtime verification targets:
|
| 229 |
+
|
| 230 |
+
- MeshScale status endpoint: `https://tostido-meshscale.hf.space/api/status`
|
| 231 |
+
- MeshScale dashboard endpoint: `https://tostido-meshscale.hf.space/api/dashboard`
|
| 232 |
+
- KEY Space: `https://huggingface.co/spaces/tostido/Ouroboros`
|
| 233 |
+
- Bucket: `https://huggingface.co/buckets/tostido/Ouroboros-storage`
|
| 234 |
+
|
| 235 |
+
## Open Technical Questions
|
| 236 |
+
|
| 237 |
+
1. Did KEY rebuild after the object-reference payload fix?
|
| 238 |
+
2. Did worker-template rebuild before any new Jobs were launched?
|
| 239 |
+
3. Does the next fresh run produce `pickle+gzip+file` node payload references?
|
| 240 |
+
4. Are object files visible under `/data/farm/objects/` during the fresh run?
|
| 241 |
+
5. Does generation time drop once job JSON size collapses?
|
| 242 |
+
6. Is `target_batch_size=1` still optimal after payload externalization, or should it be tuned to `2`, `4`, or adaptive per generation?
|
| 243 |
+
7. Should worker pulses continue during long node evaluation to avoid false pulse loss on unusually heavy nodes?
|
| 244 |
+
8. Should the dashboard separate historical large files from current-run large files to avoid false alarms?
|
| 245 |
+
|
| 246 |
+
## Suggested Next Sequence
|
| 247 |
+
|
| 248 |
+
1. Wait for Claude's payload.
|
| 249 |
+
2. Extract concrete claims only.
|
| 250 |
+
3. Ignore style-only or speculative architecture suggestions unless they map to a verified failure.
|
| 251 |
+
4. Re-check live MeshScale state.
|
| 252 |
+
5. If KEY is idle, start a small fresh run after rebuild to validate the new payload format.
|
| 253 |
+
6. Inspect the new farm run tree for small job JSON and object references.
|
| 254 |
+
7. If validated, scale up run size cautiously.
|
| 255 |
+
8. If not validated, inspect why `FarmStore._externalize_job_payloads()` did not apply.
|
| 256 |
+
|
| 257 |
+
## Current Primary Diagnosis
|
| 258 |
+
|
| 259 |
+
MeshScale's architecture is viable using Hugging Face Jobs. The immediate speed failure was a data-plane design issue: enormous serialized node payloads were embedded in job JSON and repeatedly moved through bucket-backed state folders. The deployed fix changes the queue from payload-heavy JSON to payload references plus shared object files. The next fresh run is the proof point.
|
MESHSCALE_SITREP_TRAJECTORY_FOR_CLAUDE_2026-06-19.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MeshScale / KEY Sitrep and Projected Trajectory - 2026-06-19
|
| 2 |
+
|
| 3 |
+
Role: briefing packet for Claude to audit. Codex remains primary operator. This document separates verified state, inferred trajectory, and remaining risk surfaces.
|
| 4 |
+
|
| 5 |
+
## Bottom Line
|
| 6 |
+
|
| 7 |
+
The farm path is real and functional. The main throughput problem that was making KEY feel slow was not compute failure in the farm path itself, but the old data plane: giant node payloads were being carried inside job JSON. That has been fixed in source.
|
| 8 |
+
|
| 9 |
+
The next constraint is not rendering or GUI work on the worker side. The worker should stay headless. Any visual or routing surface belongs in the controller/dashboard layer, not in the compute worker.
|
| 10 |
+
|
| 11 |
+
## Last Verified State
|
| 12 |
+
|
| 13 |
+
- KEY CPU farm integration is enabled in `config.json`.
|
| 14 |
+
- Live farm policy in config:
|
| 15 |
+
- `target_batch_size: 1`
|
| 16 |
+
- `min_batch_size: 1`
|
| 17 |
+
- `max_workers: 48`
|
| 18 |
+
- `budget_hourly_usd: 1.44`
|
| 19 |
+
- `cpu_space_hourly_usd: 0.03`
|
| 20 |
+
- `timeout_seconds: 900`
|
| 21 |
+
- `fallback_to_local: true`
|
| 22 |
+
- MeshScale was last verified idle:
|
| 23 |
+
- pending jobs: `0`
|
| 24 |
+
- claimed jobs: `0`
|
| 25 |
+
- active jobs: `0`
|
| 26 |
+
- active worker pulses: `0`
|
| 27 |
+
- controller pulse alive: `false`
|
| 28 |
+
- burn: `$0.00/h`
|
| 29 |
+
- The shared bucket is the runtime state surface:
|
| 30 |
+
- `tostido/Ouroboros-storage`
|
| 31 |
+
- farm root: `/data/farm`
|
| 32 |
+
|
| 33 |
+
## What Is Already Verified
|
| 34 |
+
|
| 35 |
+
### 1) Data-plane payload fix is correct
|
| 36 |
+
|
| 37 |
+
The new object-reference flow is coherent end-to-end:
|
| 38 |
+
|
| 39 |
+
`KEY pack -> job submit -> object externalization -> objects/<sha>.pkl.gz -> worker unpack -> SHA verify -> pickle.loads`
|
| 40 |
+
|
| 41 |
+
Verified seams:
|
| 42 |
+
|
| 43 |
+
- `key_farm/contracts.py`
|
| 44 |
+
- `key_farm/serialization.py`
|
| 45 |
+
- `key_farm/store.py`
|
| 46 |
+
- `key_farm/controller.py`
|
| 47 |
+
- `key_farm/worker.py`
|
| 48 |
+
- `key_farm/evaluator.py`
|
| 49 |
+
|
| 50 |
+
Result:
|
| 51 |
+
|
| 52 |
+
- Large payloads are externalized as files under `objects/<sha>.pkl.gz`.
|
| 53 |
+
- Job JSON keeps a small reference instead of embedding the full payload.
|
| 54 |
+
- The worker still supports the legacy inline base64 path.
|
| 55 |
+
|
| 56 |
+
### 2) KEY is wired into the farm
|
| 57 |
+
|
| 58 |
+
The evolution loop calls the farm path first and only falls back locally on failure.
|
| 59 |
+
|
| 60 |
+
Relevant behavior:
|
| 61 |
+
|
| 62 |
+
- `PopulationManager.evaluate()` routes through the CPU farm path.
|
| 63 |
+
- `fallback_to_local: true` means farm issues slow KEY, but do not break it.
|
| 64 |
+
- This is why the system was slow, not dead.
|
| 65 |
+
|
| 66 |
+
### 3) H2 partial-generation fix is in place
|
| 67 |
+
|
| 68 |
+
The previous all-or-nothing behavior was the expensive stall:
|
| 69 |
+
|
| 70 |
+
- one bad or slow node could force a full generation timeout
|
| 71 |
+
- then the whole generation would be recomputed locally
|
| 72 |
+
|
| 73 |
+
That path has been improved so partial results are preserved and only missing nodes are filled locally.
|
| 74 |
+
`PopulationManager` already stores `self.fitness_fn` in `__init__`, so the local fill path is real and only applies to missing nodes, not the full generation.
|
| 75 |
+
|
| 76 |
+
### 4) MeshScale control-plane logic is real
|
| 77 |
+
|
| 78 |
+
The OS route surface exists and is useful as a steering layer:
|
| 79 |
+
|
| 80 |
+
- status
|
| 81 |
+
- plan
|
| 82 |
+
- trace
|
| 83 |
+
- regulate
|
| 84 |
+
- scale
|
| 85 |
+
- bucket routing
|
| 86 |
+
|
| 87 |
+
This is the right place for guidance and telemetry.
|
| 88 |
+
It is not the place to do the compute.
|
| 89 |
+
|
| 90 |
+
## Current Constraints That Still Matter
|
| 91 |
+
|
| 92 |
+
### H1 - KEY and worker-template must stay in lockstep
|
| 93 |
+
|
| 94 |
+
Payloads are pickled genome objects. That makes the worker-template sensitive to code drift in `run.py` / genome classes.
|
| 95 |
+
|
| 96 |
+
If KEY rebuilds and the worker-template does not, or vice versa, the unpickle path can break or silently build the wrong object.
|
| 97 |
+
|
| 98 |
+
Practical implication:
|
| 99 |
+
|
| 100 |
+
- rebuild KEY and worker-template together
|
| 101 |
+
- do not treat them as independently safe deploys
|
| 102 |
+
|
| 103 |
+
### M1 - Long evals can still threaten pulse liveness
|
| 104 |
+
|
| 105 |
+
Worker pulses are not continuous during evaluation.
|
| 106 |
+
|
| 107 |
+
If a node runs longer than the pulse/claim assumptions, the system can look stale and either requeue or delay work.
|
| 108 |
+
|
| 109 |
+
This is not a full failure, but it is a real efficiency leak.
|
| 110 |
+
|
| 111 |
+
### M2 - Object storage can grow without bound
|
| 112 |
+
|
| 113 |
+
The new `objects/` path is content-addressed and deduped, but there is no GC by default.
|
| 114 |
+
|
| 115 |
+
If runs keep producing new genomes, the bucket will grow monotonically unless a retention policy is added.
|
| 116 |
+
|
| 117 |
+
### M3 - Trace/dashboard are not run-scoped
|
| 118 |
+
|
| 119 |
+
Historical files are still counted in trace summaries.
|
| 120 |
+
|
| 121 |
+
That means old large files can keep the dashboard looking noisy even when a fresh run is clean.
|
| 122 |
+
|
| 123 |
+
## Projected Trajectory
|
| 124 |
+
|
| 125 |
+
### Best-case near term
|
| 126 |
+
|
| 127 |
+
If the next fresh run starts after KEY and worker-template are rebuilt together:
|
| 128 |
+
|
| 129 |
+
- new job files stay small
|
| 130 |
+
- object refs appear under `/data/farm/objects/`
|
| 131 |
+
- MeshScale can scale only while there is actual backlog
|
| 132 |
+
- after backlog drains, jobs cool back to zero
|
| 133 |
+
- KEY spends time on model work instead of moving huge JSON blobs around
|
| 134 |
+
|
| 135 |
+
This is the likely path to visible speedup.
|
| 136 |
+
|
| 137 |
+
### More realistic near term
|
| 138 |
+
|
| 139 |
+
The data-plane fix should remove the worst overhead immediately, but the evolution loop will still be limited by actual fitness computation.
|
| 140 |
+
|
| 141 |
+
So the improvement will probably look like:
|
| 142 |
+
|
| 143 |
+
- less time lost to storage churn
|
| 144 |
+
- fewer stall/recompute events
|
| 145 |
+
- cleaner scaling behavior
|
| 146 |
+
- not instant magic throughput
|
| 147 |
+
|
| 148 |
+
### Failure mode if we push too hard
|
| 149 |
+
|
| 150 |
+
If batch size is raised before shard-level externalization exists:
|
| 151 |
+
|
| 152 |
+
- per-node payloads can bloat the JSON again
|
| 153 |
+
- the original bottleneck comes back in a different shape
|
| 154 |
+
|
| 155 |
+
If KEY and worker-template drift:
|
| 156 |
+
|
| 157 |
+
- pickle compatibility becomes the first failure
|
| 158 |
+
|
| 159 |
+
If long evals exceed pulse assumptions:
|
| 160 |
+
|
| 161 |
+
- claims can look stale and work can be duplicated
|
| 162 |
+
|
| 163 |
+
## What Claude Should Audit Next
|
| 164 |
+
|
| 165 |
+
1. Confirm the next fresh run was actually submitted after rebuild.
|
| 166 |
+
2. Inspect the fresh run tree in the bucket for:
|
| 167 |
+
- small job JSON
|
| 168 |
+
- object refs
|
| 169 |
+
- new `objects/<sha>.pkl.gz` files
|
| 170 |
+
3. Verify the worker-template revision matches KEY-side genome/schema expectations.
|
| 171 |
+
4. Check whether dashboard/trace should be run-scoped before the next long run.
|
| 172 |
+
5. Decide whether `target_batch_size` should stay at `1` or move to an adaptive policy after the object-ref path proves stable.
|
| 173 |
+
|
| 174 |
+
## Recommended Operator Order
|
| 175 |
+
|
| 176 |
+
1. Keep the worker headless.
|
| 177 |
+
2. Use MeshScale as the control plane, not the compute plane.
|
| 178 |
+
3. Validate a fresh run after lockstep rebuild.
|
| 179 |
+
4. Only raise batch size after the object-ref path is proven clean at scale.
|
| 180 |
+
5. Add retention and run-scoping before very long unattended runs.
|
| 181 |
+
|
| 182 |
+
## Evidence Anchors
|
| 183 |
+
|
| 184 |
+
- `config.json`
|
| 185 |
+
- `population.py`
|
| 186 |
+
- `key_farm/contracts.py`
|
| 187 |
+
- `key_farm/serialization.py`
|
| 188 |
+
- `key_farm/store.py`
|
| 189 |
+
- `key_farm/controller.py`
|
| 190 |
+
- `key_farm/evaluator.py`
|
| 191 |
+
- `key_farm/worker.py`
|
| 192 |
+
- `key_farm/scaler.py`
|
| 193 |
+
- `meshscale_space/app.py`
|
| 194 |
+
- `meshscale_space/os_routes.py`
|
| 195 |
+
- `meshscale_worker_space/app.py`
|
| 196 |
+
|
| 197 |
+
## Short Answer For Claude
|
| 198 |
+
|
| 199 |
+
The system is not blocked on rendering. It is blocked on verifying that the fresh run is using the new payload path and on keeping KEY/worker-template synchronized. The control plane is usable now; the main remaining work is proving the next run is clean and then deciding whether batch size should be increased.
|
key_farm/__init__.py
CHANGED
|
@@ -5,7 +5,7 @@ fitness-evaluation shards to CPU workers.
|
|
| 5 |
"""
|
| 6 |
|
| 7 |
from .contracts import FarmPolicy, RegulationIntent, ShardJob, ShardResult
|
| 8 |
-
from .controller import FarmController
|
| 9 |
from .evaluator import get_handler, register_handler
|
| 10 |
from .faculty import farm_faculty_manifest, load_cocoon_faculty
|
| 11 |
from .scaler import MeshScaleScaler, ScalePolicy
|
|
@@ -14,6 +14,7 @@ from .symbiotic_trace import symbiotic_trace
|
|
| 14 |
|
| 15 |
__all__ = [
|
| 16 |
"FarmController",
|
|
|
|
| 17 |
"FarmPolicy",
|
| 18 |
"FarmStore",
|
| 19 |
"MeshScaleScaler",
|
|
|
|
| 5 |
"""
|
| 6 |
|
| 7 |
from .contracts import FarmPolicy, RegulationIntent, ShardJob, ShardResult
|
| 8 |
+
from .controller import FarmController, IncompleteGenerationError
|
| 9 |
from .evaluator import get_handler, register_handler
|
| 10 |
from .faculty import farm_faculty_manifest, load_cocoon_faculty
|
| 11 |
from .scaler import MeshScaleScaler, ScalePolicy
|
|
|
|
| 14 |
|
| 15 |
__all__ = [
|
| 16 |
"FarmController",
|
| 17 |
+
"IncompleteGenerationError",
|
| 18 |
"FarmPolicy",
|
| 19 |
"FarmStore",
|
| 20 |
"MeshScaleScaler",
|
key_farm/__pycache__/__init__.cpython-310.pyc
DELETED
|
Binary file (868 Bytes)
|
|
|
key_farm/__pycache__/amalgam_bridge.cpython-310.pyc
DELETED
|
Binary file (1.05 kB)
|
|
|
key_farm/__pycache__/cli.cpython-310.pyc
DELETED
|
Binary file (7.69 kB)
|
|
|
key_farm/__pycache__/contracts.cpython-310.pyc
DELETED
|
Binary file (11 kB)
|
|
|
key_farm/__pycache__/controller.cpython-310.pyc
DELETED
|
Binary file (7.24 kB)
|
|
|
key_farm/__pycache__/evaluator.cpython-310.pyc
DELETED
|
Binary file (5.71 kB)
|
|
|
key_farm/__pycache__/faculty.cpython-310.pyc
DELETED
|
Binary file (4.92 kB)
|
|
|
key_farm/__pycache__/regulation.cpython-310.pyc
DELETED
|
Binary file (1.06 kB)
|
|
|
key_farm/__pycache__/scaler.cpython-310.pyc
DELETED
|
Binary file (20.3 kB)
|
|
|
key_farm/__pycache__/serialization.cpython-310.pyc
DELETED
|
Binary file (2.23 kB)
|
|
|
key_farm/__pycache__/store.cpython-310.pyc
DELETED
|
Binary file (13.3 kB)
|
|
|
key_farm/__pycache__/symbiotic_trace.cpython-310.pyc
DELETED
|
Binary file (7.25 kB)
|
|
|
key_farm/controller.py
CHANGED
|
@@ -17,6 +17,27 @@ from .serialization import pack_item, pack_node
|
|
| 17 |
from .store import FarmStore
|
| 18 |
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
class FarmController:
|
| 21 |
"""Controller-side API for dispatching fitness evaluation shards."""
|
| 22 |
|
|
@@ -229,7 +250,14 @@ class FarmController:
|
|
| 229 |
return fitness_by_id
|
| 230 |
if time.time() >= deadline:
|
| 231 |
missing = sorted(expected_node_ids - fitness_by_id.keys())
|
| 232 |
-
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
time.sleep(poll_seconds)
|
| 234 |
|
| 235 |
def result_map(self, run_id: str, generation: int) -> dict[str, float]:
|
|
|
|
| 17 |
from .store import FarmStore
|
| 18 |
|
| 19 |
|
| 20 |
+
class IncompleteGenerationError(RuntimeError):
|
| 21 |
+
"""Raised when a farm generation times out before all node ids return."""
|
| 22 |
+
|
| 23 |
+
def __init__(
|
| 24 |
+
self,
|
| 25 |
+
message: str,
|
| 26 |
+
*,
|
| 27 |
+
run_id: str,
|
| 28 |
+
generation: int,
|
| 29 |
+
partial_results: dict[str, float],
|
| 30 |
+
missing_node_ids: list[str],
|
| 31 |
+
timeout_seconds: float,
|
| 32 |
+
) -> None:
|
| 33 |
+
super().__init__(message)
|
| 34 |
+
self.run_id = run_id
|
| 35 |
+
self.generation = generation
|
| 36 |
+
self.partial_results = dict(partial_results)
|
| 37 |
+
self.missing_node_ids = list(missing_node_ids)
|
| 38 |
+
self.timeout_seconds = float(timeout_seconds)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
class FarmController:
|
| 42 |
"""Controller-side API for dispatching fitness evaluation shards."""
|
| 43 |
|
|
|
|
| 250 |
return fitness_by_id
|
| 251 |
if time.time() >= deadline:
|
| 252 |
missing = sorted(expected_node_ids - fitness_by_id.keys())
|
| 253 |
+
raise IncompleteGenerationError(
|
| 254 |
+
f"missing {len(missing)} fitness results: {missing[:10]}",
|
| 255 |
+
run_id=run_id,
|
| 256 |
+
generation=generation,
|
| 257 |
+
partial_results=fitness_by_id,
|
| 258 |
+
missing_node_ids=missing,
|
| 259 |
+
timeout_seconds=timeout_seconds,
|
| 260 |
+
)
|
| 261 |
time.sleep(poll_seconds)
|
| 262 |
|
| 263 |
def result_map(self, run_id: str, generation: int) -> dict[str, float]:
|
meshscale_space/__pycache__/__init__.cpython-310.pyc
DELETED
|
Binary file (194 Bytes)
|
|
|
meshscale_space/__pycache__/app.cpython-310.pyc
DELETED
|
Binary file (13 kB)
|
|
|
meshscale_space/__pycache__/os_routes.cpython-310.pyc
DELETED
|
Binary file (5.49 kB)
|
|
|
meshscale_worker_space/Dockerfile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
ENV PYTHONUNBUFFERED=1
|
| 4 |
+
ENV PORT=7860
|
| 5 |
+
ENV KEY_FARM_ROOT=/data/farm
|
| 6 |
+
ENV KEY_FARM_FORMATION=cpu-worker
|
| 7 |
+
ENV MESHSCALE_FULL_KEY_REQUIREMENTS=1
|
| 8 |
+
|
| 9 |
+
WORKDIR /app
|
| 10 |
+
COPY . /app
|
| 11 |
+
|
| 12 |
+
RUN python -m pip install --upgrade pip \
|
| 13 |
+
&& python -m pip install --no-cache-dir -r meshscale_worker_space/requirements.txt \
|
| 14 |
+
&& if [ "$MESHSCALE_FULL_KEY_REQUIREMENTS" = "1" ] && [ -f requirements.txt ]; then \
|
| 15 |
+
python -m pip install --no-cache-dir -r requirements.txt; \
|
| 16 |
+
fi
|
| 17 |
+
|
| 18 |
+
CMD ["python", "-m", "uvicorn", "meshscale_worker_space.app:app", "--host", "0.0.0.0", "--port", "7860"]
|
meshscale_worker_space/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: MeshScale CPU Worker
|
| 3 |
+
emoji: 🔩
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: false
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# MeshScale CPU Worker
|
| 12 |
+
|
| 13 |
+
This Space template runs one MeshScale CPU worker against a shared Hugging Face
|
| 14 |
+
bucket mounted at `/data`.
|
| 15 |
+
|
| 16 |
+
Required Space variable:
|
| 17 |
+
|
| 18 |
+
```text
|
| 19 |
+
KEY_FARM_ROOT=/data/farm
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
Useful Space variables:
|
| 23 |
+
|
| 24 |
+
```text
|
| 25 |
+
KEY_FARM_FORMATION=cpu-worker
|
| 26 |
+
KEY_FARM_POLL_SECONDS=2
|
| 27 |
+
KEY_FARM_TARGET_BATCH_SIZE=24
|
| 28 |
+
KEY_FARM_MAX_WORKERS=24
|
| 29 |
+
KEY_FARM_BUDGET_HOURLY_USD=0.72
|
| 30 |
+
MESHSCALE_WORKER_AUTOSTART=1
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
`KEY_FARM_WORKER_ID` is optional. When replicas are enabled, the worker app
|
| 34 |
+
adds the runtime replica/container identity to the base worker name so each
|
| 35 |
+
replica publishes a distinct pulse.
|
| 36 |
+
|
| 37 |
+
The worker claims shard files from:
|
| 38 |
+
|
| 39 |
+
```text
|
| 40 |
+
/data/farm/runs/<run_id>/generations/<generation>/jobs/pending/
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
It exits its worker loop when the controller pulse is lost or when regulation
|
| 44 |
+
mode becomes `shutdown`.
|
| 45 |
+
|
| 46 |
+
Health endpoints:
|
| 47 |
+
|
| 48 |
+
```text
|
| 49 |
+
/health
|
| 50 |
+
/state
|
| 51 |
+
```
|
meshscale_worker_space/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Hugging Face Space app template for MeshScale CPU workers."""
|
meshscale_worker_space/app.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import threading
|
| 5 |
+
import time
|
| 6 |
+
import traceback
|
| 7 |
+
from typing import Any
|
| 8 |
+
|
| 9 |
+
from fastapi import FastAPI
|
| 10 |
+
|
| 11 |
+
from key_farm.contracts import FarmPolicy
|
| 12 |
+
from key_farm.store import FarmStore
|
| 13 |
+
from key_farm.worker import worker_loop
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
app = FastAPI(title="MeshScale CPU Worker")
|
| 17 |
+
|
| 18 |
+
_worker_thread: threading.Thread | None = None
|
| 19 |
+
_supervisor_thread: threading.Thread | None = None
|
| 20 |
+
_worker_error: str | None = None
|
| 21 |
+
_worker_lock = threading.Lock()
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def _int_env(name: str, default: int) -> int:
|
| 25 |
+
try:
|
| 26 |
+
return int(os.environ.get(name, str(default)))
|
| 27 |
+
except ValueError:
|
| 28 |
+
return default
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _float_env(name: str, default: float) -> float:
|
| 32 |
+
try:
|
| 33 |
+
return float(os.environ.get(name, str(default)))
|
| 34 |
+
except ValueError:
|
| 35 |
+
return default
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _bool_env(name: str, default: bool) -> bool:
|
| 39 |
+
raw = os.environ.get(name)
|
| 40 |
+
if raw is None:
|
| 41 |
+
return default
|
| 42 |
+
return raw.strip().lower() not in {"0", "false", "no", "off"}
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _root() -> str:
|
| 46 |
+
return os.environ.get("KEY_FARM_ROOT", "/data/farm")
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def _formation() -> str:
|
| 50 |
+
return os.environ.get("KEY_FARM_FORMATION", "cpu-worker")
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def _worker_id() -> str:
|
| 54 |
+
base = os.environ.get("KEY_FARM_WORKER_ID") or os.environ.get("SPACE_ID") or "meshscale-worker"
|
| 55 |
+
runtime = (
|
| 56 |
+
os.environ.get("SPACE_REPLICA_ID")
|
| 57 |
+
or os.environ.get("REPLICA_ID")
|
| 58 |
+
or os.environ.get("HOSTNAME")
|
| 59 |
+
or str(os.getpid())
|
| 60 |
+
)
|
| 61 |
+
return f"{base}:{runtime}"
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _policy() -> FarmPolicy:
|
| 65 |
+
formation = _formation()
|
| 66 |
+
return FarmPolicy(
|
| 67 |
+
target_batch_size=_int_env("KEY_FARM_TARGET_BATCH_SIZE", 24),
|
| 68 |
+
min_batch_size=_int_env("KEY_FARM_MIN_BATCH_SIZE", 4),
|
| 69 |
+
max_depth=_int_env("KEY_FARM_MAX_DEPTH", 4),
|
| 70 |
+
max_workers=_int_env("KEY_FARM_MAX_WORKERS", 24),
|
| 71 |
+
claim_ttl_seconds=_int_env("KEY_FARM_CLAIM_TTL_SECONDS", 900),
|
| 72 |
+
pulse_ttl_seconds=_int_env("KEY_FARM_PULSE_TTL_SECONDS", 120),
|
| 73 |
+
controller_pulse_ttl_seconds=_int_env("KEY_FARM_CONTROLLER_PULSE_TTL_SECONDS", 180),
|
| 74 |
+
budget_hourly_usd=_float_env("KEY_FARM_BUDGET_HOURLY_USD", 0.72),
|
| 75 |
+
cpu_space_hourly_usd=_float_env("KEY_FARM_CPU_SPACE_HOURLY_USD", 0.03),
|
| 76 |
+
exit_when_pulse_lost=_bool_env("KEY_FARM_EXIT_WHEN_PULSE_LOST", True),
|
| 77 |
+
pulse_rate=_float_env("KEY_FARM_PULSE_RATE", 1.0),
|
| 78 |
+
allowed_formations=(formation,),
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def _run_worker() -> None:
|
| 83 |
+
global _worker_error
|
| 84 |
+
try:
|
| 85 |
+
worker_loop(
|
| 86 |
+
FarmStore(_root()),
|
| 87 |
+
_worker_id(),
|
| 88 |
+
_policy(),
|
| 89 |
+
poll_seconds=_float_env("KEY_FARM_POLL_SECONDS", 2.0),
|
| 90 |
+
once=False,
|
| 91 |
+
)
|
| 92 |
+
except Exception:
|
| 93 |
+
_worker_error = traceback.format_exc()
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def _autostart_enabled() -> bool:
|
| 97 |
+
return _bool_env("MESHSCALE_WORKER_AUTOSTART", True)
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def _controller_ready(store: FarmStore | None = None, policy: FarmPolicy | None = None) -> bool:
|
| 101 |
+
if not _bool_env("MESHSCALE_WORKER_WAIT_FOR_CONTROLLER", True):
|
| 102 |
+
return True
|
| 103 |
+
store = store or FarmStore(_root())
|
| 104 |
+
policy = policy or _policy()
|
| 105 |
+
return store.controller_pulse_alive(policy.controller_pulse_ttl_seconds)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def _start_worker_if_ready() -> bool:
|
| 109 |
+
global _worker_thread, _worker_error
|
| 110 |
+
if not _autostart_enabled():
|
| 111 |
+
return False
|
| 112 |
+
if _worker_error and not _bool_env("MESHSCALE_WORKER_RESTART_ON_ERROR", False):
|
| 113 |
+
return False
|
| 114 |
+
store = FarmStore(_root())
|
| 115 |
+
policy = _policy()
|
| 116 |
+
if not _controller_ready(store, policy):
|
| 117 |
+
return False
|
| 118 |
+
with _worker_lock:
|
| 119 |
+
if _worker_thread and _worker_thread.is_alive():
|
| 120 |
+
return True
|
| 121 |
+
_worker_error = None
|
| 122 |
+
_worker_thread = threading.Thread(target=_run_worker, name="meshscale-worker", daemon=True)
|
| 123 |
+
_worker_thread.start()
|
| 124 |
+
return True
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def _supervise_worker() -> None:
|
| 128 |
+
while _autostart_enabled():
|
| 129 |
+
_start_worker_if_ready()
|
| 130 |
+
time.sleep(_float_env("MESHSCALE_WORKER_SUPERVISOR_SECONDS", 5.0))
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def _ensure_supervisor_started() -> None:
|
| 134 |
+
global _supervisor_thread
|
| 135 |
+
if not _autostart_enabled():
|
| 136 |
+
return
|
| 137 |
+
if _supervisor_thread and _supervisor_thread.is_alive():
|
| 138 |
+
return
|
| 139 |
+
_supervisor_thread = threading.Thread(
|
| 140 |
+
target=_supervise_worker,
|
| 141 |
+
name="meshscale-worker-supervisor",
|
| 142 |
+
daemon=True,
|
| 143 |
+
)
|
| 144 |
+
_supervisor_thread.start()
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def _ensure_worker_started() -> None:
|
| 148 |
+
if not _bool_env("MESHSCALE_WORKER_AUTOSTART", True):
|
| 149 |
+
return
|
| 150 |
+
_ensure_supervisor_started()
|
| 151 |
+
_start_worker_if_ready()
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
@app.on_event("startup")
|
| 155 |
+
def startup() -> None:
|
| 156 |
+
_ensure_worker_started()
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
@app.get("/health")
|
| 160 |
+
def health() -> dict[str, Any]:
|
| 161 |
+
store = FarmStore(_root())
|
| 162 |
+
policy = _policy()
|
| 163 |
+
_ensure_worker_started()
|
| 164 |
+
return {
|
| 165 |
+
"ok": _worker_error is None,
|
| 166 |
+
"worker_id": _worker_id(),
|
| 167 |
+
"formation": _formation(),
|
| 168 |
+
"thread_alive": bool(_worker_thread and _worker_thread.is_alive()),
|
| 169 |
+
"supervisor_alive": bool(_supervisor_thread and _supervisor_thread.is_alive()),
|
| 170 |
+
"root": str(store.root),
|
| 171 |
+
"controller_alive": store.controller_pulse_alive(policy.controller_pulse_ttl_seconds),
|
| 172 |
+
"regulation": store.read_regulation().to_dict(),
|
| 173 |
+
"error": _worker_error,
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
@app.get("/state")
|
| 178 |
+
def state() -> dict[str, Any]:
|
| 179 |
+
store = FarmStore(_root())
|
| 180 |
+
policy = _policy()
|
| 181 |
+
_ensure_worker_started()
|
| 182 |
+
return {
|
| 183 |
+
"worker_id": _worker_id(),
|
| 184 |
+
"formation": _formation(),
|
| 185 |
+
"pending_jobs": len(store.pending_jobs(_formation())),
|
| 186 |
+
"active_workers": store.active_worker_count(policy.pulse_ttl_seconds, _formation()),
|
| 187 |
+
"controller_alive": store.controller_pulse_alive(policy.controller_pulse_ttl_seconds),
|
| 188 |
+
"policy": policy.to_dict(),
|
| 189 |
+
}
|
meshscale_worker_space/requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi>=0.110
|
| 2 |
+
uvicorn[standard]>=0.27
|
| 3 |
+
huggingface-hub>=0.34.0,<1.0
|
| 4 |
+
numpy>=1.24,<1.27
|
| 5 |
+
simpleeval>=0.9.13
|