Skip to content

fix(serviceoffer-controller): render Hermes config from the Agent CR + hash-skip to stop the resync-wipe#728

Open
bussyjd wants to merge 2 commits into
mainfrom
fix/agent-cr-config-source
Open

fix(serviceoffer-controller): render Hermes config from the Agent CR + hash-skip to stop the resync-wipe#728
bussyjd wants to merge 2 commits into
mainfrom
fix/agent-cr-config-source

Conversation

@bussyjd

@bussyjd bussyjd commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Closes #727.

Makes the Agent CR the source of truth for per-agent Hermes config and stops the controller rewriting it on every reconcile/restart — the recurring wipe that reverts an agent's LLM provider, MCP wiring, and turn cap (and recently took a paying endpoint down with a 502 after an upgrade).

Changes

A — Additive CR fields + render passthrough (b7b1bf4)

  • AgentSpec gains optional modelProvider, mcpServers, maxTurns, disabledToolsets (new MCPServer type). model string is untouched — no CRD version bump, no conversion.
  • renderHermesConfig now honors them: non-custom providers omit the litellm base_url/api_key; an mcp_servers: block is emitted from spec.mcpServers; turns/toolsets fall back to today's literals.
  • When all new fields are unset the rendered config is byte-identical to the prior template (golden test TestRenderHermesConfig_UnsetFieldsByteIdentical) — zero change for existing agents.
  • CRD schema + zz_generated.deepcopy.go regenerated via controller-gen.

B — Hash-skip (9a347d4)

  • buildAgentConfigMap stamps obol.org/hermes-config-hash (sha256 of the rendered config.yaml) on the ConfigMap.
  • provisionAgent gets the live ConfigMap and, when the freshly-rendered desired hash equals the stored annotation, skips the apply. The decision is purely desiredHash == storedAnnotation (pure function hermesConfigDecision), never desired-vs-live-content — so operator edits are not treated as drift to revert. Because the annotation lives on the persistent ConfigMap, the skip survives controller restarts, which is what ends the every-obol stack up re-provision.

C — ConfigDrift status condition (9a347d4)

  • When skipping, if the live ConfigMap content differs from the last-rendered hash (an out-of-band edit), a ConfigDrift condition is set instead of silently clobbering. This is the safety net for B (which deliberately stops self-healing drift).

Scope is limited to the hermes-config ConfigMap; every other manifest applies exactly as before.

Tests

New agent_confighash_test.go: table test of hermesConfigDecision (nil / match+no-drift / match+drift / mismatch / absent-annotation), plus fake-client integration tests proving skip preserves resourceVersion, stale/absent annotation applies + stamps, and drift sets the condition without clobbering. agent_render_test.go adds the byte-identical golden + override cases. go build ./..., full serviceoffercontroller + monetizeapi suites, and go vet are green.

Validated live on silvernuc3

Built + deployed this controller, applied the CRD, moved the paying hyperliquid-analyst agent's grok+xai-oauth+MCP config into its CR, then:

  • Controller reconcile renders grok + mcp_servers + max_turns: 300 from the CR and stamps the hash.
  • Forced a controller restart (the exact scenario that used to wipe): ConfigMap resourceVersion unchanged (skip) — config preserved.
  • Agent completes end-to-end on grok; Ready=True, ConfigDrift=False(InSync), offer Ready, 402 gate healthy.

Migration / compatibility

Fully additive. modelProvider defaults to custom, so existing agents render identically until an operator opts in by setting the new CR fields. The regenerated CRD is additive (existing CRs remain valid).

https://claude.ai/code/session_01VquWN9UMaSHH7MHGcG8bw1

bussyjd added 2 commits July 10, 2026 15:05
…s from Agent CR

Additive AgentSpec fields (modelProvider, mcpServers, maxTurns,
disabledToolsets) threaded through renderHermesConfig so the CR is the
source of truth for the agent's inference provider and paid MCP wiring.
Unset fields render byte-identical to the prior fixed template (no CRD
version bump, no behavior change for existing agents).

Claude-Session: https://claude.ai/code/session_01VquWN9UMaSHH7MHGcG8bw1
…rift condition

Gate the per-agent hermes-config ConfigMap write on a content-hash
annotation (obol.org/hermes-config-hash) stored on the live ConfigMap.
Skip the apply when the freshly-rendered desired hash matches the stored
annotation — so an unchanged config is not rewritten on every reconcile
or controller restart. This ends the every-'obol stack up' re-provision
that silently wiped operator config (now CR-driven after b7b1bf4).

The skip decision is purely desiredHash == storedAnnotation, never
desired-vs-live-content, so operator edits are not treated as drift to
revert. Out-of-band ConfigMap edits are surfaced via a new ConfigDrift
status condition instead of being clobbered. Annotation lives on the
persistent ConfigMap so the skip survives controller-pod restarts.

Claude-Session: https://claude.ai/code/session_01VquWN9UMaSHH7MHGcG8bw1
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.

serviceoffer-controller silently reverts per-agent Hermes config (provider, MCP servers, turn cap) on every reconcile/upgrade

1 participant