Skyhook
Bind SkyRL environments and their reward functions with decorators. One small module, with SkyRL Gym as its only direct runtime dependency.
Install
Requires Python 3.10 or newer and uv. Run from the project directory:
uv sync
This creates .venv and installs Skyhook and its dependencies. Use uv run
to run commands in that environment without manually activating it.
The distribution is named skyhook-rl; the Python import is skyhook.
The package includes type annotations for type checkers.
Use
import skyrl_gym
from skyrl_gym.envs.base_text_env import BaseTextEnv
from skyhook import env
@env.textarena_wordle
class WordleEnv(BaseTextEnv):
def __init__(self, target="crane"):
super().__init__()
self.target = target
def step(self, action):
return {
"observations": [{"role": "user", "content": action}],
"reward": env.textarena_wordle.sum_rewards(self, action),
"done": action == self.target,
"metadata": {},
"postprocessed_action": action,
}
@env.textarena_wordle.reward
def correct_word(environment, action):
return 1.0 if action == environment.target else 0.0
@env.textarena_wordle.reward
def turn_cost(environment, action):
return -0.05
game = skyrl_gym.make("textarena_wordle", target="crane")
game.init([{"role": "user", "content": "Guess the word."}])
assert game.step("crane")["reward"] == 0.95
game.close()
The class decorator calls
skyrl_gym.register("textarena_wordle", entry_point=WordleEnv). Both decorators return their inputs unchanged.
sum_rewards(*args, **kwargs) calls each registered reward once,
in registration order, forwarding the same arguments to every function, then
passes the collected results to Python's built-in sum(). No rewards means the
integer 0. Results must support 0 + value and addition with the running total.
Decimal, Fraction, and custom addition-compatible types keep their normal
addition behavior; incompatible combinations raise Python's usual TypeError.
There is no float conversion or finiteness check, and floating-point precision
follows the Python version's built-in sum(). Your environment's step() remains
responsible for producing the finite scalar reward expected by SkyRL. Exceptions
raised by reward functions or addition propagate unchanged. Async functions are
rejected at registration; coroutine results from other callables are closed and
rejected, never awaited.
To call just one reward inside step, look it up by its function name:
reward = env.textarena_wordle.rewards["correct_word"](self, action)
rewards is a read-only, insertion-ordered mapping of names to the original
callables. A single lookup calls only that function; sum_rewards still calls
them all. Direct calls return the original result without aggregation.
Missing names raise KeyError, and registering a duplicate name in the
same environment raises ValueError instead of replacing or double-counting it.
For a custom name or a callable without __name__, register it explicitly:
env.textarena_wordle.reward(lambda game, action: 0.1, name="bonus")
Rewards can be registered before or after the class, including after environment
construction. Only rewards registered before a call to sum_rewards participate.
Use env["my-env-v0"] instead of attribute access for names containing punctuation.
Skyhook does not wrap step, replace existing rewards, or install another base
class. Call sum_rewards where your environment calculates its reward; add it to
an existing base reward explicitly if needed. Duplicate environment IDs raise
SkyRL's registration error rather than replacing another environment.
Registration happens at import time and is process-local. Import the modules
containing your decorated classes and rewards in each SkyRL/Ray worker before
calling skyrl_gym.make. There is no automatic module discovery or worker sync.
Run
uv run python examples/wordle.py
uv run python tests/test_skyhook.py
The example is a tiny word-guessing environment, not the TextArena game engine.
The check exercises the real SkyRL registry and make API without a test framework.
GitHub Actions runs the check on every push and pull request using Python 3.10 and 3.12. CI tests a non-editable installation in isolated Python mode, so imports come from the installed package rather than the source checkout:
uv run --locked --no-editable --reinstall-package skyhook-rl python -I tests/test_skyhook.py
--locked enforces uv.lock; regenerate it with uv lock after changing
dependencies. --reinstall-package rebuilds Skyhook so source edits are tested
instead of an older cached wheel.
Release files for skyhook-rl 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| skyhook_rl-0.1.0.tar.gz | 6.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| skyhook_rl-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 11.1 kB
Release files / skyhook_rl-0.1.0.tar.gz
| Download URL | skyhook_rl-0.1.0.tar.gz |
|---|---|
| Size | 6.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e71b5e97248cc9a80b5d3b4a7179907a41366ea5d68833583b3c07b1ac83f692
|
|
BLAKE2b-256 checksum How to use checksums |
e6edefb19620cdea59f5a51c3ea8d9e3f74e182f80fd6102166089d6d764eaea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.5
|
Release files / skyhook_rl-0.1.0-py3-none-any.whl
| Download URL | skyhook_rl-0.1.0-py3-none-any.whl |
|---|---|
| Size | 4.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a9ed0159d31b5e10eecc211b7d7ac994b85fdadec5147ebe9b4c8b7f32df5eb5
|
|
BLAKE2b-256 checksum How to use checksums |
14a6379f29e8b39d9c2f57b9b68ea96f43d682c632eece0ce02d2035232c921f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.5
|