awpredict 0.1.0__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 (33) hide show
  1. awpredict-0.1.0/LICENSE +21 -0
  2. awpredict-0.1.0/PKG-INFO +14 -0
  3. awpredict-0.1.0/README.md +197 -0
  4. awpredict-0.1.0/awpredict/__init__.py +26 -0
  5. awpredict-0.1.0/awpredict/_doctor.py +139 -0
  6. awpredict-0.1.0/awpredict/adapters/__init__.py +3 -0
  7. awpredict-0.1.0/awpredict/adapters/code_world.py +360 -0
  8. awpredict-0.1.0/awpredict/adapters/obs_adapter.py +213 -0
  9. awpredict-0.1.0/awpredict/contracts.py +114 -0
  10. awpredict-0.1.0/awpredict/core/__init__.py +3 -0
  11. awpredict-0.1.0/awpredict/core/lewm.py +2646 -0
  12. awpredict-0.1.0/awpredict/core/mlp.py +654 -0
  13. awpredict-0.1.0/awpredict/explorer/__init__.py +14 -0
  14. awpredict-0.1.0/awpredict/explorer/fs_cartographer.py +429 -0
  15. awpredict-0.1.0/awpredict/explorer/histogram_encoder.py +63 -0
  16. awpredict-0.1.0/awpredict/memory/__init__.py +6 -0
  17. awpredict-0.1.0/awpredict/memory/landmark.py +166 -0
  18. awpredict-0.1.0/awpredict/training/__init__.py +1 -0
  19. awpredict-0.1.0/awpredict/training/online.py +132 -0
  20. awpredict-0.1.0/awpredict.egg-info/PKG-INFO +14 -0
  21. awpredict-0.1.0/awpredict.egg-info/SOURCES.txt +31 -0
  22. awpredict-0.1.0/awpredict.egg-info/dependency_links.txt +1 -0
  23. awpredict-0.1.0/awpredict.egg-info/requires.txt +9 -0
  24. awpredict-0.1.0/awpredict.egg-info/top_level.txt +1 -0
  25. awpredict-0.1.0/pyproject.toml +53 -0
  26. awpredict-0.1.0/setup.cfg +4 -0
  27. awpredict-0.1.0/tests/test_fs_explorer_protocol.py +303 -0
  28. awpredict-0.1.0/tests/test_landmark_memory.py +101 -0
  29. awpredict-0.1.0/tests/test_lint_config_agrees.py +60 -0
  30. awpredict-0.1.0/tests/test_object_masking.py +117 -0
  31. awpredict-0.1.0/tests/test_online_lr_controller.py +97 -0
  32. awpredict-0.1.0/tests/test_structured_adapter.py +86 -0
  33. awpredict-0.1.0/tests/test_value_head.py +132 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aitherium
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: awpredict
3
+ Version: 0.1.0
4
+ Summary: A small, dependency-light world-model package: LeWM JEPA + MLP engines, environment adapters
5
+ Requires-Python: >=3.10
6
+ License-File: LICENSE
7
+ Provides-Extra: torch
8
+ Requires-Dist: torch>=2.0; extra == "torch"
9
+ Requires-Dist: numpy>=1.24; extra == "torch"
10
+ Provides-Extra: dev
11
+ Requires-Dist: torch>=2.0; extra == "dev"
12
+ Requires-Dist: numpy>=1.24; extra == "dev"
13
+ Requires-Dist: pytest>=7.0; extra == "dev"
14
+ Dynamic: license-file
@@ -0,0 +1,197 @@
1
+ # awm — a small, dependency-light world model
2
+
3
+ <!-- aither-header:start GENERATED from the ecosystem registry. Edits here are overwritten; change the registry instead. -->
4
+
5
+ **[Docs](https://aitherium.github.io/awpredict/)** · [Source](https://github.com/Aitherium/awpredict) · `pip install awpredict` · [The Aither World](https://aitherium.github.io/)
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; **awpredict** is one of its 33 bricks — each installs on its own, runs offline, and needs no account.
8
+ >
9
+ > **Start here:** Wrap an environment you already have and ask it what happens next.
10
+
11
+ <!-- aither-header:end -->
12
+
13
+ `awpredict` is a learned-dynamics library: encode an observation to a
14
+ latent, predict the next latent given an action, score how surprised the
15
+ model was, optionally plan in latent space. Two engines, a small adapter
16
+ protocol, no framework lock-in.
17
+
18
+ - **`awpredict.core.lewm.LeWorldModel`** — a LeWM-style JEPA (joint-embedding
19
+ predictive architecture): a two-term loss (next-latent MSE + SIGReg
20
+ isotropy regularization), a CEM planner, and an optional value head
21
+ (`_ValueHead`/`_FsAdapter`) for value-guided planning — trained on returns
22
+ over a frozen latent, off unless you construct with a value config. This
23
+ is the full engine behind an ARC-AGI-3 solving agent, not a cut-down demo
24
+ of it.
25
+ - **`awpredict.core.mlp.MLPWorldModel`** — a lighter embedding-MLP
26
+ transition model with a tabular cold-start fallback (tabular → hybrid →
27
+ neural), for when a full JEPA is more machinery than the problem needs.
28
+ - **`awpredict.contracts`** — the two Protocols (`WorldModel`,
29
+ `EnvironmentAdapter`) everything else is written against. Write an adapter
30
+ for your environment; nothing in an engine has to change.
31
+ - **`awpredict.adapters.code_world`** — an example adapter mapping a
32
+ codebase (landmarks + code chunks) into the engine's observation/action
33
+ space. Reference implementation, not a complete environment.
34
+ - **`awpredict.training.online`** — an optional, flag-gated
35
+ surprise-modulated online learning-rate controller. Off by default. See
36
+ [`awpredict/training/PROVENANCE.md`](awpredict/training/PROVENANCE.md)
37
+ for concept provenance and license boundary — the *concepts* (not code)
38
+ are credited to an external, restricted-license research project; this
39
+ implementation is clean-room.
40
+
41
+ Every engine follows one rule: **degrade loudly, never silently.** A missing
42
+ torch install or an unreadable checkpoint sets `ok = False` and returns
43
+ `None`/`[]` — it never raises into a caller's turn loop and never fabricates
44
+ a prediction. That property mattered more than any architecture choice; see
45
+ [`RESULTS.md`](RESULTS.md).
46
+
47
+ ## Install
48
+
49
+ ```bash
50
+ pip install -e ".[torch]" # torch + numpy are optional; engines degrade loudly without them
51
+ ```
52
+
53
+ ## Quick use
54
+
55
+ ```python
56
+ from awpredict.core.mlp import MLPWorldModel
57
+
58
+ model = MLPWorldModel()
59
+ model.observe(state, action, next_state, reward=0.0, done=False)
60
+ model.train_step()
61
+ z = model.encode(state)
62
+ pred = model.predict(z, action)
63
+ print(model.surprise(state, action, next_state))
64
+ ```
65
+
66
+ ## Why this exists
67
+
68
+ The goal is to help someone bootstrap their own world model — a working
69
+ engine, a planner, an optional value head, a contract to write your own
70
+ adapter against, and no framework lock-in — not a stripped demo pointing at
71
+ a hosted service. This is the actual core out of a larger internal agent
72
+ platform (Aitherium), extracted whole rather than trimmed, plus the
73
+ *findings* from running it against ARC-AGI-3: see [`RESULTS.md`](RESULTS.md)
74
+ for the honest version, including two negative results that mattered more
75
+ than any positive one.
76
+
77
+ ## Status
78
+
79
+ Research code. The contracts and degrade-loudly discipline are load-bearing
80
+ and tested (`tests/`); the engines themselves are still moving. `lewm.py` is
81
+ the same file the ARC-AGI-3 solving agent runs, value head included — see
82
+ `awpredict/__init__.py` for what's on by default vs. opt-in.
83
+
84
+ ## License
85
+
86
+ MIT (see `LICENSE`). See `awpredict/training/PROVENANCE.md` for the one
87
+ file with an external concept-attribution.
88
+
89
+ <!-- aitherium-ecosystem:start -->
90
+ ## Aitherium open-source ecosystem
91
+
92
+ This repo is one piece of a connected set. All public, MIT/BSL-licensed:
93
+
94
+ | repo | what it is | pages |
95
+ |---|---|---|
96
+ | [awrecover](https://github.com/Aitherium/awrecover) | Labelled snapshots with an all-or-nothing restore | [docs](https://aitherium.github.io/awrecover/) |
97
+ | [awshare](https://github.com/Aitherium/awshare) | Publish an artifact and fetch it back verified | [docs](https://aitherium.github.io/awshare/) |
98
+ | [awseal](https://github.com/Aitherium/awseal) | Sign an artifact so a stranger can verify it | [docs](https://aitherium.github.io/awseal/) |
99
+ | [awnode](https://github.com/Aitherium/awnode) | Lightweight local gateway — your apps to backends you chose | [docs](https://aitherium.github.io/awnode/) |
100
+ | [awnix](https://github.com/Aitherium/awnix) | A bootable, immutable Linux base for agent-run machines | [docs](https://aitherium.github.io/awnix/) |
101
+ | [awdk](https://github.com/Aitherium/awdk) | Build AI agent fleets — 3 lines, any backend | [docs](https://aitherium.github.io/awdk/) |
102
+ | [awskills](https://github.com/Aitherium/awskills) | Free agent skills, scripts & automations | [docs](https://aitherium.github.io/awskills/) |
103
+ | [AitherZero](https://github.com/Aitherium/AitherZero) | PowerShell 7+ automation framework | [docs](https://aitherium.github.io/AitherZero/) |
104
+ | [awgit](https://github.com/Aitherium/awgit) | Semantic version control on top of git | [docs](https://aitherium.github.io/awgit/) |
105
+ | [awgraph](https://github.com/Aitherium/awgraph) | Code knowledge graph for AI agents | [docs](https://aitherium.github.io/awgraph/) |
106
+ | [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | Near-optimal KV cache quantization | [docs](https://aitherium.github.io/aitherkvcache/) |
107
+ | [awrelay](https://github.com/Aitherium/awrelay) | Agent-to-agent messaging over any chat server | [docs](https://aitherium.github.io/awrelay/) |
108
+ | [awm](https://github.com/Aitherium/awm) | A small world model (LeWM JEPA + MLP) to bootstrap your own | [docs](https://aitherium.github.io/awm/) |
109
+ | [AitherConnect](https://github.com/Aitherium/AitherConnect) | Browser extension: federated AI search & desktop bridge | — |
110
+ | [homebrew-tap](https://github.com/Aitherium/homebrew-tap) | `brew tap aitherium/tap` | — |
111
+
112
+ Built by [Aitherium](https://aitherium.com).
113
+ <!-- aitherium-ecosystem:end -->
114
+
115
+ <!-- aither-ecosystem:start GENERATED from the ecosystem registry. Edits here are overwritten; change the registry instead. -->
116
+
117
+ ## The aw family
118
+
119
+ Standalone tools that share one idea: **replace something you would otherwise have to _trust_ with something you can _check_.**
120
+
121
+ Each installs on its own, works offline, and needs no account.
122
+
123
+ | | instead of trusting | you check |
124
+ |---|---|---|
125
+ | [awdk](https://github.com/Aitherium/awdk) | a framework's idea of how your agents should run | one loop you can read, pointed at a backend you already pay for |
126
+ | [awskills](https://github.com/Aitherium/awskills) | that an agent knows your procedure | the procedure written down, versioned, and loadable by any agent |
127
+ | [awm](https://github.com/Aitherium/awm) | that memory stayed in its lane | tenant:user:project scopes, so a write cannot cross a boundary |
128
+ | [awnode](https://github.com/Aitherium/awnode) | a vendor's cloud with every prompt | a local gateway routing to backends you chose |
129
+ | [awgraph](https://github.com/Aitherium/awgraph) | that grep found everything | an AST + tree-sitter call graph an agent can traverse |
130
+ | [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 |
131
+ | [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 |
132
+ | [awshare](https://github.com/Aitherium/awshare) | that the download is intact | content-addressed bundles, verified on fetch |
133
+ | [awnest](https://github.com/Aitherium/awnest) | that there is a person on the other end | a verdict with evidence, where "we could not tell" is not "yes" |
134
+ | [awnboard](https://github.com/Aitherium/awnboard) | a share link anyone who sees it can use | an invitation addressed to one person, for one gate, revocable |
135
+ | [awnix](https://github.com/Aitherium/awnix) | that the box is what you left it as | an immutable image you built, with atomic rollback |
136
+ | [awrecover](https://github.com/Aitherium/awrecover) | that the restore worked | a restore that fully lands or does not land at all |
137
+ | [awrelay](https://github.com/Aitherium/awrelay) | a SaaS in the middle of your agents | findings, alerts and coordination over your own transport |
138
+ | [awmail](https://github.com/Aitherium/awmail) | a mailbox somebody else can read | mail your agents send and receive over your own server |
139
+ | [awfind](https://github.com/Aitherium/awfind) | one vendor's idea of the web | results from whichever providers you configured |
140
+ | [awbrowse](https://github.com/Aitherium/awbrowse) | that the page said what you were told | the render, the DOM and the requests it made |
141
+ | [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | a vendor's quantisation defaults | sub-byte KV cache kernels you can benchmark yourself |
142
+ | [AitherZero](https://github.com/Aitherium/AitherZero) | a pile of scripts nobody has numbered | numbered, discoverable automation with declarative playbooks |
143
+ | [AitherConnect](https://github.com/Aitherium/AitherConnect) | what a page tells your browser to do | a federated search and desktop bridge you host |
144
+ | [awreason](https://github.com/Aitherium/awreason) | a confident paragraph | the phases it went through, and every tool call it made to get there |
145
+ | [awrecurse](https://github.com/Aitherium/awrecurse) | that everything you pasted in was actually read | which slices it opened, and what it concluded from each |
146
+ | [awprism](https://github.com/Aitherium/awprism) | the first explanation that fits | the ranked alternatives, and the observation that separates them |
147
+ | [awrepl](https://github.com/Aitherium/awrepl) | what the agent believes the value is | the value, printed from the live session |
148
+ | [awresearch](https://github.com/Aitherium/awresearch) | a summary of pages nobody opened | every claim against the source it came from |
149
+ | **awpredict** _(you are here)_ | a model because it trained without erroring | its prediction against a self-updating lookup, on the rows that are actually novel |
150
+ | [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 |
151
+
152
+ [**awnix**](https://github.com/Aitherium/awnix) is the ground floor — A Linux you can hand to an agent — immutable base, capabilities included.
153
+
154
+ ## The Aitherium ecosystem
155
+
156
+ Every repository here is public. Each publishes an `aither-manifest.json` beside its page, so any surface can read every sibling's — the network is browsable from any node in it.
157
+
158
+ | repo | what it is | pages |
159
+ |---|---|---|
160
+ | [awdk](https://github.com/Aitherium/awdk) | Build AI agent fleets — 3 lines, any backend, local or cloud | [docs](https://aitherium.github.io/awdk/) |
161
+ | [awskills](https://github.com/Aitherium/awskills) | Portable agent skills — self-contained procedures an agent loads on demand | [docs](https://aitherium.github.io/awskills/) |
162
+ | [awm](https://github.com/Aitherium/awm) | A portable, scoped agent memory | [docs](https://aitherium.github.io/awm/) |
163
+ | [awnode](https://github.com/Aitherium/awnode) | A lightweight local gateway — bridges your apps to the AI backends you chose | [docs](https://aitherium.github.io/awnode/) |
164
+ | [awrun](https://github.com/Aitherium/awrun) | A priority-aware queue and dispatcher for agentic runs and ad-hoc CI builds | [docs](https://aitherium.github.io/awrun/) |
165
+ | [awgraph](https://github.com/Aitherium/awgraph) | A semantic code graph for agents — AST + tree-sitter, call graphs | [docs](https://aitherium.github.io/awgraph/) |
166
+ | [awgit](https://github.com/Aitherium/awgit) | Semantic version control on top of git — edit-ops and leases | [docs](https://aitherium.github.io/awgit/) |
167
+ | [awseal](https://github.com/Aitherium/awseal) | Sign an artifact so a stranger can verify it | [docs](https://aitherium.github.io/awseal/) |
168
+ | [awshare](https://github.com/Aitherium/awshare) | Publish an artifact and fetch it back verified | [docs](https://aitherium.github.io/awshare/) |
169
+ | [awdit](https://github.com/Aitherium/awdit) | An append-only audit trail whose gaps are DETECTABLE | [docs](https://aitherium.github.io/awdit/) |
170
+ | [awbac](https://github.com/Aitherium/awbac) | Role-based access control that fails closed and explains itself | [docs](https://aitherium.github.io/awbac/) |
171
+ | [awiam](https://github.com/Aitherium/awiam) | Who is this caller? A directory and session store that fails honestly | [docs](https://aitherium.github.io/awiam/) |
172
+ | [awtunnel](https://github.com/Aitherium/awtunnel) | Reach a service that has no public address | [docs](https://aitherium.github.io/awtunnel/) |
173
+ | [awnest](https://github.com/Aitherium/awnest) | Prove there is a human before you let them into the nest | [docs](https://aitherium.github.io/awnest/) |
174
+ | [awnboard](https://github.com/Aitherium/awnboard) | A front gate you can put in front of anything, and hand someone the key to | [docs](https://aitherium.github.io/awnboard/) |
175
+ | [awnix](https://github.com/Aitherium/awnix) | A Linux you can hand to an agent — immutable base, capabilities included | [docs](https://aitherium.github.io/awnix/) |
176
+ | [awrecover](https://github.com/Aitherium/awrecover) | Labelled snapshots with an all-or-nothing restore | [docs](https://aitherium.github.io/awrecover/) |
177
+ | [awrelay](https://github.com/Aitherium/awrelay) | Portable agent messaging — findings, alerts, coordination | [docs](https://aitherium.github.io/awrelay/) |
178
+ | [awmail](https://github.com/Aitherium/awmail) | Give an agent an email address — send, and actually receive | [docs](https://aitherium.github.io/awmail/) |
179
+ | [awnet](https://github.com/Aitherium/awnet) | The agentic web — agents host a mesh, and agents join one | [docs](https://aitherium.github.io/awnet/) |
180
+ | [awfind](https://github.com/Aitherium/awfind) | A portable search client — query, results, ranking | [docs](https://aitherium.github.io/awfind/) |
181
+ | [awbrowse](https://github.com/Aitherium/awbrowse) | A portable browser client — navigate, console, network, DOM, screenshot | [docs](https://aitherium.github.io/awbrowse/) |
182
+ | [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/) |
183
+ | [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | Near-optimal KV cache quantization for LLM inference — sub-byte compression | [docs](https://aitherium.github.io/aitherkvcache/) |
184
+ | [AitherZero](https://github.com/Aitherium/AitherZero) | PowerShell 7+ automation framework — numbered, self-describing scripts | [docs](https://aitherium.github.io/AitherZero/) |
185
+ | [AitherConnect](https://github.com/Aitherium/AitherConnect) | Browser extension — federated AI search, page context, and the Living OS overlay | [docs](https://aitherium.github.io/AitherConnect/) |
186
+ | [awreason](https://github.com/Aitherium/awreason) | A portable reasoning client — sessions, phases, thoughts, and the chain that produced the answer | [docs](https://aitherium.github.io/awreason/) |
187
+ | [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/) |
188
+ | [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/) |
189
+ | [awrepl](https://github.com/Aitherium/awrepl) | A REPL an agent can actually use — state that survives between turns | [docs](https://aitherium.github.io/awrepl/) |
190
+ | [awresearch](https://github.com/Aitherium/awresearch) | Ask a research question, get a cited report you can check | [docs](https://aitherium.github.io/awresearch/) |
191
+ | **awpredict** _(you are here)_ | Predict what your environment does next, and how surprised you were | [docs](https://aitherium.github.io/awpredict/) |
192
+ | [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/) |
193
+
194
+ <div id="aither-constellation" data-self="awpredict"></div>
195
+ <script src="aither-constellation.js"></script>
196
+
197
+ <!-- aither-ecosystem:end -->
@@ -0,0 +1,26 @@
1
+ """awpredict — a small, dependency-light world-model package (JEPA + MLP engines).
2
+
3
+ One package, two engines, N environment adapters:
4
+
5
+ * ``awpredict.core.lewm.LeWorldModel`` — the LeWM-style JEPA
6
+ (two-term loss: next-latent MSE + SIGReg; CEM planner), used as the
7
+ world model behind an ARC-AGI-3 solving agent. Includes an optional
8
+ value head (``_ValueHead``/``_FsAdapter``/``value()``/``train_value_step``)
9
+ for value-guided CEM planning — a frozen-latent predictor head trained
10
+ on returns, off by default, on by constructing with a value config. This
11
+ is the same engine the solving agent runs, not a cut-down demo of it —
12
+ the goal of this package is to give you everything needed to bootstrap
13
+ your own world model, not a subset of it.
14
+ * ``awpredict.core.mlp.MLPWorldModel`` — an embedding-MLP transition
15
+ model (tabular → hybrid → neural).
16
+
17
+ Contracts live in ``awpredict.contracts`` (WorldModel, EnvironmentAdapter).
18
+ Torch and numpy are OPTIONAL at import time — engines degrade loudly
19
+ (ok == False), never raise into a caller.
20
+ """
21
+
22
+ from awpredict.contracts import EnvironmentAdapter, WorldModel, conforms
23
+
24
+ __version__ = "0.1.0"
25
+
26
+ __all__ = ["EnvironmentAdapter", "WorldModel", "conforms", "__version__"]
@@ -0,0 +1,139 @@
1
+ """Stack-aware `doctor` for awpredict.
2
+
3
+ GENERATED BY gen_aw_doctor.py -- DO NOT EDIT.
4
+ Regenerate it with the generator named above; a hand-edit here is reverted by
5
+ the next run and fails the parity gate.
6
+
7
+ Why a doctor exists at all: the aw* bricks are designed to COMPOSE, so the
8
+ interesting failures live BETWEEN them. "awpredict is installed" is not the useful
9
+ fact -- "awpredict is installed and the thing it pairs with is not" is. This reports
10
+ the whole stack, not just itself.
11
+
12
+ stdlib only, on purpose: a diagnostic that cannot run because a dependency is
13
+ missing is worthless precisely when you need it.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import importlib.util
18
+ import os
19
+ import shutil
20
+ import sys
21
+
22
+ #: Frozen from AitherOS/config/ecosystem.yaml at generation time. A shipped
23
+ #: package cannot read the registry, and a doctor that guessed at the family
24
+ #: would go stale in silence. Regenerate to update.
25
+ SELF = 'awpredict'
26
+ FAMILY = ['awask', 'awask', 'awbac', 'awbrowse', 'awdit', 'awevolve', 'awevolve', 'awfind', 'awgit', 'awgraph', 'awiam', 'awkno', 'awm', 'awmail', 'awnboard', 'awnest', 'awnet', 'awnode', 'awprism', 'awreason', 'awrecover', 'awrecurse', 'awrelay', 'awrepl', 'awresearch', 'awrun', 'awseal', 'awshare', 'awtunnel']
27
+ PAIRS_WITH = ['awm']
28
+
29
+ #: This brick's OWN config, read out of its source at generation time.
30
+ #: ENV_REQUIRED is `os.environ["X"]` -- absent, that is a KeyError the moment
31
+ #: the line runs. ENV_OPTIONAL is `os.getenv("X")`, which returns None and lets
32
+ #: the caller cope. Only this brick's namespace is listed: reporting the
33
+ #: platform-wide vars it also touches would be noise, and a doctor that floods
34
+ #: gets ignored.
35
+ ENV_REQUIRED = []
36
+ ENV_OPTIONAL = []
37
+
38
+
39
+ def _installed(mod: str) -> "str | None":
40
+ """Version if importable, else None. Never raises -- a broken sibling must
41
+ not take the diagnostic down with it."""
42
+ try:
43
+ if importlib.util.find_spec(mod) is None:
44
+ return None
45
+ except (ImportError, ValueError):
46
+ return None
47
+ try:
48
+ from importlib.metadata import PackageNotFoundError, version
49
+ try:
50
+ return version(mod)
51
+ except PackageNotFoundError:
52
+ return "installed"
53
+ except Exception:
54
+ return "installed"
55
+
56
+
57
+ def report(out=None) -> int:
58
+ """Print the stack picture. 0 = this brick and its pairs are present."""
59
+ out = out or sys.stdout
60
+ print(f"{SELF} doctor", file=out)
61
+
62
+ mine = _installed(SELF)
63
+ print(f" self {SELF} {mine or 'NOT IMPORTABLE'}", file=out)
64
+ shim = shutil.which(SELF)
65
+ print(f" command {shim or 'not on PATH'}", file=out)
66
+
67
+ # The stack. Siblings this brick pairs with are called out separately,
68
+ # because a missing pair is a REASON, while a missing unrelated brick is
69
+ # just a fact about your machine.
70
+ missing_pairs, present = [], []
71
+ for name in FAMILY:
72
+ v = _installed(name)
73
+ if v:
74
+ present.append(name)
75
+ elif name in PAIRS_WITH:
76
+ missing_pairs.append(name)
77
+ print(f" stack {len(present)}/{len(FAMILY)} aw* packages installed",
78
+ file=out)
79
+ if present:
80
+ print(f" {' '.join(sorted(present))}", file=out)
81
+
82
+ missing_req = [v for v in ENV_REQUIRED if not os.environ.get(v)]
83
+ if ENV_REQUIRED or ENV_OPTIONAL:
84
+ have = sum(1 for v in ENV_REQUIRED + ENV_OPTIONAL if os.environ.get(v))
85
+ total = len(ENV_REQUIRED) + len(ENV_OPTIONAL)
86
+ print(f" config {have}/{total} of this brick's own vars set", file=out)
87
+ if missing_req:
88
+ # Not a preference. os.environ[...] raises the moment it runs.
89
+ print(f" MISSING REQUIRED: {' '.join(missing_req)}", file=out)
90
+
91
+ local = _local_checks()
92
+ for line in local:
93
+ print(f" {line}", file=out)
94
+
95
+ if mine is None:
96
+ print(f"\nverdict: {SELF} itself is not importable. Reinstall it before "
97
+ f"anything else here means much.", file=out)
98
+ return 1
99
+ if missing_req:
100
+ print(f"\nverdict: {SELF} is missing required config "
101
+ f"({', '.join(missing_req)}). Those are read with os.environ[...], "
102
+ f"so the code path that needs them raises rather than degrades.",
103
+ file=out)
104
+ return 1
105
+ if missing_pairs:
106
+ print(f"\nverdict: {SELF} works, but pairs with "
107
+ f"{', '.join(sorted(missing_pairs))} which "
108
+ f"{'is' if len(missing_pairs) == 1 else 'are'} not installed. "
109
+ f"That is a capability you are missing, not an error.", file=out)
110
+ return 0
111
+ print(f"\nverdict: {SELF} and everything it pairs with are present.", file=out)
112
+ return 0
113
+
114
+
115
+ def _local_checks() -> "list[str]":
116
+ """Per-brick checks, if this package defines them.
117
+
118
+ Kept as a HOOK rather than generated guesses: the generator knows the family
119
+ from the registry, but it does not know what awpredict needs at runtime, and a
120
+ doctor that invented config requirements would be confidently wrong. A
121
+ package supplies `_doctor_local()` returning display lines; absent, the
122
+ stack picture above still stands on its own.
123
+ """
124
+ try:
125
+ mod = importlib.import_module(f"{SELF}.doctor_local")
126
+ except Exception:
127
+ return []
128
+ try:
129
+ return list(mod._doctor_local())
130
+ except Exception as exc: # noqa: BLE001
131
+ return [f"local checks raised {type(exc).__name__}: {exc}"]
132
+
133
+
134
+ def main(argv: "list[str] | None" = None) -> int:
135
+ return report()
136
+
137
+
138
+ if __name__ == "__main__":
139
+ raise SystemExit(main())
@@ -0,0 +1,3 @@
1
+ """Environment adapters (one per domain). Concrete adapters land per-slice:
2
+ arc_game (slice 1+), code_world (slice 2), adk_sandbox (slice 3),
3
+ gym_compat (slice 3 test harness)."""