Skip to content

feat(hermes): ship the Opper session-root / affinity provider plugin#17

Merged
mattias-lundell merged 1 commit into
mainfrom
feat/hermes-session-root
Jun 15, 2026
Merged

feat(hermes): ship the Opper session-root / affinity provider plugin#17
mattias-lundell merged 1 commit into
mainfrom
feat/hermes-session-root

Conversation

@mattias-lundell

@mattias-lundell mattias-lundell commented Jun 11, 2026

Copy link
Copy Markdown
Member

What

opper launch hermes now ships a dedicated opper provider plugin into its isolated HERMES_HOME on each launch, and points Hermes at it. Together this makes a Hermes session render as one session-root tree in Opper traces (and pins each session to one provider for prompt-cache reuse).

Changes

  • data/hermes-opper-plugin/{__init__.py,plugin.yaml} — a Hermes model-provider plugin registering a first-class opper provider. On every request it emits:
    • X-Opper-Trace-Id = uuid5(session_id) — groups the session's calls into one trace + drives provider affinity.
    • X-Opper-Parent-Span-Id = the same id — makes Opper auto-create one session root span and nest each turn under it (tree instead of N sibling roots).
    • Falls back to a per-process id when Hermes passes no session_id (e.g. one-shot -z), so a launch still shares one trace.
  • hermes.ts — write model.provider: opper + a matching providers.opper block, and writeOpperPlugin() copies the plugin into HERMES_HOME/plugins/model-providers/opper/ on spawn (via the existing assetPath()/data/ pattern; rewritten each launch so CLI upgrades ship plugin changes).
  • Why the names must match — Hermes resolves the request-time api key from the active provider's key_env. model.provider and the providers.* entry are deliberately both opper; a mismatch leaves the active provider with no key, so Hermes sends a no-key-required placeholder → Opper 401.

Test

  • providers.opper / model.provider: opper / plugin-dir assertions updated.
  • Full suite green (376 tests), tsc clean.
  • Manually verified end-to-end: deleted the plugin, launched, the CLI re-created it, Hermes authenticated (200), and a session root + per-turn llm children landed in one trace.

⚠️ Sequencing

Land + deploy the task-api session-root change (opper-ai/opper#2921) first. Until it's live, the X-Opper-Parent-Span-Id header points at a not-yet-existing span and the child spans are dropped on the public.spans.parent_uuid FK. The plugin's affinity behavior is independent and safe either way.

🤖 Generated with Claude Code

@mattias-lundell mattias-lundell force-pushed the feat/hermes-session-root branch from 8295db0 to 195c83a Compare June 12, 2026 07:41
`opper launch hermes` now installs a dedicated `opper` provider plugin into its
isolated HERMES_HOME on each launch and configures Hermes to use it, so Hermes
authenticates and emits the headers Opper needs for session grouping + provider
affinity.

- data/hermes-opper-plugin/{__init__.py,plugin.yaml}: a Hermes model-provider
  plugin registering a first-class `opper` provider. On every request it emits
  X-Opper-Trace-Id and X-Opper-Parent-Span-Id (= uuid5(session_id), with a
  per-process fallback when Hermes passes no session_id). The trace id groups a
  session's calls into one trace and drives provider affinity (sticky provider
  for prompt-cache reuse across turns); the matching parent-span id makes Opper
  auto-create one "session" root span and nest each turn under it, so the trace
  renders as a tree instead of N sibling roots.
- hermes.ts: write `model.provider: opper` and the matching `providers.opper`
  block, and writeOpperPlugin() copies the plugin into HERMES_HOME on spawn
  (rewritten each launch so CLI upgrades ship plugin changes). `model.provider`
  and the providers entry share the same key on purpose: Hermes resolves the
  request-time api key from the ACTIVE provider's key_env, so a name mismatch
  leaves the provider with no key and Hermes sends a "no-key-required"
  placeholder that Opper rejects with 401.

Requires the task-api session-root change (opper-ai/opper#2921) deployed first:
without it the parent-span header points at a not-yet-existing span and the
child spans are dropped on the FK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mattias-lundell mattias-lundell force-pushed the feat/hermes-session-root branch from 195c83a to 4abe5a2 Compare June 12, 2026 07:56
@mattias-lundell mattias-lundell merged commit 3448942 into main Jun 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant