すべての気候チャットボットは記憶がない。そこで私はBackboard+Geminiでステートフルな気候コーチ「Aura」を作った

Dev.to / 2026/4/19

💬 オピニオンDeveloper Stack & InfrastructureTools & Practical UsageModels & Research

要点

  • この記事では、多くの気候チャットボットがステートレスであるため、ユーザーの意思決定やコミットメントを時間をまたいで継続できず失敗すると主張しています。
  • 著者は Aura というステートフルな気候コーチを開発し、報告された習慣、目標、後退(つまずき)を永続的なメモリーストアに保存する仕組みを作りました。
  • 新しい会話の各ターンで Aura は過去の記憶を取得し、過去の行動やパターンに合わせて助言や振り返りを行います。
  • 製品は「Green Legacy」ダッシュボードとして提示され、エコロジーへのコミットメントの記録が更新され、保存履歴から算出される Impact Score も表示されます。
  • デモでは、後のターンで Aura が(例:自転車通勤や肉を減らす目標など)以前のコミットメントを思い出して、現在の挫折に対応できる様子が示されています。

すべての気候チャットボットは記憶喪失です。そこで私は、Backboard + Gemini 上のステートフルな気候コーチとして Aura を作りました。

作ったもの

「もっと緑の多い暮らしを助ける」カテゴリにある行動変容ツールのうち、これまで私が使ってきたものには共通の欠陥があります。忘れてしまうのです。ツールを開き、「運転を減らしたい」と告白すると、きちんと整った励ましの段落を出してくれます。タブを閉じます。翌日、そのツールはよそ者のようにあなたに挨拶します。あなたが木曜に自転車に乗ると約束したことを覚えていません。そもそも、あなたがそれでも運転してしまったことも覚えていません。先月の3週間うまくいっていたことも、次の週にこっそりやめてしまったことも覚えていません。記憶のないアドバイスはノイズです。

洞察は狭くて具体的です。個人レベルで気候行動を実行するうえで難しいのは情報ではなく、コミットメントの継続なのです。経験豊富なメンターは、インターネットより良い事実をあなたに提示するわけではありません。メンターは覚えているからです。先週あなたが言ったことを参照します。パターンを崩したときに気づきます。どれか一つを続けられたときには、静かに称えます。ステートレスなツールにはそれができません。そしてそれが、行動を変えられない理由です。

Aura はステートフルな環境コーチです。あなたが報告する習慣、コミットするあらゆる目標、後退したことを正直に認めたあらゆる出来事は、すべて永続的なメモリストアに書き込まれます。その後のあらゆるターンで、Aura は話す前にそのメモリを読み出します。今日の Aura との会話は、先月あなたがした会話によって形作られています。これが製品のすべてです。UI は Green Legacy ダッシュボードで、時間とともに更新されていく、エコロジカルなコミットメントの「日付付きの記録」です。Impact Score もまた、メモリストアに対する重み付きの見方にほかなりません。

デモ

このクリップは3つのターンを示します: (1)「今日は職場まで自転車で行きました」、(2)「今月は肉を食べる量を減らしたい」、(3)「正直、今日は大変でした — 店まで運転して行って、テイクアウトを買いました」。ターン3で Aura は、肉を減らすという先のコミットメントを明示的に思い出します。この一文が存在するのは、先のターンが Backboard に書き込まれていたからです。

なぜ Backboard がこのプロジェクトの魂なのか

ステートレスであることは、行動変容ツールの根本的な失敗です。トーンでも、情報密度でも、UX の磨き込みでもなく — ステートレスです。先週あなたが何を話したかを覚えられないチャットボットは、あなたをコーチできません。反応することしかできないのです。ツールを関係性に変えたいなら、永続化レイヤーが必要です。それは、構造化され、問い合わせ可能で、タイムスタンプ付きで、持ち運べて、長寿命である必要があります。Backboard はまさにそのためのものです。

