Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion .github/workflows/deploy-backtest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,39 @@ jobs:
npm run gen:local-constants
npm run gen:state-dump

# A scenario is (regime, seed) and the regime YAML carries no seed, so --seed is required
# (ADR 0017 §1). --blocks shortens the run for CI; the regime's own 360 would take 12 minutes.
- name: Backtest against the dump
run: npm run backtest -- --regime calm-01 --blocks 12
run: npm run backtest -- --regime calm --seed 101 --blocks 12 --seconds 90

# The matrix path is what the competition actually runs, and it is a different code path from
# a single --regime run: it writes matrix.json / standings.json and has to survive a scenario
# that fails without abandoning the rest. Two scenarios is enough to exercise the loop.
- name: Replay a small scenario matrix
run: |
cat > "$RUNNER_TEMP/ci-scenarios.yaml" <<'YAML'
regimes: [calm]
seeds: [101, 202]
YAML
npm run backtest -- --scenarios "$RUNNER_TEMP/ci-scenarios.yaml" --blocks 12 --seconds 90
node -e '
const { readdirSync, readFileSync } = require("node:fs");
const dir = readdirSync("runs").filter((d) => d.startsWith("matrix-")).sort().at(-1);
if (!dir) throw new Error("no matrix directory was produced");
const m = JSON.parse(readFileSync(`runs/${dir}/matrix.json`, "utf8"));
const s = JSON.parse(readFileSync(`runs/${dir}/standings.json`, "utf8"));
const failed = m.scenarios.filter((x) => !x.agents);
if (failed.length) throw new Error(`scenarios produced no result: ${JSON.stringify(failed)}`);
if (m.scenarios.length !== 2) throw new Error(`expected 2 scenarios, got ${m.scenarios.length}`);
if (!s.agents?.length) throw new Error("standings ranked nobody");
// Both metrics must survive into the matrix, since the scoring rule is expected to
// change and matrix.json is what makes a finished run re-scorable (ADR 0017 §4).
for (const sc of m.scenarios)
for (const a of sc.agents)
if (!("netPnlUsdc" in a) || !("alphaUsdc" in a))
throw new Error(`${sc.regime}#${sc.seed} ${a.id} is missing a metric`);
console.log(`matrix ok: ${m.scenarios.length} scenarios, ${s.agents.length} ranked`);
'

