
AIエージェント前提のCLI「Swamp」でClaude CodeにAWS棚卸しを作ってもらった
System Initiative社がOSSで公開している swamp というCLIを試しました。AIエージェントと組み合わせて運用ワークフローを作るためのツールで、READMEには "AI Native Automation, built for agents" とあります。
AWS EC2の棚卸しを題材に、Claude Codeから swamp を使ってもらう流れを一通り触ってみました。
Swampとは
- System Initiative製のCLI。Deno製で、手元の環境で完結する
- AIエージェント(Claude Code / Cursor / OpenCode / Codex)と組み合わせて使う前提で設計されている
- モデル・ワークフロー・データを軸とした自動化フレームワーク
- モデル・ワークフロー・拡張の定義はGitで管理。ランタイムデータ(実行結果・ログ・秘密情報)は
.swamp/配下(gitignore済み) .swamp/のバックエンドはローカルFS・NFS・S3から選べる。チームで共有したい場合はS3等に切り替える
READMEの使い方イメージはこんな感じです。
- "Manage my EC2 fleet — inventory every instance across all regions and flag anything without a cost-center tag"
- "Audit our DNS records and compare them against what's actually running"
- "Build a workflow that rotates database credentials and stores them in the vault"
エージェントに自然言語で依頼すると、エージェントが swamp コマンドを組み立ててモデルやワークフローを作り、実行してくれる、という設計です。
コア概念
主要な要素は以下です。Terraform経験があれば右列の対応が参考になります。
| Swamp | 概要 | Terraform |
|---|---|---|
| モデル型(Model Type) | 外部システム(AWSリソースやAPI)の型定義。スキーマとメソッドを持つ | リソース種類(aws_instance) |
| モデル(Definition YAML) | モデル型を具体的な設定値で実体化したインスタンス | resource "aws_instance" "web" { ... } |
| 拡張(Extension) | モデル型を配布するためのパッケージ | プロバイダ |
| データ(Data) | メソッド実行結果。バージョン付きで .swamp/data/ に保存される |
.tfstate |
swamp.club レジストリ |
拡張を公開・配布する中央サーバー | registry.terraform.io |
Terraformとの違いは、Swampのメソッドは create / update / delete に限らず任意の操作を定義できる点です。棚卸しや監査のような「調査系」の操作もモデル化できます。
インストールとリポジトリ初期化
インストールはワンライナーです。デフォルトで ~/.local/bin に入ります。
curl -fsSL https://swamp.club/install.sh | sh
swamp --version
# swamp 20260421.213501.0-sha.0432a31a
適当なディレクトリで swamp repo init すると必要なファイルが一式生成されます。デフォルトはClaude Code向けです。
mkdir swamp-tryout && cd swamp-tryout
swamp repo init
生成されたのは以下です。
.
├── .swamp.yaml # リポジトリメタデータ
├── .swamp/ # ランタイムデータ(SQLite、ログ、監査)
├── .claude/
│ └── skills/ # 14個のswamp-*スキル(合計4700行超)
├── CLAUDE.md # エージェント向けプロジェクト指示
├── .gitignore # .swamp/ と .claude/ を無視
├── models/ # モデル定義用
├── workflows/ # ワークフロー定義用
└── vaults/ # シークレット用
ポイントは .claude/ と CLAUDE.md です。swamp repo init がClaude Code向けのスキル14個とプロジェクト指示を書き込みます。エージェントはこれを読んで swamp の使い方を理解します。
スキルの一覧は以下です。
swamp-getting-started swamp-extension-model
swamp-model swamp-extension-driver
swamp-workflow swamp-extension-datastore
swamp-vault swamp-extension-vault
swamp-data swamp-extension-publish
swamp-repo swamp-issue
swamp-report swamp-troubleshooting
CLAUDE.md には8個のルールが書かれています。印象的だったのはこのあたりです。
- Rule 1: Search before you build 新しい連携を作る前に既存のローカル型とコミュニティ拡張を検索する。shell/commandモデルはワンオフ限定で、CLIラッパーには使うな
- Rule 2: Extend, don't be clever モデルにメソッドが足りなければ拡張する。shellスクリプトで迂回するな
- Rule 6: Prefer fan-out methods over loops 同一モデルに対するメソッド呼び出しは直列化されるので、複数ターゲットを扱うときはfan-outメソッドを使うかモデルを分ける
つまりエージェントが雑にshellで済ませないように縛りをかける設計です。
エージェントにAWS棚卸しをお願いする
別ターミナルでClaude Codeを起動して、指示を出します。
「Swampを初めて使います。READMEで見たAWS EC2の棚卸しをやりたいのですが、まず何をしたらいいですか?」
エージェントはまず CLAUDE.md の指示どおり swamp model search --json を実行します。モデルが0件なので swamp-getting-started スキルが起動し、ステートマシーンベースのオンボーディングに入りました。
start → goals_understood → model_created → method_run → output_inspected → graduated
各ステップで Verify が通るまで次に進めない作りです。
goals_understood ステップでエージェントから「どこまで棚卸しするか」と聞かれたので、EC2 / EBS / AMI / セキュリティグループ の4つに絞りました。この後の拡張選定はこの範囲を前提に進みます。
拡張の選び方: 公式→コミュニティ→自作
公開されている拡張は swamp.club/extensions で一覧できます。CLIからも swamp extension search <keyword> で検索できます。
最初は公式拡張 @swamp/aws/ec2
エージェントが最初に見つけたのは公式拡張でした。
swamp extension pull @swamp/aws/ec2
# → 179個のモデル型が取り込まれる
EC2関連で179型あり、Instance / VPC / Subnet / SecurityGroup / NATGateway / TransitGateway などが揃っています。
同じEC2でも「管理型」と「調査型」で別物
@swamp/aws/ec2/instance のソースを見たところ、メソッドは create / read / update / delete / sync でした。
// Auto-generated extension model for @swamp/aws/ec2/instance
// Do not edit manually. Re-generate with: deno task generate:aws
// ...
methods: {
create: { /* ... */ },
read: { /* ... */ },
update: { /* ... */ },
delete: { /* ... */ },
sync: { /* ... */ },
},
1モデル=1リソースで、CRUD操作を提供する作りです。実体はAWS Cloud Control APIを叩いています。
import {
CloudControlClient,
CreateResourceCommand,
DeleteResourceCommand,
GetResourceCommand,
UpdateResourceCommand,
} from "@aws-sdk/client-cloudcontrol";
CloudFormationスタックを作るわけではありません。CloudFormationのリソース型スキーマをそのまま借りて、Cloud Control APIで個別リソースをCRUDしています。
このつくりは「リソースを宣言的に管理する」用途には合うのですが、棚卸し(未知のリソースを全件リストアップする)には向きません。インスタンスIDが事前に分かっていないと read できず、100台あればモデル定義も100個必要になるためです。
この「リソース管理型」に対して、棚卸し向けのモデル設計は「リソース調査型」と呼べるもので、作りが違います。
| リソース管理型 | リソース調査型 | |
|---|---|---|
| 目的 | リソースを作る・更新する・消す | リソースを調べる |
| 1モデル | 1リソース | アカウント/リージョン全体 |
| メソッド | create / read / update / delete / sync | list_* / describe_* |
| 実体 | AWS Cloud Control API、Google Cloud REST API | AWS SDK の Describe API など |
| 例 | @swamp/aws/ec2/instance、@swamp/gcp/compute/disks |
@webframp/aws/inventory |
| 適する用途 | 構成管理、IaC | 監査、棚卸し、コスト可視化 |
Terraformの概念に例えると、リソース管理型は resource ブロック、リソース調査型は data ブロックに近い役割です。
Google Cloud用の @swamp/gcp/* も同じくリソース管理型でした。Google Cloud は Cloud Control API 相当のものがないため、Google APIs の Discovery Document から生成して Google Cloud REST API を直接叩く実装になっています。
コミュニティ拡張 @webframp/aws/inventory に切り替え
棚卸し向けの拡張を探したら、コミュニティ拡張の @webframp/aws/inventory が見つかりました。中身はリソース調査型で、list_ec2 / list_ebs / list_rds / list_dynamodb / list_lambda / list_s3 が並んでいます。
swamp extension pull @webframp/aws/inventory
ただしAMIとセキュリティグループはこの拡張にはありません。
足りない分はカスタム拡張を自作
AMI と SG 用のカスタム拡張も、Claude Codeに書いてもらいました。swamp-extension-model スキルにテンプレートや実装上の規約が書かれているので、エージェントはそれを読み、@webframp/aws/inventory のソースも参考にしながら実装していました。こちらは方針だけ伝えて、実装とvalidateは任せています。
中身はTypeScriptで1ファイルです。
import { z } from "npm:zod@4.3.6";
import {
DescribeImagesCommand,
DescribeSecurityGroupsCommand,
EC2Client,
} from "npm:@aws-sdk/client-ec2@3.1010.0";
// ...スキーマ定義は省略...
export const model = {
type: "@sato/aws-inventory-extra",
version: "2026.04.22.1",
globalArguments: GlobalArgsSchema,
resources: {
inventory: {
schema: InventoryResultSchema,
lifetime: "1h" as const,
},
},
methods: {
list_ami: {
description: "List AMIs owned by this account",
arguments: z.object({}),
execute: async (_args, context) => {
const client = new EC2Client({ region: context.globalArgs.region });
// DescribeImages(Owners: ["self"]) をページングしながら呼ぶ
// ...
const handle = await context.writeResource(
"inventory",
`ami-${context.globalArgs.region}`,
{ region, resourceType: "ami", resources: amis, count, fetchedAt },
);
return { dataHandles: [handle] };
},
},
list_sg: {
// DescribeSecurityGroups を同様に
},
},
};
出力スキーマとデータ名の規約は @webframp/aws/inventory に揃えてもらいました。あとでワークフローやレポートから同じ形で扱えるようにするためです。
棚卸しを動かす
モデルを作って、validate して、メソッドを実行します。
swamp model create aws-inventory \
--type @webframp/aws/inventory \
-a region=ap-northeast-1
swamp model validate aws-inventory
swamp model method run aws-inventory list_ec2
検証用AWSアカウントでの結果はこうなりました。
| 対象 | 件数 | モデル / メソッド | データ名 |
|---|---|---|---|
| EC2 (running) | 0 | aws-inventory / list_ec2 |
ec2-ap-northeast-1 v1 |
| EBS | 0 | aws-inventory / list_ebs |
ebs-ap-northeast-1 v1 |
| AMI (self-owned) | 0 | aws-inventory-extra / list_ami |
ami-ap-northeast-1 v1 |
| SG | 3 | aws-inventory-extra / list_sg |
sg-ap-northeast-1 v1 |
データは .swamp/data/ 配下にバージョン付きで残ります。
.swamp/data/@webframp/aws/inventory/{modelId}/ec2-ap-northeast-1/
├── 1/
│ ├── raw # JSONの生データ
│ └── metadata.yaml # タグ、チェックサム、lifetime
├── 2/
└── latest # 最新版へのポインタ
同じメソッドを2回実行すると v2 が、3回目で v3 が増えていきます。ワークフローやレポートからはCEL式で参照できます。
CEL(Common Expression Language)はGoogle発祥のシンプルな式言語で、YAMLやJSONに動的な値を埋め込みたいときに使います。Google CloudのIAM ConditionsやKubernetesのCRDバリデーションでも使われています。SwampではモデルやVaultの値を定義ファイルから参照するのに使います。
data.latest("aws-inventory", "ec2-ap-northeast-1").attributes.count
ワークフロー化
4つのメソッドをまとめて実行できるようにワークフローを作りました。
ここで効いてくるのが CLAUDE.md の Rule 6 です。同一モデルに対するメソッド呼び出しはロック競合を避けるため直列化されるので、以下の構造にする必要があります。
- 同一モデル内のメソッドは直列
- 別モデル間は並列
エージェントがRule 6に従って自発的にワークフローを構築してくれました。
jobs:
- name: inventory
steps:
- name: list_ec2
task: { type: model_method, modelIdOrName: aws-inventory, methodName: list_ec2 }
dependsOn: []
- name: list_ebs
task: { type: model_method, modelIdOrName: aws-inventory, methodName: list_ebs }
dependsOn: [{ step: list_ec2, condition: { type: succeeded } }]
- name: list_ami
task: { type: model_method, modelIdOrName: aws-inventory-extra, methodName: list_ami }
dependsOn: []
- name: list_sg
task: { type: model_method, modelIdOrName: aws-inventory-extra, methodName: list_sg }
dependsOn: [{ step: list_ami, condition: { type: succeeded } }]
実行すると list_ec2 と list_ami が別モデルなので同時スタートし、各々の後続がシリアルに流れます。
inventory job (709ms total)
├─ list_ec2 (450ms) ──► list_ebs (258ms) [aws-inventory model, sequential]
└─ list_ami (446ms) ──► list_sg (259ms) [aws-inventory-extra model, sequential]
以降は swamp workflow run aws-inventory の一発で全件棚卸しできます。
レポート化
最後に、棚卸し結果をMarkdownサマリにまとめるレポート拡張を書きました。スコープは workflow で、ワークフローが成功するたびに自動で生成される作りです。
export const report = {
name: "@sato/aws-inventory-summary",
scope: "workflow" as const,
labels: ["inventory", "aws"],
execute: async (context) => {
// context.stepExecutions を走査して、各ステップが書き出した
// データ(ec2/ebs/ami/sg)を dataRepository から取得し、
// Markdownのサマリを組み立てる
// ...
return { markdown: lines.join("\n"), json: { /* ... */ } };
},
};
ワークフローYAMLに reports.require を追加すると、ワークフロー実行後に自動でレポートが動きます。
reports:
require:
- "@sato/aws-inventory-summary"
生成されたMarkdownはこんな感じです。
# AWS Inventory Summary
- **Workflow**: aws-inventory (succeeded)
- **Region**: ap-northeast-1
- **Fetched at**: 2026-04-22T07:53:07.487Z
## Counts
| Resource | Count |
|---|---:|
| EC2 (running) | 0 |
| EBS volumes | 0 |
| AMI (self-owned) | 0 |
| Security Groups | 3 |
## Security Groups
| groupId | groupName | vpcId | ingress | egress |
|---|---|---|---:|---:|
| sg-xxx... | ec2-rds-1 | vpc-xxx... | 0 | 1 |
| sg-xxx... | rds-ec2-1 | vpc-xxx... | 1 | 0 |
| sg-xxx... | default | vpc-xxx... | 1 | 1 |
レポート実装中に1回エラーで失敗したのですが、エージェントがSwamp本体のソースまで遡って原因を特定し、そのままワークアラウンドも入れてくれました。WorkflowReportContext.stepExecutions[].modelType は型定義上は文字列ですが、dataRepository.getContent() は ModelType バリューオブジェクト(.toDirectoryPath() と .normalized を使う)を期待していて、そこでズレていたという話です。value-object相当の構造体を渡すワークアラウンドで回避していました。本来は swamp 本体側で揃えるか、getContent を文字列でも受け付ける形にするのが筋で、swamp-issue スキルを使えばそのままissueを投げる導線もあります。
触ってみて
使ってみて感じたのは、Swampは人間向けのCLI使い勝手よりも、エージェントが使うことを優先して設計されているように見える ことです。
swamp repo initを打つと、CLIだけでなくClaude Code向けのスキルとCLAUDE.mdが配置される- エージェントは
swamp-getting-startedからオンボーディングされ、Search before you buildの原則で挙動が縛られる - ワークフローの並列/直列配置のような実装上の勘どころが
CLAUDE.mdに書かれていて、エージェントが自発的に守る swamp helpはデフォルトでJSONを返す。人間向けのswamp --helpとは別に、エージェントが読みやすい形式が標準で用意されている
実際に swamp help を実行した結果は以下です。
{
"version": "20260421.213501.0-sha.0432a31a",
"root": {
"name": "swamp",
"description": "AI Native Automation CLI",
"arguments": [],
"options": [
{
"flags": "--json",
"description": "Output in JSON format (non-interactive)",
"required": false,
"collect": false
},
...
],
"subcommands": [ ... ]
}
}
arguments / options / subcommands が構造化されているため、エージェントが jq でサブコマンド一覧を取るのも .root.subcommands[].name で一発です。特定の値を抽出しやすい設計になっています。
触る前は「CLIそのものはシンプルで、AI連携がおまけ」くらいの想像でしたが、逆でした。CLIのコマンド体系はシンプルな作りで、むしろ4700行を超えるスキル群とCLAUDE.mdのほうが内容の中心です。
成果物(extensions/、models/、workflows/)はYAMLとTypeScriptのファイルとして残るので、エージェントが作ったものを人間がGitでレビューする流れが成り立ちます。.swamp/ 配下のランタイムデータは .gitignore で除外されるので、Git管理するのは定義ファイルだけです。











