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?

第9回 「最近こんなことよく言ってない?」:memory hint の遊び心と実用性

0
Posted at

はじめに

前回、friend は foundation / project memory / summary を背景に、
現在の発話へやんわりツッコミを入れられるようになった。

ここまで来ると、friend はかなり面白い存在になってくる。
ただ返答するだけではない。
記憶を持ち、原則を持ち、それと照らして少しだけ異議を挟む。
かなり相棒らしい。

だが、使い続けていると、さらに別の問いが見えてくる。

project memory は、どうやって更新すればよいのか。

summary は、最近の流れだから更新しやすい。
refresh も save 時更新も終了時確認もある。
foundation は逆に、めったに変わらないから、静的でよい。

しかし project memory は、その中間にある。

  • foundation ほど不変ではない
  • summary ほど流動的でもない
  • それでも、時々は更新したい
  • しかも、勝手に書き換わるのは怖い

この微妙な位置が、かなり難しかった。

そこで出てきたのが、memory hint である。
これは project memory を自動更新する仕組みではない。
むしろ逆で、自動更新しないために必要な仕組み だった。

本稿では、friend

  • 自動昇格
    ではなく、
  • 自動気づき支援

を選んだ理由と、その遊び心と実用性について書いていきたい。

1. project memory は更新したいが、自動更新は怖い

project memory は、summary とは違う。

summary は最近の流れなので、多少書き換わっても、あとでまた refresh すればよい。
多少ぶれても、基本的には「最近」を圧縮し直せば回復する。

しかし project memory はもっと重い。
そこに置くのは、

  • その project で長く効く前提
  • 今後の提案や判断に影響するルール
  • summary に毎回書くと邪魔だが、消すと困るもの

である。

だから project memory が勝手に変わり始めると危ない。

  • いつの間にか前提が変わる
  • 何を昇格させたのか分からない
  • 実は一時的だった論点が memory に居座る
  • 後から見た時に、なぜそれが入ったのか分からない

summary の自動更新は「最近の流れの再圧縮」だから比較的安全だが、
project memory の自動更新は「長期前提の書き換え」なので、意味が重い。

だから、少なくとも最初の段階では

project memory は自動では書き換えない

という方針が自然だった。

しかし同時に、完全手動だけでは、なかなか育たない。
ここに memory hint の必然性があった。

2. 頻度だけでは昇格を決められない

最初に考えたのは、かなり素朴な案だった。

  • よく出てくるもの
  • summary に何度も出るもの

を project memory 候補にすればよいのではないか、という発想である。

たしかに、頻度は重要である。
何度も出てくるということは、それだけその project で意味を持っている可能性が高い。

しかし、少し考えるとこれだけでは足りないと分かる。

なぜなら、「よく出てくる」ものには、少なくとも三種類あるからだ。

1. project memory に上げるべきもの

  • project 固有
  • 安定している
  • 今後の判断に効く

2. foundation に上げるべきもの

  • project を超えて有効
  • より一般的
  • 作業人格や設計原則に近い

3. summary に残すべきもの

  • 最近たまたま hot
  • まだ流動的
  • 一時的な論点
  • bugfix や局所対応にすぎない

つまり、頻度は必要条件ではあっても、十分条件ではない。
ここで project memory を厳密に自動判定しようとすると、かなり苦しい。

  • スコープ
  • 寿命
  • 規範性

を評価したくなるが、それを毎回機械的に決めるのは重いし、何より運用が面倒になる。
「毎日使う friend」に入れるには、もう少し軽くしたかった。

3. “自動昇格”ではなく“自動気づき”へ

ここで発想が転換した。

project memory の問題を、

  • どう自動で昇格させるか

ではなく、

  • どう自然に気づかせるか

として捉え直したのである。

この違いはかなり大きい。

自動昇格

  • 判断が強すぎる
  • 誤昇格が怖い
  • いつ書き換わったか追いにくい

自動気づき

  • 最後の判断は人間
  • 間違っても “提案” で済む
  • うるさければ止められる
  • 徐々に育てられる

つまり memory hint は、project memory の自動更新機構ではなく、
人間が更新に気づくための伴走機構 として設計された。

この設計が、とても friend らしかった。

全部を勝手にやるのではない。
だが人間が忘れやすいところで、少しだけ肩を叩いてくれる。
「最近これ、よく出ていませんか?」という感じである。

4. friend の口調で示唆する

memory hint が面白くなったのは、
単なる candidate list を出すのではなく、
strait-aifunny-guy に軽く言う感じ にしたことだった。

たとえば、次のような口調である。

  • なんか最近、AppSDK root をかなり重視していませんか?
  • 最近、.friend/ 配下に寄せる前提が繰り返し出ている気がします。
  • これ、project memory に上げても良さそうですね。

この口調はかなり重要だった。

