Commit Graph

27 Commits

Author SHA1 Message Date
teknium1
226df89f74 refactor(redact): one secret-pattern source; a2a, gateway chat and monitoring egress scrub through redact_for_egress
plugins/platforms/a2a/security.py::redact_outbound shipped text to a REMOTE peer
through 8 private regexes (sk-, sk-ant-, ghp_ only, xox[bap] only, AKIA, JWT,
Bearer, email) and never called redact_sensitive_text, so every prefix added to
agent/redact.py (hf_, glpat-, xapp-, npm_, Telegram bot tokens, private keys,
DB URLs, env assignments, auth headers, plugin-registered patterns) was absent
on the A2A path. gateway/run.py::_GATEWAY_SECRET_PATTERNS and
agent/monitoring/redaction.py::_TOKEN_RE/_BEARER_RE were two more parallel
"fallback" lists to maintain.

Now agent/redact.py::redact_for_egress is the one egress scrub:
redact_sensitive_text(force=True) + a bearer sweep for prefix-less opaque
tokens, fail-closed ("[redaction-unavailable]"). Gateway user-facing text,
monitoring export and A2A outbound call it; A2A keeps only its e-mail pass.

Behavior changes: a2a egress now masks the full canonical set; the gateway
chat path returns the fail-closed sentinel instead of a raw string when the
redactor raises; honcho plugin registers hch-at-/hch-rt- with
register_redaction_patterns (masked on every surface; mask shape is the
shared head/tail form instead of "hch-at-[redacted]"); proxy_cli token
display uses mask_secret (4 visible prefix chars instead of 12).

Invariant test: redact_outbound masks a synthesized token for every
registered prefix pattern (fails when reverted to the private list).
2026-09-13 05:07:50 -07:00
Teknium
5280fe9987 fix: cron and local DMs reach an open Desktop Bot Chat
Route local producers to durable owner ingress before attempting the unowned
CLI lane. Preserve per-run/per-message IDs and receipt-first retry handling;
never fall back after ambiguous admission. Report cron admission as queued,
not completed or failed, in job status, the execution ledger and CLI/tool UX.

Native isolated Electron validation reproduces SESSION_NOT_OWNED on main for
both idle and busy owners. Fixed owner consumes idle cron, busy cron, local
DM and mounted-chat cron exactly once, keeps its lease, yields to queued
human input, and preserves the prior model-request prefix and tool schema.
Inference alone used a deterministic loopback wire stub; no paid model call.
2026-09-07 16:48:29 -07:00
Teknium
d63e380324 compat(plugins): warn once per name when a plugin resolves an old import path; lint step restored in CI
Every PLUGIN-COMPAT __getattr__ now calls hermes_cli.plugin_compat.warn_once(facade, name, target) before
resolving, emitting a HermesPluginCompatWarning (FutureWarning) once per process per name: old path, new
path, removal target. Importing a facade for its live API stays silent; only resolving a moved name warns.
COMPAT_MANIFEST.md documents the warning and how to silence it during migration.

Verified the runtime never routes through a pointer: every entry point (run_agent, cli, hermes_cli.main,
gateway.run, tui_gateway.server, web_server, model_tools + tool discovery, hermes_state, cron.scheduler,
browser_tool, mcp_tool, kanban, auth) imports clean and `hermes doctor` runs end to end with the warning
promoted to an error.

Also restores the check_compat_pointers CI step to .github/workflows/lint.yml, which a0be177aac dropped
when the compat layer was regenerated (the lint script itself was present; the workflow step was not).

