ルールエージェントの設定層¶
作成日: 2026-07-05
このドキュメントは、src/pca/rule_agents/
の再設計 (2026-07-05-rule-agents-redesign.md) のうち
side-car YAML の読み込み層 を説明する。対応実装:
src/pca/rule_agents/config.pysrc/pca/rule_agents/ported.pyの temp dir コピー拡張
関連ドキュメント:
- rule-agent-tuning-flow.md —
PCA_PARAMS_OVERRIDE_DIR環境変数が tuning から subprocess まで貫通する詳細フロー - rule-agent-tuning.md — Tuning 全体設計
- rule-agent-tuning-algorithm.md — TPE アルゴリズム詳細
目的¶
各 agent ディレクトリに置く params.yaml (数値パラメータ) と strategy.yaml
(立ち回り定義) を、agent の main.py から 決定論的にロード できるようにする。tuning
runner はこれらの YAML を書き換えるだけで main.py には触らず済む。
要件:
- 凍結 agent との後方互換: side-car YAML がない agent はそのまま動く
- PortedRuleAgent の temp dir 方式との整合: main.py は temp dir 内で実行されるため、YAML も同じ場所にコピーされる必要がある
- タイプヒント優先の API: main.py が
params.get("supporter_tier.critical", 22000)のように default 付きで安全に参照 できる - 深い依存を持たない: すでに依存している
pyyamlだけを使う
データフロー¶
flowchart LR
Y1["src/.../<agent>/params.yaml"]:::src
Y2["src/.../<agent>/strategy.yaml"]:::src
M["src/.../<agent>/main.py"]:::src
subgraph PORT["PortedRuleAgent._load_module_for_deck"]
T["temp dir (tmpXXXX/)"]:::tmp
end
Y1 -.copy.-> T
Y2 -.copy.-> T
M -.copy.-> T
T --> IMP["importlib.exec_module(main.py)"]:::run
IMP --> LP["load_params_alongside(__file__)"]:::api
LP --> CFG["Config"]:::val
classDef src fill:#e3f2fd,stroke:#1976d2,color:#000
classDef tmp fill:#fff3e0,stroke:#f57c00,color:#000
classDef run fill:#f3e5f5,stroke:#7b1fa2,color:#000
classDef api fill:#e8f5e9,stroke:#388e3c,color:#000
classDef val fill:#f1f8e9,stroke:#689f38,color:#000
- 配置: agent の物理ディレクトリに
params.yaml/strategy.yamlを置く - コピー:
PortedRuleAgent._load_module_for_deckが temp dir に main.py と同時に YAML をコピー (ported.py:74-81) - ロード: main.py が起動時に
load_params_alongside(__file__)を呼ぶと、temp dir のparams.yamlを Config オブジェクトとして返す - 参照: main.py 内で
PARAMS.supporter_tier.criticalまたはPARAMS.get("supporter_tier.critical", default=22000)で値を取り出す
公開API¶
Config 型¶
Immutable な dot-notation 対応 dict wrapper。
from pca.rule_agents.config import Config
cfg = Config.from_mapping({"a": {"b": 42}})
cfg.a.b # → 42
cfg.get("a.b") # → 42
cfg.get("a.missing", 100) # → 100 (default)
cfg.get("a.missing") # → raises ConfigError
"a" in cfg # → True
cfg.as_dict() # → {"a": {"b": 42}} (deep copy)
制約: yaml のキー名が Config のメソッド名 (items, keys, get,
as_dict) と衝突するとドットアクセスでメソッドが返る。この場合は .get("items") を使う。
ローダー関数¶
| 関数 | 引数 | 動作 |
|---|---|---|
load_yaml(path) |
Path | YAML を読み Config を返す。ファイル無し → 空 Config。トップレベルが mapping でないと ValueError |
load_params(agent_dir) |
Path | <agent_dir>/params.yaml を読む |
load_strategy(agent_dir) |
Path | <agent_dir>/strategy.yaml を読む |
load_params_alongside(__file__) |
Path or str | 呼び出し元ファイルの隣の params.yaml を読む (main.py 用) |
load_strategy_alongside(__file__) |
Path or str | 同上、strategy.yaml |
main.py での典型的な使い方¶
# src/pca/rule_agents/agents/<agent_id>/main.py
from pca.rule_agents.config import load_params_alongside, load_strategy_alongside
PARAMS = load_params_alongside(__file__)
STRATEGY = load_strategy_alongside(__file__)
# 数値取得 (default 付き — YAML 差分に強い)
SCORE_AKAMATSU_CRITICAL = PARAMS.get("supporter_tier.critical", 22000)
SCORE_BOSS_LETHAL = PARAMS.get("supporter_tier.lethal", 50000)
OGERPON_ACTIVE_PRIORITY = PARAMS.get("setup.active_priority.energy_engine", 100000)
# threat map 参照
def opp_max_damage_for(matchup: str) -> int:
return PARAMS.get(f"opp_max_damage.{matchup}",
PARAMS.get("opp_max_damage.generic", 220))
設計判断¶
なぜ Pydantic を使わないか¶
- スキーマがまだ流動: agent ごとに params のキーが増減する。schema を強制すると変更コストが高い
- 依存追加を避ける:
pyyamlだけで完結できる - エラー時の挙動: 未定義キーは default にフォールバックしたい (Pydantic は validation error にする)
将来 schema を安定させる場合は Pydantic Model を optional に導入可能。
なぜ dot-notation を提供するか¶
- yaml は階層構造。
cfg["supporter_tier"]["critical"]よりcfg.supporter_tier.criticalの方が読みやすい - typo は AttributeError で早期発見 (
cfg.suporter_tierは失敗する)
なぜ load_*_alongside(__file__) を用意するか¶
- PortedRuleAgent の temp dir 方式で main.py の
__file__は temp dir を指す - そのため相対パス解決を helper に閉じ込めることで、main.py 側は 1 行で済む
なぜ空ファイルを空 Config として返すか¶
- 凍結 agent が side-car YAML を持たない case をサポート
- 「YAML があるかどうか」で分岐せず、
PARAMS.get(..., default)パターンで統一できる
テスト方針¶
tests/rule_agents/test_config.py で以下を担保:
| テスト | 何を確認 |
|---|---|
ConfigDotAccessTest |
dot-notation で値取り出し / 未定義は AttributeError / private prefix ガード |
ConfigGetTest |
ドット path / default 付き / default なしで missing は ConfigError |
ConfigBoolTest |
空 Config は falsy |
ConfigAsDictTest |
as_dict の deep copy 保証 |
LoadYamlTest |
ファイル無し / 空 / トップレベル scalar / params と strategy の読み込み |
LoadAlongsideTest |
__file__ からの相対解決 |
RagingBoltIntegrationTest |
pilot agent の実 YAML が読める、主要キーの存在 |
既知の制約¶
- キー名衝突: YAML キーが Config メソッド名 (
items,keys,get,as_dict,data) と衝突するとメソッドが優先される。.get(name)を使うワークアラウンド。 - キャッシュなし:
load_*は毎回ファイルを読む。1 ゲーム 1 回程度なので現状問題ないが、tuning で数万回呼ぶ場合はキャッシュ層を足す想定。 - 無効な YAML:
yaml.safe_loadが raises した例外は素通しする (呼び出し側でハンドリング) - 相対パスの制限:
load_*_alongsideは resolve() して絶対化するため、symlink 差分に敏感
今後の拡張¶
- params.yaml の validate command:
python -m pca.rule_agents.config validate <agent_id>で必須キーを check - override 機構:
params.override.yamlを優先し、tuning 中の実験値を非破壊で試せるようにする - strategy.yaml DSL parser: 現状 strategy.yaml の
when: "turn <= 2 and ..."は Config としてロードするだけ。実際に評価する DSL は別モジュール (2026-07-05-rule-agents-redesign.md#3-層アーキテクチャの提案 の Turn planner) で実装予定
変更履歴¶
- 2026-07-05: 初版。Config 型 +
load_yaml/load_params/load_strategy/*_alongside実装。PortedRuleAgentから temp dir に yaml をコピー。