Skip to content

feat(agents): CRD-driven native MCP + complete agent model-strip + base-sepolia USDC fix#673

Open
bussyjd wants to merge 9 commits into
mainfrom
fix/agent-402-instructions-drop-model
Open

feat(agents): CRD-driven native MCP + complete agent model-strip + base-sepolia USDC fix#673
bussyjd wants to merge 9 commits into
mainfrom
fix/agent-402-instructions-drop-model

Conversation

@bussyjd

@bussyjd bussyjd commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What

This started as a focused agent model-strip and grew into broader agent discovery + runtime hardening. It now does three things, plus two smaller fixes:

  1. Complete the agent model-strip across all discovery surfaces — the original goal, now finished by also stripping /api/services.json.
  2. feat: CRD-driven native MCP servers for agents — Hermes agents get native MCP tools from the Agent CR, with payment abstracted.
  3. fix: base-sepolia USDC EIP-712 domain name"USDC", not "USD Coin" (verified on-chain).

1. Agent model-strip (now complete)

For type=agent offers the underlying model is an internal detail: the agent runs its own model/skills/memory and Hermes ignores the chat-completions model field (the buyer never selects one). It was leaking into every discovery surface; this PR removes it from all of them:

surface commit
402 page copy + pay-agent example e605f55
JSON 402 extra.agentModel 92716d6
bazaar discovery example 0f86017
/skill.md catalog (Model column + bullet) 2a88c3f
/api/services.json (entry model) f8c0d8e

buildServiceCatalogJSON was the last bypass — it set the entry model from spec.model / the resolved agent instead of the shared catalogModelName helper. f8c0d8e routes it through catalogModelName so all five surfaces agree. This reverses the earlier "services.json left untouched on purpose" note — there's no reason for the storefront feed to leak the id either. Also drops the no-op --model from buy.py pay-agent.

Inference offers are deliberately unchanged — there the buyer selects the model (paid/<remote-model>), so the real id stays buyer-facing.

2. feat: CRD-driven native MCP servers (5427f4d)

Give Hermes agents native MCP instead of a hand-patched ConfigMap:

  • New AgentSpec.MCPServers []AgentMCPServer{name,command,args,url,transport,env} on the Agent CRD.
  • The controller renders an mcp_servers: block into the agent's hermes-config; Hermes (tools/mcp_tool.py) discovers each server's tools as first-class tools.
  • stdio (command+args) for a local server, url for a remote one. env values support ${VAR} interpolation so signer creds reach the stdio subprocess without landing in the ConfigMap.

This makes native MCP durable across agent sync / stack up — it is driven by the CR, not a manual edit. In production by hyperliquid-analyst, whose stdio MCP server abstracts x402 payment per tool call (each call pays $0.001 internally; the agent never sees payment).

3. fix: base-sepolia USDC EIP-712 name (72fa830)

