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.
Files changed (35) hide show
  1. {awrun-0.2.2 → awrun-0.2.4}/PKG-INFO +19 -2
  2. {awrun-0.2.2 → awrun-0.2.4}/README.md +18 -1
  3. {awrun-0.2.2 → awrun-0.2.4}/awrun/__init__.py +1 -1
  4. {awrun-0.2.2 → awrun-0.2.4}/awrun/cli.py +51 -13
  5. {awrun-0.2.2 → awrun-0.2.4}/awrun/dispatcher.py +18 -1
  6. awrun-0.2.4/awrun/host_admission.py +90 -0
  7. {awrun-0.2.2 → awrun-0.2.4}/awrun/plugins.py +6 -0
  8. {awrun-0.2.2 → awrun-0.2.4}/awrun/runner_groups.py +17 -0
  9. {awrun-0.2.2 → awrun-0.2.4}/awrun.egg-info/PKG-INFO +19 -2
  10. {awrun-0.2.2 → awrun-0.2.4}/awrun.egg-info/SOURCES.txt +2 -0
  11. {awrun-0.2.2 → awrun-0.2.4}/pyproject.toml +1 -1
  12. awrun-0.2.4/tests/test_host_admission.py +90 -0
  13. {awrun-0.2.2 → awrun-0.2.4}/LICENSE +0 -0
  14. {awrun-0.2.2 → awrun-0.2.4}/awrun/__main__.py +0 -0
  15. {awrun-0.2.2 → awrun-0.2.4}/awrun/_doctor.py +0 -0
  16. {awrun-0.2.2 → awrun-0.2.4}/awrun/_flow_runner.py +0 -0
  17. {awrun-0.2.2 → awrun-0.2.4}/awrun/authz.py +0 -0
  18. {awrun-0.2.2 → awrun-0.2.4}/awrun/capacity.py +0 -0
  19. {awrun-0.2.2 → awrun-0.2.4}/awrun/confine.py +0 -0
  20. {awrun-0.2.2 → awrun-0.2.4}/awrun/egress.py +0 -0
  21. {awrun-0.2.2 → awrun-0.2.4}/awrun/gpu_lease.py +0 -0
  22. {awrun-0.2.2 → awrun-0.2.4}/awrun/handoff.py +0 -0
  23. {awrun-0.2.2 → awrun-0.2.4}/awrun/store.py +0 -0
  24. {awrun-0.2.2 → awrun-0.2.4}/awrun/surface.py +0 -0
  25. {awrun-0.2.2 → awrun-0.2.4}/awrun/trail.py +0 -0
  26. {awrun-0.2.2 → awrun-0.2.4}/awrun.egg-info/dependency_links.txt +0 -0
  27. {awrun-0.2.2 → awrun-0.2.4}/awrun.egg-info/entry_points.txt +0 -0
  28. {awrun-0.2.2 → awrun-0.2.4}/awrun.egg-info/requires.txt +0 -0
  29. {awrun-0.2.2 → awrun-0.2.4}/awrun.egg-info/top_level.txt +0 -0
  30. {awrun-0.2.2 → awrun-0.2.4}/setup.cfg +0 -0
  31. {awrun-0.2.2 → awrun-0.2.4}/tests/test_dispatcher_gpu_lease.py +0 -0
  32. {awrun-0.2.2 → awrun-0.2.4}/tests/test_gpu_lease.py +0 -0
  33. {awrun-0.2.2 → awrun-0.2.4}/tests/test_handoff.py +0 -0
  34. {awrun-0.2.2 → awrun-0.2.4}/tests/test_store.py +0 -0
  35. {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.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 55 bricks — each installs on its own, runs offline, and needs no account.
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 55 bricks — each installs on its own, runs offline, and needs no account.
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/) |
@@ -24,4 +24,4 @@ __all__ = [
24
24
  "runs_dir",
25
25
  ]
26
26
 
27
- __version__ = "0.2.2"
27
+ __version__ = "0.2.4"
@@ -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
- # Try to import from the monorepo dev tools
75
- # This succeeds only on the host; it fails gracefully on PyPI/strangers
76
- from AitherOS.dev.tools.awrun_capacity import (
77
- provision_capacity,
78
- reap_capacity,
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(getattr(plugins, "REAP_CAPACITY", "reap_capacity"),
85
- reap_capacity)
86
- except (ImportError, ModuleNotFoundError) as exc:
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://aitheros-veil:3000", plane="tunnel",
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://aitheros-veil:3000", "plane": "tunnel"})
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="aitheros-veil:3000", plane="tunnel")
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.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 55 bricks — each installs on its own, runs offline, and needs no account.
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.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