もしこれが

  • memory candidate #3 detected
  • promote to memory?
  • confidence score 0.78

のような、いかにも機械的な出力だったら、
たぶん使い勝手はだいぶ違ったと思う。

もちろん内部的には、ある程度の基準を持っている。
しかし表に出す時は、あくまで軽い気づきの形にする。
この「相棒っぽさ」は、遊び心のようでいて、実はかなり実用的だった。

なぜなら、人間は厳密なスコアより、

あ、たしかに最近それ多いな

の方が自然に受け止めやすいからである。

5. hint の材料は summary で十分だった

ここで面白いのは、memory hint の材料として、
結局 very raw な log 全文よりも、summary を中心に見る方がちょうどよかった ことだ。

たとえば conceptually には、こんな source text を作る。

(defun appsdk-friend--memory-hint-source-text ()
  "Build source text for project-memory hint generation."
  (let ((foundation (or (appsdk-friend-foundation-read) ""))
        (memory (or (appsdk-friend-memory-read) ""))
        (summary (or (appsdk-friend-summary-read) "")))
    (concat
     "# Foundation\n\n"
     (if (string-empty-p foundation) "(none)\n" foundation)
     "\n\n"
     "# Project Memory\n\n"
     (if (string-empty-p memory) "(none)\n" memory)
     "\n\n"
     "# Session Summary\n\n"
     (if (string-empty-p summary) "(none)\n" summary))))

つまり、

  • foundation に何があるか
  • project memory に何があるか
  • summary に最近何が書かれているか

を背景にして、「まだ memory にない recurring theme があるか」を見る。

ここで log 全文を直接使わないのは、手抜きではなく設計判断だった。
summary の時点ですでに recent log はかなり圧縮されている。
なら memory hint も、その summary を材料にした方が、ノイズが少なくてちょうどよい。

つまり memory hint は、
summary の上に乗る、さらに一段高い気づき支援 だったのである。

6. 自動発火のタイミングも重要だった

memory hint をいつ出すかも、かなり大事だった。

毎ターン出していたら、うるさい。
思いつきや冗談にまで反応しやすくなる。
何より、使っている側が「また何か言っているな」という気分になる。

そこで結局、
summary refresh の後 に memory hint を走らせるのが自然、という設計になった。

これは非常に理にかなっている。

  • summary refresh は recent log を要約した結果
  • その summary がまとまった直後なら
  • recurring theme も見つけやすい
  • 人間側も「今ここで流れが一段まとまった」と感じやすい

つまり、summary refresh が
memory hint の観測点 になっている。

この構造はきれいだった。
いきなり memory を考えるのではなく、
まず summary がまとまり、その上で recurring theme をやんわり示す。
この段差が、friend の記憶体系をより自然にしていた。


7. うるさくしない工夫

memory hint のもう一つの重要な条件は、
同じことを何度も言わない ことだった。

これは実運用上かなり大きい。

たとえば、

  • 「AppSDK root を single source of truth にする」
  • 「AI artifacts を .friend/ に寄せる」

のような recurring theme は、しばらくの間 summary に何度も現れる。
そのたびに毎回

これ、project memory に上げても良さそうですね

と言われたら、さすがにうるさい。

だから memory hint には、

  • cooldown
  • last hint の記録
  • duplicate suppression

が入ることになった。

概念的には、こういう状態を持つ。

(defvar appsdk-friend--memory-hint-busy nil
  "Non-nil while project-memory hint generation is running.")

(defvar appsdk-friend--turns-since-memory-hint 0
  "Number of completed turns since last shown project-memory hint.")

(defvar appsdk-friend--last-memory-hint nil
  "Last shown project-memory hint text.")

このあたりは、地味だが効く。
memory hint は、当てること以上に うるさくしないこと が大事だったからだ。

ここでもまた、“良い塩梅” の思想が働いている。

8. memory hint は summary と foundation の橋だった

ここまでくると、memory hint は単なる便利機能ではなく、
summary と foundation / memory の間をつなぐ橋 に見えてくる。

  • summary は最近の流れ
  • foundation はかなり不変の原則
  • project memory はその中間にある長期前提

この三層の間で、何をどこへ昇格させるべきかは、最初から完全には分からない。
だが、memory hint はその曖昧さを、無理に解消しようとしない。

代わりに、

最近これ、何度も出ていませんか?

とだけ言う。

これが絶妙だった。

  • それは summary に残すべきものかもしれない
  • project memory に上げるべきかもしれない
  • もっと長く見れば foundation 候補かもしれない

その最終判断は人間に残しつつ、
でも「見落としやすい recurring theme」だけは拾ってくれる。

つまり memory hint は、昇格ルールそのものではなく、
昇格を考え始めるための小さなノック だったのである。

9. 遊び心が、妙に効く

ここで改めて思うのは、
memory hint はかなり遊び心のある機能なのに、
妙に実用的でもあるということだ。

普通に考えれば、project memory 更新問題は、
もっと硬い方向へ行きやすい。

  • candidate list
  • score
  • promotion workflow
  • review queue

もちろん、それも理屈としては正しい。
だが、毎日使う friend に入れるには、少し重い。

一方 memory hint は、かなり柔らかい。

  • なんか最近……
  • これ、多いですね
  • そういえばそうかも

という感じである。

なのに、実際にはかなり効く。
なぜなら、日常的な道具に必要なのは、
厳密な自動昇格よりも、自然な気づき の方だからだ。

ここに friend の面白さがある。

技術的に正しそうなものをそのまま入れるのではなく、
使っていてちょうどよい形式に翻訳する。
memory hint は、その翻訳がうまくいった例だったと思う。

10. friend は原則の成長まで支援し始めた

consistency check の時点で、friend はすでに

  • 原則と現在の発話を照らす
  • 軽く異議を挟む

ことができるようになっていた。

memory hint では、そこからさらに一歩進んで、

  • 今の recurring theme が
  • project memory に育つべきものかどうか

を、少しだけ気づかせるようになった。

つまり、friend はここで初めて、

原則の保持だけでなく、原則の成長まで支援する

ようになったのである。

もちろん、それを自動で確定するわけではない。
ただ、人間が「そういえばそうだな」と気づき、必要なら memory refresh を考える。
そこまで持っていけるだけで十分だった。

この段階までくると、friend はかなり「相棒」らしい。
ただ覚えるだけでなく、
こちらが見落としている recurring theme に、少し先回りして気づかせてくれるからだ。

おわりに

第9回では、project memory 更新問題に対して
自動昇格ではなく、自動気づき支援として memory hint を導入した経緯を書いた。

ここで重要だったのは、

  • project memory を勝手に書き換えなかったこと
  • 頻度だけで無理に判定しなかったこと
  • summary refresh の上に memory hint を置いたこと
  • 遊び心のある口調にしたこと
  • 同じ hint を連発しないようにしたこと

だった。

この段階で friend は、
単に記憶と原則を持つだけでなく、
それらをどう育てるかについても伴走し始めた と言える。

次回はいよいよ最終回。
API クライアントとして始まった friend が、
help、clean copy、usage report、model split を含めて、
どうやって IDE 内 collaborator へ育っていったかを総括したい。

次回予告

第10回

Making of friend interface:APIクライアントから IDE 内 collaborator へ

  • help
  • clean copy
  • usage / cost report
  • model split 第一段階
  • friend は何になったのか

付録:本稿で扱った memory hint 関連コード断片

memory hint source

(defun appsdk-friend--memory-hint-source-text ()
  "Build source text for project-memory hint generation."
  (let ((foundation (or (appsdk-friend-foundation-read) ""))
        (memory (or (appsdk-friend-memory-read) ""))
        (summary (or (appsdk-friend-summary-read) "")))
    (concat
     "# Foundation\n\n"
     (if (string-empty-p foundation) "(none)\n" foundation)
     "\n\n"
     "# Project Memory\n\n"
     (if (string-empty-p memory) "(none)\n" memory)
     "\n\n"
     "# Session Summary\n\n"
     (if (string-empty-p summary) "(none)\n" summary))))

memory hint prompt

(defun appsdk-friend--memory-hint-prompt ()
  "Return prompt text for project-memory hint generation."
  (concat
   "Look at the Foundation, Project Memory, and Session Summary.\n\n"
   "Task:\n"
   "- Detect whether there is one recurring, project-specific theme or rule\n"
   "  that is appearing in recent work but is not yet clearly captured in Project Memory.\n"
   "- If such a theme exists, output exactly one short gentle hint in Japanese.\n"
   "- The hint should sound like a soft nudge, not a command.\n"
   "- Do not suggest Foundation updates here.\n"
   "- Do not mention this policy.\n"
   "- If there is no good hint, output exactly: NONE\n\n"
   (appsdk-friend--memory-hint-source-text)))

memory hint state

(defvar appsdk-friend--memory-hint-busy nil
  "Non-nil while project-memory hint generation is running.")

(defvar appsdk-friend--turns-since-memory-hint 0
  "Number of completed turns since last shown project-memory hint.")

(defvar appsdk-friend--last-memory-hint nil
  "Last shown project-memory hint text.")

memory hint refresh

(defun appsdk-friend-memory-hint-refresh ()
  "Try generating one gentle project-memory hint."
  (interactive)
  (when appsdk-friend--memory-hint-busy
    (user-error "Friend memory hint generation is already running"))
  (setq appsdk-friend--memory-hint-busy t)
  (funcall appsdk-friend-request-function
           (appsdk-friend--memory-hint-prompt)
           #'appsdk-friend--memory-hint-on-reply))
0
0
0

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?