したがって Backboard は、Aura のアーキテクチャ上の心臓部です。後付けの機能ではありません。このアプリの形は、「永続メモリが製品そのものだ」という事実によって決まっています:

  • すべてのユーザーターンは backboard.listMemories() から始まります。 メモリ全体のペイロードは、Gemini が呼び出される前にシステムプロンプトへシリアライズされます。Gemini は、単独のターンを見ません。
  • すべてのユーザーターンは backboard.saveMemory() の呼び出しで終わります。 新しい習慣、更新された目標、アセスメント(評価)、インパクトの変化分(ダルタ)はすべて、塊(blob)ではなく、個別でアドレス可能なメモリエントリとして永続化されます。
  • Impact Score はメモリの純粋関数です。 src/lib/impactEngine.js はメモリのリストを受け取り、{ score, breakdown } を生成します — データベースもキャッシュもありません。メモリ それ自体がデータベースです。

メモリスキーマ

各メモリエントリは aura:${userId}:memory:${key} という名前空間で区切られ、形は小さく意図的です:

{
  "key": "habit:1744973128345:0",
  "value": { "habit": "Cycled to work instead of driving", "source": "aura"},
  "createdAt": 1744973128345
}

key のプレフィックスは、エントリの種類(habit, goal, assessment, delta)、タイムスタンプ、そして序数をエンコードします。これにより、Aura が新しい種類の情報を学ぶたびにスキーマ移行を行わなくても、問い合わせ可能なストリームが得られます。

クライアント

// src/lib/backboard.js
export class BackboardClient {
  constructor() {
    this.userId = ensureUserId();
    this.prefix = `aura:${this.userId}:memory:`;
  }

  async saveMemory(key, value) {
    const entry = {key, value, createdAt:Date.now() };
    // ここに実際の SDK 呼び出しが入ります。インターフェースが契約です。
    return entry;
  }

  async listMemories() { /* ... */ }
  async getMemory(key) { /* ... */ }
  async clearMemories() { /* ... */ }
}

4つのメソッド、すべて非同期、すべて名前空間付き。これだけで、状態を持つコーチングアプリの土台として十分です。非同期契約は意図的です。ストレージのバックエンドを差し替えても App.jsx に一切触れずに済む、という意味だからです。

名前空間化と移植性

userId は初回のオープン時に 1 度だけ生成されます(crypto.randomUUID() 経由)そして保存されます。すべてのメモリキーにはこれが接頭辞として付けられます。これが、Backboard という意味でのメモリの 移植性 を生み出します。つまり、ブラウザに紐づくのではなく、アイデンティティに紐づくのです。実際の Backboard SDK が登場したとしても、同じ userId が、ラップトップから電話へ、共有デバイスのセッションへ、そして将来の、無関係な機能であってもユーザーの Green Legacy を参照しながら推論したい場合には、その同じメモリを運べます。名前空間はパスポートです。

実際に何が配線されているか

