Summary
cloudai should have one seed that governs all randomness, derived from the canonical BaseAgentConfig.random_seed (default 42). Today several RNG sites are seeded independently — or not at all — so a run is not reproducible end-to-end from a single knob. This issue records an audit of every randomness source in cloudai core and proposes threading them all from the single seed. Audit only — no fix is requested in this issue.
Motivating bug (already addressed in the env_params/DR work): the domain-randomization sampler defaulted to seed 0 while the agent default is 42, so a config without an explicit random_seed ran the agent and the env on two different seeds.
Canonical seed
BaseAgentConfig.random_seed: int = 42 — src/cloudai/configurator/base_agent.py. Single declared knob, set via TOML [agent_config] random_seed; every agent config inherits it.
Audit of RNG sites in cloudai core
| # |
Site |
RNG |
Seeded from random_seed? |
Notes |
| 1 |
DR sampler — env_params.py (random.Random(f"{seed}:{name}:{trial}")) |
Python random |
Yes (after the DR fix) |
Per-(seed, param, trial) sub-seeding keeps params independent and reproducible. |
| 2 |
Gym reset(seed) / seed() → np.random.seed(seed) — cloudai_gym.py |
numpy global |
No (only when a caller passes seed) |
In the native BaseAgent.run() loop, env.reset() is called with no seed, so numpy's global RNG is never seeded from random_seed. |
| 3 |
start_action: "random" — base_agent.py |
n/a in core |
No |
The contract implies a random first action, but core provides no RNG for it; GridSearchAgent ignores it. |
| 4 |
GridSearchAgent — grid_search.py |
none (itertools.product) |
n/a |
Deterministic; the only agent shipped in core. |
Gaps
- G1 — no single propagation path. Each RNG (Python
random, numpy global, gym reset, plus external-agent frameworks) is seeded independently; nothing guarantees they all derive from random_seed.
- G2 — numpy global RNG unseeded by default. The gym only seeds numpy when a gymnasium caller passes
seed; the native cloudai loop never does, so in-process np.random usage is nondeterministic across runs.
- G3 —
start_action="random" is unwired in core. No core RNG tied to random_seed selects the random start.
Related (out of core)
Stochastic agents (BO, GA, MAB, PPO, DQN) live in cloudaix, not core. Each must read agent_config.random_seed and seed its own framework (numpy / torch / RLlib). There is no central enforcement that they all use the same single seed; this should become a documented contract once core exposes a single derivation utility.
Proposed direction (non-binding)
- Add a single seed-derivation helper in core, e.g.
derive_seed(random_seed, component: str) reusing the f"{seed}:{component}" hashing the DR sampler already uses.
- Thread it to: the DR sampler (done), the gym (seed numpy global from
random_seed when no explicit seed is given), and start_action="random".
- Publish a contract for external agents: seed your framework from
derive_seed(random_seed, "<agent>").
Acceptance criteria
- One knob (
random_seed) makes a full cloudai run reproducible — DR draws, any numpy usage, and random start actions.
- No RNG site defaults to a seed that disagrees with
random_seed.
- Independence preserved via per-component sub-seeds (not one shared global stream).
Summary
cloudai should have one seed that governs all randomness, derived from the canonical
BaseAgentConfig.random_seed(default42). Today several RNG sites are seeded independently — or not at all — so a run is not reproducible end-to-end from a single knob. This issue records an audit of every randomness source in cloudai core and proposes threading them all from the single seed. Audit only — no fix is requested in this issue.Motivating bug (already addressed in the env_params/DR work): the domain-randomization sampler defaulted to seed
0while the agent default is42, so a config without an explicitrandom_seedran the agent and the env on two different seeds.Canonical seed
BaseAgentConfig.random_seed: int = 42—src/cloudai/configurator/base_agent.py. Single declared knob, set via TOML[agent_config] random_seed; every agent config inherits it.Audit of RNG sites in cloudai core
random_seed?env_params.py(random.Random(f"{seed}:{name}:{trial}"))random(seed, param, trial)sub-seeding keeps params independent and reproducible.reset(seed)/seed()→np.random.seed(seed)—cloudai_gym.pyseed)BaseAgent.run()loop,env.reset()is called with no seed, so numpy's global RNG is never seeded fromrandom_seed.start_action: "random"—base_agent.pyGridSearchAgentignores it.GridSearchAgent—grid_search.pyitertools.product)Gaps
random, numpy global, gym reset, plus external-agent frameworks) is seeded independently; nothing guarantees they all derive fromrandom_seed.seed; the native cloudai loop never does, so in-processnp.randomusage is nondeterministic across runs.start_action="random"is unwired in core. No core RNG tied torandom_seedselects the random start.Related (out of core)
Stochastic agents (BO, GA, MAB, PPO, DQN) live in cloudaix, not core. Each must read
agent_config.random_seedand seed its own framework (numpy / torch / RLlib). There is no central enforcement that they all use the same single seed; this should become a documented contract once core exposes a single derivation utility.Proposed direction (non-binding)
derive_seed(random_seed, component: str)reusing thef"{seed}:{component}"hashing the DR sampler already uses.random_seedwhen no explicit seed is given), andstart_action="random".derive_seed(random_seed, "<agent>").Acceptance criteria
random_seed) makes a full cloudai run reproducible — DR draws, any numpy usage, and random start actions.random_seed.