0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ツール呼び出しの55.5%はサブエージェントが出していた。画面に見えているのは半分以下だった

0
Posted at

30日ぶんのセッション記録を、本体とサブエージェントに分けて数えた。

記録 5,958 本 = 本体 2,948 / サブエージェント 3,010 = 50.5%
ツール呼び出し合計 188,040 / 本体 83,604 / サブ 104,436 = 55.5%

記録の本数で 50.5%、ツール呼び出しの回数で 55.5% がサブエージェント側。

画面に出ているのは本体のセッションだけだ。サブエージェントの記録は subagents/agent-*.jsonl に別途書かれ、こちらが見るのは最後に返ってきた報告文だけになる。実行された作業の過半数は、見ないまま終わっている。

本体セッションは、驚くほど短い

本体セッション:
  レコード数            中央値     17.0 / p90     65.0 / 最大   12677.0
  ツール呼び出し          中央値      3.0 / p90     25.0 / 最大    4329.0
  継続時間             中央値      0.6分 / p90      6.4 / 最大   12339.3
  ツールを一度も呼ばなかった 1,013 = 34.4%
  cwd が複数 346 = 11.7% / 最大 18

中央値は 17レコード / ツール3回 / 36秒。p90でも6.4分。

そして 34.4%のセッションは、ツールを一度も呼ばずに終わっている。1,013本。質問して答えが返って終わり、あるいは開いてすぐ閉じた分だ。

一方で最大値は12,677レコード、4,329ツール呼び出し、12,339分(8.5日)。桁が3つ違う。平均を出しても意味が無い分布で、少数の長いセッションが総量を作っている。

これは以前に測ったバケット集計の罠と同じ構造だ。「セッションあたり」の数字を出すときは、必ず分布を見ないと、上位数本の重みを全体の性質だと言ってしまう。

30日で本体セッション2,948本

1日あたり 本体セッション 98.3 本 / ツール 6,268 回
プロジェクト数 117 / 上位1つの占有 81.9%

1日98本。ただし中央値36秒なので、これは「1日98回まとまった作業をした」ではなく、大半は数十秒で閉じる細かい起動だ。

プロジェクトは117個にまたがっているが、上位1つが本体セッションの 81.9% を占める。残り116プロジェクトで18.1%。ここも極端に偏る。

cwd が複数あるセッションは11.7%、最大18ディレクトリ。1つのセッションが18箇所を渡り歩いている。

サブエージェントの記録は「読める」が「読まれない」

サブエージェントの作業が過半数を占めるという事実が効いてくるのは、報告を検証したいときだ。

サブエージェントが「テストを通しました」と返してきたとき、その主張の根拠は subagents/agent-*.jsonl に全部入っている。実行したコマンドも、その出力も残る。だから検証は原理的にできる。

問題は、それが別ファイルで、名前がハッシュで、本体のログから直接たどれないことだ。以前 委譲の報告が検証不能になる理由を書いたが、構造としてはこうなっている。

  • 本体のログには「Agent ツールを呼んだ」ことと「返ってきたテキスト」しか無い。
  • 実際の作業は別ファイルにある。
  • その別ファイルが、全ツール呼び出しの55.5%を持っている。

裏取りをするなら、まずこのファイル群を開けるようにしておく必要がある。~/.claude/projects/<プロジェクト>/<セッション>/subagents/ に置かれていて、1本あたり中央値279KB、最大27MBある。

スクリプト

プロジェクト名・パス・発話内容は出さない。件数と分布だけ。

"""セッション記録の形を数える。本体とサブエージェントを分けて見る。

出力は件数と分布だけ。プロジェクト名・パス・発話内容は出さない。
"""
import os, glob, collections, datetime
ROOT=os.path.expanduser("~/.claude/projects")
rows=[]
for f in glob.glob(os.path.join(ROOT,"**","*.jsonl"),recursive=True):
    sub="/subagents/" in f
    n=0; tools=0; ts=[]; cw=set(); proj=os.path.basename(os.path.dirname(f.split("/subagents/")[0]))
    with open(f,encoding="utf-8",errors="replace") as fh:
        for line in fh:
            n+=1
            i=line.find('"timestamp":"')
            if i>=0: ts.append(line[i+13:i+32])
            if '"tool_use"' in line: tools+=1
            j=line.find('"cwd":"')
            if j>=0:
                e=line.find('"',j+7)
                cw.add(line[j+7:e])
    if not n: continue
    dur=0
    if len(ts)>=2:
        try:
            a=datetime.datetime.fromisoformat(min(ts)); b=datetime.datetime.fromisoformat(max(ts))
            dur=(b-a).total_seconds()/60
        except Exception: dur=0
    rows.append((sub,n,tools,dur,len(cw),proj))
