> ## Documentation Index
> Fetch the complete documentation index at: https://docs.simmer.markets/llms.txt
> Use this file to discover all available pages before exploring further.

# Building Skills

> How to build and publish your own trading skills to the Simmer registry via ClawHub.

Skills auto-appear in the Simmer registry within \~1 hour of publishing to ClawHub.

## Option 1: Use the Skill Builder (recommended)

Install the Skill Builder and describe your strategy in plain language:

```bash theme={null}
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:

```text theme={null}
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:

| Parameter        | Value                                                                                          |
| ---------------- | ---------------------------------------------------------------------------------------------- |
| Market selection | World Cup match markets; Team A win, Team B win, Draw                                          |
| Signal           | Sum of the three YES midpoints                                                                 |
| Entry gate       | Trade 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 type       | Limit/GTC, because this is price-sensitive                                                     |
| Data dependency  | Optional `POLYNODE_API_KEY`; fallback to Simmer market search/import                           |
| Exit             | Ask the user to confirm hold-to-resolution vs sell-on-normalization if the thread does not say |

Good generated output is narrow and testable:

```text theme={null}
polymarket-worldcup-split-scanner/
  SKILL.md
  DISCLAIMER.md
  clawhub.json
  worldcup_split_scanner.py
  scripts/status.py
```

Before publishing, run a dry-run fixture:

| Prices               | Expected 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 thread                | Deterministic 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

```yaml theme={null}
---
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

```json theme={null}
{
  "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"
  }
}
```

`automaton.managed: true` means users can install the published skill and let the managed runner execute its cron for them. They do not need to keep a laptop or VPS online. Advanced users can still self-host a fork by running the script from cron on their own always-on machine; see [Skills → Where does my agent run?](/skills/overview#where-does-my-agent-run) for the managed vs self-hosted credential split.

<Warning>
  `simmer-sdk` in `requires.pip` is required. This is what causes the skill to appear in the Simmer registry automatically.
</Warning>

<Warning>
  **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:

  | Field          | Meaning                                                              | Use for                                                                |
  | -------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------- |
  | `requires.env` | **Strictly required** — skill fails without these                    | Only the minimum credentials needed for the default code path          |
  | `primaryEnv`   | Names the single main credential                                     | The one key every user will have (usually `SIMMER_API_KEY`)            |
  | `envVars[]`    | Per-variable declaration with `required` boolean + human description | Full 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 var              | Typical status                                                         |
  | -------------------- | ---------------------------------------------------------------------- |
  | `SIMMER_API_KEY`     | Always required — main credential (also set as `primaryEnv`)           |
  | `WALLET_PRIVATE_KEY` | Usually `required: false` — only needed when not using managed wallets |
  | `EVM_PRIVATE_KEY`    | Required if your skill signs EVM transactions (e.g. x402 payments)     |
  | `SOLANA_PRIVATE_KEY` | Required if your skill signs Kalshi/Solana transactions                |
  | `POLYGON_RPC_URL`    | `required: 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.
</Warning>

### Python script patterns

```python theme={null}
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:

```markdown theme={null}
> **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.

### Recommended: check context before trading

The `/context` endpoint provides trading discipline data -- flip-flop detection, slippage estimates, and edge analysis. We strongly recommend checking it before executing trades:

```python theme={null}
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 at most 1,000 matching active markets, out of 20k+. A market can be absent from that window even though it's fully tradeable — "not in `get_markets()`" does **not** mean "not imported."

Always filter for discovery. SDK filters `q=`, `tags=`, `venue=`, and `sort=` are applied server-side *before* the cap, so they narrow the whole catalog first:

```python theme={null}
# 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 one capped discovery window, silently misses markets outside it
markets = client.get_markets(limit=50)
```

In raw API responses, `total` is the size of that capped discovery window, not the full catalog count. If the server hit the ceiling, it returns `truncated: true` and `capped_at_limit: true`. To scan beyond 1,000, page across narrower slices: category tags, venues, keyword families, or time-to-resolution buckets. The REST endpoint also supports `max_hours_to_resolution=` for those time-window slices. Each slice gets its own top 1,000 after filters.

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.

```python theme={null}
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:

