Skip to main content
Skills auto-appear in the Simmer registry within ~1 hour of publishing to ClawHub. Install the Skill Builder and describe your strategy in plain language:
clawhub install simmer-skill-builder
Then tell your agent: “Build me a skill that trades X when Y happens.” You can also paste a full strategy post — a KOL thread from X, a blog post with code, a quant strategy write-up, or a campaign brief. The builder extracts the parameters (signal source, entry thresholds, Kelly fraction, position caps, order type), maps external dependencies to Simmer equivalents, and generates a complete skill folder. The Skill Builder supports three strategy patterns:
  • External API signal (weather data, RSS feeds, price oracles) → deterministic trading
  • Filter-and-trade (scan Simmer markets, filter by criteria, trade matches)
  • Agent-as-oracle (your agent estimates probabilities using LLM reasoning, the script handles bias correction, Kelly sizing, and limit order execution)
The Skill Builder generates a complete, ready-to-publish skill folder.

Worked example: turn a World Cup strategy thread into a skill

If you came from the World Cup campaign page, paste the strategy in plain language. You do not need to know the SDK first. Example prompt:
Build a World Cup skill from this strategy:

Polymarket splits each soccer match into three YES/NO markets:
Team A win, Team B win, and Draw. When the three YES midpoints sum
below 98%, buy the cheapest underpriced outcome. Only trade World Cup
matches within 24 hours of kickoff, skip markets under $5K volume, cap
each match at $15, and use limit orders. If PolyNode is available, use
its sports endpoints for game state; otherwise scan Simmer/Polymarket
markets by World Cup keywords.
The Skill Builder should extract this into a deterministic spec before writing code:
ParameterValue
Market selectionWorld Cup match markets; Team A win, Team B win, Draw
SignalSum of the three YES midpoints
Entry gateTrade only when sum is below 0.98 and the chosen outcome still clears spread/slippage checks
Risk bounds$15 max exposure per match; dry-run by default
Order typeLimit/GTC, because this is price-sensitive
Data dependencyOptional POLYNODE_API_KEY; fallback to Simmer market search/import
ExitAsk the user to confirm hold-to-resolution vs sell-on-normalization if the thread does not say
Good generated output is narrow and testable:
polymarket-worldcup-split-scanner/
  SKILL.md
  DISCLAIMER.md
  clawhub.json
  worldcup_split_scanner.py
  scripts/status.py
Before publishing, run a dry-run fixture:
PricesExpected result
[0.49, 0.24, 0.24]Trigger: sum is 0.97
[0.50, 0.25, 0.27]Skip: sum is 1.02, normal overround
This pattern also works for other World Cup ideas. The important move is to convert “market lag” or “momentum” into one measurable signal, one entry gate, one sizing rule, and one exit rule. Examples:
Idea from a threadDeterministic skill shape
”Markets lag injury news”Search/fetch context, require fresh injury source, compare current price to pre-news price
”xG pressure is not priced in”Read xG/shots/possession data, require an xG gap, trade only if market price has not moved
”Futures overreact to lucky wins”Compare post-match futures move to xG/performance data, trade only after group-stage matches
Keep the first skill small. A single World Cup signal with explicit caps is easier to test, publish, and remix than a broad “AI World Cup trader” with unclear authority.

Option 2: Build manually

A skill is a folder with three files:
your-skill-slug/
  SKILL.md          # AgentSkills-compliant metadata + docs
  clawhub.json      # ClawHub + automaton config
  your_script.py    # Main trading logic

SKILL.md frontmatter

---
name: your-skill-slug
description: One sentence describing what it does and when to use it.
metadata:
  author: "Your Name"
  version: "1.0.0"
  displayName: "Your Skill Name"
  difficulty: "intermediate"
---
Rules:
  • name must be lowercase, hyphens only, match folder name
  • description is required. AgentSkills spec allows up to 1024 chars, but keep it ≤160 chars — ClawHub truncates anything longer when generating the skill’s summary, and that truncated value is what appears as the one-line description on simmer.markets/skills/<owner>/<slug> and in social-share cards. Write a complete sentence that fits.
  • metadata values must be flat strings (AgentSkills spec)
  • No platform-specific config in SKILL.md — that goes in clawhub.json

clawhub.json

{
  "emoji": "your-emoji",
  "primaryEnv": "SIMMER_API_KEY",
  "requires": {
    "pip": ["simmer-sdk"],
    "env": ["SIMMER_API_KEY"]
  },
  "envVars": [
    {
      "name": "SIMMER_API_KEY",
      "required": true,
      "description": "Your Simmer SDK API key — get from simmer.markets/dashboard"
    },
    {
      "name": "WALLET_PRIVATE_KEY",
      "required": false,
      "description": "Only needed for external-wallet self-custody trading."
    }
  ],
  "cron": "*/15 * * * *",
  "automaton": {
    "managed": true,
    "entrypoint": "your_script.py"
  }
}
simmer-sdk in requires.pip is required. This is what causes the skill to appear in the Simmer registry automatically.
Declare credentials in all three fields, not just requires.env. ClawHub’s moderation scanner reads all of them together; getting this right prevents false-positive “Suspicious” verdicts that block non-interactive installs:
FieldMeaningUse for
requires.envStrictly required — skill fails without theseOnly the minimum credentials needed for the default code path
primaryEnvNames the single main credentialThe one key every user will have (usually SIMMER_API_KEY)
envVars[]Per-variable declaration with required boolean + human descriptionFull list including optional credentials; explains when each is needed
Common anti-patterns that trigger scanner flags:
  • Listing WALLET_PRIVATE_KEY in requires.env when your SKILL.md documents managed wallets as an alternative → “disproportionate requirement” verdict
  • Mentioning WALLET_PRIVATE_KEY in SKILL.md body but not declaring it anywhere → “hidden credential” verdict
  • Declaring unrelated credentials (e.g. OPENAI_API_KEY for a skill that only hits Simmer) → “scope creep” verdict
The fix: put strictly-required vars in requires.env, keep the full list (including optional ones marked required: false) in envVars with plain-language descriptions of when each is needed.Common env vars for Simmer skills:
Env varTypical status
SIMMER_API_KEYAlways required — main credential (also set as primaryEnv)
WALLET_PRIVATE_KEYUsually required: false — only needed when not using managed wallets
EVM_PRIVATE_KEYRequired if your skill signs EVM transactions (e.g. x402 payments)
SOLANA_PRIVATE_KEYRequired if your skill signs Kalshi/Solana transactions
POLYGON_RPC_URLrequired: false — only if the user wants a custom RPC endpoint
If you don’t touch an env var, don’t declare it. If your SKILL.md mentions one, declare it in envVars with the right required flag.

Python script patterns

import os
from simmer_sdk import SimmerClient

_client = None
def get_client():
    global _client
    if _client is None:
        _client = SimmerClient(
            api_key=os.environ["SIMMER_API_KEY"],
            venue="polymarket"
        )
    return _client

TRADE_SOURCE = "sdk:your-skill-slug"
SKILL_SLUG = "your-skill-slug"  # Must match ClawHub slug

# Always include reasoning
client = get_client()
client.trade(
    market_id=market_id,
    side="yes",
    amount=10.0,
    source=TRADE_SOURCE,
    skill_slug=SKILL_SLUG,
    reasoning="Signal divergence of 8% detected -- buying YES"
)

Hard rules

  1. Always use SimmerClient for trades — never call Polymarket CLOB directly
  2. Always default to dry-run — pass --live explicitly for real trades
  3. Always tag trades with source and skill_slug
  4. Always include reasoning — it’s shown publicly
  5. Read API keys from env — never hardcode credentials
  6. skill_slug must match your ClawHub slug — this tracks per-skill volume
  7. Frame as a remixable template — your SKILL.md should explain what the default signal is and how to remix it (see below)
  8. Pass venue= explicitly when reading state/trades, /portfolio, and /context support a venue filter. Rely on the default (all) only if you truly want cross-venue state. If your skill only trades on one venue, pass that venue on reads so you don’t confuse yourself with unrelated positions.

