docs: profile-scope invariant in AGENTS.md — one process serves many profiles; out-of-turn code binds its scope
Root AGENTS.md § Code Shape Rules replaces "module-level constants are fine — they cache after _apply_profile_override() sets HERMES_HOME" (true for `hermes -p x <cmd>`, inverted under the multiplex gateway and the Desktop/dashboard `serve` backend, where os.environ holds the LAUNCH profile) with the invariant: a profile = home + secret scope + terminal scope, bound per profile ACTIVITY, and every execution point with no turn on the stack binds it explicitly. Names the real seams: gateway/run.py::_profile_runtime_scope, tui_gateway @_profile_scoped + _session_profile_runtime_scope (+ _profile_runtime_scope_tokens, launch_profile_policy -> set_multiplex_active), cron/scheduler_provider.py::_profile_cron_scope, gateway/run_agent_cache.py::_run_release_in_profile_scope, tools/environments/local.py:: served_profile_child_env, agent/memory_provider.py::spawn_context_thread. Adds a routing-table row for profiles / multiplex / secret scope. Area AGENTS.md paragraphs, one per seam, for gateway/ (activity-not-turn binding, hooks per profile, adapter YAML never reaches os.environ, unserved shared-ingress reported via _note_unserved_secondary_platform + needs_attention at the single writer), tui_gateway/ (RPC binding is home AND secret AND terminal; HOME-only is half-bound; teardown chokepoint), cron/ (per-home tick lock, ticker scope incl. pre-loop code, kanban notifier routing, worker liveness by (pid, worker_started_at) fingerprint, descendant fence as a path), hermes_cli/ (DEFAULT_CONFIG key <-> reader parity, service-install matrix, -p vs multiplex home binding), tools/ (check_fn reads through get_secret and is cached per hermes_home_key, one env builder per spawn, MCP trust per profile), plugins/ (lifecycle hooks are bound by the caller; never cache the home from initialize()), apps/desktop/src/ (pooled serve per (connection, profile); remote topologies), agent/ (end-of-session flush is caller-bound; set_multiplex_active gates fail-closed). Corrects the statements the multiplex model made wrong, in the same PR: root module-constant sentence; hermes_cli "sets HERMES_HOME before any import" (+ cli-internals.md); ADDING_A_PLATFORM.md §2 raw os.getenv loader (now an _ENV_STEPS row through config.py::_getenv) and §4 platform_env_map in gateway/run.py (now _PLATFORM_ALLOWLIST_ENV in pairing.py + registry allowed_users_env); platform_registry.py "may set os.environ (guard with not os.getenv)"; cron/AGENTS.md hardcoded ~/.hermes/cron/.tick.lock; gateway-internals.md agent:main as THE key format, ~/.hermes/hooks/, single-profile `gateway stop`, plus a new "Multiplexed profiles" section; tools/AGENTS.md os.getenv check_fn sample; "installed per turn" wording; "one temp HERMES_HOME" E2E wording; multi-profile-gateways.md intro lists system units, Windows tasks, s6 and the Desktop backend.
This commit is contained in:
@@ -104,6 +104,19 @@ image-gen plugins (all in `plugins/AGENTS.md`). `agent/curator.py` + `curator_ba
|
||||
the skill curator (`skills/AGENTS.md`). Cron sessions pass `skip_memory=True` by default — memory
|
||||
providers intentionally do not run during cron.
|
||||
|
||||
- End-of-session memory extraction and provider `on_session_end` run wherever the session ends —
|
||||
turn, eviction, shutdown, `tui_gateway` teardown — and the CALLER binds the owning profile's scope
|
||||
first (`_run_release_in_profile_scope`, `_session_profile_runtime_scope`); the agent never derives
|
||||
its home from `os.environ` at flush time (`Path(_session_db.db_path).parent` is the ground truth).
|
||||
Provider background work starts through `memory_provider.py::spawn_context_thread` (copies the
|
||||
contextvars), never a bare `threading.Thread`; `title_generator.py` is the shape.
|
||||
- `agent/secret_scope.py::get_secret` fails closed (`UnscopedSecretError`) only after
|
||||
`set_multiplex_active(True)`; the gateway, cron, migrate and `serve` set it. A new multi-home host
|
||||
must too, or every guard is silently off. Isolation is BETWEEN profiles; children inherit via
|
||||
`copy_context`; a child's `UnscopedSecretError` is a spawn-site bug, never grounds for an
|
||||
`os.getenv` fallthrough. Delegated children carry `delegation_context.py::
|
||||
DELEGATED_CHILD_ENV_MARKER` valued as the fenced Kanban board root, not a bare flag.
|
||||
|
||||
## Tests
|
||||
|
||||
Loop/phase tests go in `tests/agent/`; patch the binding the phase actually reads (siblings often
|
||||
|
||||
Reference in New Issue
Block a user