main=[r for r in rows if not r[0]]; sub=[r for r in rows if r[0]]
print(f"記録 {len(rows):,} 本 = 本体 {len(main):,} / サブエージェント {len(sub):,}"
      f" = {len(sub)/len(rows)*100:.1f}%")
def st(xs,label,unit=""):
    xs=sorted(xs)
    print(f"  {label:16} 中央値 {xs[len(xs)//2]:8.1f}{unit} / p90 {xs[int(len(xs)*.9)]:8.1f} / 最大 {xs[-1]:9.1f}")
print("本体セッション:")
st([r[1] for r in main],"レコード数")
st([r[2] for r in main],"ツール呼び出し")
st([r[3] for r in main],"継続時間","")
one=sum(1 for r in main if r[2]==0)
print(f"  ツールを一度も呼ばなかった {one:,} = {one/len(main)*100:.1f}%")
multi=sum(1 for r in main if r[4]>1)
print(f"  cwd が複数 {multi:,} = {multi/len(main)*100:.1f}% / 最大 {max(r[4] for r in main)}")
tt=sum(r[2] for r in rows)
print(f"ツール呼び出し合計 {tt:,} / 本体 {sum(r[2] for r in main):,}"
      f" / サブ {sum(r[2] for r in sub):,} = {sum(r[2] for r in sub)/tt*100:.1f}%")
days=30
print(f"1日あたり 本体セッション {len(main)/days:.1f} 本 / ツール {tt/days:,.0f}")
p=collections.Counter(r[5] for r in main)
print(f"プロジェクト数 {len(p)} / 上位1つの占有 {p.most_common(1)[0][1]/len(main)*100:.1f}%")

自分の環境で数える

上のスクリプトをそのまま保存して python3 で走らせれば、同じ数字が自分のログから出る。引数は無い。

最初に見るのは2つだけでいい。

ツール呼び出し合計のうちサブエージェント側の割合。 ここが手元では55.5%だった。この値が高いほど、画面で追える範囲は狭い。委譲を使っていなければ0%になるし、その場合は ~/.claude/projects/**/subagents/ 自体が存在しない。

ツールを一度も呼ばなかった本体セッションの割合。 数字を「セッションあたり」で語るときに、この空セッションが分母に入っているかどうかで平均が大きく動く。中央値と並べて見ないと、平均は簡単に嘘をつく。

プロジェクト数と上位1つの占有率も出る。手元では上位1プロジェクトが81.9%だったので、この分布は実質そのプロジェクトの性質を見ていることになる。自分の環境で占有率が低ければ、逆に分布はもっと素直な形になるはずだ。

この計測の限界

  • 「サブエージェントの記録」は subagents/ ディレクトリに置かれているかどうかで判定している。別の方法で起動された並行処理があれば、この分類には入らない。
  • 継続時間はレコードの最初と最後のタイムスタンプの差。開いたまま放置したセッションが最大12,339分(8.5日)として入っており、実作業時間ではない。p90の6.4分のほうが実感に近い
  • ツール呼び出しは "tool_use" という文字列が行に含まれるかで数えている。1行に複数含まれる場合を1と数えるので、わずかに過小
  • 30日ぶんしか無い(ログは30日で消える)。季節性や長期の傾向は見えない。
  • 上位1プロジェクトが81.9%を占めるので、この分布は実質そのプロジェクトの性質を見ている。他の使い方をすれば別の形になる。

結論

エージェントの作業を目で追っているつもりでも、見えているのは半分以下だった。

ツール呼び出しの55.5%は、画面に出ない場所で実行されている。


関連

ツール呼び出しの55.5%がサブエージェント側だということは、規約が届いているかを確かめるべき相手も、半分以上はそちらだということです。親にだけ効く書き方をしていると、作業量の半分は素通りします。

層の切り方と、規約が効かなくなる4つのパターンをまとめた本があります。

AIコンテキスト設計ガイド ── 規約を配ったのに守られない理由(2,000円)

第3章「規約が効かなくなる4つのパターン」まで無料で読めます。
/compact の後もルートの CLAUDE.md は再注入されるが、サブディレクトリと paths: 付きは再注入されない ── 長いセッションで静かに落ちるのはどちらか、という章です。

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?