Remixable template pattern

Skills are templates, not black boxes. Your SKILL.md should include a callout like:
> **This is a template.** The default signal is [your signal source] —
> remix it with [alternative signals, different models, etc.].
> The skill handles all the plumbing (market discovery, trade execution,
> safeguards). Your agent provides the alpha.
The skill handles plumbing: market discovery, order execution, position management, and safeguards. The user’s agent swaps in their own signal — a different API, a custom model, additional data sources. Make it clear what’s swappable and what’s structural. The /context endpoint provides trading discipline data — flip-flop detection, slippage estimates, and edge analysis. We strongly recommend checking it before executing trades:
def get_market_context(market_id, my_probability=None):
    """Fetch context with safeguards and optional edge analysis."""
    params = {}
    if my_probability is not None:
        params["my_probability"] = my_probability
    return get_client().get_market_context(market_id, **params)

# Before buying
context = get_market_context(market_id, my_probability=0.85)

# Check warnings
trading = context.get("trading", {})
flip_flop = trading.get("flip_flop_warning")
if flip_flop and "SEVERE" in flip_flop:
    print(f"Skipping: {flip_flop}")
    # Don't trade -- you've been reversing too much

slippage = context.get("slippage", {})
if slippage.get("slippage_pct", 0) > 0.15:
    print("Skipping: slippage too high")
    # Market is too illiquid for this size

# Check edge (requires my_probability)
edge = context.get("edge_analysis", {})
if edge.get("recommendation") == "HOLD":
    print("Skipping: edge below threshold")
This isn’t a hard rule — some high-frequency skills skip context checks for speed. But for most strategies, checking context prevents costly mistakes like flip-flopping or trading into illiquid books.

Discovering markets already in Simmer

client.get_markets() is how you browse Simmer’s index. The trap: an unfiltered browse is server-windowed to roughly the newest ~1,000 active markets, out of 20k+. A market that has been in the index for a while is absent from an unfiltered result even though it’s fully tradeable — “not in get_markets()” does not mean “not imported.” Always filter for discovery. q=, tags=, and sort= are applied server-side before the window, so they reach the whole catalog:
# Filtered — reaches the full catalog
wc_legs    = client.get_markets(tags="world-cup", limit=50)
liquid     = client.get_markets(sort="volume", limit=20)
by_keyword = client.get_markets(q="win group", limit=50)

# Unfiltered — only the newest ~1,000, silently misses older markets
markets = client.get_markets(limit=50)
Before you conclude a market is missing and reach for import_market (below), confirm with a filtered q= search or client.check_market_exists(...). Re-importing a market that’s already indexed just burns your daily import quota.

Discover-then-trade for arbitrary markets

If your skill discovers markets off-Simmer (e.g. via the Polymarket Gamma API by event slug, or by walking external trader portfolios), you can’t trade them via SimmerClient.trade() until they’re in Simmer’s index — every trade endpoint, including paper modes, fetches the price from /api/sdk/context/{market_id} and 404s for unindexed markets. The canonical pattern is: check (free) → import on miss → trade. Cache the result so subsequent scans skip both calls.
def ensure_market_indexed(polymarket_url, cache):
    """Return (simmer_market_id, error). Hits cache, then check (free), then
    import (consumes quota)."""
    if polymarket_url in cache:
        return cache[polymarket_url], None

    # Free pre-flight — does NOT consume import quota
    check = client.check_market_exists(url=polymarket_url)
    if check["exists"]:
        cache[polymarket_url] = check["market_id"]
        return check["market_id"], None

    # Not indexed — consume one import from the daily quota
    result = client.import_market(polymarket_url)
    if result.get("status") in ("imported", "already_exists"):
        cache[polymarket_url] = result["market_id"]
        return result["market_id"], None
    return None, f"import status={result.get('status')}"
Why this matters:
  • check_market_exists is free and doesn’t consume the import quota
  • import_market is rate-limited (10/100/250 per day by tier; on 429 the response includes an x402_url for $0.005/import overflow via USDC on Base)
  • Most popular markets are already in Simmer’s index — the check often returns existing IDs at no cost
  • Persisting the cache across scans means steady-state cost approaches zero
