~/.claude/history.jsonl には、自分が実際に打鍵したプロンプトが全部残っている。ツール結果でもコンパクション要約でも自動注入でもない、キーボードを叩いた分だけが入っている。
320日分、21,286件あった。数えた。
期間 2025-10-03 〜 2026-08-19 = 320 日 / 1日あたり 67 件
打鍵 21,286 件 / 総字数 1,524,461
長さ 中央値 26 / 平均 72 / 最大 32,771
25パーセンタイル 15 字
50パーセンタイル 26 字
75パーセンタイル 57 字
90パーセンタイル 117 字
95パーセンタイル 181 字
99パーセンタイル 529 字
上位1% が占める字数 522,920 / 1,524,461 = 34.3%
中央値26文字。 4分の1は15文字以下。
そして 上位1%(213件)が、打った文字の34.3%を占めている。
「プロンプト設計」は1%の出来事だった
平均72字、中央値26字。この差は分布が極端に偏っていることを意味する。
丁寧に設計された長いプロンプト——役割を与え、制約を並べ、出力形式を指定し、例を添えたもの——は確かに存在する。最長は32,771字だった。だがそれは 213件、全体の1% である。
残りの99%、21,073件は中央値26文字の短い発話だ。1日67件のうち、66件がそれにあたる。
日々やっていることは、プロンプトを設計することではない。26文字で指示を出し、返ってきたものを見て、また26文字で次を言うことだった。
何を言っているのか
文末の形で分類した。日本語は文末に機能が出るので、末尾数文字でだいたい割れる。
=== 種別(スラッシュ・シェル実行を除く 17,265 件)
問いかけ 2,381 = 13.8%
依頼・命令 7,508 = 43.5%
承認・同意 242 = 1.4%
差し戻し・否定 196 = 1.1%
どれにも当たらない 6,938 = 40.2%
43.5%が「〜して」「〜してください」で終わる依頼・命令だった。
中身を見に行くと(案件名が入るので本文は出せないが)、固有名詞をひとつも含まない発話が大量にある。「実行して。」「進めて良いよ。」「承認するよ。」「また止まってるでしょ。」——このまま公開しても何の情報も漏れない種類の文である。
これらは指示ではなく、監督だ。作業を渡した相手が動いているかを見て、進めさせ、承認し、止まっていたら突く。
長く書いても、やっていることは変わらない
長さ帯ごとに種別の構成を出した。ここが一番意外だった。
=== 長さ帯ごとの構成
1-20字 n= 5,609 問い 10.7% 依頼 39.8% 承認 3.0%
21-60字 n= 6,993 問い 17.8% 依頼 45.5% 承認 0.9%
61-200字 n= 3,885 問い 11.5% 依頼 47.1% 承認 0.2%
201字以上 n= 778 問い 11.2% 依頼 33.8% 承認 0.4%
依頼の比率が、20字でも200字でも40〜47%でほぼ平ら。 問いかけも10〜18%で動かない。
長いプロンプトは「短いプロンプトとは違う高度なこと」をしているのではなかった。同じ依頼に、文脈を貼り付けて長くしただけである。実際、貼り付けを含む打鍵は1,258件=5.9%あり、これが長さの上位を作っている。
「プロンプトを長く丁寧に書けば結果が良くなる」という話は、この分布の中では1%の領域についての話でしかない。残り99%で効いているのは長さではない。
数えなかったこと・当たらなかったこと
40.2%が「どれにも当たらない」に落ちた。 分類は成功していない。
最初は「継続の合図」「再実行」といったキーワード辞書で分けようとして、75%が未分類になった。文末の形に切り替えて40%まで下げたが、そこから先は下がらなかった。断定形(「〜です」「〜だと思う」)、体言止め、箇条書き、エラーメッセージの貼り付けが混ざっていて、単純な形態では割れない。
したがって 43.5%という依頼比率は下限である。未分類の中にも依頼はある。逆に「承認1.4%」は明らかに過小で、承認の多くは依頼と一体になった文(「はい、直してください」)として依頼側に数えられている。
もうひとつ。この21,286件は「打鍵した分」だけである。以前、セッションログ側から測ったときは、エージェントに届く user メッセージの総字数のうち人間が打った文字は0.63%しかなかった。残りはコンパクション要約と自動注入だ。ここで見ているのは、その0.63%の中身にあたる。
何が言えるか
自分の使い方について言えるのは、次のことだ。
エージェントとの対話は、書く作業ではなく見る作業になっている。 1日67回、中央値26文字。打鍵量は1日あたり4,764字にすぎない。時間の大半は、打つのではなく、返ってきたものを読んで次の26文字を決めることに使われている。
だとすると、改善の効き目があるのはプロンプトを長くする方向ではない。「返ってきたものを読む回数を減らす」方向——つまり、間違いを自分の目で見つけずに済ませる仕組み(検証を落とす、終了コードで止める)のほうに寄る。
これは別の記事で書いた「拒否する仕組みだけが効く」という話と、同じ結論に別の入口から着いている。
計測に使ったスクリプトを置いておく。集計値しか出力しない(打鍵内容には案件名・固有名詞・貼り付けた資料が入るため、本文は表示しない)。
#!/usr/bin/env python3
"""~/.claude/history.jsonl(自分が打鍵したプロンプト)の長さと種別を数える。
python3 typed_prompts.py
出すのは集計値だけ。プロンプト本文は一切表示しない
(打鍵内容には案件名・固有名詞・貼り付けた資料が入るため)。
"""
import json, os, re, collections, statistics, datetime
PATH = os.path.expanduser("~/.claude/history.jsonl")
# 文末の形で「何をしているか」を分ける。
# 日本語は文末に機能が出るので、末尾数文字でだいたい割れる。
CAT = [
("問いかけ", r"[??]\s*$"),
("依頼・命令", r"(して|してね|下さい|ください|せよ|しろ|お願いします|おねがい|頼む)[。..!!]?\s*$"),
("承認・同意", r"^(はい|うん|そう|ok|OK|いい|良い|了解|承認|大丈夫|yes|y)\b|^(はい|承認|了解|いい|良い|大丈夫)"),
("差し戻し・否定", r"(違う|ちがう|やめ|止め|だめ|ダメ|いいえ|戻して|もどして|ではない|じゃない)"),
]
def band(n):
return "1-20字" if n <= 20 else "21-60字" if n <= 60 else "61-200字" if n <= 200 else "201字以上"
def main():
rows = []
with open(PATH, encoding="utf-8", errors="replace") as fh:
for line in fh:
try:
rows.append(json.loads(line))
except ValueError:
pass
txt = [(r.get("display") or "").strip() for r in rows]
txt = [t for t in txt if t]
ts = [r.get("timestamp") for r in rows if r.get("timestamp")]
if ts:
to_d = lambda x: datetime.datetime.fromtimestamp(x / 1000 if x > 1e11 else x).date()
a, b = to_d(min(ts)), to_d(max(ts))
days = max(1, (b - a).days)
print(f"期間 {a} 〜 {b} = {days} 日 / 1日あたり {len(txt) / days:.0f} 件")
print(f"打鍵 {len(txt):,} 件 / 総字数 {sum(len(t) for t in txt):,}")
print(f"プロジェクト {len({r.get('project') for r in rows})} / セッション {len({r.get('sessionId') for r in rows})}")
paste = sum(1 for r in rows if r.get("pastedContents"))
print(f"貼り付けを含む {paste:,} = {paste / len(rows) * 100:.1f}%")
L = sorted(len(t) for t in txt)
print(f"\n長さ 中央値 {statistics.median(L):.0f} / 平均 {statistics.mean(L):.0f} / 最大 {max(L):,}")
for q in (0.25, 0.5, 0.75, 0.9, 0.95, 0.99):
print(f" {int(q * 100):2}パーセンタイル {L[int(len(L) * q)]:6,} 字")
top1 = sum(L[int(len(L) * 0.99):])
print(f" 上位1% が占める字数 {top1:,} / {sum(L):,} = {top1 / sum(L) * 100:.1f}%")
n_slash = sum(1 for t in txt if t.startswith("/"))
print(f"\nスラッシュコマンド {n_slash:,} = {n_slash / len(txt) * 100:.1f}%")
body = [t for t in txt if not t.startswith("/") and not t.startswith("!")]
print(f"\n=== 種別(スラッシュ・シェル実行を除く {len(body):,} 件)")
hit = collections.Counter()
for t in body:
hit[next((k for k, p in CAT if re.search(p, t)), "どれにも当たらない")] += 1
for k, _ in CAT:
print(f" {k:12} {hit[k]:6,} = {hit[k] / len(body) * 100:5.1f}%")
print(f" {'どれにも当たらない':12} {hit['どれにも当たらない']:6,}"
f" = {hit['どれにも当たらない'] / len(body) * 100:5.1f}%")
print("\n=== 長さ帯ごとの構成")
tab = collections.defaultdict(collections.Counter)
for t in body:
tab[band(len(t))][next((k for k, p in CAT if re.search(p, t)), "その他")] += 1
for b in ("1-20字", "21-60字", "61-200字", "201字以上"):
tot = sum(tab[b].values())
if not tot:
continue
print(f" {b:9} n={tot:6,} 問い {tab[b]['問いかけ'] / tot * 100:4.1f}%"
f" 依頼 {tab[b]['依頼・命令'] / tot * 100:4.1f}%"
f" 承認 {tab[b]['承認・同意'] / tot * 100:4.1f}%")
if __name__ == "__main__":
main()
関連
人間が打っているのは中央値26文字で、長文は全体の1%でした。つまりエージェントに届く指示のほとんどは、その場で打った文ではなくあらかじめ置いた規約とコンテキストです。そちらが効いていなければ、何も伝わっていません。
何をどの層に置くかと、効かなくなる4つのパターンをまとめた本があります。
→ AIコンテキスト設計ガイド ── 規約を配ったのに守られない理由(2,000円)
第3章「規約が効かなくなる4つのパターン」まで無料で読めます。
/compact の後もルートの CLAUDE.md は再注入されるが、サブディレクトリと paths: 付きは再注入されない ── 長いセッションで静かに落ちるのはどちらか、という章です。