The docstring still called a remote client's session unchanged. Since
fa99680a2b6b it drops the gateway's prompt and provider behaviour
values and keeps only the host ones.
Refs #134889 (T3)
Refs #139523
A shell client's session reads HERMES_EPHEMERAL_SYSTEM_PROMPT, the
provider request, the prefill file and the project-plugins opt-in from
its client, but its tool subprocesses (terminal, background processes,
execute_code) still inherited the gateway daemon's own values, e.g. an
operator-started gateway's ephemeral prompt.
toolchain_environ now drops those names for a bound session: all four
for a local client, and for a client not attached locally the two it
takes from its client (prompt, provider request), keeping the daemon's
host values that session still uses. Unbound sessions (messaging, cron,
Desktop) keep the gateway's environment unchanged. No tool subprocess
reads these names; only a nested hermes run from a terminal would, and
it now falls back to its own config instead of the daemon's values.
Refs #134889 (T3)
Refs #139523
A local client's session replaced the daemon's toolchain values only for
the names the client sent. A configured name the client didn't export
kept the value seeded in the daemon's environment and reached that
session's terminal commands and background processes.
The session now binds the client_env rules frozen with it, and every
built-in toolchain name, terminal.client_env_passthrough name or prefix
those rules cover that the client didn't send is unset for its tools.
Credential and HERMES_* names are left alone. PATH stays the gateway's
when the client sent none, and the backend's own terminal.env still wins.
Remote clients, sessions without client_env, and messaging, cron and
Desktop sessions are unchanged.
Refs #134889 (T3)
Refs #139523
An exact session.create retry was checked only against the names the
stored, filtered client_env kept, so a retry that added a name the rules
keep was accepted, and an identical overlapping create could be refused
at commit.
The rules in force when the session is committed (behaviour names, the
built-in toolchain names and configured extras, prefixes and secret
substrings) are now stored with its policy as plain data
(client_env_rules_json). A retry's complete client_env, filtered by those
rules, must equal the stored map, both at staging and in the
transactional commit: adding a name the rules keep, dropping a stored name
or changing a value is refused, and a name the rules drop is ignored. The
losing create's own map is carried only in memory on its staged receipt,
never written; a receipt that has lost it (a plain dict copy, for
example) is refused at commit (invalid_params) rather than judged by its
own filtered map. A session created without client_env still retries
only without one, and the policy identity leaves the new field out when
it is empty, so existing fingerprints don't change.
Refs #134889 (LC-8)
Refs #139523
On main each `hermes` process scanned ./.hermes/plugins in its own cwd
when HERMES_ENABLE_PROJECT_PLUGINS was set. Every session now runs in the
one gateway, which discovers plugins once from its own directory, so a
project's plugins silently never load.
The plugins guide now says so in a Project-local plugins section. When a
CLI or TUI session starts with a forwarded HERMES_ENABLE_PROJECT_PLUGINS
in a folder whose ./.hermes/plugins has entries, the gateway logs a line
to gateway.log and session.info carries it as config_warning: the TUI
already renders that at session start, and the classic CLI now prints it.
A folder that can't be listed skips the notice; it never fails
session.create or session.info. Nothing is loaded; loading project
plugins in a plugin host is left for later.
Host-specific client values also need proof that the client is on this
host. A connection is native_owner only when it redeemed a ticket minted
over the gateway's private control socket, from loopback with no Origin
header; session.create passes that as client_local, and a client can't
send it (an unknown field). Without it only HERMES_EPHEMERAL_SYSTEM_PROMPT
and HERMES_INFERENCE_PROVIDER from client_env apply: the session keeps the
gateway's toolchain, prefill file and project-plugins check
(client_env_remote on its policy, left out of the policy identity when
false), and its config_warning says the client's environment was not
applied because the client is not attached to this gateway locally.
Refs #134889 (LC-4)
Refs #134889 (LC-8)
Refs #134889 (T3)
Refs #139523
On main the terminal tool, its background processes and execute_code ran
with the launching shell's environment: an activated venv, nvm/pyenv
shims, PATH. Under the one gateway they ran with the daemon's
environment, so `python`, `node` and friends resolved to whatever the
gateway was started with.
The allowlisted toolchain part of a session's client_env (PATH,
VIRTUAL_ENV, CONDA_*/NODE_*/NVM_*/PYENV_*, GOPATH, GOROOT, CARGO_HOME,
RUSTUP_HOME, JAVA_HOME, plus names a profile lists in the new
terminal.client_env_passthrough) now replaces the daemon's values for that
session's local terminal commands, background processes and execute_code
children only, including execute_code's project-mode interpreter choice.
PATH stays the gateway's when the client sent none. The backend's own
terminal.env still wins. No full shell snapshot is taken. Messaging, cron
and Desktop sessions keep the gateway's environment.
Credential-looking names are never sent, stored or bound: one predicate,
is_secret_env_name, now shared with execute_code's secret policy, drops
them on the client, before the policy is stored and again before binding
(secrets keep using terminal.env_passthrough). execute_code applies its
whole scrub, including the delegated-child restrictions, after merging the
client's toolchain, and its list now also strips *COOKIE* names.
Refs #134889 (T3)
Refs #139523
On main each `hermes` process read HERMES_EPHEMERAL_SYSTEM_PROMPT,
HERMES_PREFILL_MESSAGES_FILE and HERMES_INFERENCE_PROVIDER from its own
shell. Since the one gateway runs every session, the values exported by
whichever client auto-started it became daemon-wide (messaging and cron
included), and every later client's exports were ignored.
The classic CLI and the Ink TUI now send an allowlisted `client_env` map
(plain strings) with session.create; the classic CLI takes the
allowlist's extra names from the profile's effective config (managed
layer included), as the gateway reads them, and a safe-mode or
ignore-user-config launch reads no profile. The gateway filters the map
again against the session profile's allowlist, freezes it with the
session's policy (outside launch_request, so viewers never see the
values) and binds it for that session's turns only: the ephemeral
prompt, the prefill file and the provider request read the session's
value, in-process and for a managed worker. Desktop sends nothing and
keeps the gateway's environment, as does a session created before this
change.
The auto-started daemon no longer inherits these variables (nor
HERMES_ENABLE_PROJECT_PLUGINS); a gateway the operator starts keeps its
own environment. Clients send the field only when runtime.describe lists
it.
An exact retry of a session created before this change returns its
original session; the retry and commit comparisons and the policy identity
treat an absent client_env as before. A retry is judged against the
committed, filtered map: every stored name must come back with its stored
value, and names dropped at creation don't count, whatever the allowlist
is now. Fresh creates use the current rules, and the unfiltered map is
never stored.
Refs #134889 (LC-8)
Refs #139523