For Kalshi-discovered markets, swap check_market_exists(url=...) for check_market_exists(ticker=...) and import_market for import_kalshi_market.

Reading state across venues

A Simmer agent can hold positions across three venues at once: sim (paper trading with $SIM), polymarket (real USDC on Polygon), and kalshi (real USDC on Solana). They’re independent — a single agent can hold a $SIM paper position AND a real Polymarket position on the same market simultaneously. The read endpoints are venue-aware. Always pass venue= explicitly when you know which one your skill cares about:
# I only paper-trade on sim
trades = client.get_trades(venue="sim")

# I'm a real-money Polymarket skill
positions = client.get_portfolio(venue="polymarket")

# I want everything (default)
all_state = client.get_briefing()
The default is venue="all", which returns merged state across every venue. That’s what you want for a dashboard-style heartbeat check-in. It’s also what a single-venue skill should avoid on reads — you don’t want your polymarket copytrading skill to see a leftover paper position from a week ago and think it’s real exposure.
Relying on the legacy flat fields on /portfolio and /context will silently miss cross-venue state:
  • portfolio.positions_count counts only Polymarket positions
  • context.position mirrors one venue only (picks the first non-null)
Use the per-venue buckets/containers instead:
  • portfolio.sim, portfolio.polymarket, portfolio.kalshi, portfolio.total
  • context.positions.sim, context.positions.polymarket, context.positions.kalshi
/api/sdk/briefing is the canonical cross-venue snapshot. Every agent heartbeat loop should use it:
briefing = client.get_briefing()
# briefing.venues.sim        → {balance, pnl, positions_count, positions_needing_attention}
# briefing.venues.polymarket → {balance, pnl, positions_count, redeemable_count, ...}
# briefing.venues.kalshi     → {balance, pnl, positions_count, ...}
/api/sdk/trades returns merged trade history by default, with each row tagged:
for trade in client.get_trades(limit=20)["trades"]:
    print(f"{trade['venue']}: {trade['side']} {trade['shares']} @ {trade['avg_price']}")
/api/sdk/portfolio and /api/sdk/context/{market_id} have per-venue buckets alongside the legacy flat fields. Read from the buckets:
portfolio = client.get_portfolio()  # default venue=all
print(f"Sim exposure: {portfolio['sim']['total_exposure']} $SIM")
print(f"Polymarket exposure: ${portfolio['polymarket']['total_exposure']}")
print(f"Total position count: {portfolio['total']['positions_count']}")

ctx = client.get_market_context(market_id)
sim_pos = ctx["positions"]["sim"]
if sim_pos and sim_pos["has_position"]:
    print(f"Holding {sim_pos['shares']} $SIM shares")
All legacy flat fields (portfolio.sim_balance, context.position, etc.) remain populated for backwards compatibility, but new skills should prefer the bucketed fields. Call auto_redeem() once per cycle to collect payouts from resolved markets. This handles both wallet types — managed wallets redeem server-side, external wallets sign and broadcast locally.
# At the start or end of each cycle -- safe to call frequently
results = get_client().auto_redeem()
for r in results:
    if r["success"]:
        print(f"Redeemed {r['market_id']}: tx={r['tx_hash']}")
Without this, winning positions sit unredeemed until the user manually collects them from the dashboard. For external wallets (self-custody), this is the only automated redemption path — the server can’t sign on your behalf.
auto_redeem() checks the agent’s auto_redeem_enabled setting and returns an empty list if disabled. It catches all errors internally and never raises — safe to call unconditionally.
The SDK ships helper modules that handle common skill-builder tasks. Prefer these over rolling your own — they encode patterns from top traders, are tested, and are maintained alongside the SDK. Full reference: Position Sizing.

Position sizing — simmer_sdk.sizing

Don’t hard-code stake amounts and don’t write your own Kelly. Use size_position(). It returns 0.0 when the edge is below your min_ev threshold, so the skill can simply skip the trade.
from simmer_sdk.sizing import size_position