```python theme={null}
# 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.

<Warning>
  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`
</Warning>

**`/api/sdk/briefing`** is the canonical cross-venue snapshot. Every agent heartbeat loop should use it:

```python theme={null}
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:

```python theme={null}
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:

```python theme={null}
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.

### Recommended: redeem winning positions

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.

```python theme={null}
# 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.

<Tip>
  `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.
</Tip>

## Recommended primitives

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](/sdk/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.

```python theme={null}
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="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).

```python theme={null}
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.

<Warning>
  **`polymarket_condition_id` can be `None`.** Not every market in Simmer has a mapped Polymarket condition ID — new imports, Kalshi markets, and some edge cases may return `None`. Always guard with `if market.polymarket_condition_id:` (as in the example above).

  For cross-referencing Polymarket markets, prefer `market.polymarket_token_id` (YES CLOB token) and `market.polymarket_no_token_id` (NO CLOB token) — these are populated for any market with a live CLOB and are the reliable keys for wallet-position lookups and order routing. `polymarket_condition_id` is only needed for the `get_top_holders()` call.
</Warning>

### 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](https://gamma-api.polymarket.com/) 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

<Note>
  **Node.js >= 22.12 required** for all `npx clawhub@latest` commands. Run `node --version` to check. The ClawHub CLI uses native `fetch` APIs introduced in Node 22.12; older versions will fail with a runtime error on the publish or install step.
</Note>

```bash theme={null}
# 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
```

**License:** Publishing accepts Simmer's **MIT-0** skill license terms automatically when using `npx clawhub@latest` (CLI 0.15.0+). MIT-0 is a no-attribution variant of MIT — no copyright notice required in distributions. To publish under a different license, include a `LICENSE` file in your skill folder before publishing; ClawHub's moderation scanner will use it instead of the MIT-0 default.

Within **\~6 hours**, the Simmer sync job will:

1. Discover your skill via ClawHub search
2. Verify it has `simmer-sdk` as a `requires.pip` dependency (this is the trigger — skills without it are ignored)
3. Add it to the registry at [simmer.markets/skills](https://simmer.markets/skills?ref=docs\&utm_campaign=docs)

No approval process. No submission form. Subsequent updates (version bumps, metadata changes) sync within \~1 hour once the skill is initially indexed.

<Warning>
  **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.
</Warning>

### 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:

```bash theme={null}
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](https://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:

```bash theme={null}
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.

<Note>
  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.
</Note>

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:

```markdown theme={null}
# 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.

```markdown theme={null}
# 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.
```

<Warning>
  **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.
</Warning>

### 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:

| Flagged                          | Clean                                                                      |
| -------------------------------- | -------------------------------------------------------------------------- |
| "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](#clawhub-json) above)

<Tip>
  After publishing, verify the moderation result:

  ```bash theme={null}
  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.
</Tip>

## Naming conventions

| Type                | Slug pattern            | Example                     |
| ------------------- | ----------------------- | --------------------------- |
| Polymarket-specific | `polymarket-<strategy>` | `polymarket-weather-trader` |
| Kalshi-specific     | `kalshi-<strategy>`     | `kalshi-election-sniper`    |
| Platform-agnostic   | `<strategy>`            | `prediction-trade-journal`  |
| Simmer utility      | `simmer-<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:

```yaml theme={null}
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:

```yaml theme={null}
tags:
  - world-cup
```

or in `clawhub.json`:

```json theme={null}
{ "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

```bash theme={null}
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`:

```yaml theme={null}
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).

## Linking your content (links)

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:

```yaml theme={null}
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](/agent-support) for the `simmer-mcp` server setup.