# The backtest exits 0 even when the scorer quietly read nothing, which is the failure mode
# this whole job exists to catch, so assert on the run's own output.
Expand Down
42 changes: 25 additions & 17 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,27 @@ deployer/ venue デプロイ(自己完結サブパッケージ。workspace
|------|------|--------|
| `agent.ts`(`decide(obs, ctx)` export) | ルール戦略 | runtime/bot.ts が read→decide→send のループで駆動(`export const config = { intervalMs }` で間隔指定可) |
| `agent.ts`(`run(ctx)` export) | 自走型 | bot.ts はループせず ctx(clients/observe/submit/log)を渡して委譲(例 liquidator) |
| `prompt.md`(frontmatter: name/description 必須) | プロンプト型 | bot.ts が observation を添えて毎判断 LLM に action を出させる(例 my-arb) |

`runtime/`(汎用スクリプト: bot/read/send/llm/prompt/deploy/agentLog)と `lib/`(共有戦略ヘルパ)は予約名。
同梱の全 agent は agent.ts と併置で **prompt.md も持ち、両方の動かし方を提供する**(runtime の既定は
agent.ts 優先 = ADR 0015 §2。ただし **雛形 `config/example.yaml` のロスターは取引 agent を prompt モードで
出荷**=Quick Start 既定は LLM 駆動。要 OLLAMA_API_KEY in .env.local、LLM 判断 ~10s/回なので run 長は
100 blocks/300s 目安。noop はルールのまま。API キー無しでも `model: codex[:<m>]` / `claude-cli[:<m>]` で
**Codex/Claude Code サブスク CLI 実行**が可能 = docs/guide/llm-agents.md)。ロスターの `env` で切り替え:

- `ERIS_AGENT_MODE: "prompt"` — agent.ts があっても prompt.md(毎判断 LLM)で動かす
- `ERIS_PROMPT_REVISE_EVERY: "<N>"` — prompt モードで N 判断サイクルごとに LLM が prompt 本文を
**自己改訂**する(既定 0=off。改訂版は `runs/<id>/agents/<agentId>.prompt.v<K>.md` に版付き保存され
以後のサイクルで使用。`ERIS_PROMPT_REVISE_PERSIST: "1"` で agent ディレクトリの prompt.md にも書き戻し)
| `agent.ts` + `improve.md`(frontmatter: name/description 必須) | **自己改善型**(ADR 0018) | decide を毎ブロック駆動しつつ、LLM が取引経路の**外**で戦略コードを書き換える |

`runtime/`(汎用スクリプト: bot/read/send/llm/improve/deploy/agentLog)と `lib/`(共有戦略ヘルパ)は予約名。

**プロンプト型(毎判断 LLM)は ADR 0018 で廃止**。実測で 1 判断 8〜28 ブロック・行動回数がルール型の
1/64 で競技として成立しなかった(ADR 0017 §5 B1)。`ERIS_AGENT_MODE` / `ERIS_PROMPT_*` は fail-fast する。
`improve.md` は prompt.md の改名ではない(前者は「いつ・何を根拠に・どう直すか」、後者は「この observation で
どう動くか」)。ロスターの `env`:

- `ERIS_AGENT_FROZEN: "1"` — improve.md を無視して戦略を固定。**ADR 0018 §5 が要求する frozen 対照**
(自己改善が効いたかを毎 run 見えるようにする)をディレクトリ複製なしで作る
- `ERIS_LLM_MODEL: "<model>"` — 改訂呼び出しのバックエンド(improve.md の frontmatter が優先)。
API キー無しでも `codex[:<m>]` / `claude-cli[:<m>]` でサブスク CLI 実行可 = docs/guide/llm-agents.md
- `ERIS_IMPROVE_LOG_CALLS: "1"` — 改訂の生のやり取りを `agents/<id>.llm.jsonl` に残す(既定 off)

改訂は `{notes, executorTs}` か `{notes, revertTo: <version>}` を返し、`executorTs: null` は
「今の戦略を維持」。生成コードは **cheatcode 静的検査 → コンパイル → 2 秒の実行上限**を通ってから設置。
**自動 rollback は無い**(閾値に妥当な値が無いため。旧実装は 18 run 中 0 件発火、逆に「少しでも負けたら」
だと全員が負けるレジームで毎回巻き戻る)。戻すかどうかはモデルの判断で、版履歴を渡して `revertTo` で行う。
LLM バックエンドが無くても run は完走し、改訂失敗が記録されて戦略は無改変で走り続ける。
directShim / relay / stdin-stdout プロトコルは廃止済み(ERIS_AGENT_DIRECT_TX は退役)。
プロンプト型の action 形式は sdk の zod スキーマ(`sdk/src/actionSchema.ts`)から `<schema>` を生成し、
validate 失敗はエラー内容を会話に追記して再試行(上限超過は noop = fail-closed)。

## 設定(YAML 単一ソース。ADR 0013)

Expand Down Expand Up @@ -72,7 +77,10 @@ agents:
- `npm run build:contracts` — モックオラクル + PriceFeed を forge build(sim:realtime の前提。`out/` 未生成なら最低 1 回)
- `npm run gen:local-constants` — deployments.json → `sdk/src/constants.local.ts` 生成(同梱 `deployer/` のローカルデプロイ出力を読む)
- `npm run gen:state-dump` — 稼働中の deployer anvil から配布用 state dump + manifest(生成元コミット・deployments 同梱・fingerprint)を `backtest/state/` へ生成(ADR 0016。dump 前に `.local-snapshot` のクリーン断面へ revert し、constants.local.ts も同じ deployments から再生成)
- `npm run backtest -- --regime <name>` — 参加者バックテスト(ADR 0016 Phase 0 = B1 実時間再生)。state dump をロードした専用 anvil(既定 port 8547)で `config/regimes/<name>.yaml`(+seed)を再生する。`--repeat N`(snapshot/revert 反復・run 毎に採点再構成)/ `--agents <roster>`(regime 既定ロスターの差し替え)/ `--protocols` 等の一回上書き。**override は実効 regime YAML に書き出されて agent プロセスにも伝播**(coordinator だけに効かせると agent が観測で死ぬ)。fingerprint 不一致は manifest 同梱 deployments から constants を自動再生成、genesis 不一致は fail-fast
- `npm run backtest -- --regime <name> --seed <N>` — シナリオ 1 本を再生(ADR 0016 Phase 0 = B1 実時間再生)。state dump をロードした専用 anvil(既定 port 8547)で `config/regimes/<name>.yaml` + seed を再生する。**シナリオ = (regime, seed)** で regime YAML は seed を持たないので `--seed` は必須(ADR 0017 §1)。`--agents <roster>`(regime 既定ロスターの差し替え)/ `--protocols`/`--blocks`/`--score-every` 等の一回上書き。**override は実効 regime YAML に書き出されて agent プロセスにも伝播**(coordinator だけに効かせると agent が観測で死ぬ)。fingerprint 不一致は manifest 同梱 deployments から constants を自動再生成、genesis 不一致は fail-fast
- `npm run backtest -- --scenarios config/scenarios/public.yaml` — シナリオ行列を 1 つの anvil 上で全部再生し順位を出す(ADR 0017)。`{regimes, seeds}` の直積で、シナリオ間は snapshot/revert。`runs/matrix-<id>/matrix.json`(シナリオ × agent の生スコア。**netPnlUsdc と alphaUsdc の両方**)と `standings.json`(レジーム内 z-score → レジーム等重み平均)を書く。順位は派生物で、採点方法は将来見直す前提(matrix.json から再計算できる)。`--metric netPnlUsdc|alphaUsdc` / `--repeat N`(較正の診断用。採点は 1 回が既定)
- **公式レジーム**: `calm` / `cex-drift`(OU に drift、kappa 弱化)/ `informed-flow`(相関した方向性フロー)/ `whale`(単発大口の点イベント)/ `lending-incident`(暴落 + victim + 清算)/ `crash`(価格ギャップのみ。流動性引き抜きは issue #52 待ち)。`depeg` は採点方法の見直し待ち。`lst` は競技セット外
- `--score-every N` は採点断面の間引き。成績は初期/最終断面しか使わない(`alphaByAgent = alphaLast − alphaFirst`)ので**スコアは不変**、equity curve が粗くなるだけ
- `npm run typecheck` / `npm run test` — 型チェック / ユニットテスト
- `npm run check:strategy` — 戦略コードの cheatcode 静的検査(入口ゲート)
- `npm run check:boundaries` — workspace 依存方向(example → sdk ← core)の検査
Expand Down Expand Up @@ -180,7 +188,7 @@ OU の base price はそのまま進め、その上に **SEED 由来でランダ
runtime/send.ts が同じファイルに mempool 活動(`kind:"mempool"`: submitted / submit_failed /
rejected)を自己申告で追記する(coordinator が submitted を数えられなくなる穴を塞ぐ。ADR 0006 §5)。
出力先は coordinator が渡す env `ERIS_RUN_DIR` / `ERIS_AGENT_ID` で決まる。run 後の診断はこれを一次情報にする。
prompt 型は `ERIS_PROMPT_LOG_CALLS: "1"`(ロスターの env)で LLM との生の対話(system 全文・送信
自己改善型は `ERIS_IMPROVE_LOG_CALLS: "1"`(ロスターの env)で LLM との生の対話(system 全文・送信
messages・生応答・エラー)を `agents/<agentId>.llm.jsonl` に残せる(opt-in。プロンプト調整の一次情報)。

## spot EC2 で重い run を回す(ローカル逼迫の回避。spot skills)
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ flowchart LR
- **Multi-agent competition** — agents run as fully independent processes, subscribe to blocks at their own pace, and sign and send directly themselves. In-block ordering is determined by anvil `--order fees` (descending priority fee).
- **Controllable fair price** — the coordinator generates a SEED-derived deterministic fair price every block and writes it to the on-chain `PriceFeed` and mock oracles. Aave health factors and GMX mark prices follow it.
- **Market stress & liquidation** — price spikes/crashes can be injected to trigger the Aave liquidation path.
- **LLM-driven autonomous agents** — a single `prompt.md` is the strategy itself. The LLM emits an action on every decision and can even self-revise the prompt (no hand-written trading logic).
- **Self-improving agents** — the strategy trades every block on its own, and an LLM periodically rewrites it in-run from its own track record. The LLM is never in the trade path.
- **Fork-free local deploy mode** — avoids cold-state RPC round trips to the fork backend (fork RPC latency), and multi-asset (WETH/WBTC) works too.
- **Backtesting** — with a distributed state dump plus official regimes (market scenarios), a strategy can be verified over and over under the same environment and the same scoring (`--repeat` to read the distribution).

Expand Down Expand Up @@ -77,13 +77,13 @@ cd ..

### Choose an LLM backend

**The default roster is LLM-driven**: the trading agents run in prompt mode (`prompt.md`, one LLM call per decision), so they need an LLM backend to trade. Pick one — without it the run still completes, but the trading agents fail closed to `noop` and trade nothing:
**The default roster is self-improving**: the trading agents are rule strategies that trade every block on their own, and an LLM periodically rewrites them ([Self-improving agents](docs/guide/llm-agents.md)). A backend is therefore optional — without one the run completes normally, the revisions are recorded as failed, and the strategies keep trading unchanged. Pick one to see the improvement loop actually work:

| backend | setup |
|---|---|
| **Ollama Cloud** (default; model `gpt-oss:120b`) | put `OLLAMA_API_KEY=...` in `.env.local` |
| **Local ollama** (no key) | `ERIS_OLLAMA_BASE_URL=http://127.0.0.1:11434/api` in `.env.local`, and set a locally-pulled model via the roster env `ERIS_LLM_MODEL` |
| **Claude Code / Codex subscription** (no API key; spawns the logged-in CLI) | in `config/local.yaml`, swap each prompt agent's `env:` for the commented variant with `ERIS_LLM_MODEL: "claude-cli:haiku"` (or `"codex"`) |
| **Claude Code / Codex subscription** (no API key; spawns the logged-in CLI) | in `config/local.yaml`, add `ERIS_LLM_MODEL: "claude-cli:haiku"` (or `"codex"`) to the agent's `env:` |

To skip LLMs entirely and run the same strategies rule-based (`agent.ts`), remove the `env:` line from each agent in the roster. Details: [LLM Agents](docs/guide/llm-agents.md).

Expand Down Expand Up @@ -119,8 +119,8 @@ Once you bake a state dump from a deployed anvil, you can **replay official regi

```bash
npm run gen:state-dump # bake once from the running deployer anvil
npm run backtest -- --regime calm-01 --repeat 5 # calm market, 5 times (prints mean alphaUsdc)
npm run backtest -- --regime crash-01 # crash + Aave liquidation scenario
npm run backtest -- --regime calm --seed 101 # one scenario (regime + seed)
npm run backtest -- --scenarios config/scenarios/public.yaml # the whole public set + standings
```

For details, see [Backtesting](docs/guide/backtest.md).
Expand All @@ -138,7 +138,7 @@ For details, see [Backtesting](docs/guide/backtest.md).
| [Backtesting](docs/guide/backtest.md) | Replaying state dump + official regimes, iterating with `--repeat`, sparring, what is and isn't measurable |
| [Run Output and Analysis](docs/guide/run-output.md) | The output files under `runs/<id>/` and how to analyze a run afterwards |
| [Protocols and Actions](docs/guide/protocols-and-actions.md) | Reference: actions per venue, stablecoin accounting, oracle control |
| [LLM-driven Autonomous Agents](docs/guide/llm-agents.md) | prompt.md-type agents (per-decision LLM, self-revision, conversation log) |
| [Self-improving Agents](docs/guide/llm-agents.md) | agent.ts + improve.md (in-run strategy rewriting, sandbox, rollback, frozen control) |

**How the environment works / operations**:

Expand Down
Loading
Loading