amount = size_position(
    p_win=0.70,         # your model's probability
    market_price=0.55,  # current YES price
    bankroll=bankroll,
    min_ev=0.03,        # skip trades with edge < 3%
)
if amount > 0:
    client.trade(market_id=..., side="BUY", outcome="YES",
                 amount=amount, reasoning="...")
Expose sizing as user-tunable config by merging SIZING_CONFIG_SCHEMA into your skill’s CONFIG_SCHEMA — users get SIMMER_POSITION_SIZING, SIMMER_KELLY_MULTIPLIER, and SIMMER_MIN_EV env vars for free.

Top holders — client.get_top_holders()

See who else holds positions in a market before you trade. Calls the public Polymarket data API (free, no auth).
market = client.get_market_by_id(market_id)
if market.polymarket_condition_id:
    holders = client.get_top_holders(market.polymarket_condition_id, limit=10)
    for h in holders:
        print(f"{h['display_name']}: {h['amount']:.0f} shares ({h['outcome']})")
Returns address, display_name, amount, outcome, and profile_url per holder. Use market.polymarket_condition_id (0x hex) — not the Simmer UUID or CLOB token ID.

External market data

The SDK doesn’t bundle third-party API clients. If your skill needs Polymarket metadata beyond what SimmerClient.get_markets() exposes — categories, descriptions, full event groupings, raw volume and liquidity — call Polymarket’s Gamma API directly from the skill. It’s free, no auth, well-documented. Keep the helper next to the skill so the SDK stays scoped to Simmer’s surface area plus universal primitives.

Publishing

# From inside your skill folder
npx clawhub@latest publish . --slug your-skill-slug --version 1.0.0

# Or auto-bump version
npx clawhub@latest publish . --slug your-skill-slug --bump patch
Within 6 hours, the Simmer sync job will:
  1. Discover your skill via ClawHub search
  2. Verify it has simmer-sdk as a dependency
  3. Add it to the registry at simmer.markets/skills
No approval process. No submission form.
Always pass --slug explicitly. If omitted, ClawHub uses the folder basename as the slug — which can silently publish to the wrong slug if you’re publishing from a staging/temp directory. Make the slug explicit every time.

After publishing, verify the install path

Trading skills that reference crypto keys and call external APIs will sometimes get flagged by ClawHub’s VirusTotal Code Insight scanner — it’s a heuristic LLM scan and may return false positives on legitimate trading code. Verify installs work:
npx clawhub@latest install your-skill-slug
If you see:
⚠️ Warning: “your-skill-slug” is flagged as suspicious by VirusTotal Code Insight. Error: Use —force to install suspicious skills in non-interactive mode
Two fixes:
  1. Manifest mismatch (most common): make sure every env var and every capability your SKILL.md teaches is declared in clawhub.json requires.env. Republish as a new patch version. OpenClaw re-scans and clears its verdict.
  2. VT Code Insight false positive: if OpenClaw is clean but VirusTotal still flags behavioral patterns (crypto keys + external HTTP + credential handling), email simmer@agentmail.to and we’ll request a manual override from ClawHub. Include your skill slug and the scan report link from https://clawhub.ai/skills/<your-slug>.

Distribute beyond Simmer (skills.sh)

Publishing to ClawHub (above) is what lists your skill in the Simmer registry. Keep doing that. For extra reach across other coding agents (Claude Code, Codex, Cursor, OpenCode, and 60+ more), you can also make the skill installable via skills.sh, the open agent-skills ecosystem. There is no separate publish step. skills.sh resolves skills straight from a public git repo:
  1. Push your skill folder to a public GitHub (or GitLab) repo, e.g. your-org/your-skills/<skill-slug>/SKILL.md.
  2. Anyone, on any supported agent, can install it:
npx skills add your-org/your-skills --skill your-skill-slug
The same SKILL.md frontmatter (name + description) that ClawHub reads is what skills.sh reads, so no extra config is needed.
A public repo makes your skill installable immediately, but the skills.sh search and leaderboard rank by install count, so a brand-new skill won’t surface in discovery until it accrues installs. Share the direct npx skills add command to drive those first installs. To keep a skill installable but hidden from skills.sh discovery, set metadata.internal: true in the frontmatter.
Distribution is additive: ClawHub feeds the Simmer registry (primary), skills.sh adds cross-agent reach (optional).

