awrun 0.2.2__tar.gz → 0.2.4__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.
- {awrun-0.2.2 → awrun-0.2.4}/PKG-INFO +19 -2
- {awrun-0.2.2 → awrun-0.2.4}/README.md +18 -1
- {awrun-0.2.2 → awrun-0.2.4}/awrun/__init__.py +1 -1
- {awrun-0.2.2 → awrun-0.2.4}/awrun/cli.py +51 -13
- {awrun-0.2.2 → awrun-0.2.4}/awrun/dispatcher.py +18 -1
- awrun-0.2.4/awrun/host_admission.py +90 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun/plugins.py +6 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun/runner_groups.py +17 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun.egg-info/PKG-INFO +19 -2
- {awrun-0.2.2 → awrun-0.2.4}/awrun.egg-info/SOURCES.txt +2 -0
- {awrun-0.2.2 → awrun-0.2.4}/pyproject.toml +1 -1
- awrun-0.2.4/tests/test_host_admission.py +90 -0
- {awrun-0.2.2 → awrun-0.2.4}/LICENSE +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun/__main__.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun/_doctor.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun/_flow_runner.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun/authz.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun/capacity.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun/confine.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun/egress.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun/gpu_lease.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun/handoff.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun/store.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun/surface.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun/trail.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun.egg-info/dependency_links.txt +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun.egg-info/entry_points.txt +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun.egg-info/requires.txt +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/awrun.egg-info/top_level.txt +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/setup.cfg +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/tests/test_dispatcher_gpu_lease.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/tests/test_gpu_lease.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/tests/test_handoff.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/tests/test_store.py +0 -0
- {awrun-0.2.2 → awrun-0.2.4}/tests/test_suspend_apply_confine.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: awrun
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: A priority-aware queue and dispatcher for agentic runs and ad-hoc CI builds — real dynamic priority for the class of work we control, instead of GitHub Actions' opaque FIFO.
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://github.com/Aitherium/awrun
|
|
@@ -33,7 +33,7 @@ Dynamic: license-file
|
|
|
33
33
|
|
|
34
34
|
**[Docs](https://aitherium.github.io/awrun/)** · [Source](https://github.com/Aitherium/awrun) · `pip install awrun` · [The Aither World](https://aitherium.github.io/)
|
|
35
35
|
|
|
36
|
-
> **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awrun** is one of its
|
|
36
|
+
> **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awrun** is one of its 65 bricks — each installs on its own, runs offline, and needs no account.
|
|
37
37
|
>
|
|
38
38
|
> **Start here:** Queue two runs at different priorities and watch the urgent one overtake.
|
|
39
39
|
|
|
@@ -225,6 +225,7 @@ Each installs on its own, works offline, and needs no account.
|
|
|
225
225
|
| [awgraph](https://github.com/Aitherium/awgraph) | that grep found everything | an AST + tree-sitter call graph an agent can traverse |
|
|
226
226
|
| [awgit](https://github.com/Aitherium/awgit) | that no one else is editing this file | a lease, refused at commit time if you do not hold it |
|
|
227
227
|
| [awdelphi](https://github.com/Aitherium/awdelphi) | one agent's confident take on a decision | the round trace, the anonymity, and who dissents |
|
|
228
|
+
| [awclassify](https://github.com/Aitherium/awclassify) | a filename, a folder, or whoever last touched it | doc_type, visibility, audience and topics, with the evidence lines that decided each |
|
|
228
229
|
| [awtoll](https://github.com/Aitherium/awtoll) | that your tooling is saving you context | the measured token cost of each tool call, and what the alternative cost |
|
|
229
230
|
| [awseal](https://github.com/Aitherium/awseal) | that the artifact came from who you think | an Ed25519 seal — the key that verifies is not the key that forges |
|
|
230
231
|
| [awshare](https://github.com/Aitherium/awshare) | that the download is intact | content-addressed bundles, verified on fetch |
|
|
@@ -237,11 +238,14 @@ Each installs on its own, works offline, and needs no account.
|
|
|
237
238
|
| [awrelay](https://github.com/Aitherium/awrelay) | a SaaS in the middle of your agents | findings, alerts and coordination over your own transport |
|
|
238
239
|
| [awask](https://github.com/Aitherium/awask) | that anyone read the paragraph where you asked | the ask itself, with a button that steers the session that raised it |
|
|
239
240
|
| [awmail](https://github.com/Aitherium/awmail) | a mailbox somebody else can read | mail your agents send and receive over your own server |
|
|
241
|
+
| [awswarm](https://github.com/Aitherium/awswarm) | that a model either fits your GPU or it doesn't run at all | a placement plan and an acquisition-probability estimate before you spend on a run |
|
|
240
242
|
| [awfind](https://github.com/Aitherium/awfind) | one vendor's idea of the web | results from whichever providers you configured |
|
|
241
243
|
| [awbrowse](https://github.com/Aitherium/awbrowse) | that the page said what you were told | the render, the DOM and the requests it made |
|
|
242
244
|
| [awvoice](https://github.com/Aitherium/awvoice) | that a cloud vendor may hold your audio | a transcript and a wav from a service you host |
|
|
243
245
|
| [awvision](https://github.com/Aitherium/awvision) | a filename and a caption somebody wrote | what a model actually reports about the pixels |
|
|
244
246
|
| [awscreen](https://github.com/Aitherium/awscreen) | a selector that was true when the page was written | the elements actually rendered, by what they look like |
|
|
247
|
+
| [awbeads](https://github.com/Aitherium/awbeads) | that a layout your users built survives the next deploy | the arrangement as data you can read back, diff, and hand to another surface |
|
|
248
|
+
| [awbonsai](https://github.com/Aitherium/awbonsai) | that inference always means a request left the machine | a WebGPU model answering on the tab's own GPU, with a consent record logged before it ever loaded |
|
|
245
249
|
| [gawbbonet](https://github.com/Aitherium/gawbbonet) | the model to keep a 300-message campaign coherent by itself | campaign facts recalled from scoped memory you can list and edit |
|
|
246
250
|
| [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | a vendor's quantisation defaults | sub-byte KV cache kernels you can benchmark yourself |
|
|
247
251
|
| [awrtifact](https://github.com/Aitherium/awrtifact) | a hand-rolled split script and a hand-edited worker manifest | byte-verified parts in a release, served with Range + CORS, sizes asserted by a live gate |
|
|
@@ -251,11 +255,14 @@ Each installs on its own, works offline, and needs no account.
|
|
|
251
255
|
| [awrecurse](https://github.com/Aitherium/awrecurse) | that everything you pasted in was actually read | which slices it opened, and what it concluded from each |
|
|
252
256
|
| [awprism](https://github.com/Aitherium/awprism) | the first explanation that fits | the ranked alternatives, and the observation that separates them |
|
|
253
257
|
| [awrepl](https://github.com/Aitherium/awrepl) | what the agent believes the value is | the value, printed from the live session |
|
|
258
|
+
| [awreport](https://github.com/Aitherium/awreport) | that the report you pasted carried no token in it | a redacted report, and the duplicate it merged into instead of filing twice |
|
|
254
259
|
| [awresearch](https://github.com/Aitherium/awresearch) | a summary of pages nobody opened | every claim against the source it came from |
|
|
255
260
|
| [awfocus](https://github.com/Aitherium/awfocus) | twelve terminal tabs and a bad memory | one command that names every session, finds any transcript, and opens or steers the one you want |
|
|
256
261
|
| [awgym](https://github.com/Aitherium/awgym) | that a world model learned anything from the games it saw | transitions captured from real play, fed back, and the retrodiction score falling on grids it never saw |
|
|
257
262
|
| [awpredict](https://github.com/Aitherium/awpredict) | a model because it trained without erroring | its prediction against a self-updating lookup, on the rows that are actually novel |
|
|
263
|
+
| [awevolve](https://github.com/Aitherium/awevolve) | that your optimisation loop is finding anything | every version it kept, the score that version earned, and the edit that produced it |
|
|
258
264
|
| [awsh](https://github.com/Aitherium/awsh) | that you already know the name of the command | what it decided your line meant, before it acts on it |
|
|
265
|
+
| [awmine](https://github.com/Aitherium/awmine) | that a session's lesson survived the session | a row per outcome, a candidate per lesson, and the transcript line each one came from |
|
|
259
266
|
| [awrise](https://github.com/Aitherium/awrise) | that a scheduled agent ran at all, and ran exactly once | a durable record of every wake -- fired, skipped, overlapped or timed out -- each with its reason |
|
|
260
267
|
| [awkno](https://github.com/Aitherium/awkno) | that the docs site is up, or that you remember the family | the whole ecosystem in your terminal, with no network at all |
|
|
261
268
|
| [awwall](https://github.com/Aitherium/awwall) | that a service only talks to the hosts you think it talks to | an explicit egress allowlist, where a denial names the rule that denied it |
|
|
@@ -282,6 +289,7 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
|
|
|
282
289
|
| [awgraph](https://github.com/Aitherium/awgraph) | A semantic code graph for agents — AST + tree-sitter, call graphs | [docs](https://aitherium.github.io/awgraph/) |
|
|
283
290
|
| [awgit](https://github.com/Aitherium/awgit) | Semantic version control on top of git — edit-ops and leases | [docs](https://aitherium.github.io/awgit/) |
|
|
284
291
|
| [awdelphi](https://github.com/Aitherium/awdelphi) | Anonymous multi-round expert panels — a converged answer with a trace | [docs](https://aitherium.github.io/awdelphi/) |
|
|
292
|
+
| [awclassify](https://github.com/Aitherium/awclassify) | Classify any document -- what it is, who may read it, who it is for, what it is about | — |
|
|
285
293
|
| [awtoll](https://github.com/Aitherium/awtoll) | What every tool call costs you in context, measured from your own transcripts | [docs](https://aitherium.github.io/awtoll/) |
|
|
286
294
|
| [awseal](https://github.com/Aitherium/awseal) | Sign an artifact so a stranger can verify it | [docs](https://aitherium.github.io/awseal/) |
|
|
287
295
|
| [awshare](https://github.com/Aitherium/awshare) | Publish an artifact and fetch it back verified | [docs](https://aitherium.github.io/awshare/) |
|
|
@@ -299,12 +307,17 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
|
|
|
299
307
|
| [awask](https://github.com/Aitherium/awask) | Your agent asks you a question — and acts on your answer | [docs](https://aitherium.github.io/awask/) |
|
|
300
308
|
| [awmail](https://github.com/Aitherium/awmail) | Give an agent an email address — send, and actually receive | [docs](https://aitherium.github.io/awmail/) |
|
|
301
309
|
| [awnet](https://github.com/Aitherium/awnet) | The agentic web — agents host a mesh, and agents join one | [docs](https://aitherium.github.io/awnet/) |
|
|
310
|
+
| [awswarm](https://github.com/Aitherium/awswarm) | Run one model too big for any single GPU across a pool of small ones | — |
|
|
302
311
|
| [awfind](https://github.com/Aitherium/awfind) | A portable search client — query, results, ranking | [docs](https://aitherium.github.io/awfind/) |
|
|
303
312
|
| [awbrowse](https://github.com/Aitherium/awbrowse) | A portable browser client — navigate, console, network, DOM, screenshot | [docs](https://aitherium.github.io/awbrowse/) |
|
|
304
313
|
| [awvoice](https://github.com/Aitherium/awvoice) | Hear and speak — transcribe audio, synthesize a voice | [docs](https://aitherium.github.io/awvoice/) |
|
|
305
314
|
| [awvision](https://github.com/Aitherium/awvision) | See an image — describe it, ask it a question, compare two | [docs](https://aitherium.github.io/awvision/) |
|
|
306
315
|
| [awscreen](https://github.com/Aitherium/awscreen) | See this machine — what is on screen, and where to click it | [docs](https://aitherium.github.io/awscreen/) |
|
|
316
|
+
| [awkit](https://github.com/Aitherium/awkit) | Render an agent panel from a tool result — one component, any React app | — |
|
|
317
|
+
| [awbeads](https://github.com/Aitherium/awbeads) | A spatial canvas for a page — arrange things, connect them, and keep the arrangement | — |
|
|
318
|
+
| [awbonsai](https://github.com/Aitherium/awbonsai) | Run a real model in the visitor's own browser — no server round trip, no upload | — |
|
|
307
319
|
| [awknowledge](https://github.com/Aitherium/awknowledge) | How to run a coding agent so the result survives — the laws, with evidence | [docs](https://aitherium.github.io/awknowledge/) |
|
|
320
|
+
| [awbrain](https://github.com/Aitherium/awbrain) | Your history as a wiki of linked markdown — claims pinned to the evidence | — |
|
|
308
321
|
| [gawbbonet](https://github.com/Aitherium/gawbbonet) | GobboNet campaigns with a real agent brain — scoped memory, graph recall | [docs](https://aitherium.github.io/gawbbonet/) |
|
|
309
322
|
| [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | Near-optimal KV cache quantization for LLM inference — sub-byte compression | [docs](https://aitherium.github.io/aitherkvcache/) |
|
|
310
323
|
| [awrtifact](https://github.com/Aitherium/awrtifact) | Deliberately chunk artifacts into GitHub release assets — the productized aitherkvcache mirror lane | [docs](https://aitherium.github.io/awrtifact/) |
|
|
@@ -314,14 +327,18 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
|
|
|
314
327
|
| [awrecurse](https://github.com/Aitherium/awrecurse) | Answer a question over a context far larger than the window — recursively, with the trace kept | [docs](https://aitherium.github.io/awrecurse/) |
|
|
315
328
|
| [awprism](https://github.com/Aitherium/awprism) | Turn a failure into ranked hypotheses — and say what would confirm each one | [docs](https://aitherium.github.io/awprism/) |
|
|
316
329
|
| [awrepl](https://github.com/Aitherium/awrepl) | A REPL an agent can actually use — state that survives between turns | [docs](https://aitherium.github.io/awrepl/) |
|
|
330
|
+
| [awreport](https://github.com/Aitherium/awreport) | File a bug report that has already scrubbed your secrets and collapsed the duplicate | — |
|
|
317
331
|
| [awresearch](https://github.com/Aitherium/awresearch) | Ask a research question, get a cited report you can check | [docs](https://aitherium.github.io/awresearch/) |
|
|
318
332
|
| [awfocus](https://github.com/Aitherium/awfocus) | See, search and steer every Claude session from one command | [docs](https://aitherium.github.io/awfocus/) |
|
|
319
333
|
| [awgym](https://github.com/Aitherium/awgym) | An ARC training gym — a game a world model can watch, and six roles that play through it | [docs](https://aitherium.github.io/awgym/) |
|
|
320
334
|
| [awpredict](https://github.com/Aitherium/awpredict) | Predict what your environment does next, and how surprised you were | [docs](https://aitherium.github.io/awpredict/) |
|
|
335
|
+
| [awevolve](https://github.com/Aitherium/awevolve) | Point an agent at a file and a command that scores it, and let it improve | — |
|
|
321
336
|
| [awsh](https://github.com/Aitherium/awsh) | Your terminal answers you -- type a question where a command would go | [docs](https://aitherium.github.io/awsh/) |
|
|
337
|
+
| [awmine](https://github.com/Aitherium/awmine) | Mine what your agents did -- outcomes, lessons and procedures out of the transcripts they left behind | — |
|
|
322
338
|
| [awrise](https://github.com/Aitherium/awrise) | Wake an agent on a schedule, let it do one thing, and put it back to sleep | [docs](https://aitherium.github.io/awrise/) |
|
|
323
339
|
| [awkno](https://github.com/Aitherium/awkno) | The man page for the Aither World — every brick, stack and law, offline | [docs](https://aitherium.github.io/awkno/) |
|
|
324
340
|
| [awwall](https://github.com/Aitherium/awwall) | Say what a workload may reach, and watch everything else fail closed | [docs](https://aitherium.github.io/awwall/) |
|
|
341
|
+
| [awrouter](https://github.com/Aitherium/awrouter) | OpenRouter for your own fleet: pick a model backend by cost/latency/ capability, fail over, fit the context window, stream. Standalone, OpenAI-compatible, no Aither-specifics required to be valuable | — |
|
|
325
342
|
| [awembed](https://github.com/Aitherium/awembed) | Train an embedding model that knows your corpus, and prove it beats the big one | [docs](https://aitherium.github.io/awembed/) |
|
|
326
343
|
| [awtax](https://github.com/Aitherium/awtax) | Turn any tax PDF -- returns, W-2, 1099, statements, even scans -- into structured data you can check | [docs](https://aitherium.github.io/awtax/) |
|
|
327
344
|
| [awflow](https://github.com/Aitherium/awflow) | A deterministic workflow runtime — chain agent calls with journal replay and budget control | [docs](https://aitherium.github.io/awflow/) |
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**[Docs](https://aitherium.github.io/awrun/)** · [Source](https://github.com/Aitherium/awrun) · `pip install awrun` · [The Aither World](https://aitherium.github.io/)
|
|
6
6
|
|
|
7
|
-
> **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awrun** is one of its
|
|
7
|
+
> **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awrun** is one of its 65 bricks — each installs on its own, runs offline, and needs no account.
|
|
8
8
|
>
|
|
9
9
|
> **Start here:** Queue two runs at different priorities and watch the urgent one overtake.
|
|
10
10
|
|
|
@@ -196,6 +196,7 @@ Each installs on its own, works offline, and needs no account.
|
|
|
196
196
|
| [awgraph](https://github.com/Aitherium/awgraph) | that grep found everything | an AST + tree-sitter call graph an agent can traverse |
|
|
197
197
|
| [awgit](https://github.com/Aitherium/awgit) | that no one else is editing this file | a lease, refused at commit time if you do not hold it |
|
|
198
198
|
| [awdelphi](https://github.com/Aitherium/awdelphi) | one agent's confident take on a decision | the round trace, the anonymity, and who dissents |
|
|
199
|
+
| [awclassify](https://github.com/Aitherium/awclassify) | a filename, a folder, or whoever last touched it | doc_type, visibility, audience and topics, with the evidence lines that decided each |
|
|
199
200
|
| [awtoll](https://github.com/Aitherium/awtoll) | that your tooling is saving you context | the measured token cost of each tool call, and what the alternative cost |
|
|
200
201
|
| [awseal](https://github.com/Aitherium/awseal) | that the artifact came from who you think | an Ed25519 seal — the key that verifies is not the key that forges |
|
|
201
202
|
| [awshare](https://github.com/Aitherium/awshare) | that the download is intact | content-addressed bundles, verified on fetch |
|
|
@@ -208,11 +209,14 @@ Each installs on its own, works offline, and needs no account.
|
|
|
208
209
|
| [awrelay](https://github.com/Aitherium/awrelay) | a SaaS in the middle of your agents | findings, alerts and coordination over your own transport |
|
|
209
210
|
| [awask](https://github.com/Aitherium/awask) | that anyone read the paragraph where you asked | the ask itself, with a button that steers the session that raised it |
|
|
210
211
|
| [awmail](https://github.com/Aitherium/awmail) | a mailbox somebody else can read | mail your agents send and receive over your own server |
|
|
212
|
+
| [awswarm](https://github.com/Aitherium/awswarm) | that a model either fits your GPU or it doesn't run at all | a placement plan and an acquisition-probability estimate before you spend on a run |
|
|
211
213
|
| [awfind](https://github.com/Aitherium/awfind) | one vendor's idea of the web | results from whichever providers you configured |
|
|
212
214
|
| [awbrowse](https://github.com/Aitherium/awbrowse) | that the page said what you were told | the render, the DOM and the requests it made |
|
|
213
215
|
| [awvoice](https://github.com/Aitherium/awvoice) | that a cloud vendor may hold your audio | a transcript and a wav from a service you host |
|
|
214
216
|
| [awvision](https://github.com/Aitherium/awvision) | a filename and a caption somebody wrote | what a model actually reports about the pixels |
|
|
215
217
|
| [awscreen](https://github.com/Aitherium/awscreen) | a selector that was true when the page was written | the elements actually rendered, by what they look like |
|
|
218
|
+
| [awbeads](https://github.com/Aitherium/awbeads) | that a layout your users built survives the next deploy | the arrangement as data you can read back, diff, and hand to another surface |
|
|
219
|
+
| [awbonsai](https://github.com/Aitherium/awbonsai) | that inference always means a request left the machine | a WebGPU model answering on the tab's own GPU, with a consent record logged before it ever loaded |
|
|
216
220
|
| [gawbbonet](https://github.com/Aitherium/gawbbonet) | the model to keep a 300-message campaign coherent by itself | campaign facts recalled from scoped memory you can list and edit |
|
|
217
221
|
| [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | a vendor's quantisation defaults | sub-byte KV cache kernels you can benchmark yourself |
|
|
218
222
|
| [awrtifact](https://github.com/Aitherium/awrtifact) | a hand-rolled split script and a hand-edited worker manifest | byte-verified parts in a release, served with Range + CORS, sizes asserted by a live gate |
|
|
@@ -222,11 +226,14 @@ Each installs on its own, works offline, and needs no account.
|
|
|
222
226
|
| [awrecurse](https://github.com/Aitherium/awrecurse) | that everything you pasted in was actually read | which slices it opened, and what it concluded from each |
|
|
223
227
|
| [awprism](https://github.com/Aitherium/awprism) | the first explanation that fits | the ranked alternatives, and the observation that separates them |
|
|
224
228
|
| [awrepl](https://github.com/Aitherium/awrepl) | what the agent believes the value is | the value, printed from the live session |
|
|
229
|
+
| [awreport](https://github.com/Aitherium/awreport) | that the report you pasted carried no token in it | a redacted report, and the duplicate it merged into instead of filing twice |
|
|
225
230
|
| [awresearch](https://github.com/Aitherium/awresearch) | a summary of pages nobody opened | every claim against the source it came from |
|
|
226
231
|
| [awfocus](https://github.com/Aitherium/awfocus) | twelve terminal tabs and a bad memory | one command that names every session, finds any transcript, and opens or steers the one you want |
|
|
227
232
|
| [awgym](https://github.com/Aitherium/awgym) | that a world model learned anything from the games it saw | transitions captured from real play, fed back, and the retrodiction score falling on grids it never saw |
|
|
228
233
|
| [awpredict](https://github.com/Aitherium/awpredict) | a model because it trained without erroring | its prediction against a self-updating lookup, on the rows that are actually novel |
|
|
234
|
+
| [awevolve](https://github.com/Aitherium/awevolve) | that your optimisation loop is finding anything | every version it kept, the score that version earned, and the edit that produced it |
|
|
229
235
|
| [awsh](https://github.com/Aitherium/awsh) | that you already know the name of the command | what it decided your line meant, before it acts on it |
|
|
236
|
+
| [awmine](https://github.com/Aitherium/awmine) | that a session's lesson survived the session | a row per outcome, a candidate per lesson, and the transcript line each one came from |
|
|
230
237
|
| [awrise](https://github.com/Aitherium/awrise) | that a scheduled agent ran at all, and ran exactly once | a durable record of every wake -- fired, skipped, overlapped or timed out -- each with its reason |
|
|
231
238
|
| [awkno](https://github.com/Aitherium/awkno) | that the docs site is up, or that you remember the family | the whole ecosystem in your terminal, with no network at all |
|
|
232
239
|
| [awwall](https://github.com/Aitherium/awwall) | that a service only talks to the hosts you think it talks to | an explicit egress allowlist, where a denial names the rule that denied it |
|
|
@@ -253,6 +260,7 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
|
|
|
253
260
|
| [awgraph](https://github.com/Aitherium/awgraph) | A semantic code graph for agents — AST + tree-sitter, call graphs | [docs](https://aitherium.github.io/awgraph/) |
|
|
254
261
|
| [awgit](https://github.com/Aitherium/awgit) | Semantic version control on top of git — edit-ops and leases | [docs](https://aitherium.github.io/awgit/) |
|
|
255
262
|
| [awdelphi](https://github.com/Aitherium/awdelphi) | Anonymous multi-round expert panels — a converged answer with a trace | [docs](https://aitherium.github.io/awdelphi/) |
|
|
263
|
+
| [awclassify](https://github.com/Aitherium/awclassify) | Classify any document -- what it is, who may read it, who it is for, what it is about | — |
|
|
256
264
|
| [awtoll](https://github.com/Aitherium/awtoll) | What every tool call costs you in context, measured from your own transcripts | [docs](https://aitherium.github.io/awtoll/) |
|
|
257
265
|
| [awseal](https://github.com/Aitherium/awseal) | Sign an artifact so a stranger can verify it | [docs](https://aitherium.github.io/awseal/) |
|
|
258
266
|
| [awshare](https://github.com/Aitherium/awshare) | Publish an artifact and fetch it back verified | [docs](https://aitherium.github.io/awshare/) |
|
|
@@ -270,12 +278,17 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
|
|
|
270
278
|
| [awask](https://github.com/Aitherium/awask) | Your agent asks you a question — and acts on your answer | [docs](https://aitherium.github.io/awask/) |
|
|
271
279
|
| [awmail](https://github.com/Aitherium/awmail) | Give an agent an email address — send, and actually receive | [docs](https://aitherium.github.io/awmail/) |
|
|
272
280
|
| [awnet](https://github.com/Aitherium/awnet) | The agentic web — agents host a mesh, and agents join one | [docs](https://aitherium.github.io/awnet/) |
|
|
281
|
+
| [awswarm](https://github.com/Aitherium/awswarm) | Run one model too big for any single GPU across a pool of small ones | — |
|
|
273
282
|
| [awfind](https://github.com/Aitherium/awfind) | A portable search client — query, results, ranking | [docs](https://aitherium.github.io/awfind/) |
|
|
274
283
|
| [awbrowse](https://github.com/Aitherium/awbrowse) | A portable browser client — navigate, console, network, DOM, screenshot | [docs](https://aitherium.github.io/awbrowse/) |
|
|
275
284
|
| [awvoice](https://github.com/Aitherium/awvoice) | Hear and speak — transcribe audio, synthesize a voice | [docs](https://aitherium.github.io/awvoice/) |
|
|
276
285
|
| [awvision](https://github.com/Aitherium/awvision) | See an image — describe it, ask it a question, compare two | [docs](https://aitherium.github.io/awvision/) |
|
|
277
286
|
| [awscreen](https://github.com/Aitherium/awscreen) | See this machine — what is on screen, and where to click it | [docs](https://aitherium.github.io/awscreen/) |
|
|
287
|
+
| [awkit](https://github.com/Aitherium/awkit) | Render an agent panel from a tool result — one component, any React app | — |
|
|
288
|
+
| [awbeads](https://github.com/Aitherium/awbeads) | A spatial canvas for a page — arrange things, connect them, and keep the arrangement | — |
|
|
289
|
+
| [awbonsai](https://github.com/Aitherium/awbonsai) | Run a real model in the visitor's own browser — no server round trip, no upload | — |
|
|
278
290
|
| [awknowledge](https://github.com/Aitherium/awknowledge) | How to run a coding agent so the result survives — the laws, with evidence | [docs](https://aitherium.github.io/awknowledge/) |
|
|
291
|
+
| [awbrain](https://github.com/Aitherium/awbrain) | Your history as a wiki of linked markdown — claims pinned to the evidence | — |
|
|
279
292
|
| [gawbbonet](https://github.com/Aitherium/gawbbonet) | GobboNet campaigns with a real agent brain — scoped memory, graph recall | [docs](https://aitherium.github.io/gawbbonet/) |
|
|
280
293
|
| [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | Near-optimal KV cache quantization for LLM inference — sub-byte compression | [docs](https://aitherium.github.io/aitherkvcache/) |
|
|
281
294
|
| [awrtifact](https://github.com/Aitherium/awrtifact) | Deliberately chunk artifacts into GitHub release assets — the productized aitherkvcache mirror lane | [docs](https://aitherium.github.io/awrtifact/) |
|
|
@@ -285,14 +298,18 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
|
|
|
285
298
|
| [awrecurse](https://github.com/Aitherium/awrecurse) | Answer a question over a context far larger than the window — recursively, with the trace kept | [docs](https://aitherium.github.io/awrecurse/) |
|
|
286
299
|
| [awprism](https://github.com/Aitherium/awprism) | Turn a failure into ranked hypotheses — and say what would confirm each one | [docs](https://aitherium.github.io/awprism/) |
|
|
287
300
|
| [awrepl](https://github.com/Aitherium/awrepl) | A REPL an agent can actually use — state that survives between turns | [docs](https://aitherium.github.io/awrepl/) |
|
|
301
|
+
| [awreport](https://github.com/Aitherium/awreport) | File a bug report that has already scrubbed your secrets and collapsed the duplicate | — |
|
|
288
302
|
| [awresearch](https://github.com/Aitherium/awresearch) | Ask a research question, get a cited report you can check | [docs](https://aitherium.github.io/awresearch/) |
|
|
289
303
|
| [awfocus](https://github.com/Aitherium/awfocus) | See, search and steer every Claude session from one command | [docs](https://aitherium.github.io/awfocus/) |
|
|
290
304
|
| [awgym](https://github.com/Aitherium/awgym) | An ARC training gym — a game a world model can watch, and six roles that play through it | [docs](https://aitherium.github.io/awgym/) |
|
|
291
305
|
| [awpredict](https://github.com/Aitherium/awpredict) | Predict what your environment does next, and how surprised you were | [docs](https://aitherium.github.io/awpredict/) |
|
|
306
|
+
| [awevolve](https://github.com/Aitherium/awevolve) | Point an agent at a file and a command that scores it, and let it improve | — |
|
|
292
307
|
| [awsh](https://github.com/Aitherium/awsh) | Your terminal answers you -- type a question where a command would go | [docs](https://aitherium.github.io/awsh/) |
|
|
308
|
+
| [awmine](https://github.com/Aitherium/awmine) | Mine what your agents did -- outcomes, lessons and procedures out of the transcripts they left behind | — |
|
|
293
309
|
| [awrise](https://github.com/Aitherium/awrise) | Wake an agent on a schedule, let it do one thing, and put it back to sleep | [docs](https://aitherium.github.io/awrise/) |
|
|
294
310
|
| [awkno](https://github.com/Aitherium/awkno) | The man page for the Aither World — every brick, stack and law, offline | [docs](https://aitherium.github.io/awkno/) |
|
|
295
311
|
| [awwall](https://github.com/Aitherium/awwall) | Say what a workload may reach, and watch everything else fail closed | [docs](https://aitherium.github.io/awwall/) |
|
|
312
|
+
| [awrouter](https://github.com/Aitherium/awrouter) | OpenRouter for your own fleet: pick a model backend by cost/latency/ capability, fail over, fit the context window, stream. Standalone, OpenAI-compatible, no Aither-specifics required to be valuable | — |
|
|
296
313
|
| [awembed](https://github.com/Aitherium/awembed) | Train an embedding model that knows your corpus, and prove it beats the big one | [docs](https://aitherium.github.io/awembed/) |
|
|
297
314
|
| [awtax](https://github.com/Aitherium/awtax) | Turn any tax PDF -- returns, W-2, 1099, statements, even scans -- into structured data you can check | [docs](https://aitherium.github.io/awtax/) |
|
|
298
315
|
| [awflow](https://github.com/Aitherium/awflow) | A deterministic workflow runtime — chain agent calls with journal replay and budget control | [docs](https://aitherium.github.io/awflow/) |
|
|
@@ -42,6 +42,44 @@ def _add_monorepo_root_to_syspath() -> None:
|
|
|
42
42
|
_sys.path.insert(0, str(root))
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
def _load_host_plugin():
|
|
46
|
+
"""Load the host capacity plugin from a FILE path and return the module.
|
|
47
|
+
|
|
48
|
+
The path is ``$AWRUN_CAPACITY_PLUGIN`` when set, else the marker file found
|
|
49
|
+
by :func:`_monorepo_root`. Raises ImportError when there is neither, or the
|
|
50
|
+
file fails to load -- the caller records why.
|
|
51
|
+
"""
|
|
52
|
+
import importlib.util
|
|
53
|
+
|
|
54
|
+
override = os.environ.get("AWRUN_CAPACITY_PLUGIN", "").strip()
|
|
55
|
+
if override:
|
|
56
|
+
path = Path(override)
|
|
57
|
+
else:
|
|
58
|
+
root = _monorepo_root()
|
|
59
|
+
if root is None:
|
|
60
|
+
raise ImportError("no host capacity plugin: AWRUN_CAPACITY_PLUGIN is "
|
|
61
|
+
"unset and no checkout marker was found")
|
|
62
|
+
path = root.joinpath(*_MONOREPO_MARKER)
|
|
63
|
+
if not path.is_file():
|
|
64
|
+
raise ImportError(f"host capacity plugin not found: {path}")
|
|
65
|
+
name = "_awrun_host_capacity_plugin"
|
|
66
|
+
cached = sys.modules.get(name)
|
|
67
|
+
if cached is not None and getattr(cached, "__file__", None) == str(path):
|
|
68
|
+
return cached
|
|
69
|
+
spec = importlib.util.spec_from_file_location(name, path)
|
|
70
|
+
if spec is None or spec.loader is None:
|
|
71
|
+
raise ImportError(f"cannot load host capacity plugin: {path}")
|
|
72
|
+
module = importlib.util.module_from_spec(spec)
|
|
73
|
+
sys.modules[name] = module
|
|
74
|
+
try:
|
|
75
|
+
spec.loader.exec_module(module)
|
|
76
|
+
except Exception as exc: # noqa: BLE001 - surfaced as ImportError, with cause
|
|
77
|
+
sys.modules.pop(name, None)
|
|
78
|
+
raise ImportError(f"host capacity plugin failed to load ({path}): "
|
|
79
|
+
f"{type(exc).__name__}: {exc}") from exc
|
|
80
|
+
return module
|
|
81
|
+
|
|
82
|
+
|
|
45
83
|
def _register_capacity_provider() -> None:
|
|
46
84
|
"""Register the host's capacity provisioner with awrun.plugins if available.
|
|
47
85
|
|
|
@@ -71,19 +109,19 @@ def _register_capacity_provider() -> None:
|
|
|
71
109
|
_add_monorepo_root_to_syspath()
|
|
72
110
|
|
|
73
111
|
try:
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
plugins.register(plugins.PROVISION_CAPACITY, provision_capacity)
|
|
112
|
+
# The provider is a host FILE, loaded by path -- never a package import,
|
|
113
|
+
# so the published awrun names no module it does not ship.
|
|
114
|
+
# AWRUN_CAPACITY_PLUGIN overrides the discovered path.
|
|
115
|
+
host = _load_host_plugin()
|
|
116
|
+
plugins.register(plugins.PROVISION_CAPACITY, host.provision_capacity)
|
|
81
117
|
# Registered in the SAME place as the provisioner on purpose: an
|
|
82
118
|
# autoscaler that can grow and cannot shrink is a spend generator, and
|
|
83
119
|
# wiring the two halves apart is how one of them stays unwired.
|
|
84
|
-
plugins.register(
|
|
85
|
-
|
|
86
|
-
|
|
120
|
+
plugins.register(plugins.REAP_CAPACITY, host.reap_capacity)
|
|
121
|
+
token_source = getattr(host, "github_token_source", None)
|
|
122
|
+
if callable(token_source) and plugins.get(plugins.GITHUB_TOKEN_SOURCE) is None:
|
|
123
|
+
plugins.register(plugins.GITHUB_TOKEN_SOURCE, token_source)
|
|
124
|
+
except (ImportError, AttributeError) as exc:
|
|
87
125
|
# Absent is FINE for a stranger: awrun ships to PyPI and still measures
|
|
88
126
|
# saturation without a provisioner. But on a host that plainly HAS the
|
|
89
127
|
# monorepo, a swallowed ImportError is how a provisioner stays dead for
|
|
@@ -685,7 +723,7 @@ def _self_test() -> int:
|
|
|
685
723
|
tunnel_args = argparse.Namespace(
|
|
686
724
|
kind="tunnel", priority=0, paths=[], json=False,
|
|
687
725
|
action="expose", hostname="demo.example.com",
|
|
688
|
-
origin="http://
|
|
726
|
+
origin="http://app-web:3000", plane="tunnel",
|
|
689
727
|
)
|
|
690
728
|
os.environ.pop("AWRUN_TUNNEL_OPERATORS", None)
|
|
691
729
|
rc5 = cmd_submit(tunnel_args, store)
|
|
@@ -697,7 +735,7 @@ def _self_test() -> int:
|
|
|
697
735
|
check("a tunnel operator's expose reaches the queue with the decided spec shape",
|
|
698
736
|
rc6 == 0 and len(queued_t) == 1 and queued_t[0].spec == {
|
|
699
737
|
"action": "expose", "hostname": "demo.example.com",
|
|
700
|
-
"origin": "http://
|
|
738
|
+
"origin": "http://app-web:3000", "plane": "tunnel"})
|
|
701
739
|
bad = argparse.Namespace(kind="tunnel", priority=0, paths=[], json=False,
|
|
702
740
|
action="retire", hostname="demo.example.com",
|
|
703
741
|
origin="http://x:1", plane="tunnel")
|
|
@@ -707,7 +745,7 @@ def _self_test() -> int:
|
|
|
707
745
|
and len(store.list(statuses=["queued"], kind="tunnel")) == tunnels_before)
|
|
708
746
|
bad2 = argparse.Namespace(kind="tunnel", priority=0, paths=[], json=False,
|
|
709
747
|
action="expose", hostname="demo.example.com",
|
|
710
|
-
origin="
|
|
748
|
+
origin="app-web:3000", plane="tunnel")
|
|
711
749
|
check("expose without a scheme on the origin is refused (exit 2)",
|
|
712
750
|
cmd_submit(bad2, store) == 2)
|
|
713
751
|
finally:
|
|
@@ -66,7 +66,7 @@ import urllib.request
|
|
|
66
66
|
from pathlib import Path
|
|
67
67
|
from typing import Callable, Optional
|
|
68
68
|
|
|
69
|
-
from awrun import confine, trail
|
|
69
|
+
from awrun import confine, host_admission, trail
|
|
70
70
|
from awrun.store import GPU_KINDS, RunItem, RunStore, get_store
|
|
71
71
|
|
|
72
72
|
logger = logging.getLogger(__name__)
|
|
@@ -680,6 +680,23 @@ def dispatch_once(store: RunStore, *, worker_id: str,
|
|
|
680
680
|
if gap is not None:
|
|
681
681
|
runnable = False
|
|
682
682
|
|
|
683
|
+
# ── Host admission: a heavy item never starts on a starving host ────────
|
|
684
|
+
# Asked before the GPU lease so a deferral holds no card. "Not now" is a
|
|
685
|
+
# requeue with backoff, exactly like a lease refusal -- never `failed`.
|
|
686
|
+
if runnable:
|
|
687
|
+
admitted, why, host_now = host_admission.admit(claimed.kind)
|
|
688
|
+
if not admitted:
|
|
689
|
+
delay = _lease_backoff_s(claimed.requeues, None)
|
|
690
|
+
back = store.requeue(claimed.id, now_fn() + delay,
|
|
691
|
+
wait={"reason": f"host over budget: {why}",
|
|
692
|
+
"host": host_now})
|
|
693
|
+
logger.warning("awrun: %s [%s] host over budget (%s) -- requeued, retry in %.0fs",
|
|
694
|
+
claimed.id, claimed.kind, why, delay)
|
|
695
|
+
if back is not None:
|
|
696
|
+
_broadcast(back, f"requeued: host over budget ({why}), retry in {delay:.0f}s",
|
|
697
|
+
client=relay_client)
|
|
698
|
+
return back
|
|
699
|
+
|
|
683
700
|
# ── GPU lease: asked BEFORE start, so a refusal is claimed -> queued ────
|
|
684
701
|
# Only when there is something to run: a lease taken for a handler that
|
|
685
702
|
# can only say "not implemented" would evict real work for nothing.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Host admission: do not start heavy work on a host that is already starving.
|
|
2
|
+
|
|
3
|
+
A queue that only asks "is a runner free?" will happily start three builds on a
|
|
4
|
+
box whose memory is already stalled, and the services sharing that box pay for
|
|
5
|
+
it. Before a heavy item runs, awrun reads Linux pressure-stall information
|
|
6
|
+
(``/proc/pressure``), the load average and ``MemAvailable``. When any is over
|
|
7
|
+
budget, the item goes back to ``queued/`` with a backoff -- "not now", never
|
|
8
|
+
"failed".
|
|
9
|
+
|
|
10
|
+
Budget: the defaults below, overridden by ``AWRUN_HOST_BUDGET`` (a JSON object
|
|
11
|
+
with any of the same keys); ``AWRUN_HOST_ADMISSION=off`` disables the check.
|
|
12
|
+
A host without ``/proc/pressure`` (Windows, macOS, an old kernel) cannot be
|
|
13
|
+
judged and is ADMITTED, and the verdict says so.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Dict, Optional, Tuple
|
|
22
|
+
|
|
23
|
+
#: Kinds that build, render, train or deploy: the ones that can starve a host.
|
|
24
|
+
HEAVY_KINDS = frozenset({"ci", "comet-deploy", "render", "artpack", "solve"})
|
|
25
|
+
|
|
26
|
+
DEFAULT_BUDGET: Dict[str, float] = {
|
|
27
|
+
"memory_full_avg60_pct": 10.0,
|
|
28
|
+
"cpu_some_avg60_pct": 60.0,
|
|
29
|
+
"load_per_cpu": 3.0,
|
|
30
|
+
"mem_available_min_gb": 12.0,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def budget() -> Dict[str, float]:
|
|
35
|
+
out = dict(DEFAULT_BUDGET)
|
|
36
|
+
raw = os.environ.get("AWRUN_HOST_BUDGET", "").strip()
|
|
37
|
+
if raw:
|
|
38
|
+
try:
|
|
39
|
+
out.update({k: float(v) for k, v in json.loads(raw).items()})
|
|
40
|
+
except (ValueError, TypeError, AttributeError):
|
|
41
|
+
out = dict(DEFAULT_BUDGET) # a malformed override keeps the defaults
|
|
42
|
+
return out
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _avg60(path: Path, head: str) -> float:
|
|
46
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
47
|
+
if line.startswith(head + " "):
|
|
48
|
+
for field in line.split()[1:]:
|
|
49
|
+
k, _, v = field.partition("=")
|
|
50
|
+
if k == "avg60":
|
|
51
|
+
return float(v)
|
|
52
|
+
return 0.0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def sample(proc: Path = Path("/proc")) -> Dict[str, float]:
|
|
56
|
+
mem_kb = 0
|
|
57
|
+
for line in (proc / "meminfo").read_text(encoding="utf-8").splitlines():
|
|
58
|
+
if line.startswith("MemAvailable:"):
|
|
59
|
+
mem_kb = int(line.split()[1])
|
|
60
|
+
load1 = float((proc / "loadavg").read_text(encoding="utf-8").split()[0])
|
|
61
|
+
return {
|
|
62
|
+
"memory_full_avg60_pct": _avg60(proc / "pressure" / "memory", "full"),
|
|
63
|
+
"cpu_some_avg60_pct": _avg60(proc / "pressure" / "cpu", "some"),
|
|
64
|
+
"load_per_cpu": load1 / max(os.cpu_count() or 1, 1),
|
|
65
|
+
"mem_available_gb": mem_kb / (1024 * 1024),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def decide(now: Dict[str, float], limits: Dict[str, float]) -> Tuple[bool, str]:
|
|
70
|
+
for key, limit in limits.items():
|
|
71
|
+
if key == "mem_available_min_gb":
|
|
72
|
+
if now.get("mem_available_gb", limit) < limit:
|
|
73
|
+
return False, f"MemAvailable {now['mem_available_gb']:.1f} GB < {limit:g} GB"
|
|
74
|
+
elif key in now and now[key] > limit:
|
|
75
|
+
return False, f"{key} {now[key]:.1f} > {limit:g}"
|
|
76
|
+
return True, "within host budget"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def admit(kind: str, proc: Path = Path("/proc")) -> Tuple[bool, str, Optional[dict]]:
|
|
80
|
+
"""(run?, why, sample-or-None). Light kinds and unjudgeable hosts are admitted."""
|
|
81
|
+
if kind not in HEAVY_KINDS:
|
|
82
|
+
return True, f"{kind} is not a heavy kind", None
|
|
83
|
+
if os.environ.get("AWRUN_HOST_ADMISSION", "").strip().lower() == "off":
|
|
84
|
+
return True, "host admission disabled (AWRUN_HOST_ADMISSION=off)", None
|
|
85
|
+
try:
|
|
86
|
+
now = sample(proc)
|
|
87
|
+
except (OSError, ValueError, IndexError) as exc:
|
|
88
|
+
return True, f"host pressure unreadable ({exc}) -- admitted unjudged", None
|
|
89
|
+
ok, why = decide(now, budget())
|
|
90
|
+
return ok, why, now
|
|
@@ -40,6 +40,12 @@ REAP_CAPACITY = "reap_capacity"
|
|
|
40
40
|
#: environment and reports "no door" when there is none.
|
|
41
41
|
GPU_LEASE_CLIENT = "gpu_lease_client"
|
|
42
42
|
|
|
43
|
+
#: () -> str. A GitHub token from wherever the HOST keeps credentials (a vault,
|
|
44
|
+
#: a keyring). Consulted only after GH_TOKEN / GITHUB_TOKEN / AWRUN_GH_TOKEN are
|
|
45
|
+
#: all unset; an empty string or a failure means "none here" and the next
|
|
46
|
+
#: fallback is tried. awrun itself knows no vault.
|
|
47
|
+
GITHUB_TOKEN_SOURCE = "github_token_source"
|
|
48
|
+
|
|
43
49
|
#: Why the host provisioner failed to import, when it did. None means it was
|
|
44
50
|
#: never attempted or it succeeded. Recorded rather than swallowed: a silent
|
|
45
51
|
#: ImportError leaves `awrun capacity --add` exiting 0 having done nothing,
|
|
@@ -49,11 +49,28 @@ class RunnerGroupError(RuntimeError):
|
|
|
49
49
|
"""Something we could not do, stated plainly enough to act on."""
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
def _vault_token() -> str:
|
|
53
|
+
"""A token from the HOST's credential store, via the plugin seam.
|
|
54
|
+
|
|
55
|
+
Inside a larger system the token may live in a vault rather than the
|
|
56
|
+
environment (an env copy can be stale and answer 401 while the vault's
|
|
57
|
+
answers 200). awrun ships standalone and knows no vault: the host registers
|
|
58
|
+
``plugins.GITHUB_TOKEN_SOURCE``. Nothing registered, or a hook that fails
|
|
59
|
+
or returns nothing, means "not a path here" and the env rule stands."""
|
|
60
|
+
from . import plugins
|
|
61
|
+
|
|
62
|
+
v = plugins.call(plugins.GITHUB_TOKEN_SOURCE)
|
|
63
|
+
return v.strip() if isinstance(v, str) else ""
|
|
64
|
+
|
|
65
|
+
|
|
52
66
|
def _token() -> str:
|
|
53
67
|
for var in ("GH_TOKEN", "GITHUB_TOKEN", "AWRUN_GH_TOKEN"):
|
|
54
68
|
v = os.environ.get(var, "").strip()
|
|
55
69
|
if v:
|
|
56
70
|
return v
|
|
71
|
+
v = _vault_token()
|
|
72
|
+
if v:
|
|
73
|
+
return v
|
|
57
74
|
raise RunnerGroupError(
|
|
58
75
|
"no GitHub token: set GH_TOKEN (or GITHUB_TOKEN). It is read from the "
|
|
59
76
|
"environment and never accepted as an argument, because an argv token "
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: awrun
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: A priority-aware queue and dispatcher for agentic runs and ad-hoc CI builds — real dynamic priority for the class of work we control, instead of GitHub Actions' opaque FIFO.
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://github.com/Aitherium/awrun
|
|
@@ -33,7 +33,7 @@ Dynamic: license-file
|
|
|
33
33
|
|
|
34
34
|
**[Docs](https://aitherium.github.io/awrun/)** · [Source](https://github.com/Aitherium/awrun) · `pip install awrun` · [The Aither World](https://aitherium.github.io/)
|
|
35
35
|
|
|
36
|
-
> **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awrun** is one of its
|
|
36
|
+
> **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awrun** is one of its 65 bricks — each installs on its own, runs offline, and needs no account.
|
|
37
37
|
>
|
|
38
38
|
> **Start here:** Queue two runs at different priorities and watch the urgent one overtake.
|
|
39
39
|
|
|
@@ -225,6 +225,7 @@ Each installs on its own, works offline, and needs no account.
|
|
|
225
225
|
| [awgraph](https://github.com/Aitherium/awgraph) | that grep found everything | an AST + tree-sitter call graph an agent can traverse |
|
|
226
226
|
| [awgit](https://github.com/Aitherium/awgit) | that no one else is editing this file | a lease, refused at commit time if you do not hold it |
|
|
227
227
|
| [awdelphi](https://github.com/Aitherium/awdelphi) | one agent's confident take on a decision | the round trace, the anonymity, and who dissents |
|
|
228
|
+
| [awclassify](https://github.com/Aitherium/awclassify) | a filename, a folder, or whoever last touched it | doc_type, visibility, audience and topics, with the evidence lines that decided each |
|
|
228
229
|
| [awtoll](https://github.com/Aitherium/awtoll) | that your tooling is saving you context | the measured token cost of each tool call, and what the alternative cost |
|
|
229
230
|
| [awseal](https://github.com/Aitherium/awseal) | that the artifact came from who you think | an Ed25519 seal — the key that verifies is not the key that forges |
|
|
230
231
|
| [awshare](https://github.com/Aitherium/awshare) | that the download is intact | content-addressed bundles, verified on fetch |
|
|
@@ -237,11 +238,14 @@ Each installs on its own, works offline, and needs no account.
|
|
|
237
238
|
| [awrelay](https://github.com/Aitherium/awrelay) | a SaaS in the middle of your agents | findings, alerts and coordination over your own transport |
|
|
238
239
|
| [awask](https://github.com/Aitherium/awask) | that anyone read the paragraph where you asked | the ask itself, with a button that steers the session that raised it |
|
|
239
240
|
| [awmail](https://github.com/Aitherium/awmail) | a mailbox somebody else can read | mail your agents send and receive over your own server |
|
|
241
|
+
| [awswarm](https://github.com/Aitherium/awswarm) | that a model either fits your GPU or it doesn't run at all | a placement plan and an acquisition-probability estimate before you spend on a run |
|
|
240
242
|
| [awfind](https://github.com/Aitherium/awfind) | one vendor's idea of the web | results from whichever providers you configured |
|
|
241
243
|
| [awbrowse](https://github.com/Aitherium/awbrowse) | that the page said what you were told | the render, the DOM and the requests it made |
|
|
242
244
|
| [awvoice](https://github.com/Aitherium/awvoice) | that a cloud vendor may hold your audio | a transcript and a wav from a service you host |
|
|
243
245
|
| [awvision](https://github.com/Aitherium/awvision) | a filename and a caption somebody wrote | what a model actually reports about the pixels |
|
|
244
246
|
| [awscreen](https://github.com/Aitherium/awscreen) | a selector that was true when the page was written | the elements actually rendered, by what they look like |
|
|
247
|
+
| [awbeads](https://github.com/Aitherium/awbeads) | that a layout your users built survives the next deploy | the arrangement as data you can read back, diff, and hand to another surface |
|
|
248
|
+
| [awbonsai](https://github.com/Aitherium/awbonsai) | that inference always means a request left the machine | a WebGPU model answering on the tab's own GPU, with a consent record logged before it ever loaded |
|
|
245
249
|
| [gawbbonet](https://github.com/Aitherium/gawbbonet) | the model to keep a 300-message campaign coherent by itself | campaign facts recalled from scoped memory you can list and edit |
|
|
246
250
|
| [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | a vendor's quantisation defaults | sub-byte KV cache kernels you can benchmark yourself |
|
|
247
251
|
| [awrtifact](https://github.com/Aitherium/awrtifact) | a hand-rolled split script and a hand-edited worker manifest | byte-verified parts in a release, served with Range + CORS, sizes asserted by a live gate |
|
|
@@ -251,11 +255,14 @@ Each installs on its own, works offline, and needs no account.
|
|
|
251
255
|
| [awrecurse](https://github.com/Aitherium/awrecurse) | that everything you pasted in was actually read | which slices it opened, and what it concluded from each |
|
|
252
256
|
| [awprism](https://github.com/Aitherium/awprism) | the first explanation that fits | the ranked alternatives, and the observation that separates them |
|
|
253
257
|
| [awrepl](https://github.com/Aitherium/awrepl) | what the agent believes the value is | the value, printed from the live session |
|
|
258
|
+
| [awreport](https://github.com/Aitherium/awreport) | that the report you pasted carried no token in it | a redacted report, and the duplicate it merged into instead of filing twice |
|
|
254
259
|
| [awresearch](https://github.com/Aitherium/awresearch) | a summary of pages nobody opened | every claim against the source it came from |
|
|
255
260
|
| [awfocus](https://github.com/Aitherium/awfocus) | twelve terminal tabs and a bad memory | one command that names every session, finds any transcript, and opens or steers the one you want |
|
|
256
261
|
| [awgym](https://github.com/Aitherium/awgym) | that a world model learned anything from the games it saw | transitions captured from real play, fed back, and the retrodiction score falling on grids it never saw |
|
|
257
262
|
| [awpredict](https://github.com/Aitherium/awpredict) | a model because it trained without erroring | its prediction against a self-updating lookup, on the rows that are actually novel |
|
|
263
|
+
| [awevolve](https://github.com/Aitherium/awevolve) | that your optimisation loop is finding anything | every version it kept, the score that version earned, and the edit that produced it |
|
|
258
264
|
| [awsh](https://github.com/Aitherium/awsh) | that you already know the name of the command | what it decided your line meant, before it acts on it |
|
|
265
|
+
| [awmine](https://github.com/Aitherium/awmine) | that a session's lesson survived the session | a row per outcome, a candidate per lesson, and the transcript line each one came from |
|
|
259
266
|
| [awrise](https://github.com/Aitherium/awrise) | that a scheduled agent ran at all, and ran exactly once | a durable record of every wake -- fired, skipped, overlapped or timed out -- each with its reason |
|
|
260
267
|
| [awkno](https://github.com/Aitherium/awkno) | that the docs site is up, or that you remember the family | the whole ecosystem in your terminal, with no network at all |
|
|
261
268
|
| [awwall](https://github.com/Aitherium/awwall) | that a service only talks to the hosts you think it talks to | an explicit egress allowlist, where a denial names the rule that denied it |
|
|
@@ -282,6 +289,7 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
|
|
|
282
289
|
| [awgraph](https://github.com/Aitherium/awgraph) | A semantic code graph for agents — AST + tree-sitter, call graphs | [docs](https://aitherium.github.io/awgraph/) |
|
|
283
290
|
| [awgit](https://github.com/Aitherium/awgit) | Semantic version control on top of git — edit-ops and leases | [docs](https://aitherium.github.io/awgit/) |
|
|
284
291
|
| [awdelphi](https://github.com/Aitherium/awdelphi) | Anonymous multi-round expert panels — a converged answer with a trace | [docs](https://aitherium.github.io/awdelphi/) |
|
|
292
|
+
| [awclassify](https://github.com/Aitherium/awclassify) | Classify any document -- what it is, who may read it, who it is for, what it is about | — |
|
|
285
293
|
| [awtoll](https://github.com/Aitherium/awtoll) | What every tool call costs you in context, measured from your own transcripts | [docs](https://aitherium.github.io/awtoll/) |
|
|
286
294
|
| [awseal](https://github.com/Aitherium/awseal) | Sign an artifact so a stranger can verify it | [docs](https://aitherium.github.io/awseal/) |
|
|
287
295
|
| [awshare](https://github.com/Aitherium/awshare) | Publish an artifact and fetch it back verified | [docs](https://aitherium.github.io/awshare/) |
|
|
@@ -299,12 +307,17 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
|
|
|
299
307
|
| [awask](https://github.com/Aitherium/awask) | Your agent asks you a question — and acts on your answer | [docs](https://aitherium.github.io/awask/) |
|
|
300
308
|
| [awmail](https://github.com/Aitherium/awmail) | Give an agent an email address — send, and actually receive | [docs](https://aitherium.github.io/awmail/) |
|
|
301
309
|
| [awnet](https://github.com/Aitherium/awnet) | The agentic web — agents host a mesh, and agents join one | [docs](https://aitherium.github.io/awnet/) |
|
|
310
|
+
| [awswarm](https://github.com/Aitherium/awswarm) | Run one model too big for any single GPU across a pool of small ones | — |
|
|
302
311
|
| [awfind](https://github.com/Aitherium/awfind) | A portable search client — query, results, ranking | [docs](https://aitherium.github.io/awfind/) |
|
|
303
312
|
| [awbrowse](https://github.com/Aitherium/awbrowse) | A portable browser client — navigate, console, network, DOM, screenshot | [docs](https://aitherium.github.io/awbrowse/) |
|
|
304
313
|
| [awvoice](https://github.com/Aitherium/awvoice) | Hear and speak — transcribe audio, synthesize a voice | [docs](https://aitherium.github.io/awvoice/) |
|
|
305
314
|
| [awvision](https://github.com/Aitherium/awvision) | See an image — describe it, ask it a question, compare two | [docs](https://aitherium.github.io/awvision/) |
|
|
306
315
|
| [awscreen](https://github.com/Aitherium/awscreen) | See this machine — what is on screen, and where to click it | [docs](https://aitherium.github.io/awscreen/) |
|
|
316
|
+
| [awkit](https://github.com/Aitherium/awkit) | Render an agent panel from a tool result — one component, any React app | — |
|
|
317
|
+
| [awbeads](https://github.com/Aitherium/awbeads) | A spatial canvas for a page — arrange things, connect them, and keep the arrangement | — |
|
|
318
|
+
| [awbonsai](https://github.com/Aitherium/awbonsai) | Run a real model in the visitor's own browser — no server round trip, no upload | — |
|
|
307
319
|
| [awknowledge](https://github.com/Aitherium/awknowledge) | How to run a coding agent so the result survives — the laws, with evidence | [docs](https://aitherium.github.io/awknowledge/) |
|
|
320
|
+
| [awbrain](https://github.com/Aitherium/awbrain) | Your history as a wiki of linked markdown — claims pinned to the evidence | — |
|
|
308
321
|
| [gawbbonet](https://github.com/Aitherium/gawbbonet) | GobboNet campaigns with a real agent brain — scoped memory, graph recall | [docs](https://aitherium.github.io/gawbbonet/) |
|
|
309
322
|
| [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | Near-optimal KV cache quantization for LLM inference — sub-byte compression | [docs](https://aitherium.github.io/aitherkvcache/) |
|
|
310
323
|
| [awrtifact](https://github.com/Aitherium/awrtifact) | Deliberately chunk artifacts into GitHub release assets — the productized aitherkvcache mirror lane | [docs](https://aitherium.github.io/awrtifact/) |
|
|
@@ -314,14 +327,18 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
|
|
|
314
327
|
| [awrecurse](https://github.com/Aitherium/awrecurse) | Answer a question over a context far larger than the window — recursively, with the trace kept | [docs](https://aitherium.github.io/awrecurse/) |
|
|
315
328
|
| [awprism](https://github.com/Aitherium/awprism) | Turn a failure into ranked hypotheses — and say what would confirm each one | [docs](https://aitherium.github.io/awprism/) |
|
|
316
329
|
| [awrepl](https://github.com/Aitherium/awrepl) | A REPL an agent can actually use — state that survives between turns | [docs](https://aitherium.github.io/awrepl/) |
|
|
330
|
+
| [awreport](https://github.com/Aitherium/awreport) | File a bug report that has already scrubbed your secrets and collapsed the duplicate | — |
|
|
317
331
|
| [awresearch](https://github.com/Aitherium/awresearch) | Ask a research question, get a cited report you can check | [docs](https://aitherium.github.io/awresearch/) |
|
|
318
332
|
| [awfocus](https://github.com/Aitherium/awfocus) | See, search and steer every Claude session from one command | [docs](https://aitherium.github.io/awfocus/) |
|
|
319
333
|
| [awgym](https://github.com/Aitherium/awgym) | An ARC training gym — a game a world model can watch, and six roles that play through it | [docs](https://aitherium.github.io/awgym/) |
|
|
320
334
|
| [awpredict](https://github.com/Aitherium/awpredict) | Predict what your environment does next, and how surprised you were | [docs](https://aitherium.github.io/awpredict/) |
|
|
335
|
+
| [awevolve](https://github.com/Aitherium/awevolve) | Point an agent at a file and a command that scores it, and let it improve | — |
|
|
321
336
|
| [awsh](https://github.com/Aitherium/awsh) | Your terminal answers you -- type a question where a command would go | [docs](https://aitherium.github.io/awsh/) |
|
|
337
|
+
| [awmine](https://github.com/Aitherium/awmine) | Mine what your agents did -- outcomes, lessons and procedures out of the transcripts they left behind | — |
|
|
322
338
|
| [awrise](https://github.com/Aitherium/awrise) | Wake an agent on a schedule, let it do one thing, and put it back to sleep | [docs](https://aitherium.github.io/awrise/) |
|
|
323
339
|
| [awkno](https://github.com/Aitherium/awkno) | The man page for the Aither World — every brick, stack and law, offline | [docs](https://aitherium.github.io/awkno/) |
|
|
324
340
|
| [awwall](https://github.com/Aitherium/awwall) | Say what a workload may reach, and watch everything else fail closed | [docs](https://aitherium.github.io/awwall/) |
|
|
341
|
+
| [awrouter](https://github.com/Aitherium/awrouter) | OpenRouter for your own fleet: pick a model backend by cost/latency/ capability, fail over, fit the context window, stream. Standalone, OpenAI-compatible, no Aither-specifics required to be valuable | — |
|
|
325
342
|
| [awembed](https://github.com/Aitherium/awembed) | Train an embedding model that knows your corpus, and prove it beats the big one | [docs](https://aitherium.github.io/awembed/) |
|
|
326
343
|
| [awtax](https://github.com/Aitherium/awtax) | Turn any tax PDF -- returns, W-2, 1099, statements, even scans -- into structured data you can check | [docs](https://aitherium.github.io/awtax/) |
|
|
327
344
|
| [awflow](https://github.com/Aitherium/awflow) | A deterministic workflow runtime — chain agent calls with journal replay and budget control | [docs](https://aitherium.github.io/awflow/) |
|
|
@@ -13,6 +13,7 @@ awrun/dispatcher.py
|
|
|
13
13
|
awrun/egress.py
|
|
14
14
|
awrun/gpu_lease.py
|
|
15
15
|
awrun/handoff.py
|
|
16
|
+
awrun/host_admission.py
|
|
16
17
|
awrun/plugins.py
|
|
17
18
|
awrun/runner_groups.py
|
|
18
19
|
awrun/store.py
|
|
@@ -27,5 +28,6 @@ awrun.egg-info/top_level.txt
|
|
|
27
28
|
tests/test_dispatcher_gpu_lease.py
|
|
28
29
|
tests/test_gpu_lease.py
|
|
29
30
|
tests/test_handoff.py
|
|
31
|
+
tests/test_host_admission.py
|
|
30
32
|
tests/test_store.py
|
|
31
33
|
tests/test_suspend_apply_confine.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "awrun"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.4"
|
|
8
8
|
description = "A priority-aware queue and dispatcher for agentic runs and ad-hoc CI builds — real dynamic priority for the class of work we control, instead of GitHub Actions' opaque FIFO."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Host admission: a heavy item never starts on a starving host, and a deferral
|
|
2
|
+
is a requeue with backoff -- never a failure, never a run."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from awrun import dispatcher, host_admission
|
|
8
|
+
from awrun.dispatcher import dispatch_once
|
|
9
|
+
from awrun.store import STATUS_FAILED, STATUS_QUEUED, RunStore
|
|
10
|
+
|
|
11
|
+
CALM = {"memory_full_avg60_pct": 0.5, "cpu_some_avg60_pct": 5.0, "load_per_cpu": 1.0,
|
|
12
|
+
"mem_available_gb": 30.0}
|
|
13
|
+
STARVING = {**CALM, "memory_full_avg60_pct": 84.6, "load_per_cpu": 21.2,
|
|
14
|
+
"mem_available_gb": 9.0}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture(autouse=True)
|
|
18
|
+
def _on(monkeypatch):
|
|
19
|
+
monkeypatch.delenv("AWRUN_HOST_ADMISSION", raising=False)
|
|
20
|
+
monkeypatch.delenv("AWRUN_HOST_BUDGET", raising=False)
|
|
21
|
+
monkeypatch.setattr(dispatcher, "_relay_client", lambda: None)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _fake_proc(tmp_path, mem_full: float):
|
|
25
|
+
(tmp_path / "pressure").mkdir()
|
|
26
|
+
(tmp_path / "pressure" / "memory").write_text(
|
|
27
|
+
f"some avg10=0 avg60=1.00 avg300=0 total=1\n"
|
|
28
|
+
f"full avg10=0 avg60={mem_full:.2f} avg300=0 total=1\n", encoding="utf-8")
|
|
29
|
+
(tmp_path / "pressure" / "cpu").write_text(
|
|
30
|
+
"some avg10=0 avg60=2.00 avg300=0 total=1\n", encoding="utf-8")
|
|
31
|
+
(tmp_path / "loadavg").write_text("1.0 1.0 1.0 1/1 1\n", encoding="utf-8")
|
|
32
|
+
(tmp_path / "meminfo").write_text("MemAvailable: 33554432 kB\n", encoding="utf-8")
|
|
33
|
+
return tmp_path
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_decide_calm_and_starving():
|
|
37
|
+
assert host_admission.decide(CALM, host_admission.budget())[0] is True
|
|
38
|
+
ok, why = host_admission.decide(STARVING, host_admission.budget())
|
|
39
|
+
assert ok is False and "memory_full_avg60_pct" in why
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_admit_reads_proc(tmp_path):
|
|
43
|
+
ok, why, now = host_admission.admit("ci", proc=_fake_proc(tmp_path, 45.0))
|
|
44
|
+
assert ok is False and now["memory_full_avg60_pct"] == 45.0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_light_kind_always_admitted(tmp_path):
|
|
48
|
+
ok, why, _ = host_admission.admit("agent", proc=_fake_proc(tmp_path, 99.0))
|
|
49
|
+
assert ok is True and "not a heavy kind" in why
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_unreadable_host_is_admitted_and_says_so(tmp_path):
|
|
53
|
+
ok, why, now = host_admission.admit("ci", proc=tmp_path / "missing")
|
|
54
|
+
assert ok is True and "unjudged" in why and now is None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_budget_override_and_malformed(monkeypatch):
|
|
58
|
+
monkeypatch.setenv("AWRUN_HOST_BUDGET", '{"memory_full_avg60_pct": 90}')
|
|
59
|
+
assert host_admission.budget()["memory_full_avg60_pct"] == 90.0
|
|
60
|
+
monkeypatch.setenv("AWRUN_HOST_BUDGET", "not json")
|
|
61
|
+
assert host_admission.budget() == host_admission.DEFAULT_BUDGET
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_dispatch_requeues_heavy_item_on_starving_host(tmp_path, monkeypatch):
|
|
65
|
+
store = RunStore(tmp_path / "q")
|
|
66
|
+
item = store.submit("ci", {"job": "x"})
|
|
67
|
+
monkeypatch.setattr(host_admission, "sample", lambda proc=None: dict(STARVING))
|
|
68
|
+
ran: list = []
|
|
69
|
+
|
|
70
|
+
def fn(i):
|
|
71
|
+
ran.append(i.id)
|
|
72
|
+
return 0, "ok"
|
|
73
|
+
|
|
74
|
+
back = dispatch_once(store, worker_id="w1", run_fns={"ci": fn}, now_fn=lambda: 1000.0)
|
|
75
|
+
assert back is not None and back.id == item.id
|
|
76
|
+
assert back.status == STATUS_QUEUED and ran == []
|
|
77
|
+
assert store.list(statuses=[STATUS_FAILED]) == []
|
|
78
|
+
assert "host over budget" in (back.wait or {}).get("reason", "")
|
|
79
|
+
assert back.not_before > 1000.0
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_dispatch_runs_heavy_item_on_calm_host(tmp_path, monkeypatch):
|
|
83
|
+
store = RunStore(tmp_path / "q")
|
|
84
|
+
store.submit("ci", {"job": "x"})
|
|
85
|
+
monkeypatch.setattr(host_admission, "sample", lambda proc=None: dict(CALM))
|
|
86
|
+
ran: list = []
|
|
87
|
+
dispatch_once(store, worker_id="w1",
|
|
88
|
+
run_fns={"ci": lambda i: (ran.append(i.id), (0, "ok"))[1]},
|
|
89
|
+
now_fn=lambda: 1000.0)
|
|
90
|
+
assert len(ran) == 1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|