Skip to content

Latest commit

 

History

History
523 lines (434 loc) · 44.1 KB

60_json-array-pruning.md

File metadata and controls

523 lines (434 loc) · 44.1 KB

Spec: JSON オブジェクト直下の配列で返るツール結果を、要素単位で落とす

  • 起票: 2026-09-22
  • 状態: Done(2026-09-22)。rev2(P0 の実測 + 査読 2 系統 18 点 → 採用 12 / 訂正して採用 5 / 反証 1。 採否は Notes 4)→ 承認 → P0 完了 → P1 完了(同日。裁定 2 点は利用者が yes = _pruned キーを足すことを 認める / 縮んだ効き目で進める。tool_prune_contract へ「配列型」の節を凍結。P1 = prune.rs に JsonScanner と配列型の割り方・組み立て。core 810 全緑・ミューテーション 4 本のうち 1 本で予測を外した。 P2 完了 = 結合 2 本・変異 2 本とも予測どおり。P3 完了 = DETAIL 3 言語 / PRIVACY 日英 / README 3 言語。 P4 = 実機 5 走行で Done)。起票から Done まで同日。 rev1 の D1〜D5 は P0 で前提が覆ったので全面的に書き直した — rev1 の文面は Notes 5 に写しを残す
  • 起点: Spec 59 の P0 で測った返り値の形(2026-09-22)。対象の呼び出しのうち 4,000 字以上の 5,246,474 字を、実際にツールを叩いて分類したら 30.1% が JSON の配列型だった。 Spec 59 は「JSON は包装型だけ扱う」で着地したので、この 30.1% は素通しのまま残っている。 利用者裁定(2026-09-22)—「B は足す、C は別 Spec に切る」
  • 覆す凍結: 無い。Spec 59 の tool_prune_contract に足す形(既存の凍結は 1 つも動かさない)