Scanner compliance — passing ClawHub moderation

ClawHub runs an LLM-based moderation scanner (engine v2.4.22+) on every published skill. Skills flagged suspicious are hidden from search and blocked from non-interactive installs. The scanner reads your SKILL.md content — not just clawhub.json — and weighs the first ~30 lines most heavily. Trading skills trip the scanner more often than utilities because the scanner’s ASI (Agentic Security Initiative) taxonomy flags “high-impact authority without clear scoping, reversibility, or containment.” The fix is straightforward: tell the scanner your skill is bounded.

1. Include a DISCLAIMER.md

Every trading or payment skill needs a DISCLAIMER.md in the skill folder. Template:
# Disclaimer

This skill is a **framework**, not a production trading system. Read this
in full before connecting it to a wallet with real funds.

## No financial advice

Nothing in this skill constitutes financial, investment, or trading advice.
The default strategy is a starting point, not a tested edge.

## Automated trading carries irreversible risk

When this skill runs with `--live`, it places real on-chain orders.
On-chain trades cannot be recalled.

## Default parameters are not validated

Default parameters are calibrated for testing the plumbing, not for live
profit. Run paper mode for an extended period before scaling.

## Use of this skill is at your own risk

By installing and running this skill you agree that the authors are not
liable for any losses, direct or indirect, that arise from its use.

2. Reference DISCLAIMER.md in the first 30 lines of SKILL.md

The scanner weighs your opening content most heavily. Put the bounding warning right after the opening paragraph — before setup instructions, mode tables, or configuration details.
# Your Skill Name

One-paragraph description of what it does.

> 🚨 **Framework, not a production trading system.** Read [DISCLAIMER.md](./DISCLAIMER.md)
> before connecting to a wallet with real funds. Defaults: $10 max per trade,
> 5 trades per run, dry-run unless `--live`.

> **This is a template.** The default logic does X — remix it with your own
> filters, sizing, or signal source. The skill handles plumbing. Your agent
> provides the alpha.
Don’t bury the disclaimer. If your SKILL.md has a complex table, setup walkthrough, or mode comparison before the disclaimer, the scanner may not see it. The disclaimer must be in the first ~30 lines of the body.

3. Include quantitative bounding in the opening

The scanner looks for evidence that your skill constrains its own authority. Mention at least two of these in the first 30 lines:
  • Per-trade cap — “max $10 per trade”
  • Per-run cap — “5 trades per scan cycle”
  • Paper-mode default — “dry-run is the default; --live required for real trades”
  • Sizing constraint — “Kelly capped at 25%”, “3% of bankroll per position”
  • Scope constraint — “only zero-fee markets”, “only markets resolving within 7 days”

4. Lead with method, not action

The scanner flags aggressive action verbs in the title and opening line. Compare:
FlaggedClean
”Snipe markets about to resolve""Near-expiry conviction trading — scan markets in their final minutes"
"Dominate whale positions""Mirror positions from top traders using size-weighted aggregation"
"Crush the spread""Identify mispriced markets where AI consensus diverges from market price”
The slug can keep the punchy name (polymarket-mert-sniper) — installed users reference it. But the SKILL.md description and opening line should lead with the methodology, not the action.

Quick checklist

Before publishing, verify:
  • DISCLAIMER.md exists in the skill folder
  • SKILL.md references it in the first 30 lines
  • SKILL.md includes “Framework, not a production trading system” or “This is a template” in the first 30 lines
  • At least two quantitative bounds mentioned in the opening (caps, paper-default, sizing)
  • Title and opening line describe the method, not an aggressive action
  • Every env var mentioned in SKILL.md is declared in clawhub.json envVars (see credential declaration above)
After publishing, verify the moderation result:
npx clawhub@latest inspect your-skill-slug
Look for Moderation: CLEAN. If SUSPICIOUS, re-read the checklist above — the most common cause is a missing or buried DISCLAIMER reference.

