やってみた 2026年10月16日

AIエージェントのハーネス ruflo をモデル抜きで試したら、npx初回は66秒・1.9GB、initは252ファイルとホームのCLAUDE.mdを書いた

XECIN TypeScriptAIエージェントMCPClaude Code

GitHubのトレンドを見ていたら、ruvnet/ruflo が6位に入っていました。確認した時点でスターは 75,420。説明には「The original agent harness」とあり、README には「Agent = Model + Harness」という一文があります。モデルが書き、ハーネスが道具・記憶・ループ・制御を与える、という整理です。

私は受託の検収を長く担当してきたので、新しいツールを見ると、機能より先に「入れたら何が書き込まれるのか」が気になります。動いた=品質OK ではありません。init が成功と表示することと、チームのリポジトリに入れてよいことは、別の話なんですよね。

そこで今回は、あえてモデルを外しました。Claude Code も API キーも使わず、ハーネス単体で何が起きるのかを測っています。

試した環境はこんな感じです。

  • 使い捨ての Docker コンテナ(node:22-bookworm、Debian 12.14、x86_64)。メモリ 4GB・CPU 2 に制限し、ホストのフォルダはマウントしない
  • Node v22.23.1 / npm 10.9.8
  • ruflo は README の手順どおり npx ruflo@latest で実行(表示された版は ruflo v3.56.10)。リポジトリの HEAD は 1882006a(2026-10-14)
  • 作業対象は、README を1行だけ置いた空の git リポジトリ

npx の初回は66秒、キャッシュは1.9GB

Claude Code を使わない場合、README が最初に示すのは npx ruflo@latest init wizard です。いきなり init を流す前に、まず版を表示するだけのコマンドを叩きました。これが返ってくるまでに1分以上かかりました。

# 1回目(npx のキャッシュが空の状態。所要は date +%s%N の差で計測)
$ npx -y ruflo@latest --version
npm warn deprecated koa-router@14.0.0: Please use @koa/router instead, starting from v9!
npm warn deprecated node-domexception@1.0.0: Use your platform's native DOMException instead
npm warn deprecated prebuild-install@7.1.3: No longer maintained. ...
ruflo v3.56.10
rc=0 npx_cold_ms=65966

# npx のキャッシュの大きさと、大きい順の中身
npx_cache=1.9G
pkgs_in_npx=371 top_level        # package.json は全部で 776 個
  547.8 MB  onnxruntime-node
  273.4 MB  @agntcy
  221.1 MB  @xenova
  140.4 MB  onnxruntime-web
  103.7 MB  @opentelemetry
   72.4 MB  @anthropic-ai

# 2回目(キャッシュあり)
ruflo v3.56.10
npx_warm_ms=788

npm のメタデータ上、ruflo 本体の展開サイズは約8.2MBです。依存は @claude-flow/cli の1つだけで、その先に通常の依存28個と optional 12個がぶら下がっています。結果として、手元には1.9GBが展開されました。半分近くは ONNX Runtime と Transformers.js、つまりローカルで埋め込みを計算するための部品です。

正直なところ、ここが最初のつまずきでした。初回の66秒、出力は npm の警告と版の表示だけで、進み具合は分かりません。もし対話型の init wizard から始めていたら、固まったと思って Ctrl+C を押していたと思います。

もう一つ気になったのは、後で見る .mcp.json が npx -y ruflo@latest mcp start で MCP サーバを起動する設定になっている点です。版を固定していないので、新しい版が出れば次の起動でそれを取りにいく書き方です。手元と同じ条件なら、新しいメンバーは最初の起動でこの66秒と1.9GBを払うことになります。

init が書いたものを数えた

空のリポジトリで npx -y ruflo@latest init を流しました。7,748ms で終わり、終了コードは0です。最後に要約が表示されます。

$ npx -y ruflo@latest init
Transformers.js loaded: Xenova/all-MiniLM-L6-v2
RuFlo V3 initialized successfully!
| Directories: 12 created |
| Files: 102 created      |
| Skills:      .claude/skills/ (21 skills)     |
| Commands:    .claude/commands/ (16 commands) |
| Agents:      .claude/agents/ (17 agents)     |
[INFO] Hooks: 7 hook types enabled in settings.json
[WARN] No runnable claude binary on PATH: the plugins are enabled in settings, not installed.

# 実際に増えたファイル(.git と元からある README.md を除く)
$ find . -path ./.git -prune -o -type f -print | wc -l
253        # README.md を除くと 252。合計 4.3M
    148 .claude/commands
     44 .claude/helpers
     21 .claude/skills
     18 .claude/agents      # .md は 17
$ git status --porcelain
?? .agents/
?? .claude-flow/
?? .claude/
?? .gitignore
?? .mcp.json
?? .swarm/
?? CLAUDE.md
?? ruvector.db

数え直すと、表示と実際が合わない箇所がいくつかありました。README の Quick Start の表に書かれた数字も並べておきます。

