CLAUDE.md · git:20260730.96244b6 · 2026-07-30 · sha256 e62f635359efd019

CLAUDE.md git:20260730.96244b6A

Immutable. This exact content is served forever at /api/v1/blob/e62f635359efd019.

# Senpi Skills — Repo Conventions for AI Editors

This repo is almost entirely AI-generated. The rules below exist because they have been forgotten or stripped during past rewrites. Preserve them on every edit.

---

## ▶▶ Architecture — strategies are PACKAGES, not skills

A trading strategy is a **deployable package, not an agent skill**. All packages live under
`strategies/`:

```
strategies/<id>/                   # a strategy package (e.g. strategies/spider/)
  strategy.yaml                    # THIN deploy manifest & single source of truth for
                                   #   id, version, catalog facets, instances[] — tunables do
                                   #   NOT live here (no `params`; that design is retired)
  <instance>/                      # one dir per instance (`main`, or e.g. `swing`/`scalp`)
    runtime.yaml                   # the self-contained runtime spec for this instance —
                                   #   scanners+inputs (the tunables), actions, exit (DSL), risk
    scanners/
      scan.py                      # exports scan(inputs, ctx) -> list[dict]; read-only, pure,
                                   #   single-pass; the runtime spawns + supervises it
      scoring.py                   # optional pure math (no I/O) so the edge unit-tests
```

- A strategy package has **no `SKILL.md`** and no attribution file. `strategy.yaml` is the single
  source of truth for `id`/`version`.
- **Skills** are the agent's lifecycle capability: **`senpi-strategy-discover`** (find/recommend),
  **`senpi-strategy-author`** (build/edit), **`senpi-strategy-ops`** (install/monitor/close).
  **`senpi-trading-runtime`** is the infra-contract skill — how `@senpi-ai/runtime` behaves
  (`scan(inputs, ctx)`, runtime.yaml schema, DSL engine, `openclaw senpi …` CLI). The remaining
  skills (portfolio, improve-trades, market-pulse, smart-money, trader-research, account-status,
  deposit-withdraw-transfer, why) are analysis/guidance utilities.
- **Install/teardown is `senpi-strategy-ops`**, always: `deploy.py create <id> --budget <usd>`
  (one named wallet per instance via `strategy_create_custom_strategy`, budget split by
  `funding_share`, min $100/wallet, resumable) → `deploy.py runtime <id>` (renders each
  runtime.yaml onto its wallet, `openclaw senpi runtime create`) → optional `verify`. Teardown is
  `close.py <id>` (or `--all`) — **never a raw `strategy_close`** (it strands the runtime).
  Attribution is automatic: `deploy.py` passes **`skillName`/`skillVersion` from `strategy.yaml`
  `id`/`version`** on every wallet-creation call.
- **`strategies/catalog.json` is GENERATED** from `strategies/*/strategy.yaml` via
  `senpi-trading-runtime/scripts/gen_catalog.py` — never hand-edit it. It is written to **two
  places**: repo `strategies/catalog.json` (source of truth) AND
  `senpi-strategy-discover/catalog.json` (bundled so the catalog travels with the discover skill
  when installed standalone). Keep both in sync by re-running `gen_catalog.py`.
- **Validate** a package with `senpi-strategy-author/scripts/validate_strategy.py <dir>` and
  `senpi-strategy-ops/scripts/validate_universe.py <dir>` (every hardcoded ticker must be a live
  HL instrument — a dead name silently no-trades; `deploy.py create` runs this as a preflight and
  refuses to fund a bad universe).

---

## ▶ User wants help with a "strategy"? Classify the intent FIRST

The word "strategy" is overloaded in Senpi and the wrong path is the most common silent failure:

- **Operational** — "Buy me HYPE 10x", "Open a short on BTC", "Copy this trader". The user named
  the position or trader. Execute via MCP `strategy_create_custom_strategy` (multi-asset
  positions) or `strategy_create` (copy-trader). No recommendation, no package.
- **Strategic** — "Help me pick a strategy", "What should I trade?", "Recommend a strategy" →
  **`senpi-strategy-discover`** (the analyst-style picker over `catalog.json`; deploy handoff to
  ops). "Build a strategy", "I have a trading idea", or anything needing a DSL exit →
  **`senpi-strategy-author`** (template-first, then interview). A NAMED strategy to
  install/monitor/close ("deploy spider") → **`senpi-strategy-ops`**.
- **Ambiguous** — ask the user before acting. A single disambiguation question costs nothing.

**Never default to `strategy_create_custom_strategy` for an ambiguous "what should I trade?"**
That tool is for specific positions the user named. And **never stand up a named / DSL-protected /
persistent strategy via raw MCP calls** — a raw call can't carry a DSL and never registers with
the runtime (the confirmed Decoupling failure). If protection is anywhere in the intent, the path
is author → ops.

---

## ▶ Writing or editing a trading strategy? Start here

Do **not** improvise a strategy from scratch. **`senpi-strategy-author/SKILL.md`** owns the flow
(template-first offer → the 7-decision interview → staged build), and its
**`references/creating-a-strategy.md`** is the self-contained build guide — archetype table with a
**Clone from** column of real packages under `strategies/`, the `scan(inputs, ctx)` skeleton, a
complete `runtime.yaml`, DSL presets, and the gotchas.

Deep references (edge cases; the guide links them):

- **`senpi-trading-runtime/references/runtime-yaml.md`** — the authoritative `runtime.yaml`
  schema. If any other doc disagrees, **the runtime wins**; ultimate source of truth is the
  runtime repo itself (`~/workspace/senpi/senpi-trading-runtime`).
- **`senpi-trading-runtime/references/scan-contract.md`** — `scan(inputs, ctx)` in depth: the
  `ctx` surface, the signal shape, `signal_data_schema`.
