feat(agents): CRD-driven native MCP + complete agent model-strip + base-sepolia USDC fix#673
Open
bussyjd wants to merge 9 commits into
Open
feat(agents): CRD-driven native MCP + complete agent model-strip + base-sepolia USDC fix#673bussyjd wants to merge 9 commits into
bussyjd wants to merge 9 commits into
Conversation
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
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
/api/services.json."USDC", not"USD Coin"(verified on-chain).1. Agent model-strip (now complete)
For
type=agentoffers the underlying model is an internal detail: the agent runs its own model/skills/memory and Hermes ignores the chat-completionsmodelfield (the buyer never selects one). It was leaking into every discovery surface; this PR removes it from all of them:pay-agentexamplee605f55extra.agentModel92716d60f86017/skill.mdcatalog (Model column + bullet)2a88c3f/api/services.json(entrymodel)f8c0d8ebuildServiceCatalogJSONwas the last bypass — it set the entry model fromspec.model/ the resolved agent instead of the sharedcatalogModelNamehelper.f8c0d8eroutes it throughcatalogModelNameso 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--modelfrombuy.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:
AgentSpec.MCPServers []AgentMCPServer{name,command,args,url,transport,env}on the Agent CRD.mcp_servers:block into the agent'shermes-config; Hermes (tools/mcp_tool.py) discovers each server's tools as first-class tools.stdio(command+args) for a local server,urlfor a remote one.envvalues 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 byhyperliquid-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 402extramakes a real facilitator reject otherwise-valid EIP-3009 signatures from third-party buyers (obol's ownbuy.pyis offline-guarded and unaffected). Fixed inchains.go+tokens.go. Base mainnet ("USD Coin") is correct and left unchanged — verified on-chain: the reconstructedDOMAIN_SEPARATORfor native USDC0x8335…2913matches"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.f8c0d8ealso folds in a stale-test fix (agentcrdcontractlifetime_seconds 90 → 180).Verify
go build ./...,go vet,go test ./internal/x402/... ./internal/serviceoffercontroller/... ./internal/monetizeapi/...✅intel.v1337.org:gemma4-aeon-uncensorednow appears 0× 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 advertisesname=USDC(base-sepolia); base mainnetDOMAIN_SEPARATORmatches"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 plainobol stack upwould revert them (and prune themcpServersschema). The next release — official images carrying these commits + a bumpedx402.yamlpin + the updated CRD — closes that drift.https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD