はじめに
ここまでの連載で、friend はかなり育ってきた。
- 短期記憶
- state 永続化
- summary
- foundation
- project memory
- consistency check
- memory hint
この時点で、思想としてはかなり面白いものになっている。
単なる API 直叩きの窓ではなく、記憶と原則を持つ相棒になりつつある。
しかし、ここで一つの現実が立ちはだかる。
面白いものを作るのと、毎日使えるものにするのは別である。
どれだけ構造が美しくても、
- keymap が増えて覚えにくい
- コードをコピーしたら変なマーカーが混ざる
- API コストが見えない
- 品質は良いが、使いすぎると財布に効く
となれば、結局は日常運用に耐えない。
この回で扱うのは、まさにその「最後の現実」である。
friend を 思想の産物 から 毎日使い続けられる道具 に変えるために入ってきたもの
- help
- clean copy
- usage / cost report
- model split
をまとめて振り返りたい。
これらは一見すると細部の話に見える。
だが、実際にはここが入って初めて、friend は本当に使えるものになった。
1. 機能が増えると、help が必要になる
friend は、育つにつれて当然機能が増えていった。
最初は単に
- 送る
- ログを見る
くらいで足りていた。
だが、後になると少なくとも次のような操作が出てくる。
- save
- reload tail
- older log 読み込み
- summary open
- summary refresh
- foundation open
- memory open
- consistency check toggle
- memory hint refresh
- usage report
- clean copy
これだけ増えると、さすがに覚えきれない。
しかも friend は、単なる補助機能ではなく、すでに一つの subsystem に近くなっている。
ならば当然、その subsystem には 自己説明能力 が必要になる。
ここで導入されたのが help だった。
発想としてはとても自然で、
- key 一覧
- 各レイヤの説明
- どこに何を書くか
- 典型的な workflow
を、その場で見られるようにする。
つまり friend はここで初めて、
自分の使い方を自分で説明できる ようになったのである。
これはかなり大きい。
なぜなら、内部構造が豊かになるほど、
それを人間が思い出せるようにする仕組みが必要になるからだ。
2. help は単なる親切機能ではなかった
最初は「キーが増えてきたから help があると便利かも」という、かなり自然な流れだった。
しかし実際に入れてみると、help はそれ以上の役割を持っていた。
friend の help が担ったもの
- key の一覧
- Foundation / Project Memory / Session Summary / State / Log の区別
- それぞれの意味
- Typical workflow
つまり help は単なるショートカット一覧ではなく、
friend 全体の層構造の説明書 に近かった。
たとえば、help の中にはこんな説明が入る。
- Foundation: global long-term principles
- Project Memory: project-specific long-term assumptions
- Session Summary: medium-term working summary
- State: short-term conversation chain
- Log: official interaction record
これによって、friend はかなり「見通しの良い存在」になる。
機能が増えるほど、内部構造が頭から抜けやすくなる。
その時、help は単なる補助ではなく、
構造を再認識するためのインタフェイス になるのである。
ここは地味だが、とても効いた。
3. 見た目は綺麗、でもコピーすると壊れる
friend の上段、strait-ai は pretty 表示がかなり効いていた。
- タイトル
- 区切り線
- Markdown 風の見た目
- 引用表示
- コードブロック
としては、かなり読みやすい。
実際、見た目の満足度は高かった。
だが、ここで妙な問題が出る。
AI の返答に含まれている Lisp コードをそのままコピーして貼ると、壊れるのである。
原因は、内部的には本文が blockquote 的に保持されていたことだった。
つまり表示上は綺麗に見えていても、実体には > が混ざっている。
表示では隠しているので気づきにくいが、貼るときに初めて露見する。
これがかなり厄介だった。
- 目には見えない
- でも貼ると壊れる
- 毎回手で
>を消すのは面倒 - しかも friend を使う頻度が上がるほど、痛みも増える
ここでわかったのは、
表示に成功していても、コピー UX が壊れていると日常利用ではつらい
ということだった。
4. clean copy を M-w に統合する
この問題に対して、最初に考えられたのは
- 表示方式そのものを変える
- 手で
>を消す - clean 抽出コマンドを作る
といった案だった。
結果的に、一番良かったのは
clean copy を M-w に統合する
という案だった。
これはかなり重要な判断だった。
もし専用キーにしていたら、
- 普通の Emacs 操作とは別のことを覚えなければならない
- UI が一段重くなる
一方、strait-ai バッファだけ M-w を friend 専用にすれば、
- 普通に選択して
- 普通に
M-w - 普通に
C-y
で済む。
つまり、UX 的にはまったく自然である。
内部的にだけ、
>## ...- ``
などの表示用マーカーを落として、clean text を kill-ring に入れる。
これで、見た目の良さは維持したまま、
コピーだけ賢くなる。
これはまさに
表示モデルを壊さずに、操作性だけ改善する
という、非常に良い修正だったと思う。
5. 親切さは、ショートカットよりも習慣に乗せた方がよい
help と clean copy の両方に共通していたのは、
「機能を増やす」よりも
既存の習慣の上に賢さを載せる
という方向だった。
- help は、必要なときにその場で見られる
- clean copy は、
M-wという既存操作に乗せる
これはかなり大事だった。
もし friend が新しいルールばかり要求してくると、
どれだけ中身が優れていても、使い続けるのがしんどくなる。
だからこそ、
- 既存の手触りを壊さない
- でも中では少し賢くする
という設計が効いた。
このあたりから friend は、
内部の多層記憶や原則だけでなく、
外側の操作感まで含めて相棒らしく なっていったのだと思う。
6. そして最後の現実問題:API コスト
friend がすこぶる調子良くなってくると、
別の意味での「困りごと」が出てくる。
API なので、使えば使うほどコストがかかる。
これは実に現実的な問題だった。
ChatGPT の Web 版とは違って、API に「使い放題」はない。
調子に乗って使っていると、ちゃんと財布に返ってくる。
だが、これはある意味、
friend が「本当に使えるものになってきた」証拠でもあった。
もし使いづらければ、そんなに連続して使わない。
しかし、実際には
- main conversation が快適
- summary も効く
- hint も気が利く
となると、自然に使用量が増える。
ここで必要だったのは、単に「節約しよう」ではない。
まずは、
何にどれくらいコストがかかっているのかを見えるようにする
ことだった。
7. usage log と usage report の意味
そのために入ってきたのが、
ai-usage.logusage report
である。
ここで記録するのは、各 API 呼び出しについての
- task_type
- model
- elapsed_ms
- input_tokens
- output_tokens
- total_tokens
- reasoning_tokens
- cached_tokens
などである。
この仕組みが入ると、
friend の改善は「なんとなく」ではなくなる。
たとえば、
- main は高いが納得感がある
- summary は意外と重い
- hint は軽い
- cached_tokens はまだ 0 が多い
といったことが、数字で見えるようになる。
これは非常に大きい。
なぜなら、ここに来て初めて friend は、
自分の賢さだけでなく、自分の運用コストも観測できる
ようになったからだ。
8. model split 第一段階は、とても現実的だった
usage report が入ると、次の問題もかなり明確になる。
- 何を高いモデルに残すべきか
- 何を cheaper model へ逃がせるか
ここで導入されたのが、第一段階の model split だった。
main conversation
gpt-5.4
summary refresh
gpt-5.4-mini
memory hint
gpt-5.4-nano
この分け方が良かったのは、
いきなり main conversation に手を入れなかったことだ。
main は、friend の「賢さ」の本体である。
ここを cheap 化し始めると、体感品質が崩れやすい。
一方で summary や hint は、補助タスクとしてかなり分離しやすい。
つまり、
- 価値の高い本体は守る
- 補助タスクから先に安くする
という、非常に現実的な順序になっていた。
この段階で得られた感触は、かなり面白かった。
summary と hint を cheaper model に逃がしても、
main conversation の「すこぶる調子が良い」感じはそのまま保てたからだ。
9. 「賢さ」と「持続可能性」の両立
ここで重要なのは、cost optimization は
単なるケチではない、ということだ。
friend の目的は、安さだけではない。
むしろ大事なのは、
使い続けられる賢さ
である。
どれだけ賢くても、高すぎて日常運用できなければ意味がない。
逆に、安くても main conversation の質が落ちてしまえば、friend らしさがなくなる。
だから usage report と model split の導入は、
単なるコスト削減ではなく、
- 品質
- 体感
- コスト
- 運用継続性
のバランスを取るための仕組みだった。
ここに来て friend は、
記憶や原則だけでなく、
現実世界で持続可能に使うための知恵 まで持ち始めたのである。
10. 第10回の意味
ここまで見てくると、この回で扱ったものは一見バラバラに見える。
- help
- clean copy
- usage report
- model split
だが、実は全部ひとつのテーマにまとまっている。
それは、
friendを “毎日使えるもの” にする
というテーマである。
help
内部構造を見失わないため
clean copy
日常の作業を邪魔しないため
usage report
コストを見えるようにするため
model split
賢さを保ちつつ安く回すため
つまりこの回は、
記憶と原則を持つ存在を、実際の道具として成立させる話
だったのだと思う。
思想的な設計だけでは、相棒にはなれない。
最後には必ず、
- 手触り
- 可視性
- 継続性
- コスト
という現実の壁がある。
第10回は、その壁を越えるための話だった。
おわりに
この回では、friend を日常運用に耐えるものにするために入ってきた、
- help
- clean copy
- usage / cost report
- model split
をまとめて振り返った。
ここまでで friend は、かなり本格的に
- 覚える
- 原則を持つ
- ツッコむ
- 気づかせる
- 使いやすい
- しかも現実に運用できる
存在になってきた。
次回はいよいよ最終回。
ここまで積み上げてきたものを振り返りながら、
friendは何になったのか
何を設計していたのか
を、少し落ち着いて総括したい。
次回予告
第11回
friend は何になったのか:記憶と原則を持つ相棒の設計
- API クライアントから collaborator へ
- 記憶層の意味
- 原則層の意味
-
friendの哲学 - 今後の展望
付録:本稿で扱った運用改善関連コード断片
help
(defun appsdk-friend-help ()
"Show Friend help."
(interactive)
(appsdk-log-show
(concat
"Friend Help\n\n"
"Layers\n\n"
" Foundation : ~/.friend/foundation.md\n"
" Project Memory : <project>/.friend/ai-interaction.memory.md\n"
" Session Summary : <project>/.friend/ai-interaction.summary.md\n"
" State : <project>/.friend/ai-interaction.state.json\n"
" Log : <project>/.friend/ai-interaction.log\n\n"
"Friend keys (strait-ai)\n\n"
(appsdk-help-from-annotations appsdk-friend-strait-mode-map))
"FRIEND HELP"
t))
clean copy
(defun appsdk-friend-copy-clean (&optional beg end)
"Copy cleaned Friend text to kill-ring.
If region is active, use region.
Otherwise copy current Friend block."
(interactive)
(let* ((bounds (if (use-region-p)
(cons (region-beginning) (region-end))
(appsdk-friend--current-block-bounds)))
(raw (buffer-substring-no-properties
(or beg (car bounds))
(or end (cdr bounds))))
(clean (appsdk-friend--clean-text raw)))
(kill-new clean)
(when (use-region-p)
(deactivate-mark))
(message "[AppSDK] friend: copied clean text")))
usage report
(defun appsdk-friend-usage-report ()
"Show Friend usage/cost summary report."
(interactive)
...)
model split のイメージ
(defcustom appsdk-friend-main-model "gpt-5.4"
"Model used for main Friend conversation."
:type 'string
:group 'appsdk-friend)
(defcustom appsdk-friend-summary-model "gpt-5.4-mini"
"Model used for summary refresh."
:type 'string
:group 'appsdk-friend)
(defcustom appsdk-friend-hint-model "gpt-5.4-nano"
"Model used for memory hint generation."
:type 'string
:group 'appsdk-friend)