Commit Graph

6 Commits

Author SHA1 Message Date
carlotestor
74183e1793 perf(stream): resolve plugins.stream_reasoning_deltas once per stream, not per token
_fire_reasoning_delta called stream_reasoning_deltas_enabled() on every reasoning
token; each call took _CONFIG_LOCK and paid load_config()'s full deepcopy on a
cache hit, so every concurrently streaming thread in a WebUI/gateway process
serialized behind one lock on the token path.

Cache the opt-in on the agent for the life of one stream (reset in
_reset_stream_delivery_tracking, so the next request re-reads config) and use
load_config_readonly() for the lookup itself.

(cherry picked from commit 20fab007e5c3e399e3923bb6d66649918292825c)
2026-09-22 16:15:34 +05:30
teknium1
e49436b78d fix(profiles): plugin hooks from a launch-profile turn run under a bound profile scope
Under a multiplexed gateway / serve backend the launch profile's own turns bound
secrets and terminal policy but NO HERMES_HOME override (launch_profile_runtime_scope,
_profile_runtime_scope_tokens(None)). Once multiplexing is active an unset override
is the fail-closed "unbound context" signal (serves_routed_profile, per-home slots,
third-party runtime bindings such as OMH's pre_tool_call gate), so every plugin hook
a launch-profile turn fired looked unscoped and a fail-closed plugin vetoed every
tool call ("OMH runtime binding unavailable", #118538). Routed turns already bind
theirs via gateway/run.py::_profile_runtime_scope; the launch profile is a tenant
like any other, so its scope now binds the launch home too.

Sibling gap in the two hook kinds delivered off-turn by long-lived worker threads:
agent.plugin_stream_hooks (on_stream_*/on_interim_message) and the plugin event bus
(plugins_dispatch._dispatch_event) ran callbacks in the worker's empty context, so
even a routed turn's observers saw no scope. Both now capture the emitter's
contextvars at enqueue and run each delivery under a copy.

Single-profile hosts are unchanged (no override until activation).

Closes #118538
2026-09-21 18:43:43 -07:00
teknium1
3f80dc9a5a fix(plugins): stream observer hooks and event subscribers fail-report once, not per event
Two per-call WARNING surfaces were still outside the warn-once reporter:

- agent/plugin_stream_hooks.py::_worker — the on_stream_start/on_stream_delta/
  on_stream_end consumer, which fires once per streaming delta (far more often than
  per tool call). A mis-declared callback (signature naming tool_data) logged
  "Hook ... raised" at WARNING on every delta: 20 deltas -> 20 WARNING lines.
- hermes_cli/plugins_dispatch.py::_deliver_event — plugin event subscribers that
  raise identically were warned on every emit.

Both now go through PluginManager._report_hook_failure (keyed by module/qualname,
cleared on unload): the first failure warns and names the fields the hook/event
provides, identical repeats are DEBUG. Skip-and-continue semantics are unchanged.

Part of #111922
2026-09-17 09:03:29 -07:00
Teknium
7c76e5f219 refactor(agent): moa_loop guidance-input helper, explicit MoAClient delegation, blank squeeze 2026-09-02 19:10:21 -07:00
Teknium
63abd4d174 refactor(agent/adapters): simplify plugin_llm, backend_identity, stream hooks (-447 LOC)
PluginLlm's four public entry points share _gate/_finish/_host_kwargs; drop
dead classify_failure_scope/_REASON_SCOPES (and their tests) and unify the
three _norm_* helpers; should_skip_candidate routes through a scope predicate
table. Injected caller kwargs, audit dicts and log lines unchanged.
2026-09-02 13:29:47 -07:00
Dineth Hettiarachchi
00f4da01ec feat(plugins): add streaming output observer hooks
Salvage of PR #64317 (@deaneeth) onto current main, implementing #64161:
observer-only on_stream_start / on_stream_delta / on_stream_end /
on_interim_message plugin hooks dispatched through a host-owned bounded
queue (one worker per callback) so plugin callbacks never run inline on
the token path. Reasoning deltas are opt-in via
plugins.stream_reasoning_deltas.
2026-08-12 18:38:25 -07:00