Base-Sepolia USDC is FiatTokenV2_2, whose EIP-712 domain name is "USDC", not the mainnet "USD Coin". Advertising "USD Coin" in the 402 extra makes a real facilitator reject otherwise-valid EIP-3009 signatures from third-party buyers (obol's own buy.py is offline-guarded and unaffected). Fixed in chains.go + tokens.go. Base mainnet ("USD Coin") is correct and left unchanged — verified on-chain: the reconstructed DOMAIN_SEPARATOR for native USDC 0x8335…2913 matches "USD Coin", not "USDC".

Also included

  • 0229b8f — serve the catalog as UTF-8 (stop em-dash mojibake).
  • e83bd81 — make sold agents actually run their skills.
  • f8c0d8e also folds in a stale-test fix (agentcrd contract lifetime_seconds 90 → 180).

Verify

  • go build ./..., go vet, go test ./internal/x402/... ./internal/serviceoffercontroller/... ./internal/monetizeapi/...
  • Deployed + verified live on intel.v1337.org: gemma4-aeon-uncensored now appears across the 402 page, JSON 402, /skill.md, and /api/services.json; a native-MCP analyst turn returns real data with payment abstracted; the 402 advertises name=USDC (base-sepolia); base mainnet DOMAIN_SEPARATOR matches "USD Coin" on-chain.

Deploy note

The live silvernuc3 cluster currently runs hand-pinned local images built from this branch (serviceoffer-controller, x402-verifier); the embedded helmfile still pins the old ghcr controller, so a plain obol stack up would revert them (and prune the mcpServers schema). The next release — official images carrying these commits + a bumped x402.yaml pin + the updated CRD — closes that drift.

https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD

bussyjd added 4 commits June 26, 2026 12:18
An Obol Agent (type=agent) runs its own model, skills, and memory — the buyer
never selects one, and Hermes ignores the chat-completions `model` field
(resolved from its own config). Surfacing the underlying model in the 402 page
is noise and exposes an implementation detail, and pay-agent's required
`--model` flag had no effect on agent calls.

- paymentrequired.go agentCopy: remove '(running <model>)', the '"model":' line
  in the example body, and '--model <id>' from the pay-agent example. The agent
  copy is now model-free.
- buy-x402 buy.py: pay-agent no longer accepts/sends --model (synthesised body
  is just {messages, stream}); usage strings updated.
- buy-x402 SKILL.md: pay-agent documented without --model.

go build + x402/buyer/embed tests green.

Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD
mergeAgentExtras no longer adds extra.agentModel — an Obol Agent runs its own
model and the buyer never selects one, so the model id is an internal detail,
not buyer-facing info (it also rendered in the HTML 402 page's raw-JSON card).
agentSkills/agentRuntime still surface so clients can tell it's an agent.

Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD
…t offers

For agent offers the buyer never selects a model — the agent runs its own and
ignores the chat-completions `model` field — so the bazaar discovery example
now seeds the neutral 'your-model-id' placeholder instead of the real upstream
id (which also rendered in the HTML 402 page's embedded raw-JSON card). Inference
offers are unchanged: there the model IS buyer-selectable, so the real id stays.

Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD
…for agent offers

The /skill.md catalog (and its Service Details section) showed the agent's
underlying model in the Model column / **Model** bullet. An agent runs its own
model and ignores the request `model` field, so the id is an internal detail —
agent rows now render '—' and omit the **Model** bullet. Inference offers keep
their model (there the buyer selects it). Mirrors the 402 page/extra/bazaar
model-strip in internal/x402. The /api/services.json feed still resolves the
agent model on purpose (drives the storefront UI) — left untouched.

Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD
@bussyjd bussyjd changed the title fix(x402): drop the underlying model from agent 402 copy + pay-agent fix: drop the underlying model from agent discovery surfaces (402 + /skill.md) Jun 26, 2026
bussyjd added 5 commits June 26, 2026 13:19
…mojibake

The skill-catalog busybox httpd served .md/.html with a bare text/* Content-Type
(no charset), so clients fell back to Latin-1/CP1252 and rendered UTF-8 em dashes
(the catalog's '—' placeholders, accented operator descriptions) as '—'. Add
charset=utf-8 to the text MIME mappings. JSON stays clean (always UTF-8 per RFC 8259).

Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD
Two fixes for type=agent offers whose skills shell out to scripts (buy-x402, the
hyperliquid data skill), found while debugging the hyperliquid-analyst:

- Disable the code_execution toolset. execute_code runs arbitrary in-process
  Python that bypasses the terminal DANGEROUS_PATTERNS gate, so Hermes fails it
  closed without a per-script approval no human can grant in an unattended paid
  turn. Small models then loop on it until the turn dies (observed: gemma4
  returning a templated placeholder answer instead of fetching data). Disabling
  it routes skills through the terminal tool, where benign python3 auto-approves
  and dangerous commands stay gated.

- Raise terminal.timeout/lifetime_seconds 80s/90s -> 170s/180s. Sold agents run
  behind a named tunnel (no ~100s quick-tunnel cut), and a paid data call (x402
  round-trip + a first-party query) can exceed 80s, timing out heavier queries.

Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD
…json for agent offers

The catalog JSON builder set the entry model from spec.model / the resolved
agent model, bypassing catalogModelName -- so /api/services.json still leaked
the internal model id for type=agent offers even though skill.md, the 402
page/extra, and the bazaar example already strip it (#673). Route the JSON
builder through catalogModelName so all discovery surfaces agree: agent offers
omit the model (they run their own and ignore the request `model`), inference
offers keep it. Invert the test that pinned the old leaky behaviour.

Also folds in pre-existing #673 housekeeping that was still uncommitted:
- buy-x402 SKILL.md + buy.py: drop stale --model residue
- agentcrd contract test: fix stale lifetime_seconds 90 -> 180 drift

Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD
…oin"

Base-Sepolia USDC is FiatTokenV2_2, whose EIP-712 domain name is "USDC" --
unlike mainnet USDC ("USD Coin"). Advertising "USD Coin" in the 402 accepts
extra makes a real facilitator reject otherwise-valid EIP-3009 signatures from
third-party buyers (obol's own buy.py is offline-guarded and unaffected). This
is the recurring base-sepolia "name" bug that a stub facilitator silently
masks; forward-ports 31604a1 so a verifier rebuilt off this branch stays
correct.

- chains.go: ChainBaseSepolia.EIP3009Name -> "USDC"
- tokens.go: USDC/base-sepolia EIP712Name -> "USDC"

Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD
Give Hermes agents native MCP instead of a hand-patched ConfigMap. Adds
AgentSpec.MCPServers ([]AgentMCPServer{name,command,args,url,transport,env})
to the Agent CRD; the controller renders an `mcp_servers:` block into the
agent's hermes-config so Hermes (tools/mcp_tool.py) discovers each server's
tools as first-class tools. stdio (command+args) for a local server, url for a
remote one; env values support ${VAR} interpolation so signer creds reach the
stdio subprocess without landing in the ConfigMap.

This makes native MCP durable across `agent sync` / `stack up` -- it is driven
by the CR, not a manual edit. Used in production by hyperliquid-analyst, whose
stdio MCP server (hl_mcp.py) abstracts x402 payment per tool call.

- monetizeapi: AgentMCPServer type + deepcopy
- agent-crd.yaml: mcpServers schema (maxItems 32)
- agent_render.go: renderMCPServersBlock (sorted env keys, %q quoting)
- agent_render_test.go: TestRenderHermesConfig_MCPServers

Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD
@bussyjd bussyjd changed the title fix: drop the underlying model from agent discovery surfaces (402 + /skill.md) feat(agents): CRD-driven native MCP + complete agent model-strip + base-sepolia USDC fix Jun 27, 2026
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