Goal

  1. トップレベルがオブジェクトで、その直下に配列を持つ JSON のツール結果から、そのターンの依頼に 関係の無い要素を、モデルへ返す前に落とす。 落とした後もJSON として読めるままで、 配列の要素の型は揃ったままであること(印を要素として混ぜない)
  2. 残る要素は逐語。 要素の中身を要約も言い換えもしない
  3. 落としたことと、落としたものへ戻る道を書く(failures.md #44)
  4. Spec 59 の機構を借りる — ParagraphScorer / 閾値 / 設定(jev.json)/ omitted / 計器。 新しく作るのは割り方と組み立てだけ
  5. 形が揃わなければ丸ごと諦める(outcome=structured のまま)。部分的に解釈して構文を壊す 経路を作らない(AionUi の toon と Spec 59 D4b と同じ規律)

やらないこと(範囲外): Spec 59 が扱う範囲(本文と包装型)/ トップレベルが配列の JSON (実例 0 件。印の置き場が無い)/ 直下より深い配列(elyth の 3 段目。P0 実測 1)/ 要素の中身を切り詰めること(要素は落とすか残すかの 2 値)

実測(Spec 59 の P0 から引き継ぐ。測り直さない)

分類 字数 割合 実測した例
A. JSON でない(Spec 59 の対象) 1,283K 24.5% MCP_DOCKER__fetch / rag
B. JSON・包装型(Spec 59 D4b の対象) 291K 5.5% manuale__read
C. JSON・配列型(本 Spec の対象) 1,581K 30.1% outcasts__recent_posts / memoria__recall_memory / manuale__search
D. 同サーバーの他ツール(高確度で JSON) 949K 18.1% alphaxiv__* / manuale__navigate
E. 不明(この端末から叩けない) 1,142K 21.8% elyth__* / browsermcp__*

通しの効き目(rev1 の見積もり): Spec 59 だけ 6.0% → 本 Spec を足すと 12.1% = 約 2 倍 → rev2 で訂正(P0 の母数 4,903,900 字で数え直した。査読 #1 矛盾 10):

Spec 59 が割れる(A + B)           1,245K = 25.4%   fetch 875 / get_paper_content 75 / manuale__read 291 …
Spec 59 の対象だが割れない(A')      810K = 16.5%   answer_pdf_queries 787(XML・空行なし)/ discover_papers 23
本 Spec が効く見込み(C の一部)    1,343K = 27.4%   outcasts 989 / manuale__search 204 / memoria の長い文字列 ≈150
本 Spec の対象だが効かない            948K = 19.3%   elyth 713(3 段目・50 字抜粋)/ navigate 85 / memoria の規律文 ≈150
対象外・未分類                        558K = 11.4%

本 Spec が足す母数は Spec 59 と同規模(1.34M 対 1.25M)で、「2 倍」ではなく「同じくらいをもう 1 つ」。 しかも削減率は依頼の狭さで 12% ↔ 68% に割れる(P0 実測 2)ので、門を超えるのは狭い依頼のときだけ。

Design(rev2。P0 の実測と査読で確定した側。rev1 の文面は Notes 5)

D1. 対象の形(対象の門)

閉じた条件 3 つをすべて満たす本文だけを対象にする(Spec 59 D4b と同じ流儀 — 外れたら outcome=structured のまま素通し。部分的に解釈しない):

  1. トップレベルがオブジェクト(配列なら対象外 — 印の置き場が無い。実例 0 件)
  2. 直下に配列が 1 つ以上あり、そのうち JSON 表現が 1,000 字以上の配列を対象にする。 門は要素数ではなく字数(P0 実測 2-3: 3 件で 12K 字を占める配列が実例)。1,000 字未満の配列 (next_hints / capabilities / counts_by_type)は触らない
  3. 直下より深い配列は探さない(elyth の data.timeline.items。届いても要素が 50 字の抜粋で Jev に判定材料が無い = P0 実測 1・2-2)

複数の配列が対象になりうる(memoria__recall_memory は 7 つ、manuale は 2 つ)。 対象の配列それぞれが独立に採点・判定される(D6)。対象の配列が 0 なら structured。

D2. 要素をどう採点するか

要素の JSON 表現(serde_json の compact 表現ではなく、元テキストの上でその要素が占める 範囲の逐語)を Spec 59 の「1 段落」として ParagraphScorer へ渡す。

  • 採るのは JSON 表現。構造が見えるぶん判断材料が増える → P0 実測 2-5 で否定(Jev は authored_mode のような構造の条件を読み分けない。見ているのは本文の語)。採る理由は 「落とすものと採点に送ったものが同一物」の 1 つだけ(文字列連結との差は小さい = 2-4)
  • 6,000 字超の要素は採点せず残す(Spec 59 D6 の凍結をそのまま借りる)。search の base64 画像 (20K 字超)はここで自然に外れる。6,000 字の崖は Spec 59 の凍結で、本 Spec では動かさない
  • 全対象配列の要素を 1 本の候補列に平坦化して prune::batch へ渡す(束ね方の唯一の実装。 Spec 59 D4 の 6)。戻ったスコアは (配列の鍵, 要素の index) の対応表で戻す — Spec 59 の Prepared が段落の index でやっていることと同じで、鍵が 1 つ増えるだけ

D3. 落としたことをどこに書くか(B で確定。A と C は除外)

トップレベルに _pruned を 1 キー足す。 配列の中に印の要素は入れない(A — 要素の型が揃わず Post[] として読む側が壊れる = Goal 1 / Goal 5 違反)。JSON の外にも置かない(C — 本文が JSON で なくなる = Goal 1 違反)。

{"posts":[…残った要素…],
 "_pruned":{
   "note":"関連度で要素を省略しています。omitted(id, from, to) で逐語が読めます",
   "arrays":{
     "posts":{"id":"P2","total":12,"dropped":[0,3,11]}
   }}}
  • 配列ごとに id を 1 つ振る(P{n} の通し番号。Spec 59 の self.pruned.len() + 1 をそのまま使う)。 omitted { id, from, to } の引数は変えない — id が配列を、from / to がその配列の要素の index を指す(査読 #2 矛盾 1 の解)
  • note はモデルへの案内(Spec 59 の印の文面と同じ役割。【…】 の代わりに JSON の値で運ぶ。 ja / en は Language で選ぶ → P1 で訂正: Spec 59 の印も日本語固定で prepare / apply は Language を受け取らない。2 つの印を同じ規律にそろえ、本 Spec だけ二言語にしない)
  • 元の JSON に _pruned が既にあれば丸ごと諦める(structured)。上書きも改名もしない(Goal 5)
  • これは「元の JSON に無いキーを足す」変更で、Spec 59 の「圧縮した文字列以外は 1 バイトも 変わらない」より一段ゆるい。 要素を消す以上、構造は必ず変わる(カンマが動く)ので、 「変わるのは対象の配列の中身と _pruned の 1 キーだけ」を凍結の文面にする(未決 1 = 利用者裁定)

D4. 差し替えの方法(位置ベースで確定。再シリアライズは除外)

元テキストの上で、落とす要素の範囲(先行または後続のカンマ 1 つを含む)を消し、末尾の } の 直前に ,"_pruned":{…} を挿入する。 他は 1 バイトも触らない。

  • 再シリアライズは除外 — serde_json はキー順・空白・数値表現(1.0 と 1)を変える(Goal 2 違反。 実物は整形あり / なしが混在するので、整形も必ず変わる)
  • 要素の範囲は自前のスキャナで取る(文字列リテラルとエスケープ・括弧の深さだけを追う)。 Spec 59 D4b が「最長の文字列リテラルの位置」を探す走査を既に持っている — 着手時に共有できる 部分を数える(P1 の 1 本目)
  • カンマの規則: 落とす要素が末尾なら先行のカンマを、それ以外なら後続のカンマを消す。 空白・改行は触らない(整形が崩れて見えても JSON として正しい — 崩れ方は fixture で焼く)
  • バイト golden: 素の JSON(outcasts)と整形 JSON(memoria / manuale)の 2 fixture で、 「対象の配列の中身と _pruned 以外は 1 バイトも変わらない」を留める(Spec 59 の everything_outside_the_compressed_string_is_byte_identical と同じ形)

D5. 戻り道

Spec 59 の omitted { id, from, to } と PrunedRaw { id, paragraphs, dropped } を無改変で共有する。 PrunedRaw.paragraphs に要素の逐語を入れれば、omitted は段落と同じ経路で返す — PrunedRaw は中身が段落か要素かを知らない(kind 欄は要らない。査読 #1 矛盾 9 の訂正)。 モデルが読む区別は _pruned.note と arrays の鍵が担う。

D6. 配列ごとの判定と、全部落ちたとき

判定は配列ごと(Spec 59 の apply を配列 1 つに 1 回):

  • ある配列で採点した要素がすべて閾値未満なら、その配列は触らない(Spec 59 の all_dropped と 同じ理由 — 依頼と噛み合っていないか採点の失敗。空配列 [] は作らない)
  • ある配列で1 つも落ちなければ、その配列は触らない
  • 対象の配列がすべて「触らない」なら、本文全体を素通し。outcome は全配列が all_dropped なら all_dropped、それ以外は nothing_dropped
  • 1 つ以上の配列で落ちたら組み立て、正味の門(D7)へ

D7. 2 つの門を分ける

門 何を見るか どこで
対象の門(D1) 配列の JSON 表現が 1,000 字以上か 採点の前。外れた配列は Jev へ送らない
正味の門(Spec 59 D6b) 組み立てた本文全体の削減率が 25% 以上か 組み立ての後。MIN_REDUCTION をそのまま借りる

正味の門は本文全体で測る(配列ごとではない)。固定欄が大きくて届かなければ below_floor で 素通しになるが、それが正しい — モデルに届くのは本文全体で、そこが 25% 減らないなら印を足す 価値が無い(Spec 59 rev4 の論拠そのまま。査読 #2 不整合 5 は「明記されていない」の指摘として採り、 配列ごとに測る案は採らない)。

Tasks

P0 — 形を数え直してから凍結する

  • D と E を分類した(2026-09-22。下の「P0 実測」)— alphaxiv__answer_pdf_queries(787K)は XML で JSON ではない。elyth(713K)は配列が 3 段目に居て本文が 50 字の抜粋 = 届かず、届いても 判定できない(利用者が GUI のツール行で確認)。どちらも本 Spec の効き目に乗らない
  • 実測の 3 例で D1 の条件が成立するか — 成立しない。 複数配列は memoria だけでなく manuale の 2 本(navigate / search)にもあり、rev1 の D1 では配列型の 38%(595K)が落ちる
  • 配列型の削減率を実測した(3 本・4 依頼文・JSON 表現と文字列連結の 2 通り)
  • data_contract.yaml の tool_prune_contract へ「配列型」の節を凍結(2026-09-22。退けた形に 4 つ足し、shape=json_arrays を計器へ)

P1 — 純機構(prune.rs に配列型の割り方と組み立て)

  • 対象の門(D1): トップレベルがオブジェクト / 直下の配列 / 1,000 字 / _pruned 衝突なら諦める
  • 自前スキャナで直下の配列と要素の元テキスト上の範囲を取る — D4b と共有できる部分は無かった (D4b はスキャナを持たず match_indices。P1 実装記録)。JsonScanner を新設
  • 平坦化した候補列 → prune::batch → 対応表で配列へ戻す(D2)— Prepared.layout の 3 値化で candidates / batches / spread は無改変
  • 配列ごとの apply(D6)→ 組み立て(D4)→ 正味の門(D7)
  • PrunedRaw を配列ごとに積む(D5)— Pruned.entries を apply が作り、turn.rs は積むだけ
  • 素 / 整形の両方でカンマの 3 位置(先頭 / 中 / 末尾)+ 配列の外と印以外はバイト同一 + 全滅の配列は 触らない + _pruned 衝突 + 1,000 字未満は対象外 + 文字列の中の括弧 + 包装型が先 + 6,000 字超の要素 + 正味の門は全体。14 本。変異 4 本で赤を確かめた(1 本は予測を外した)

P2 — 配線

  • prune::prepare の入口で D4b(包装型)と本 Spec(配列型)を1 箇所で分岐(Notes 1)— P1 で済んだ
  • 計器 tool prune: に shape=json_arrays arrays=<対象の配列数> pruned=<落とした配列数> — P1 で済んだ
  • 結合 2 本(tests/tool_prune_arrays.rs。偽の採点器で経路を丸ごと通す): 配列型が圧縮されても本文が JSON のまま・omitted が次の周で要素を逐語で返す / 落とした配列ごとに別の id・omitted の index は その配列の中で数える(P2, 1 が y の要素 1 を返し、x の要素 1 も平坦化の 1 番目も返らない)。 変異 2 本(turn.rs が entries の 1 件目しか積まない / entry の paragraphs を平坦化した全要素にする)は どちらも予測どおり 2 本目だけ赤

P3 — 台帳 / P4 — 実機

  • DETAIL 日英に「配列型は要素単位で落とす」の段落(包装型の段落の直後。中国語版は抄訳に 1 項)/ PRIVACY 日英の 4-4 に**素通しの条件へ「配列型でもない」**と、**送るもの = 段落の代わりに要素の JSON 表現(キー名を含む)**の 1 文 / README 3 言語は既存の行に「(JSON なら配列の要素)」を挟んだだけ (169 / 169 / 165 行のまま)。隣の欠けを 1 件回収 — PRIVACY 日英の「送る本文には rag が読んだ 宣言フォルダの中身が含まれる」は Spec 59 rev4 で rag を外した後も残っていた(rev4 の P3 の追従漏れ)
  • 実機: outcasts__read_thread を狭い依頼で outcome=ok(話題で 62%。「誰が」では 6% = Jev が答えられる 問いの形は話題)/ memoria__recall_memory で複数配列の _pruned(pruned=3)/ 広い依頼で below_floor (6.3% と 23.5%)。記録は「P4 実機記録」

P1 実装記録(2026-09-22)

core 810 / 結合 47 バイナリ 全緑(+14)・clippy 0。新しい依存は 0。次に触る人が要る判断だけを置く。

  • Spec 59 D4b と共有できる走査は無かった。 D4b は「serde_json で読み、最長の文字列を 再シリアライズして match_indices で位置を探す」形で、スキャナを持っていない。要素は整形 (改行・インデント)されていると再シリアライズと一致しないので、配列型は JsonScanner を新設した(文字列リテラルとエスケープ・括弧の深さだけを追う約 100 行。serde_json が 読めた本文にだけ掛けるので構文の誤りは想定せず、形が合わなければ None → structured)
  • 分岐は prepare の 1 箇所(Notes 1)。順は 包装型 → 配列型 → structured。包装型が先なのは、 巨大な文字列 1 つを持つ本文は配列があってもそちらが本体だから(arrays_yield_to_the_wrapper_shape)
  • Prepared の wrapper: Option<WrapperSlot> を layout: Layout の 3 値へ(Text / Wrapper / Arrays)。配列型は要素 1 つが段落 1 つで、空行で割らず短い要素も寄せない(境界は JSON が 決めている)。candidates / batches / spread は無改変で乗った
  • apply の id: &str を first_seq: usize へ変えた。 配列型は落とした配列ごとに id を 1 つ 使うので、番号は apply が振る。帰結として Pruned が entries: Vec<PrunedEntry> を持ち、 呼び出し側(turn.rs)は落とした index を数え直さずにそれを積むだけになった — rev4 までは turn.rs が scores < threshold で dropped を再計算しており、規律が 2 箇所にあった。 Spec 59 の段落型・包装型は 1 件の entry で、omitted も PrunedRaw も無改変
  • カンマの規則は「隙間」で書いた(D4 の精緻化)。[ と最初の要素の間を g0、要素 i-1 と i の 間(カンマ入り)を g_i、最後の要素と ] の間を g_n とし、出力は [ + g0 + 残す要素を出現順に (最初に残す要素の前には何も置かず、2 つ目以降は自分の前の g_i を置く)+ g_n + ]。 どの落とし方でも余分なカンマが出ず、1 つも落とさなければ入力と同一バイト。契約の 「先行または後続のカンマ 1 つ」はこの規則の帰結で、「空白・改行は触らない」は落とした要素に 付随する隙間は消えると読む(整形は崩れて見えるが JSON として正しい)
  • below_floor の ratio は実測値のまま返す — 一度 net_ratio を Option にして門の外で 0.0 を埋めかけ、a_small_gain_is_not_worth_the_marker が赤で止めた(#72 の規律を自分で破りかけた)
  • Spec 59 のテスト 1 本の前提が Spec 60 で変わった — a_json_without_a_direct_string_value_is_skipped は「配列型は素通し」を {"posts":["…"]} で確かめていたが、それは今や配列型の対象。fixture を 入れ子だけの JSON へ差し替え、配列型の側は arrays_* の 14 本が担う
  • 印の案内文(_pruned.note)は Spec 59 の印と同じく日本語だけ。 D3 に「ja / en は Language で 選ぶ」と書いたが、prepare / apply は Language を受け取らず、Spec 59 の render も日本語固定 だった。2 つの印を同じ言語規律にそろえるのが正しく、本 Spec だけ二言語にしない(Spec 35 の 追従漏れ候補として別件。rev2 の D3 の文面は訂正)
  • 計器: shape=json_arrays arrays=<対象の配列数> は Prepared::shape_fields、pruned=<落とした 配列数> は Pruned::extra_fields。前者は採点の前から分かり、後者は組み立ての後にしか無いので 置き場が分かれている
  • 作業の罠 #130 の 3 例目: bash のヒアドキュメント → Python の文字列 → Rust ソースの 2 層を 通した \n が実改行になり、テストがコンパイルエラーで止まった(赤くなる側の形。緑のまま汚れる 形ではなかった)。行番号で当てて chr(92) で戻した

ミューテーション 4 本(予測を先に書いてから回した):

変異 予測 実測
MR1 _pruned の衝突検査を外す 1 本 1 本(予測どおり)
MR2 全滅の配列を触る([] を作る) 2 本 2 本(予測どおり)
MR3 カンマ規則(最初に残す要素の前に隙間を置かない)を外す 3 本 3 本(予測どおり)
MR4 対象の門を字数から要素数 5 件へ戻す 1 本 7 本(外した)

MR4 で外した理由: 対象の門は arrays_of の入口に居るので、要素数へ戻すと 3〜4 要素の fixture が 全部 structured へ倒れ、門のテスト以外の 6 本が unwrap で落ちた。Spec 59 rev4 の MR2(不等号の 反転が 7 本を赤にした)と同じ形 — 門は 1 箇所に見えて、その先の全経路の入口に居る。予測は 「門を直接見るテスト」だけを数えていた。

P4 実機記録(2026-09-22。利用者の走行)

1 回目 — outcasts__read_thread を「誰が書いたか」で絞る依頼

依頼: 「outcasts__read_thread で b2d73586-… のスレッドから、Outcasts 管理人の書いた投稿をだけ確認してください」 (進行役 agent_8 = gpt-5.6-terra)。

23:33:51 tool prune: agent=agent_8 name=outcasts__read_thread outcome=below_floor
         shape=json_arrays arrays=1 raw_chars=18448 paragraphs=20 dropped=1
         ratio=0.063 calls=2 jev_tokens=16990 ms=1353
  • 機構は合格。 配列型の経路を通り(shape=json_arrays arrays=1)、posts の 20 要素が段落になり、束ねは 16 + 4 の 2 呼び出し。門が 6.3% で止め、本文は 18,448 字のまま渡った(次の周の cache_write=10839)
  • 効き目は不合格。 スレッドの 20 投稿のうち管理人(authored_mode: master)が書いたのは 12 で、 理想の判定なら 8 本が落ち、しかもその 8 本が字数の大半(境界測量士の 3 本だけで 6,000 字超)。 実際に落ちたのは 1 本。 落ちるべき 8 本のほぼ全部が >>11 管理人さん と管理人を名指しして返信し、 管理人の論点を引いて議論しているので、「管理人の書いた投稿」に対して Jev は「管理人について書かれた 投稿」も関係ありと判定した
  • 一般化: 「誰が書いたか」はメタデータの問いで、本文の語で判定する Jev には答えられない。 P0 実測 2-5 (authored_mode を読まない)が、実機では「宛名に管理人が居るだけで残る」という形で出た。P0 の 68% は、 あのスレッドが相互参照の少ない形だった偶然。read_thread で効くのは「話題」で絞る依頼だけで、 それも 1 つの話題で回っているスレッドでは大半が残る(P0 の 12% 側)

2 回目 — 同じスレッドを「話題」で絞る依頼

依頼: 「さっきのスレッドで 8bit CPU や Lean の検証企画について書かれた投稿だけ確認して」(同じ agent_8)。

23:38:48 tool prune: name=outcasts__read_thread outcome=ok shape=json_arrays arrays=1 pruned=1
         raw_chars=18448 kept_chars=6959 paragraphs=20 dropped=16 ratio=0.623 calls=2 jev_tokens=16918 ms=2341
  • 16 / 20 が落ち、62.3%。残った 4 本の中に該当の #16〜18 が居て、ルナの答えは #16〜#18 を正確に挙げた (利用者の画面で確認)— 走行前に私が「関係するのは 3 本」と書いた集合そのもの
  • 検収 1(前方一致): round 2 prompt=17777 → round 3 cached=17774、3 トークン差(Spec 59 から通算 5 例目)
  • 収支: 1 回目(全文)の round 3 cache_write=10839 に対し 4,297 = 6,542 トークン減 ≈ $0.016。Jev 16,918 トークン ≈ $0.0007 → 約 23 倍の黒字。omitted は呼ばれていない
  • 同じ 20 要素で、依頼が「誰が」なら 1 本、「何について」なら 16 本。 Jev が答えられる問いの形は 「話題」で、「属性」ではない — これが read_thread の効き目の境界

3 回目 — memoria__recall_memory で複数配列(3 走行)

時刻 個体 依頼 outcome arrays 要素 dropped ratio Jev
23:37:04 イクス(grok-4.7) 「過去を思い出して」(36 字) nothing_dropped 2 10 0 - 4,849 / 5.8 秒
23:44:28 ザリ(muse-spark) 66 字 ok・pruned=3 3 29 22 0.611 13,471 / 1.5 秒
23:47:47 イクス 41 字 below_floor 2 10 4 0.235 4,839 / 3.2 秒
  • 複数配列で複数の id が実機で出た(ザリ: arrays=3 pruned=3 = P1 / P2 / P3。14,237 → 5,533 字)。 結合テスト each_pruned_array_has_its_own_id が留めていた配線の実物
  • イクスの 1 回目は依頼が広く(「過去」)、記憶 10 件のどれも材料なので nothing_dropped が正しい判定。 代償は Jev 4,849 トークンと 5.8 秒 — Jev 1 呼び出しとして今日いちばん遅い(他は 0.2〜2.3 秒)
  • イクスの 2 回目が今日いちばん惜しい観測 — 4 / 10 が落ちて正味 23.5%、門(25%)に 1.5 ポイント 届かず全文が渡った。門の値を動かす根拠にはしない(1 点。#47 の規律)。Spec 59 の実機では 0.000 / 0.069 / 0.063 と門から遠い側ばかりだったので、門の近くに実在する入力があることだけを記録する

副産物

21:46:29 elyth__get_information outcome=structured raw_chars=6128 calls=0 — 入れ子の配列を探さない(D1 の 3)が 実機で効き、1 バイトも外へ出ていない。

判定

  • outcasts__read_thread を狭い依頼で outcome=ok と omitted — ok は 62% で観測。omitted はどの走行でも 呼ばれなかった(= 落とした要素は要らなかった。結合 2 本が経路を留めている)
  • memoria__recall_memory で複数配列の _pruned — ザリの走行で pruned=3
  • 広い依頼で below_floor — 「誰が」の 6.3% と、イクスの 23.5%

この 5 走行で Done(2026-09-22 利用者裁定)。

P0 実測(2026-09-22)

1. D の分類 — 叩けるものを全部叩いた

母数は fuseforks.log 2026-08-09〜09-22 の MCP 由来・4,000 字以上の出力 4,903,900 字 (Spec 59 P0 の 5.25M とは期間と rag の有無で差がある)。

ツール 字数 形(実物) 分類
MCP_DOCKER__fetch 875K Markdown(Spec 59 P0) A
alphaxiv__answer_pdf_queries 787K XML <paper id><page num=N>…</page>…</paper>。PDF 抽出の行で空行が無い A' = JSON でないが D4 で割れない
outcasts__recent_posts 485K {"posts":[…]} 配列 1・素の JSON C
outcasts__read_thread 396K {"board_slug","id","posts":[…],"title"} 配列 1・素の JSON・要素は投稿 C
memoria__recall_memory 306K 配列 7 つ(schema_entries / semantic_matches / … )・要素は文字列とオブジェクトが混在・整形 C'(複数配列)
manuale__read 291K 包装型(Spec 59 D4b) B
manuale__search 204K {"query",…,"matches":[…],"next_hints":[…]} 配列 2 つ・整形。context に base64 画像(20K 字超)が入ることがある C'
outcasts__list_threads 108K {"threads":[…]} 配列 1 C
manuale__navigate 85K {"entries":[…],"next_hints":[…]} 配列 2 つ・整形 C'
memoria__list_memories 126K(1 回) 先頭にテキスト行 Total memories: N + JSON 配列。要素の 9 割が embedding(384 次元) 対象外(JSON でない)
alphaxiv__discover_papers 23K Markdown の番号付きリスト・空行なし A
elyth__get_information(get_notifications / get_my_posts も同じサーバー) 713K {"data":{…}} の 1 キー包装。配列は 3 段目(data.timeline.items / data.trending.items など)に 5 件ずつ、1 要素 ≈ 350 字で本文は truncated: true の 50 字抜粋。字数の大半は UUID・時刻・author の入れ子(利用者が GUI のツール行で確認・2026-09-22) C'' = 入れ子の配列・短い要素
browsermcp__* / admin-procedures__* 157K 未分類 E

分かったこと 3 つ:

  • D の最大は配列型ではなかった。 787K は XML で、Spec 60 の効き目は rev1 の見積もり (+C で約 2 倍)から伸びない。逆にこの 787K は Spec 59 の対象なのに空行が無いので割れない — rag と同じ形。<page> 境界で割る腕を D4 に足せば効く余地がある(Spec 59 の追補の話で、本 Spec の 範囲外)
  • 複数配列が主流。 C の 1,584K のうち C' が 595K(38%)。rev1 の D1「配列がちょうど 1 つ」は memoria のための例外ではなく、設計の前提を外していた
  • 配列型の中でも要素の型が 3 種ある — 投稿(長い散文のオブジェクト)/ 規律文(文字列)/ ディレクトリ項目(短い構造)。下の採点で判定の質が型ごとに違った
  • E の大半(elyth 713K = MCP 出力の 15%)は配列型だが本 Spec の効き目に乗らない。 配列が 3 段目に居るので D1 のどの案でも届かず、届いても本文が 50 字の抜粋で Jev に判定材料が無い (下の採点の観測 2 と同じ形)。字数の出どころが本文ではなく構造(id / 時刻 / 入れ子)なので、 効くとすれば要素を落とす機構ではなく構造を畳む機構(toon の系譜)だが、author が入れ子なので あちらの「フラットな同型配列」の条件も満たさない。本 Spec では対象外として閉じる

2. 要素単位の採点(3 本・4 依頼文・2 表現・Jev 11 呼び出し)

叩いた実物を scratchpad に置き、Spec 59 と同じ問い(instructions / criteria は jev.rs の逐語)で 要素ごとに Noul 1 問。閾値 0.2。JSON 表現(要素をそのまま)と文字列連結(要素の中の文字列を 改行で連ねたもの)の 2 通りで採点した(未決 2 の対測)。

材料 依頼文 要素 落ちた 削減率 Jev
read_thread(12 投稿・12,290 字) 「管理者(人間・authored_mode が master)が何を書いたかだけ」 12 8 67.9% 11,955
同 「拒否権や権限ゲートの設計についての提案をまとめて」 12 2 12.1% 11,946
recall_memory(17,441 字) 「Spec 60 について過去に決めたことを思い出して」 20 + 5 7〜12 + 2 11.1〜17.8% 9,897
navigate(1,150 字 = 実機では 4,000 字未満で対象外) 「Fuseforks の仕様書やアーキテクチャの文書を探して」 12 + 5 9〜10 + 4 62〜68% 3,447

読めたこと 5 つ:

  1. 依頼文で 12% ↔ 68% に割れる(Spec 59 P0 と同じ性質)。同じ 12 投稿でも「1 人の投稿だけ」なら 8 本落ち、「設計の提案を全部」なら 2 本しか落ちない。門(25%)は配列型でも要る — 後者は below_floor で止まるのが正しい
  2. 要素の型で判定の質が違う。 投稿(400〜1,500 字の散文)は両端がよく分かれる (0.07〜0.10 対 0.93)。規律文(Mandate)は 0.2〜0.45 の中間帯に溜まる — 「Spec 60 について」に 対して規律が「関係ある」と判定されがち(Jev が判断型に弱い側)。ディレクトリ項目({"type":"dir", "path":"spec","name":"spec"} = 40 字)は全部 0.05〜0.23 で、依頼文が名指しした spec / architecture も 0.20 / 0.21 — 閾値ぎりぎりで残った。短い要素は判定材料が無く、Jev は決められない
  3. 「要素 5 件以上」の門は逆効果の実例がある。 recall_memory の semantic_matches は 3 件だが 1 件 4,000〜6,000 字で、17K 字のうち 12K 字を占める。うち 2 件(Spec 35 / 38 の索引)は依頼と 無関係で、落とせれば削減率は 11% → 60% 超に跳ねる。門は要素数ではなく配列の字数で切る
  4. JSON 表現と文字列連結の差は小さく、文字列連結がやや落とす側。 read_thread は落ちる本数が 一致(スコアの差 0.01〜0.2)、recall_memory は 7 対 12。採るなら JSON 表現 — 差し替え(D4)で 落とすのは要素の JSON 表現そのものなので、採点に送ったものと落とすものが同一物になる
  5. Jev は構造の条件を読み分けなかった。 依頼文に authored_mode が master と書いたのに、 author_name が「Outcasts@管理者」のサーヴァント投稿 2 本(post 7 / 11)が 0.61 / 0.62 で残った。 配列型の売りは構造があることだが、Jev が見ているのは本文の語。構造の条件で落とす期待は持たない

3. 正味の収支(Spec 59 P4 実機記録 3 と同じ形)

read_thread の依頼 1 では 8,346 字(日本語・約 8,000 トークン)が落ち、Jev は 11,955 トークン。 gpt-5.6-terra の書き込み単価で節約 ≈ $0.020 対 Jev ≈ $0.0005 = 約 40 倍の黒字。 依頼 2 では 1,486 字で門に届かず、Jev 11,946 トークン($0.0005)が捨て金。 Spec 59 と同じ結論 — 効くかどうかは依頼文と要素の形が決め、門が赤字を止める。

4. 未決 5 点への向き(裁定は利用者。査読へ)

未決 測定が指した向き
1. 複数配列 直下の配列すべてを対象にし、門は要素数ではなく配列の JSON 表現が 1,000 字以上。next_hints のような小さい配列は自然に外れる。トップレベルが配列(包装なし)の実例は 0 件なので、その形は対象外として閉じる(印の置き場 B が使えない形を作らない)
2. 表現 JSON 表現(差は小さく、落とすものと同一物)
3. 印の置き場 B(トップレベルに _pruned 1 キー) — 未決 1 でトップレベルが必ずオブジェクトになるので置き場が定まる。複数配列を 1 つのキーで記述できる({"_pruned":{"id":"P2","arrays":{"schema_entries":{"dropped":[…]},…}}})
4. 差し替え 位置ベース。実物は整形あり(memoria / manuale)と素(outcasts)が混在し、再シリアライズは整形を必ず変える = 「圧縮した文字列以外は 1 バイトも変わらない」を破る。着手時に Spec 59 D4b の文字列リテラルの位置探索が再利用できるか数える
5. omitted PrunedRaw に種別を持たせ、印の文面で「要素」と書く。from / to は要素の index(段落と同じ意味の別名)

効き目の見積もりの訂正: rev1 は「+C で約 2 倍」と書いたが、C 1,584K のうち実際に効く形は outcasts の 989K(長い投稿・配列 1 つ)と memoria の semantic_matches(長い文字列)まで。 manuale__navigate は要素が短く、elyth は届かない。伸びるのは MCP 出力の 20〜25% ぶんで、 かつ依頼が狭いときだけ(P0 の依頼 1 と 2 の差)。

測っていないこと: browsermcp__* / admin-procedures__* の 157K(3%)の形 / 実機の manuale__navigate(21K 字・深い階層)で要素が 短いまま 4,000 字を超えたときの判定 / search の base64 画像(6,000 字超の要素として採点対象外に なるはずだが、実物で確かめていない)。

Notes

  1. Spec 59 との境界: あちらは「本文(と、本文を包んだ JSON)」、こちらは「構造そのものが データである JSON」。判定は 1 箇所で分岐する(prune.rs の入口)ので、2 つの Spec が 同じ関数を取り合わない形にすること

  2. RepeatGuard は圧縮前(Spec 59 D2 と同じ)。配列型でも変わらない

  3. 効き目の上限は D と E の分類次第で 2 倍動く → P0 で決着。D の最大は XML、E の大半は 3 段目の配列で、どちらも乗らない。本 Spec が足す母数は Spec 59 と同規模(「実測」の節)

  4. 査読 2 系統の採否(rev2・2026-09-22):

    # 指摘 採否 反映
    1-1 Goal 1 と D3 案 C が両立しない 採用 C を除外(D3)
    1-2 Goal 2 と再シリアライズが両立しない 採用 位置ベース一択(D4)
    1-3 Goal 5 と D3 案 A(型が揃わない) 採用 A を除外・Goal 1 に「型は揃ったまま」を足した
    1-4 D1 が対象の 38% を自ら落とす 採用 D1 を書き直し
    1-5 要素 5 件の門が字数に比例しない 採用 1,000 字の門へ(D1)
    1-6 トップレベル配列と案 B が衝突 採用 トップレベル配列を範囲外へ
    1-7 案 B は「無いキーを足す」= 凍結より一段ゆるい。_pruned 衝突が未定義 採用 + 裁定へ 衝突は諦める(D3)。キーを足すこと自体は未決 1
    1-8 D2 の「構造が見える」利点は実測で否定済み 採用 理由を「同一物」だけに(D2)
    1-9 PrunedRaw に kind が要る 訂正して採用 PrunedRaw は中身が段落か要素かを知らない(paragraphs: Vec<String> に逐語を入れるだけ)。区別は _pruned.note と配列ごとの id が担う(D5)。欄は足さない
    1-10 母数のすり替え 採用 P0 の母数で再計算(「実測」の節)
    2-1 複数配列で omitted { id, from, to } が配列を指せない 採用 配列ごとに P{n} を振る(D3・D5)。omitted は無改変
    2-2 トップレベル配列 + B の構文的破綻。用語がずれている 採用 タイトル・Goal 1・範囲外を書き直し
    2-3 位置ベース + キー挿入は整形で壊れる「リスクが極めて高い」 訂正して採用 挿入点は末尾 } の直前で、JSON は空白を無視するので構文は壊れない。壊れうるのはカンマだけなので規則を D4 に明記し、3 位置の fixture で留める。「極めて高い」は退け、_pruned 衝突の未定義は採った
    2-4 全部落ちた配列の振る舞いが未定義 採用 D6 を新設(触らない・[] を作らない)
    2-5 2 つの門が混同されている / 正味は配列ごとか全体か 訂正して採用 D7 で分けた。正味は全体(配列ごと案は採らない — モデルに届くのは全体)
    2-6 6,000 字の崖と base64 反証(半分) 崖は Spec 59 D6 の凍結で本 Spec は動かさない。20K の base64 は崖で自然に外れる(送らない)。6K 未満の base64 は未観測 — 機構は足さない(#47)
    2-7 複数配列の束ね方が未定義 訂正して採用 平坦化して prune::batch の 1 実装へ(D2)。「マッピングが複雑化」は Prepared に鍵が 1 つ増えるだけ
    2-8 D1〜D5 と P0 と未決の乖離 採用 Design を全面書き直し・rev1 は Notes 5 へ
  5. rev1 の Design の写し(P0 で前提が覆った側。何を覆したかが読めるように残す):

    Design(起票段階。決めどころを挙げるところまで)

    D1. どれを配列型と認めるか

    Spec 59 D4b と同じく閉じた条件にする。案:

    • トップレベルが配列、またはトップレベルのオブジェクトの直下に配列がちょうど 1 つ
    • その配列の要素が 5 件以上
    • 配列の JSON 表現が本文全体の 60% 以上
    • 入れ子の中は探さない

    実測の 3 例はこの形に収まる — {"posts":[…]} / {"query":…,"matches":[…]} / {"schema_entries":[…],"semantic_matches":[…],…}。ただし 3 例目(memoria__recall_memory)は 直下の配列が複数あるので、上の条件では落ちる(未決 1)。

    D2. 要素をどう採点するか

    1 要素 = Spec 59 の「1 段落」として ParagraphScorer へ渡す。渡すのは要素の JSON 表現か、 要素の中の文字列を連ねたものか(未決 2)。前者は構造が見えるぶん Jev の判断材料が増えるが、 キー名が毎回同じなので雑音にもなる。

    6,000 字を超える要素は採点せず残す(Spec 59 D4 と同じ)。

    D3. 落としたことをどこに書くか(最大の決めどころ)

    JSON の構文を壊さずに印を入れる方法が 3 つあり、どれも代償がある(未決 3):

    案 形 代償
    A. 配列の中に印の要素 [{…}, {"_omitted": 7}, {…}] 要素の型が揃わなくなる。読む側がパースしていれば壊れる
    B. トップレベルにキーを足す {"posts":[…], "_pruned":{"dropped":7,"id":"P2"}} 元の JSON に無いキーが生える。Goal 2 の逐語は要素についてだが、構造は変わる
    C. JSON の外に印を置く 【… 7 件を省略 …】\n{"posts":[…]} 本文全体が JSON でなくなる。Goal 1 と正面から衝突

    私の推し は B — 読む側がパースしても壊れず(未知のキーは無視される)、印の置き場が 1 つに定まる。 ただし「元の JSON に無いキーを足さない」を凍結する選択肢もありうる。

    D4. 差し替えの方法

    Spec 59 D4b は「元テキストの上で文字列リテラルだけを差し替える」形で、再シリアライズを避けた。 配列から要素を消す場合は同じ手が使えない(消すとカンマの位置が変わる)。案は 2 つ(未決 4):

    • 位置ベース — 要素の範囲を元テキストで特定し、その範囲とカンマを消す。他は 1 バイトも 変わらないが、JSON のスキャナを自前で書くことになる
    • 再シリアライズ — parse → 要素を落とす → to_string。実装は単純だが、整形・キーの順序・ 数値の表現が変わりうる(serde_json の preserve_order feature の有無を着手時に数える)

    D5. 戻り道

    Spec 59 の omitted { id, from, to } を共有する。from / to は配列の要素の index (Spec 59 は段落の index)。同じツールで 2 つの意味を持つことになるので、id が指す先が 本文か配列かを置き場(PrunedRaw)に持たせる(未決 5)。

未決

ゼロ。 rev2 の 2 点は 2026-09-22 に利用者が裁定した — (1) _pruned キーを足すことを認める(「圧縮した文字列以外は 1 バイトも変わらない」の射程は 配列型では「対象の配列の中身と _pruned の 1 キー」へ一段ゆるくなる。契約に明記) (2) 縮んだ効き目で進める(足す母数は Spec 59 と同規模。狭い依頼で 68%・広い依頼で 12%)。

rev1 の未決 5 点 → P0 と査読で全部向きが出た(D1 / D2 / D3 / D4 / D5)。