hermes_cli/plugin_compat.py, tests/test_plugin_compat_warning.py and the two-line insert per facade are
part of the compat layer and go away with it.
2026-09-04 00:15:16 -07:00
Teknium
2776813df3 compat(plugins): temporary import-path shims for external plugins — ONE commit, revert on schedule
The Sep 2026 decomposition (PR #102117) makes internal import paths a non-API: names now live in
the focused modules that define them. This commit is the ONLY thing keeping the old paths alive,
so external plugins have time to update. It is deliberately a single, unsquashed commit:

    git revert <this sha>

removes every shim, stub and manifest at once on the announced date. Nothing in-tree may depend on
these pointers: scripts/check_compat_pointers.py (wired into lint.yml) fails CI if it does.

What it adds (see COMPAT_MANIFEST.md, compat_manifest.json):
- 332 facade modules get one delimited `PLUGIN-COMPAT` block appended at the end of the file
- 1,172 moved names resolved lazily via a module `__getattr__` (PEP 562) — never a top-level import,
  so no import cycles; facades that already had `__getattr__` get a chained one
- 592 third-party/stdlib names the old modules used to expose, with their original import statements
- 266 public definitions that had been deleted as unused, restored byte-for-byte from the pre-decomposition
  tree (+40 private helpers and 16 imports pulled in only because a restored definition needs them)
- 3 deleted modules recreated as re-export stubs (gateway/startup_watchdog, hermes_cli/observability/
  relay_runtime, tools/environments/modal_utils)
- private names (`_x`) get no pointer: they were never API (3,792 skipped)

Verified: all 335 touched modules import under a fresh HERMES_HOME and every manifest name resolves;
the lint reports zero in-tree uses; ruff clean; targeted suites unchanged.
2026-09-03 17:13:22 -07:00
Teknium
fcbe4acbef simplify(compat): tools/mcp_tool — repoint 20 non-test callers to the defining mcp_tool_* siblings 2026-09-03 13:29:35 -07:00
Teknium
575b54d457 refactor(agent): iron_proxy/monitoring/outbound_webhooks pass-2 structural cut (helpers unified, single-use helpers inlined, packed literals) 2026-09-02 22:44:07 -07:00
Teknium
af0ddcb2ec refactor(agent): iron_proxy signal guard ctx manager + yaml loader helper; table/kwargs compaction across group 2026-09-02 18:58:26 -07:00
Teknium
eea8e8060e refactor(agent): compact module docstrings (outbound_webhooks, otlp_exporter, emitter, gateway_health_export) 2026-09-02 18:53:00 -07:00
Teknium
79c704f34f refactor(agent): monitoring/iron_proxy third pass — gateway.status dispatch helper, SDK symbol table by module, compact comments and inner blanks 2026-09-02 18:48:19 -07:00
Teknium
052582de2d refactor(agent): compact monitoring/* and lsp/workspace — contextlib.suppress, shared platform error-code helper, nearest_root marker probe, docstring compaction 2026-09-02 18:32:02 -07:00
Teknium
7eb8ccc315 refactor(agent/runtime): monitoring — shared OTLP plumbing in otlp_exporter, event base class, EmitterStreamer, dedupe health export
- otlp_exporter hosts SDK loading (symbol table), header/endpoint/resource helpers
  shared with gateway_health_export; export module imports them instead of
  keeping copies (_otlp_config/_resolve_headers/_install_id/_safe_resource_attributes
  unified). _KEEP_BY_KIND attribute allowlist reused for diagnostic log attrs.
- events: _MonitoringEvent base supplies to_dict; field order (wire order) unchanged.
- redaction: redact_bounded() replaces 3 inline redact+truncate try/excepts.
- EmitterStreamer base owns the shared unsubscribe/flush/shutdown for
  OTLPStreamer and GatewayDiagnosticLogStreamer.
- gateway_health: metric construction helpers, _contains_any shared with
  cron_health, dead _allowed_logger/redact_gateway_message removed (0 refs).
- Comment/docstring compaction keeping every stated invariant.
2026-09-02 13:29:47 -07:00
Teknium
9de5460c12 feat(cron): acked failure signatures stop re-pinging — durable incidents + ack CLI (salvage #94692) (#95017)
* feat(cron): durable failure incidents with signature dedup and ack

Introduce a durable cron incident store (cron_incidents in the shared
cron/executions.db) that groups "same job + same error signature" across
runs, so a known recurring failure stops re-pinging the operator every run
once it has been acknowledged.

- cron/incidents.py: lazily-created incident table (detected -> alerted ->
  reviewed -> closed lifecycle; closed is per-signature terminal), sha256
  signature dedup over job_id + normalized error, redacted/truncated error
  storage, failure-type classification, and ack/list/get/count helpers.
- cron/scheduler.py: record an incident on the failure delivery path and
  suppress the per-run failure ping when the exact signature is acked (both
  the normal failure path and the processing-raised retry path). Best-effort:
  an incident-store error never breaks the cron run or delivery. Streak nudge,
  alert-once markers, and delivery-error behavior are untouched.
- hermes_cli: add `hermes cron incidents [--state ...]` and
  `hermes cron incidents ack <id>`.
- tests/cron/test_cron_incidents.py: dedup, lifecycle, redaction,
  classification, lazy-schema, scheduler gating, and CLI coverage.

Non-goals deferred to later slices: Discord buttons/review view, HMAC action
tokens, owner-agent review launch, approval-gated fixes, incident playbooks.

* refactor(cron): tighten incident lifecycle, wire alerted state and suppressed_acked outcome

Follow-ups on top of the salvaged #94692:
- Drop the dead 'reviewed' state and the SQLite CHECK (state validity
  lives in INCIDENT_STATES so future slices can add states without a
  table rebuild); lifecycle is detected -> alerted -> closed.
- Actually mark incidents 'alerted' after a failure ping reaches
  delivery, on both the normal and exception delivery paths.
- Record ack-suppressed runs with a distinct 'suppressed_acked'
  delivery outcome (registered in cron_health monitoring) instead of
  the ambiguous generic 'suppressed'.
- Drift-skip alerts explicitly bypass the ack gate (they carry the
  remediation command and alert once via drift_alerted already).
- Docs: failure-incidents section in the cron guide.
- Tests for the alerted transition + never-resurrect-closed.

---------

Co-authored-by: Laura López Real <113060513+laulopezreal@users.noreply.github.com>
2026-08-25 14:02:40 -07:00
joaomarcos
628372de46 fix(otlp): span exporter now inherits configured resource_attributes
_resource_attributes() in otlp_exporter.py built its own hardcoded
resource dict (service.name/instance.id/telemetry.scope only) instead
of reusing gateway_health_export.py's _runtime_resource_attributes(),
which already applies the resource_attributes allowlist from config.
Result: operator-configured attributes like deployment.environment.name
reached metrics and diagnostic logs but never spans.

Span resource building now delegates to the same
_runtime_resource_attributes() helper metrics/logs already use,
removing the duplicate implementation instead of patching it in place.
2026-08-08 18:11:02 -07:00
Victor Kyriazakos
cc80dba96f feat(monitoring): add hermes.gateway.background_delegations (unit/slot count)
Complements the task-granular background_work with the async-delegation
UNIT count (each dispatch/batch = 1), recovering the pool-slot semantics
active_count() gives. Together: background_work = real concurrent subagent
load (batch expanded), background_delegations = slot pressure to alert
against delegation.max_concurrent_children. Registered in metric_names,
documented, and covered by a task-vs-unit contract test.
2026-07-25 02:30:20 +00:00
Victor Kyriazakos
0ef0816284 feat(monitoring): count background_work task-granular (expand delegate batches)
A delegate_task fan-out batch occupies ONE async-pool slot by design, so
active_count() (unit/slot count) reports a 3-task batch as 1 — which
undercounts real concurrent subagent load on the background_work metric.
Add active_task_count() that expands a running batch to its child count
(N-task batch -> N, single -> 1) and switch the background_work reader to
it. active_count() is unchanged (capacity semantics preserved). Adds a
contract test for the unit-vs-task distinction and documents both counts.
2026-07-25 02:12:12 +00:00
Victor Kyriazakos
9edcb7b091 feat(monitoring): emit hermes.gateway.background_work (subagent/bg jobs)
active_agents counts foreground turns + cron + API runs but never the
backgrounded delegate_task subagents / terminal(background) processes /
kanban workers tracked only for scale-to-zero. Emit them as a distinct
content-free gauge so the fleet dashboard can show subagent/background
load per peer. Best-effort, sums async_delegation.active_count +
process_registry.count_running; 0 if a source is unavailable.

(cherry picked from commit 1b7ec684e253651a5aa5760b3bd527a3073ce207)
2026-07-25 01:54:31 +00:00
Victor Kyriazakos
6de6e36dbe fix(monitoring): surface cron snapshot failures at WARNING (content-free)
The cron health snapshot failure path logged only at DEBUG, so a cron
telemetry regression would silently drop all hermes.cron.* metrics while
gateway health metrics kept flowing. Promote to WARNING with the
exception *type* name only (no message text, preserving the no-raw-error
contract); keep the traceback on DEBUG.

(cherry picked from commit 1c9a3e737b7c57fa9886bc927f1623753a9a811b)
2026-07-25 01:54:31 +00:00
Victor Kyriazakos
a65a647b04 fix(monitoring): correct cron operational signals 2026-07-24 18:54:46 +00:00
Victor Kyriazakos
efbf2fd79c feat(monitoring): add cron operational telemetry 2026-07-24 18:54:46 +00:00
Victor Kyriazakos
9b098e7f78 fix(monitoring): enrich gateway diagnostic scope 2026-07-24 18:54:46 +00:00
Victor Kyriazakos
42bc6c8626 fix(monitoring): keep diagnostics content-free 2026-07-24 18:54:46 +00:00
Victor Kyriazakos
18b4664543 fix(monitoring): drain terminal lifecycle events 2026-07-24 18:54:45 +00:00
Victor Kyriazakos
73190cdbfe fix(monitoring): complete production OTLP runtime 2026-07-24 18:54:45 +00:00
Victor Kyriazakos
98a0260b0a fix(otel): identify Hermes resources safely 2026-07-24 18:54:45 +00:00
Victor Kyriazakos
16e774f223 fix(monitoring): harden gateway health OTLP egress 2026-07-24 18:54:45 +00:00
Victor Kyriazakos
87a15733c0 fix(monitoring): address review — persist install_id, drop dead config, single redaction path
- Cut the leftover telemetry.* DEFAULT_CONFIG block (nothing reads it) and
  the legacy telemetry-key fallback in policy.py.
- install_id: persist the minted UUID back to config.yaml on first use so
  service.instance.id survives gateway restarts (fail-open when the write
  is not possible); regression test covers the restart path.
- Remove the no-op gateway_health_export.redaction config keys. Redaction
  is always-on by design and deliberately not configurable; status output
  now says so.
- Collapse redaction to one unconditional secrets+PII scrub: drop the
  none/pii content modes (they served the dropped trajectories plane) and
  fold gateway_health.py's duplicate bearer/token/email/phone regex layer
  into agent/monitoring/redaction.py.
2026-07-24 18:54:45 +00:00
Victor Kyriazakos
505d12f662 refactor(monitoring): scope telemetry substrate to gateway health/diagnostics export
Salvages the event-spine foundation from feat/telemetry-observability
(emitter, typed events, OTLP streaming, redaction — authorship preserved in
the preceding commits) and scopes it to the plane enterprise operators need
today: gateway Service Health Monitoring plus redacted Operational
Diagnostics, exported over OTLP.

Dropped from the salvaged branch, deliberately:
- run/model/tool trajectory capture (plugins/telemetry hooks, tel_spans)
- the local JSONL + state.db tel_* store (monitoring is egress, not storage)
- usage rollups/metrics, /insights integration, bulk export
- hermes telemetry CLI (replaced by hermes monitoring status)

Those planes — shared client usage metrics and enterprise trace telemetry —
are being designed on the NeMo Relay integration with distinct consent,
policy, and export boundaries; this keeps the monitoring plane content-free
and independently enableable.

Renames agent/telemetry -> agent/monitoring, config telemetry.* ->
monitoring.*, and pins the otlp extra at OpenTelemetry 1.39.1 (matching
uv.lock; 1.30.0 conflicts with mistralai>=2.4 on opentelemetry-api).
2026-07-24 18:54:45 +00:00