Under gateway.multiplex_profiles a secondary profile X is ticked, dispatched
and notified from the default profile's process, where os.environ holds the
DEFAULT profile's .env and X's values live only in the per-turn secret scope /
HERMES_HOME override. Every remaining read that skipped that scope made X
behave differently from `hermes -p X gateway run`:
- cron: HERMES_CRON_TIMEOUT, HERMES_MODEL (job/preflight fallback),
HERMES_CRON_MAX_PARALLEL, inflight allowance, prefill file and the script
timeout were bare os.getenv → the default profile's values; a job without a
model silently ran on the default's HERMES_MODEL instead of refusing.
cron/env_settings.py::cron_env_setting reads the scope (fire) or the ticked
home's .env (tick thread), plain environ when multiplexing is off.
- child env: the restart-safe cron worker, the Bot Chat delivery child and the
kanban worker inherited the launch profile's non-credential .env settings
and bridged TERMINAL_* policy (TERMINAL_ENV=docker, default's image,
HERMES_MODEL) — X's worker ran in the default's docker image on the
default's model. tools/environments/local.py::strip_launch_profile_env drops
them when the child targets another served profile.
- kanban: the worker --toolsets pin was silently dropped for every served
assignee (toolset probes call get_secret without a scope → swallowed
UnscopedSecretError); notifier pings, artifact uploads and the wake text ran
under the default's media policy / display language (only wake() was scoped).
- /loop: _post_turn_loop_completion hopped to the executor without contextvars,
writing the completed tick into the DEFAULT profile's state.db and leaving
X's row awaiting_response forever; the --until judge ran with the default's
aux credentials.
- background processes: a secondary's processes.json (scope-relative since
adf23550f5) was never read at startup; its processes were not re-adopted
and notify_on_complete notices were lost. Startup recovers every served
home under its scope; recovery adopts each session once.
- completion delivery: background_process_notifications was evaluated once per
drain for the ambient profile (default's mode for everyone; X's `off`
dropped a sibling's `all` event), recovered watchers used the default's
mode, HERMES_BACKGROUND_NOTIFICATIONS was read raw from environ;
_deliver_platform_notice used the default's GatewayConfig so a secondary's
notice_delivery: private went public.
Not changed: gateway/run.py and tools/async_delegation.py (PR #106742
rewrites both). Known residue left for the env-bridge lane:
HERMES_SESSION_STALL_TIMEOUT is bridged once from the launch config.
scheduler bug, not a parity gap; unchanged here.
27 lines
1.1 KiB
Python
27 lines
1.1 KiB
Python
"""Profile-scoped reads of the ``HERMES_*`` tuning settings cron honours from ``.env``.
|
|
|
|
A standalone ``hermes -p X gateway run`` loads X's ``.env`` into ``os.environ``, so a bare
|
|
``os.getenv("HERMES_CRON_TIMEOUT")`` is X's value. Under ``gateway.multiplex_profiles`` the same
|
|
tick runs inside the default profile's process, where ``os.environ`` holds the DEFAULT profile's
|
|
``.env``. With a secret scope installed (job run + delivery) the scope is authoritative; the tick
|
|
loop itself (due-job scan, pool sizing) runs under the profile's home override only, so the
|
|
setting is read from that home's ``.env``. Outside multiplex the read is the plain environ.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
from agent.secret_scope import current_secret_scope, is_multiplex_active, load_env_file
|
|
from hermes_constants import get_hermes_home
|
|
|
|
|
|
def cron_env_setting(name: str, default: str = "") -> str:
|
|
if not is_multiplex_active():
|
|
return os.getenv(name) or default
|
|
scope = current_secret_scope()
|
|
if scope is None:
|
|
scope = load_env_file(get_hermes_home() / ".env")
|
|
value = scope.get(name)
|
|
return default if value is None else str(value)
|