src/lib/backboard.js では 1つのクライアントの裏側に2つのバックエンドを用意しています:

  • BackboardBackend — 実際の Backboard REST API(https://app.backboard.io/api)、X-API-Key 認証、ユーザーごとのアシスタントパターン(aura-user-${userId} が初回呼び出し時に自動作成)、POST/GET/DELETE /assistants/{aid}/memories によるメモリ CRUD。公式 Backboard cookbook のパターンをそのまま使って構築されています。
  • LocalStorageBackend — ブラウザで支えられた、同一の非同期インターフェース。VITE_BACKBOARD_API_KEY がないときに使われ、また最初のネットワーク/CORS エラー時の自動フォールバックとしても働きます。ちょっとした一時的なエラーが、動いているデモを壊さないためです。

アクティブなバックエンドは構築時に決定されます。アプリのコードは、どちらが有効かを決して知りません。これは、抽象化が強制するために作られた契約です。有効なデプロイ先(https://aura-eternal-planet-guardian.vercel.app)は実際の Backboard API に配線されています。検証手順は BLOCKER.md を見てください。

脳としての Gemini

Backboard は覚えます。Gemini は、覚えられていることをもとに推論します。

Aura は gemini-2.5-flash を使い、429/5xx に対して 1 回だけリトライし、指数バックオフを行います。重要なポイントは、システムプロンプトが固定ではないことです。毎ターン、実際のメモリペイロードを注入したテンプレートになっています:

const systemPrompt = AURA_SYSTEM_PROMPT.replace(
  '{MEMORY_JSON_HERE}',
  JSON.stringify(memoryPayload, null, 2)
);
const {text } = await askGemini(messages, systemPrompt);

このたった 1 つの .replace() が、Aura を他のすべての Gemini チャットボットと分けています。毎ターン、Gemini にはユーザーの生態系としての履歴全体が、構造化された JSON として渡され、名前を明示して参照するよう指示されます。

契約の後半は 構造化された更新ブロック です。Gemini の文章による返信の後、それは次を出力します:

<aura-update>
{"newHabits": ["Cycled to work"], "updatedGoals": [], "impactDelta": 6, "assessment": null}
</aura-update>

アプリはこのブロックをレスポンスから解析し、文章部分をユーザーに表示し、構造化されたフィールドを Backboard に書き戻します。これが、Gemini がメモリストアに書き込む方法です。自由形式のノリではなく、型付きで監査可能なプロトコルとしてです。システムプロンプトが固定されているためパーソナリティは一貫し、スキーマが固定されているためメモリはきれいなままです。

Copilot について正直なメモ

今回のビルドにおいて、私は GitHub Copilot を実質的な意味で一切使いませんでした。このプロジェクトは空のフォルダから始めた単一の自律的なセッションで書かれており、IDE 統合の補完インターフェースがループの中にありません。Copilot は賞のカテゴリとして挙げられていましたが、正直に言うと、今回の提出内容を形作ることには影響していません。

なぜこれが地球にとって重要なのか

個人レベルでの気候アクションにおけるボトルネックは、意識ではありません――人々は知っています。意思でもありません――人々はやろうとします。問題は 日常生活の慣性に負けないための、長い時間軸にわたる持続的な行動変容 です。効く介入(食事、運動、断酒、資金)は、状態を持つコーチとの持続的な関係を通じて効きます。人間であれ、それ以外であれ。失敗する介入は、ツールが忘れてしまうから失敗します。Aura は、持続的なメモリ――具体的には Backboard――が、気候チャットボットをリアクティブなアシスタントから長期的なメンターへと変えるための欠けていた要素であることを示す概念実証です。メモリに賭ける環境面での理由は、UX 側の理由と同じです。あなたは、覚えていない人生は変えられない。

次に何をするか

  • Backboard の共有名前空間によるコミュニティ順位表。 個々の習慣を漏らさずに Green Legacy の連勝記録を比較できる、オプトインの近所コミュニティ。
  • リアルなカーボン API 連携。 Gemini が手作業で引用している数値を、ライブな情報源から得られる、習慣ごとの CO₂/水の相当量で置き換える。
  • 習慣の喪失検出と再エンゲージメント。 Backboard 上で定期的にチェックして途切れた連勝を見つけ、Aura をそっと送り出す――決して罪悪感で追い詰めず、常に具体的に。

テックスタック

  • React 18、Vite 5、Tailwind 3(状態管理ライブラリなし――useState + useReducer のみ)
  • Gemini gemini-2.5-flash を REST 経由(generativelanguage.googleapis.com/v1beta)で利用
  • Backboard(4メソッドの BackboardClient 抽象化経由。BLOCKER.md を参照)
  • Recharts の radial-bar、Lucide のアイコン、Google Fonts から Inter + Fraunces
  • クライアントサイドのみ。静的ビルドとして Vercel にデプロイ可能

リンク

Auraは記憶します。Geminiは推論します。あなたのレガシーは成長し続けます。