blog.aspooky.dev

二重顎とOOMKill

/ diary, gym, go, llm

東京駅の近く、五畳一間の物置くらいの我が家から徒歩二分のジムに通い始めた。

きっかけは、盆休みの帰省で久しぶりに会った家族や友人に二重顎を馬鹿にされたことだ。 広島生まれで、東京に出てきて1年4か月くらいが過ぎたが自分では気づかないうちに今の私は太っているらしい。 ぶっちゃけていえば、気づいていた。上司の写真に写る私、寝起きの鏡に映る私、最寄り駅の階段に設置されたカーブミラーに映る私。明らかに認識の私よりは、二回りは巨大な私がそこにいた。

そんなことから、ジムに行くことを決意した私は、まずGoでアプリケーションを開発した。 目標管理ツールである。一点だけ、目標設定さえしてしまえばあとはLLM(職場の同僚がAIという言葉を忌み嫌っているため、ここではAIの中でも大規模言語モデルを指すLLMとした。)が管理してくれる代物だ。 長期目標、デイリー目標などのentityはまずざっくり定義して、コーディングエージェントにCRUDを書かせた。usecaseは最初から全部洗い出さず、フロントエンドを先に書いて必要になるたびに足していった。Goのinterfaceは事後定義できるので、要件が固まる前から実装に着手できたのもよかった。usecaseごとに必要な分だけの小さなinterfaceをその場で定義していく。

// src/usecase/ai/plan_goal.go
type ConversationMessageReader interface {
    FindByConversationID(conversationID string) ([]*entity.ConversationMessage, error)
}

ConversationMessageReaderは使う側のusecaseパッケージに置かれた1メソッドだけのinterfaceで、追う範囲がその場で完結する。だからコードリーディングもかなりイージーだった。

アプリケーションについては、ものの二日程度で完成し早速そこに目標体重と期限を管理し始めた。デイリータスクはLLMが用意したもので、1600〜1700kcal、タンパク質120g、歩数8000〜10000歩といった数値が並んでいた。私はそれを実行するだけである。

LLM呼び出しは2種類ある。ひとつは目標・タスクの生成で、GeminiのresponseSchema(responseMimeType: application/json)でJSON出力を縛っている。プロンプトでお願いしてパースする方式ではない。

var planSchema = map[string]any{
    "type": "object",
    "properties": map[string]any{
        "goals": map[string]any{"type": "array", "items": goalSchema}, // goalSchemaは略
    },
    "required": []string{"goals"},
}

実際に返ってくるのはこういうJSONだ。

{
  "goals": [
    {
      "title": "二重顎から卒業する",
      "achievement_condition": "体重65kg以下を3日連続で記録",
      "end_date": "2026-11-30",
      "mode": "strict",
      "tasks": [
        { "content": "タンパク質120gとる", "start_date": "2026-08-18", "end_date": "2026-11-30", "rule_type": "interval", "interval_days": 1 }
      ]
    }
  ]
}

goalにはmode(strict/want)がある。strictは未達成の日があると期限を1日後ろにpostponeし、その回数を記録する。wantは期限が動かない。

もうひとつはデイリータスクの進捗コメントで、こちらはJSONで縛っていない。達成率・先延ばし回数・直近のメモをテキストのプロンプトに詰めて渡し、3〜4文の自然文で返させている。精度の検証はまだしていない。トリガーは自動化していなくて、私がボタンを押した時だけ動く。

今日も、LLMに指示されて契約した近くのジムに足を運び、カニエ・ウェストのドキュメンタリー『jeen-yuhs』を見ながら(LLMの指示ではない)、タスクを遂行した。

このあまりにもディストピアライクな日々が心地が良い。三日坊主の私は長期的すぎるタスクを置きがちで今ここでなにをすべきか、それが目標に通じているのかが不安だった。strict目標のpostponeは、未達成のたびに期限が1日ずつ後ろへずれてその回数が記録され続ける仕組みでもある。要するに、自分のサボりを機械に記録させている。 この管理方法は、着実に目標に一日一歩近づけている実感を得られる。運動もよい。呼吸が荒くなってメモリが逼迫してくると、あらかじめoom_score_adjを盛ってある雑念プロセスたちから真っ先にOOMKillされていく。気づけば一つの物事だけが生き残っている。

目標を諦めるためのabandonedというstatusも用意してある。LLM自身がそこに遷移させたことは、まだ一度もない。

こんなにも快楽が得られるとは思わなかった。つづける。