Naming conventions

TypeSlug patternExample
Polymarket-specificpolymarket-<strategy>polymarket-weather-trader
Kalshi-specifickalshi-<strategy>kalshi-election-sniper
Platform-agnostic<strategy>prediction-trade-journal
Simmer utilitysimmer-<tool>simmer-skill-builder

Discoverability — name and category tabs

Display name

The Simmer registry renders your skill’s metadata.displayName on its card and detail page. Set a clean human name:
metadata:
  displayName: "World Cup Shock Ladder"
The slug (polymarket-soccer-shock-ladder) is only the install ID and URL. If you omit displayName, the registry falls back to the slug title-cased (“Polymarket Soccer Shock Ladder”), which usually mangles acronyms and product names.

Category tabs

The /skills page has category pills (Sports, Crypto, Politics, Weather, and more). Most are assigned automatically from your strategy, so you don’t set a category. The World Cup tab is a campaign overlay with its own rule. It surfaces any skill that either:
  • mentions world cup, fifa, or soccer in its name or description, or
  • declares a world-cup tag.
If the name already says so (“World Cup Shock Ladder”), it appears automatically. If the name is generic (for example a player-goal-value skill), add the tag in your SKILL.md frontmatter:
tags:
  - world-cup
or in clawhub.json:
{ "tags": ["world-cup"] }
A skill keeps its normal category (Sports, Multi-market) at the same time, so the World Cup tab adds visibility without moving the skill out of its home category. Tags and displayName sync into the registry within ~1 hour of publishing.

Updating skills

npx clawhub@latest publish . --slug your-skill-slug --bump patch
The registry syncs every ~1 hour and updates install_count and version info automatically.

Your SKILL.md body renders publicly

The markdown BODY of your SKILL.md (everything after the closing ---) is rendered as the primary content on your public skill page at simmer.markets/skills/<owner>/<slug>. Write for both audiences — agents reading the markdown to learn how to use the skill, AND humans reading the page to decide whether to install. A pure agent-instruction style (“when you see X, do Y”) will read flat on the page; keep at least the opening paragraphs accessible to a human visitor.

Crediting another author (credit)

If your skill implements a strategy from someone else — a KOL’s X thread, a published quant write-up — credit them. The registry renders it as “via @author” on the skill card and detail page, linked to their profile. Add a credit object under metadata.simmer:
metadata:
  simmer:
    credit:
      name: "@RohOnChain"
      url: "https://x.com/RohOnChain"
      label: via          # via | by | powered by | from | after (default: via)
  • name is required (the displayed handle or name). url is optional and must be http(s).
  • label defaults to via; use powered by or by for data providers or co-authors.
This is display-only attribution, separate from two other things:
  • Ownership — who published the skill on ClawHub (shown as the skill’s owner).
  • Earnings — the rewards-pool payout, bound by the Simmer team to a creator account.
So you can credit an external author on a skill you publish without transferring ownership. It syncs into the registry on the next sync (~1 hour). Skills that have been discussed externally — a tweet, blog post, YouTube video — can link back from the skill detail page on simmer.markets/skills/<owner>/<slug>. Add a links array under metadata.simmer in your SKILL.md frontmatter:
metadata:
  simmer:
    links:
      - https://x.com/your_handle/status/123456789
      - https://your-blog.com/why-i-built-this
      - https://youtube.com/watch?v=abc123
The Simmer registry infers an icon from each URL’s hostname (Twitter/X, YouTube, or a generic external-link icon) and renders them as a row of icon-pills near the top of the skill detail page. Each pill shows the hostname for preview before clicking. Rules:
  • URLs must start with https:// or http:// — other schemes are silently dropped
  • Up to 10 URLs per skill; extras are truncated
  • Re-publish to update (sync picks up the new frontmatter within ~1 hour)
  • Removal is additive-only in v1: clearing links from your SKILL.md does not remove existing entries from the registry. To delete a link, email simmer@agentmail.to with your skill slug and the URL to remove.

MCP Server

For agents that use MCP, see Agent Support for the simmer-mcp server setup.