Files
hermes-agent/plugins/observability/langfuse
teknium1 ca2ca5b9e1 fix(secret-scope): mem0, langfuse and azure credential readers stop swallowing UnscopedSecretError
Three shims caught UnscopedSecretError and degraded to "" (mem0._scoped_env)
or to os.environ (langfuse._secret, azure_identity_adapter._scoped_env). Under
multiplex os.environ holds the DEFAULT profile's .env, so the langfuse/azure
fallback could ship another profile's keys, and the mem0 fallback silently
routed a mis-spawned turn's memories into the default profile's account. The
exception exists to surface exactly that spawn-site bug (agent/AGENTS.md:
never add environ fallthrough, never swallow it). All three now call
agent.secret_scope.get_secret directly: with a scope installed a miss returns
the default; single-profile deployments (multiplex off) still read the process
env inside get_secret; a scope-less multiplex caller raises.

Behavior change: a mis-spawned child under gateway.multiplex_profiles now
fails loud with UnscopedSecretError instead of running silently unauthenticated
/ on the default profile's identity. The #99121 contract (OSS mode needs no
MEM0_API_KEY in scope) is unchanged and its test now installs an empty profile
scope, which is the situation the issue described; a genuinely scope-less
caller is asserted to raise in a new test.

Tests: tests/plugins/test_scoped_secret_readers_fail_closed.py (scope wins over
environ; scope-less multiplex raises) for langfuse + azure, sabotage red when
the langfuse fallthrough is restored; tests/plugins/memory/test_mem0_v3.py::
test_load_config_fails_closed_without_scope_even_for_identity_settings,
sabotage red with a swallowing wrapper reinstated.
2026-09-13 05:19:48 -07:00
..

Langfuse Observability Plugin

This plugin ships bundled with Hermes but is opt-in — it only loads when you explicitly enable it.

Enable

Pick one:

# Interactive: walks you through credentials + SDK install + enable
hermes tools  # → Langfuse Observability

# Manual
pip install langfuse
hermes plugins enable observability/langfuse

Required credentials

Set these in ~/.hermes/.env (or via hermes tools):

HERMES_LANGFUSE_PUBLIC_KEY=pk-lf-...
HERMES_LANGFUSE_SECRET_KEY=sk-lf-...
HERMES_LANGFUSE_BASE_URL=https://cloud.langfuse.com   # or your self-hosted URL

Without the SDK or credentials the hooks no-op silently — the plugin fails open.

Verify

hermes plugins list                 # observability/langfuse should show "enabled"
hermes chat -q "hello"              # then check Langfuse for a "Hermes turn" trace

Generation observations include the Hermes system prompt when the provider uses a separate system param (Anthropic Messages API). Open an LLM call child span to inspect role: system (truncated via HERMES_LANGFUSE_MAX_CHARS).

Optional tuning

HERMES_LANGFUSE_ENV=production       # environment tag
HERMES_LANGFUSE_RELEASE=v1.0.0       # release tag
HERMES_LANGFUSE_SAMPLE_RATE=0.5      # sample 50% of traces
HERMES_LANGFUSE_MAX_CHARS=12000      # max chars per field (default: 12000)
HERMES_LANGFUSE_CAPTURE=sanitized    # content capture mode (see below)
HERMES_LANGFUSE_DEBUG=true           # verbose plugin logging

Capture modes

HERMES_LANGFUSE_CAPTURE controls how much content (prompts, responses, tool arguments/results) is exported. Structural metadata — IDs, roles, tool names, token usage, cost, timing — is always captured in every mode.

mode behavior
metadata No content. Each content field is replaced by a shape/size stub ({"omitted": true, "type": "text", "chars": N}).
sanitized (default) Content is exported after secret-pattern redaction (API keys, tokens, JWTs, private keys, password=-style assignments) and truncation. Redaction runs before truncation.
full Raw content, truncated only. Explicit opt-in — traces will contain whatever passed through the conversation, including injected memory and file contents.

The active mode is recorded on every trace as metadata.capture_mode.

Note: sanitized is pattern-based defense in depth, not a DLP guarantee. For personal sessions or shared Langfuse projects, prefer metadata.

Error + shutdown coverage

  • Failed model requests (api_request_error hook) close their generation with level=ERROR, status code, retry counters, and a capture-mode-scrubbed error message. Non-retryable failures also finish the turn trace.
  • Session end/finalize closes any still-open traces for that session and flushes queued events, so interrupted or tool-only turns don't dangle.

Disable

hermes plugins disable observability/langfuse