項目init の表示README・—help の記載実際に数えた値
作成ファイル102記載なし252(4.3M)
commands1660+(README)直下 16 項目(13 フォルダ+3 ファイル)、.md は 148
agents1798(README)/既定は約24(—help)6 フォルダに .md が 17
skills2130(README)21
hooks7 hook types記載なし10 種類のイベントに 16 コマンド
MCP ツール表示なし314(README)358(tools/list)

断っておくと、README の 98 agents は --all-agents や --full を付けた場合の数かもしれません。init --help には「既定は約24の substrate agents、--all-agents で約89に戻す」とあります。今回はどちらも試していないので、既定の init で何が起きるかだけを書いています。

それでも、要約の「102」と実際の252は、同じ既定の init での食い違いです。検収でファイル数を受け入れ基準にしていたら、ここで止めていたところです。

hooks は .claude/settings.json に入ります。PreToolUse・PostToolUse・UserPromptSubmit・SessionStart・SessionEnd・Stop・PreCompact・SubagentStart・SubagentStop・Notification の10種類です。どれも node .claude/helpers/hook-handler.cjs などを呼びます。プロジェクトにそのファイルが無いときは $HOME/.claude/helpers/ の同名ファイルに切り替える書き方でした。timeout が 30 のフックと 3000〜15000 のフックが同じファイルに並んでいたのも気になりましたが、単位の扱いまでは確かめていません。