- **`senpi-trading-runtime/references/runtime-cli.md`** + **`runtime-concepts.md`** +
  **`dsl-protection-check.md`** — the `openclaw senpi …` CLI, the engine mental model, and the
  DSL-coverage verdict procedure.
- **`senpi-strategy-author/references/dsl-presets.yaml`** — copy a preset, change ≤1 field.

Hard-won invariants (each failed silently in production once):

- **Per-signal sizing is `marginPct` (percent of withdrawable, (0,100]) + `leverage` — there is
  no `marginUsd`.** An unknown top-level signal key is dropped with only a stderr warn and sizing
  falls back to config.
- **Never hardcode a ticker you didn't verify** against `market_list_instruments`
  (`validate_universe.py`). `xyz:XYZ100`, not `xyz:NASDAQ`; quoted-in-the-leaderboard-feed ≠
  tradeable.
- **Copy identifiers from their source, never from memory** — a plausible field name, enum, or
  unit compiles fine, ticks clean, and trades nothing.
- **Coin symbols on every Senpi tool call: plain for main-dex crypto, dex-prefixed for HIP-3
  assets — never mix the two up.** This applies to any tool taking a coin/asset param — market
  data, trading limits, position details, ratchet stops, and all money-moving tools (where a
  wrong symbol costs the most). Main-dex crypto stays bare (`BTC`, not `xyz:BTC`); HIP-3
  builder-dex assets carry their dex prefix — today that's the XYZ dex (equities, metals,
  indices, energy): `xyz:BRENTOIL`, not `BRENTOIL`. When unsure which side a symbol falls on,
  check `market_list_instruments`. Unknown coins are rejected as `INVALID_ARGUMENT` — never
  retry them. In `discovery_*` output, `coinDisplayName` (`"NVDA"`) is display-only — **always
  copy the `coin` field into tool calls**, it holds the tradeable form.

---

## ▶ Editing skill text? Rules + snippets, not narration

Skill hot paths (SKILL.md, build guides like `creating-a-strategy.md`) carry **rules and runnable
snippets**; rationale lives in reference files and PR/commit history. Keep only the one
justification clause that stops a future editor from "correcting" the rule (e.g. "`float` of a
number is a no-op" is what keeps `_f()` from being stripped as redundant). A paragraph explaining
*why* at length: move the why out, leave the rule.

---

## Skill Attribution

Strategies are attributed via the MCP wallet-creation call's **`skillName`/`skillVersion`**,
valued from the package's `strategy.yaml` `id`/`version`. `senpi-strategy-ops/scripts/deploy.py`
does this automatically — never deploy a package another way, and never strip those params from
example `strategy_create*` calls in docs. Utility/guide skills (portfolio, discover, author, ops,
etc.) do not create strategy wallets themselves and carry no attribution files.

**Why this matters:** strategies created in the wild are tracked back to the originating package
for performance attribution, fleet analytics, and debugging. A strategy created without these
fields is effectively orphaned — we can see the trades but not which thesis produced them.

---

## Runtime npm package — `@senpi-ai/runtime` (NOT `@senpi/runtime`) on `main`

**Any skill committed to `main` that references the runtime plugin MUST use `@senpi-ai/runtime`.**
That's the production npm package — what end users install on their Railway / OpenClaw hosts.
Source of truth: the runtime repo (`~/workspace/senpi/senpi-trading-runtime/package.json`, name
`@senpi-ai/runtime`).

`@senpi/runtime` (no `-ai`) is a **separately-published internal package for runtime-side dev
testing on Railway boxes**. It only ships `-dev.*` pre-releases — there is no stable release.
Feature branches doing runtime-plugin validation may legitimately pin it; **anything on `main`
must not**. Operators running `npm install @senpi/runtime` from a skill on `main` hit a 404 — the
user's box stays broken until someone tells them which package they actually wanted.

This mistake recurs. If you see `@senpi/` (no `-ai`) in any doc, `runtime.yaml` comment, scanner
docstring, or error-message string on `main`, treat it as a bug and fix it. Grep every diff that
touches `@senpi/` — if there's no `-ai` after `@senpi`, it's the wrong package for `main`.
(`senpi-strategy-author/scripts/validate_strategy.py` also flags any bare `@senpi/runtime` in a
package.)

### Before you write `@senpi/runtime` (no `-ai`), ask the user — on every branch

If you (the AI) are about to commit `@senpi/runtime` anywhere on **any** branch — scanner, doc,
`runtime.yaml`, comment, anywhere — **stop and ask first**. The rule applies regardless of which
branch you're on, because the AI can't reliably tell whether the current branch will land on
`main` later. Use plain language:

> "I'm about to write `@senpi/runtime` (no `-ai`). Before I do, please confirm which package you mean.
>
> - **`@senpi-ai/runtime`** — the package your end users install on their Railway / OpenClaw hosts. Use this for anything that may land on `main`.
>   - **If you pick this when you actually wanted the internal one**: nothing breaks. The file just won't reference your in-flight test build.
>
> - **`@senpi/runtime`** — internal package; we use it to test runtime-plugin changes on Railway boxes ourselves. Only correct on a feature branch validating a runtime build, never on anything users install.
>   - **If you pick this when users will install this skill**: their `npm install @senpi/runtime` returns **404**. Their OpenClaw host won't boot. Their trading agent goes offline until someone tells them the right package name.
>
> Default if you're not sure: **`@senpi-ai/runtime`**.
>
> Which one do you want?"

The consequences are asymmetric. The wrong `@senpi-ai/runtime` pick is recoverable on a single
feature branch. The wrong `@senpi/runtime` pick on `main` blocks every user from installing the
skill. When in doubt, default to **`@senpi-ai/runtime`**.