権限の allow には Bash(node .claude/*) と mcp__claude-flow__* が入りました。.claude/ 配下のスクリプトと ruflo の MCP ツールは、確認なしで実行できる設定です。

リポジトリの外にも書いていた

init のあとでホームディレクトリを見て、少し驚きました。プロジェクトの外にも書き込みがあったからです。

# init の前後で変わった /root 配下のファイル(npm のキャッシュを除く)
/root/.config/ruflo/policy-trust/111b1182.../ledger-anchor-head.json
/root/.claude/CLAUDE.md

$ cat /root/.claude/CLAUDE.md        # 322 バイト・4 行
# Ruflo Integration (auto-generated by ruflo init)
When working on multi-file tasks or complex features, use ToolSearch to find and invoke ruflo MCP tools.
Key tools: memory_store, memory_search, hooks_route, swarm_init, agent_spawn.
Check system-reminder tags for [INTELLIGENCE] pattern suggestions before starting work.

# .claude/settings.json に足された設定(init のログより)
  + enabledPlugins["ruflo-mods@ruflo"] = true
  + enabledPlugins["ruflo-swarm@ruflo"] = true
  + enabledPlugins["ruflo-console@ruflo"] = true
  + extraKnownMarketplaces.ruflo
  + env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS = "1"
Each teammate's first trusted interactive Claude Code start clones github.com/ruvnet/ruflo; ...

# このまま git add -A したら入るもの(抜粋)
$ git add -A --dry-run | wc -l
263
add '.claude/settings.json.bak-ruflo-mods-1792152483699'
add '.swarm/agentdb-memory.db'
add '.swarm/memory.db'
add 'ruvector.db'

~/.claude/CLAUDE.md は、そのユーザーが Claude Code で開くすべてのプロジェクトに効くファイルです。そこに「ruflo の MCP ツールを探して使え」という4行が足されます。init --help には、これを止める --no-global が載っていました(今回は試していません)。

settings.json の plugins の設定も、ログ自身がはっきり書いています。この settings.json をコミットすると、チームメンバーがそのプロジェクトで最初に Claude Code を信頼して起動したときに、github.com/ruvnet/ruflo が clone されます。悪いことだとは言いません。ただ、init を流した本人だけでなく、チーム全員の端末の挙動が変わる設定です。レビューで見落とさないようにしたいところです。

.gitignore も init が作ります。ただし直下の .gitignore の対象は .env 系と .claude-flow/ の data・logs・sessions だけでした。.claude-flow/ の中にも .gitignore がありますが、こちらも data・logs・sessions・neural・console とログ類だけです。リポジトリ直下の ruvector.db(1,589,248 バイト)や .swarm/ の SQLite は、どちらの対象にも入っていません。後で試すメモリの書き込みで更新されたのも、この ruvector.db と .swarm/ の DB でした。書き込みのあとに測ると、WAL を含めて約5.3MBあります。git add -A すれば、これがまとめてコミット対象になります。

モデル抜きのハーネスは何をするのか

ここからが本題です。npx -y ruflo@latest mcp start を自作の小さな stdio クライアントから起動し、JSON-RPC で直接叩きました。Claude Code は使っていません。

まず tools/list が返したツールは358個、定義の JSON は271,522バイトでした。接頭辞で数えると、hooks が44、wasm が27、agentdb が20、metaharness が16、memory が15です。MCP クライアントにつなぐと、この定義がまるごとクライアントに渡ります。ToolSearch のような仕組みが無いクライアントだと、このサイズを毎回抱えることになるはずです(これは推測で、今回は確かめていません)。

次に memory_store で4件を記憶させました。英語と日本語で、認証の話とDB移行の話を1件ずつです。API キーは何も設定していませんが、応答には "hasEmbedding": true と "embeddingDimensions": 384 が付いていました。init のときに取得したローカルの埋め込みモデル(Xenova/all-MiniLM-L6-v2、model.onnx は90,387,606バイト)で計算しているようです。1件目だけ1,702msかかり、2件目以降は17〜96msでした。

そのうえで、意味で探す memory_search を英語と日本語で投げました。

記憶させた文(key)クエリ「token-based login flow」クエリ「トークンでログインする仕組み」
Use JWT access tokens stored in httpOnly cookies for login sessions(en-auth)0.520(1位)0.025(3位)
ログイン状態は httpOnly Cookie に入れた JWT で保つ(ja-auth)0.229(2位)0.597(2位)
デプロイ前に Flyway でデータベースのマイグレーションを流す(ja-db)0.002(3位)0.676(1位)
Run database migrations with Flyway before deploying(en-db)0(4位)0.004(4位)

英語のクエリは、ちゃんと認証の文を1位に返しました。日本語のクエリでは、DB移行の文が0.676で1位になり、認証の文(0.597)を上回っています。日本語どうしは、中身に関係なく近いと判定されているように見えます。ただし4件・2クエリだけの観測なので、断定はしません。init --help には --embedding-model で別の ONNX モデルを指定する口があります。日本語で記憶を貯めるなら、まず自分のデータでこの表を作るのが先だと思います。

最後に、ハーネスらしい操作を順に呼びました。

# swarm を作り、エージェントを1体「spawn」し、タスクを作る(モデルも API キーも無し)
>>> swarm_init {"topology":"hierarchical","maxAgents":4} | 8ms | isError=false
    { "success": true, "swarmId": "swarm-1792152616943-xuwike", ..., "persisted": true }
>>> agent_spawn {"agentType":"coder","model":"haiku","task":"write a hello world in Python"} | 191ms
    { "success": true, "agentId": "agent-1792152616949-lmryi0", "status": "registered",
      "note": "Agent registered for coordination. Three execution paths: (1) call agent_execute(agentId, prompt)
      — direct LLM call via Anthropic Messages API (requires ANTHROPIC_API_KEY); (2) Claude Code Task tool
      — spawns a real subagent; (3) claude -p — headless background instance." }
>>> task_create {"type":"feature","description":"add a /health endpoint"} | 10ms
    { "taskId": "task-1792152617152-a149wg", ..., "status": "pending", ..., "assignedTo": [], ... }
>>> swarm_status {} | 2ms
    { "status": "running", "agentCount": 1, "taskCount": 0, ... }

# 呼び出しのあとに更新されたファイル(抜粋)
./.swarm/agentdb-memory.db
./ruvector.db
./.claude-flow/tasks/store.json
./.claude-flow/swarm/swarm-state.json
./.claude-flow/agents/store.json

全部 success です。ただ、agent_spawn が返した状態は registered で、note に書かれているとおり、実際に動かすには API キーか Claude Code が要ります。swarm_status は running と答えますが、ps で見ても、エージェントらしきプロセスは何も起動していませんでした。作ったタスクも assignedTo は空のままです。

ここは使い方次第の部分です。私は「running」を、プロセスが走っている意味ではなく、台帳上の状態として読みました。ハーネスはモデルが来るまで、状態を JSON と SQLite に書き留めて待っている。README の「Agent = Model + Harness」は、文字どおりの意味だったわけです。

試してみての所感

今思えば、モデル抜きで触ったのは正解でした。モデルが動いていると、成果物の出来に目が行きます。その裏でハーネスがリポジトリやホームに何を書いたかは、意外と見落とします。

テストは目的ではなく手段です。今回の目的は、チームに入れてよいかを判断する材料をそろえることでした。私がこのツールを現場に入れるなら、受け入れ基準はこう置きます。

  • init は使い捨てのディレクトリかコンテナで一度流し、増えたファイルを git status で全部見てからにする
  • ホームの ~/.claude/CLAUDE.md を書き換えさせない(--no-global を付けて確かめる)
  • .mcp.json の ruflo@latest は版を固定し、初回の66秒・1.9GBを CI やオンボーディングの手順に書いておく
  • ruvector.db と .swarm/ を .gitignore に足し、記憶の中身をコミットしない
  • 日本語でメモリを使うなら、自分の文章で検索の順位表を作ってから使う

358個のツールと10種類のフックを一度に入れるより、--minimal(今回は試していません)から始めて必要な分だけ足すほうが、私の性分には合っています。一方で、モデル無しでも TUI(ruflo tui --plain、2,381ms)や状態の台帳はきちんと動きました。土台としての作りは丁寧だと感じます。モデルをつないだ本番の動きは、この受け入れ基準を満たしてから、改めて試すつもりです。