WOS を選ぶ理由

AI エージェントのための長期記憶。

WOS は記憶 API です。ユーザーの記憶を一度保存すれば、あとはクエリごとに関連する記憶だけを呼び出して、モデルのプロンプトに渡せます。

検索は純粋にセマンティックで、キーワードや BM25 のマッチングは一切ありません。そのためリコール品質は言語を問わず同一です。どれだけ保存しても各クエリは小さく上限のあるコンテキストを返し、保存された記憶の上でモデルが実行されることはありません。

主要な操作

  • store - ユーザーの記憶を保存します。
  • recall - クエリに関連する記憶を取得します。これが中心となる呼び出しです。
  • search - 保存済みの記憶に対する生のセマンティック検索です。
  • supersede - 古くなった記憶を更新または置き換えます。
  • forget - 単一の記憶、またはユーザー全体を削除します(GDPR 対応)。
左のセクションを選択すると 各トピックの詳細をご覧いただけます。
モデル

3 つのモデル、ひとつの系譜。

WOS のモデル名は、人類が歴史の中で知識を残してきた手段に由来します - Tablet、Scroll、Book。石板、巻物、綴じられた本。後のモデルほど、エージェントにできることが増えていきます。

Tablet

提供中
石に刻む · 保存と呼び出し

記憶を刻み、呼び出すための軽量・高速・低コストな方式。すべてのモデルはこの土台の上に築かれます。

Scroll

提供中
巻物を広げる · LLM 支援リコール

言語モデルを加えて質問をより深く読み取り、より充実したコンテキストを持ち帰ります。散らばった手がかりが 1 ピース欠けることなく、まとまって戻ってきます。

Book

次期
製本と索引 · 自己ルーティング

自ら正しいページを開きます - その瞬間に必要な記憶とツールを選び、使うほど鋭くなっていきます。

Tablet 1 の完全なベンチマークレポートはベンチマークページにあります。

コスト

WOS に払うのは $2。LLM 側でその何倍も節約。

WOS がクエリごとに LLM へ渡すのは約 1,200 トークン - 上限があり関連性の高いスライスです。全履歴を毎回プロンプトに詰め込む場合との差は非常に大きく、履歴が増えるほど広がります。

クエリ 1,000 件あたりの LLM コスト Tablet 1 基準
ユーザー履歴100K
クエリ数 / 月1,000
使用する LLM
45× 低コスト - 月 $244 の節約
WOS なし$250.00
WOS あり$5.50

WOS に使う $1 ごとに、LLM 側で ~$98 を節約できます。履歴が大きいほど、モデルが高価なほど、ROI は大きくなります。

節約が生まれる仕組み

  • WOS なしでは、毎回のプロンプトに全履歴を詰め込みます - 100K tokens × $2.50/1M = $0.25 がクエリ 1 件ごとに(GPT-4o の入力単価基準。Opus 級モデルでは約 2 倍)。
  • WOS ありでは、取り込みは一度だけ($2/1M)。以降の各クエリは小さな取得($3/1M × 1,200)と、約 1,200 トークンだけを読む LLM のコストで済みます。
  • LLM が読むトークンが少ないほど、支払いは減ります。そして WOS は、記憶が増えてもその数を一定に保ちます。
コンテキスト圧縮率 = 履歴 ÷ 投入トークンであり、コストの倍率ではありません(コストは上の計算機で)。 25K → 21× · 100K → 83× · 200K → 167×.
多言語

どの言語でも、同じ精度。

検索は純粋なセマンティックで、埋め込みのみ。キーワードや BM25 のマッチングはゼロです。だからユーザーが日本語、中文、Español、English のいずれで書いても、リコール品質は同一です。

BM25 のような字句マッチングは、特定の言語のかたち - 形態素、分かち書き、文字体系 - に合わせて調整されています。多言語のストアでは、それが言語ごとの検索品質のばらつきになります。WOS は字句マッチングを一切使わないため、すべての言語が同じ経路を通ります。

ひとつのストアに 3 言語を同時に

ストアごとに言語を選ぶ必要はなく、自由に混在できます。以下では 1 人のユーザーの記憶に日本語・英語・スペイン語が同時に入っており、どの質問も言語に関係なく正しい記憶を見つけています。これは本番 API に対する実際のやり取りです:

# one user, three languages stored together
mem.add("彼女はコーヒーより紅茶が好き", user_id="alice")                      # Japanese
mem.add("she works at a design studio in Brooklyn", user_id="alice")       # English
mem.add("A ella le encanta hacer senderismo los sábados", user_id="alice")  # Spanish
実際の結果 - 各質問はそれぞれ別の言語をまたいでいます
"¿Qué bebe ella?"               -> 彼女はコーヒーより紅茶が好き
"what does she do on weekends?" -> A ella le encanta hacer senderismo los sábados
"彼女の仕事は?"                  -> she works at a design studio in Brooklyn

翻訳ステップも、言語検出も、言語ごとの設定もありません。記憶と質問は言語ではなく意味で配置されます - 意味が一致すれば、言語は関係ありません。

ここで 3 言語なのは、ページに収まる分だけだからです - 「対応言語リスト」というものは存在しません。同じライブテストは中文、Русский、العربية の記憶でも通過しており、すべて本番 API で検証済みです。

キーワードを意図的に禁止した理由

BM25 のような字句スコアリングは、一部の言語の検索を他の言語より強めてしまい、ひとつのストアに多くの言語が入る場面では妨げになります。そこでエンジンから完全に取り除き、このルールをコードレビューでも徹底しています。経路に字句スコアリングがひとつでもあれば、リコール品質は言語によって変わってしまうからです。

LongMemEval は英語のみのベンチマークで、多言語リコールは測定しません。上のデモは、本番 API に対して直接検証できる方法です。
アーキテクチャ

記憶の上でモデルは動きません。

保存は原文のまま、エンジンは埋め込みで検索します - 安価で、高速で、決定的です。保存された記憶の上でモデルが実行されることはありません。Tablet はモデルを一切使わず、Scroll と Book はより強い結果のためにエンジンの周囲にモデルを加えますが、モデルが見るのはクエリだけで、保存内容は決して見ません。

  • 決定的なエンジン。同じクエリには毎回同じ記憶を返します - ベンチマークの分散がリーダーモデル由来のみである理由です。
  • スケールしても安価。保存にも取得にも生成コストがかからないため、記憶が増えても請求はストレージに比例します - モデル使用量ではありません。

あなたの言葉を、そのまま

よくある設計のひとつに、書き込み時に言語モデルを走らせてテキストから「事実」を抽出・書き換えるものがあります。この設計は 3 つを犠牲にします。書き込みごとの生成コスト、追加のレイテンシ、そして原文ではなくモデルの言い換えを保存すること。WOS は逆のトレードを選びます - 言われたことを変えずに保存し、読み取り時に原文を手にしたあなたの LLM に解釈させます。

WOS でないもの:自分で運用するベクトル DB でも、組み立てる必要のある RAG フレームワークでもありません。保存されたデータの上でモデルが実行されることはなく、その経路は純粋な埋め込みです。Scroll と Book はより強い結果のために言語モデルを使いますが、見るのはクエリだけで、保存された記憶は決して見ません - あなたのデータで学習することも、収集することもありません。
実証

67.5%。実測で、再現可能。

BEAM 1M で 67.5%。独立した 5 回の実行の平均(σ 0.22%、選り好みなし)で、gpt-4.1-mini がベンチマーク自身の判定プロンプトで採点しています。

同じベンチマークでも、点数は採点手順で大きく変わります。判定者、プロンプト、そして検索層に何を許すかです。私たちは著者のリポジトリが同梱する判定者で採点し、その判定プロンプトを書かれたまま使い、試験に合わせて何も変えません。ハーネス、採点コード、リーダープロンプトを公開しているので、誰でも 67.5% をそのまま再現できます。

プロトコルを 1 つの表で

項目私たちのやり方
データセットBEAM 1M - 会話 35 件、74,630 ターン、記憶 220 万件、700 問
ジャッジgpt-4.1-mini、temperature 0。BEAM 自身の判定プロンプトをそのまま実行します。著者リポジトリの既定値であり、私たちが選んだ判定者ではありません
実行回数独立した 5 回の実行。全スコアを公開し、平均を報告(σ 0.22%)
リーダーリーダーモデルとプロンプトを固定し、原文のまま公開

誠実さを支えるもの:サードパーティのジャッジ、無修正で公開されたリーダープロンプト、純粋にセマンティックな検索、そしてベストだけでなく全実行の報告。検索エンジンは決定的で、もう一度実行しても同じ記憶が返ります。

より難しいベンチマークへ登り続けます

私たちは、まだ制覇していない最も難しい標準ベンチマークでテストします - 数字はすべての WOS モデルを通じた最高記録で、より良いモデルが出るたびに更新されます。94% を超えたら、さらに難しいベンチマークへ進みます。

BEAM 1M挑戦中
Tablet67.5%
gpt-4.1-mini 判定 · 5 回実行の平均卒業ラインは 94%
以前のベンチマーク LongMemEval-S クリア済み
Tablet95.7%
Scroll92.3%
GPT-4o ジャッジ · 全 WOS モデル中のベスト卒業ラインは 94%
完全なレポートを見る
料金

モデルごとに 2 つのトークン単価、
加えてリクエストあたり $0.0001。

100 万トークンあたりの単価に、リクエストごとの一律 $0.0001 を加えた従量課金です。サブスクリプションなし、ストレージ賃料なし、記憶数の上限なし。支払いが発生するのはエージェントが書き込むか読み取るときだけで、覚えている分には一切かかりません。

モデル入力 / 1M出力 / 1M
Tablet$2$3提供中
Scroll$4$8提供中
Book--未定
  • リクエストあたり $0.0001。トークン使用量に加えて、すべての API 呼び出しにかかる一律の手数料です。
  • ストレージは無料。取り込み時に一度支払えば、保持は無料です。件数制限も保持期間の制限もありません。
  • 私たちは保管するだけ。学習にも利用にも閲覧にも使いません。エージェントの記憶はあなたのものです - 私たちは取り出せるように整理するだけです。
  • Tablet がこの価格である理由:そのエンジンはモデルを動かさないため、私たちのコストは埋め込みとディスクであって GPU ではありません。Scroll と Book はモデルを加えており、その分が価格差になります。
他の課金モデルは保存量に月額を課したり、プランごとに記憶数の上限を設けたりします。WOS は保存データには、量にも期間にも関係なく課金しません。

利用ティア別のレート制限 →

開発者向け

呼び出しは 3 つ: store、recall、answer。

API はひとつ。recall() は短期記憶・長期記憶・周辺コンテキストを 1 回の往復で返し、そのままプロンプトに入れられます。

1

保存

add() で事実や会話を保存します。ユーザーの言葉、アシスタント自身の言葉(speaker "me")、名前付きの人の言葉のどれでも。取り込み時に埋め込まれ、LLM 呼び出しはありません。

2

呼び出し

recall() は短期 + 長期 + コンテキストを 1 回の呼び出しで返します - 上限のある固定サイズのコンテキストです。

3

回答

その限られたコンテキストをあなたの LLM に渡すだけ - プロバイダーは自由、キーはあなたのもの。

from wontopos import Client
mem = Client(api_key="wos-...")
mem.add("she prefers tea over coffee", user_id="alice")
mem.add("I suggested the jasmine tea", user_id="alice", speaker="me")  # its own words
# one call: short + long + context
ctx = mem.recall("what does alice drink?", user_id="alice")

記憶には話者があります。既定はユーザーの言葉で、speaker "me" はアシスタント自身の発言を、"Bob" のような名前はユーザーの周りの誰の発言かを記憶します。人単位で想起できるようになります。

話者はストアと同じく明示的です。先に人を登録し、その名前で保存します。タイプミスが静かに新しい人になることはありません。ストアあたりまず 50 人まで登録でき(順次拡大予定)、"me" は登録も数えられることもありません。
クイックスタート

5 分で最初のリコールを。

キーひとつ、インストール 1 行、呼び出し 3 つで、エージェントに記憶が備わります。このページのスニペットはすべて実際に実行したもので、レスポンスは原文のまま掲載しています。

1

API キーを取得

コンソールで作成します。wos-live- で始まる 155 文字のキーは一度だけ表示されます。環境変数に保管し、コードには決して書かないでください。

2

インストール

pip install wontopos        # Python
npm install wontopos        # TypeScript / JavaScript
cargo add wontopos          # Rust
# curl - nothing to install, just set WOS_API_KEY
# latest: SDK v2.2.32 · MCP v1.0.15
3

ストアを作成し、保存と呼び出しを

ストアとは、読み書きの単位となる user_id です。ストアは明示的で、まず作成し(下の呼び出し)、その中で保存・呼び出しを行います。保存 - 取り込み時に埋め込み化、LLM 呼び出しなし。呼び出し - 短期 + 長期 + コンテキストを 1 往復で。

from wontopos import Client

mem = Client(api_key="wos-live-...", user_id="alice")  # set the store once
mem.create_store()              # create it (stores are explicit)
mem.add("she prefers tea over coffee")  # no user_id needed

# one call → short-term + long-term + context
ctx = mem.recall("what does alice drink?")
import { Client } from "wontopos";

const mem = new Client({ apiKey: "wos-live-...", userId: "alice" });  // set the store once
await mem.createStore();            // create it (stores are explicit)
await mem.add("she prefers tea over coffee");  // no userId needed

// one call → short-term + long-term + context
const ctx = await mem.recall("what does alice drink?");
use wontopos::Client;

let mem = Client::new("wos-live-...").with_user("alice");  // set the store once
mem.create_store(None).await?;            // create it (stores are explicit)
mem.add("she prefers tea over coffee", None, json!({})).await?;

// one call → short-term + long-term + context
let ctx = mem.recall("what does alice drink?", None).await?;
# create the store once - stores are explicit
curl -X POST https://api.wontopos.com/api/v1/memory/collection \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice"}'

# store - embedded on the way in, no LLM call
curl -X POST https://api.wontopos.com/api/v1/memory/store \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","content":"she prefers tea over coffee"}'

# one call → short-term + long-term + context
curl -X POST https://api.wontopos.com/api/v1/memory/recall \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what does alice drink?"}'
実際のレスポンス - create_store()
{"user_id": "alice", "status": "created"}
実際のレスポンス - add()
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}
ストアは一度だけ設定。クライアントに user_id を渡せば、以降のすべての呼び出しがそれを使うため、繰り返す必要はありません。個別の呼び出しに user_id を渡せば、その呼び出しだけ上書きできます。ストアは明示的:存在しないストアへの保存・呼び出しは 404 を返します - まず作成してください。すべてのアカウントには最初から default ストアがあるため、user_id を一切指定しないゼロ設定の経路もそのまま動きます。一覧と管理は ストア を参照してください。

recall() は 4 つのブロックを返します - short_term(直近のターン)、long_term(関連する記憶)、context(ベストマッチの前後)、そして LLM に使い方を伝える instruction。まるごとプロンプトに入れてください。

どの言語でも動きます。英語で保存し、韓国語・日本語・中国語で尋ねても、同じ記憶が返ってきます。キーワードマッチングではなく、埋め込み検索だからです。

言語別の全メソッド →

クライアント1つを設定違いで使い回す

mem = Client.from_env()                 # reads WONTOPOS_API_KEY
scroll = mem.with_model("scroll-1.2")  # this copy only: another engine
alice  = mem.with_user("alice")       # this copy only: another default store
const alice = mem.withUser("alice");
const scroll = mem.withModel("scroll-1.2");
let alice = mem.with_user("alice");
let scroll = mem.with_model("scroll-1.2");
# curl has no copies — send model and user_id with each request
curl ... -d '{"user_id":"alice","model":"scroll-1.2","query":"…"}'
ストア

ストア - 作成、一覧、削除。

ストアとは、読み書きの単位となる user_id です - エンドユーザー、エージェント、トピックごとに隔離された記憶空間になります。ストアは明示的です。保存や呼び出しの前に作成しないと、呼び出しは 404 を返します。すべてのアカウントには最初から default ストアがあるため、作成の呼び出しなしでも始められます。

隔離の入れ子構造。アカウントがワークスペースを持ち、各ワークスペースは記憶・API キー・使用量をそれぞれ隔離します(請求はアカウントで共有)。ストアはワークスペースの中にあり、同じワークスペースのキーはそのストアを共有し、異なるワークスペース同士が互いの記憶を見ることはありません。アカウント → ワークスペース → ストア (user_id) → 記憶
mem.create_store("alice")        # create (idempotent)
mem.list_stores()              # [{"user_id","created_at"}, ...]
mem.delete_store("alice")        # delete the store + all its memories
await mem.createStore("alice");
await mem.listStores();          // [{ user_id, created_at }, ...]
await mem.deleteStore("alice");     // store + all its memories
mem.create_store("alice").await?;
let stores = mem.list_stores().await?;
mem.delete_store("alice").await?;
# create
curl -X POST https://api.wontopos.com/api/v1/memory/collection \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" -d '{"user_id":"alice"}'
# list
curl https://api.wontopos.com/api/v1/memory/collections -H "X-API-Key: $WOS_API_KEY"
# delete (store + all its memories)
curl -X DELETE https://api.wontopos.com/api/v1/memory/collection \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" -d '{"user_id":"alice"}'
実際のレスポンス - 作成
{ "user_id": "alice", "status": "created" }   // "exists" if it already did
実際のレスポンス - 一覧
{ "collections": [
  { "user_id": "default", "created_at": "2026-06-26T02:23:14Z" },
  { "user_id": "alice",   "created_at": "2026-06-26T02:24:01Z" }
], "count": 2 }
存在しないストアへの recall
{ "error": { "type": "not_found_error",
  "message": "Store 'ghost' does not exist. Create it first with
              POST /api/v1/memory/collection {\"user_id\":\"ghost\"}, then store or recall." } }
エンドユーザーごとにストアをひとつ("alice""user_42")使えば、各人の記憶を分離できます。個人用エージェントなら default ストアひとつでも構いません。コンソール(Memory ids → Issue)からコードを書かずにストアの作成・閲覧もできます。ストアの削除は恒久的で、その中のすべての記憶が消えます。 ストア id は保存前に畳まれます。小文字化され、[a-z0-9_] 以外の文字は _ になるため、Alice.Smithalice-smith は同じストアです。既存 id と同じ形に畳まれる二つ目の id は、黙って共有されるのではなく 409 で拒否されます。id 自体も [A-Za-z0-9][A-Za-z0-9._-]{0,63} を満たす必要があるため、メールアドレスや非ラテン文字の名前はストア id にできません。内部識別子をお使いください。

ストアの一覧取得と削除

mem.list_stores()                # [{"user_id","created_at"}, …]
mem.delete_store("alice")      # the store and every memory in it
await mem.listStores();
await mem.deleteStore("alice");
let stores = mem.list_stores().await?;
mem.delete_store("alice").await?;
curl -X POST   .../api/v1/memory/collections -d '{}'
curl -X DELETE .../api/v1/memory/collection  -d '{"user_id":"alice"}'
リコールキャッシング

繰り返しのリコールは、10分の1の価格で。

リクエストごとにオプトインすると、WOSは検索結果をクエリテキストに紐づけてキャッシュします。ルールはLLMのプロンプトキャッシングと同じプレフィックス方式です。キャッシュが有効な間、繰り返しや続きのクエリは前回の結果を再利用し、キャッシュされた部分は通常のトークン単価の10%で課金されます。

Tablet・Scroll限定。 キャッシングは現在および将来のすべてのTablet・Scrollモデルで動作します。Bookはサポートしていません。Bookは記憶の上で推論し、呼び出しの間に学習するため、同じ質問でも答えが正当に変わることがあり、キャッシュされた結果は設計上誤った答えになります。Bookにcache_controlを送ると、明確に403を返します。

1つの会話、3つのターン

エージェントが記憶と会話し続けるとき、実際に起きることです。毎ターン、それまでの会話をクエリとして送り、cache_controlをオンにします。

writeターン1 - 「アリス: 去年の春リスボンに引っ越したの。」

クエリ全体が検索されキャッシュされます: 入力は2倍 (TTL 5分)。

extendターン2 - 同じテキスト + 「ボブ: そっちの天気はどう?」

ボブの文だけが埋め込まれ検索されます。前の部分は0.1倍、新しい文は2倍。キャッシュはその文で終わるようになります。

hitターン3 - まったく同じクエリをもう一度 (リトライ、リフレッシュ)

エンジン呼び出しは一切ありません。すべて0.1倍: 90%割引です。

料金

操作トークン課金意味
キャッシュ書き込み - TTL 5分最初のリクエストです。結果は5分間保持され、読み取るたびに有効期間が延長されます。
キャッシュ書き込み - TTL 1時間最初のリクエストで、1時間保持されます。
キャッシュ読み取り - ヒットまたはプレフィックスヒット0.1×書き込み後のすべてのリクエスト: キャッシュされた部分は通常のトークン単価の10分の1になります。

どれだけ節約できるか

具体例: エージェントが3,000トークンの会話をクエリとして送り、5分以内に10回繰り返すか続けるとします。キャッシュなしでは定価で30,000入力トークン。5分キャッシュなら最初の書き込み6,000(2倍) + 9回のキャッシュ読み取り約2,700 = 課金トークン8,700で、71%の節約です。会話が長くなるほど節約は大きくなります。

プレフィックスのルール

マッチングはクエリの先頭部分で行われます。先頭がそのままで、末尾に新しいテキストが追加されただけなら、キャッシュ部分を再利用し、新しい部分だけを検索します。キャッシュされたテキストの終わりより前で何かが変わると、何も再利用できません。

prefix match
cached    [ A B C D E F G ]

○   [ A B C D E F G ] E
✗   [ B C D E F G ] E

ヒット - 先頭は変わらず、Eだけが新しい部分です
ミス - 先頭が変わったため、クエリ全体を再検索して再キャッシュします

覚えておくべき3つのルール

  • 延長すると新しい末尾まで再キャッシュされます。 [A B C D E F G] + E の後、キャッシュはEで終わります。末尾は書き込み料金で一度だけ課金され、次のターンはAからEまで全体をプレフィックスとして再びマッチできます。
  • 1リクエストにつき連続したプレフィックスは1つです。 1つのクエリを2つのキャッシュ断片に分割することはできず、マッチするのは先頭部分だけです。
  • 書き込みは即座に無効化します。 store、store-turn、bulk-store、forget、supersede、ストア削除が起きると、そのストアのキャッシュは破棄されます。キャッシュされた回答が古い記憶を返すことはありません。

有効にする方法

hits = mem.search(
    "...the conversation so far...", user_id="alice",
    cache_control={"ttl": "5m"},   # or "1h"
)
const hits = await mem.search(
  "...the conversation so far...", "alice", 10,
  { cache_control: { ttl: "5m" } },   // or "1h"
);
let hits = mem.search_with(
    "...the conversation so far...", "alice", 10,
    serde_json::json!({"cache_control": {"ttl": "5m"}}),   // or "1h"
).await?;
curl -X POST https://api.wontopos.com/api/v1/memory/search \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice",
       "query":"...the conversation so far...",
       "cache_control":{"ttl":"5m"}}'   # or "1h"
レスポンス - cacheオブジェクトが何が起きたかを報告します
{ "memories": [ ... ],
  "cache": { "status": "hit",              // "write" | "hit" | "extend"
             "ttl": "5m",
             "cache_read_input_tokens": 412,
             "cache_creation_input_tokens": 0 } }

この機能に SDK は必須ではありません。キャッシングは 1 回の HTTP 呼び出しに付くフィールド 1 つなので、どのプログラミング言語からでも動作します。curl タブが万能のレシピで、Python・TypeScript・Rust の SDK は同じ呼び出しを包んだ便利ツールにすぎません。

キャッシングはワークスペース内でストアごと、モデルごとに分離されており、デフォルトはオフです。cache_controlを送らなければ、リクエストは何も変わりません。
誰の発言か

誰が言ったかを知っている記憶。

人の記憶は人単位で動きます。Bob が何を約束したか、自分が何をすると言ったか。記憶ごとに話者を付ければ、エージェントも同じように覚えます。すべての Tablet・Scroll モデルで。

話者はストアと同じく明示的です。先に人を登録し、その名前で保存します。タイプミスが静かに新しい人になることはありません。ストアあたりまず 50 人まで登録でき(順次拡大予定)、"me" は登録も数えられることもありません。

1 つのチーム、3 つの記憶

1 つのストアが複数の声を混ざらないように保ちます。人を一度登録し、発言のたびに話者を付けて保存し、あとで人単位で尋ねてください。

addBob を一度登録します。POST /speakers、SDK では add_speaker("Bob") です。

ストアはもう Bob を知っています。50 人の上限はここ、登録時にのみ数え、store 呼び出しが上限エラーを返すことはありません。

BobBob が締め切りは火曜に延びたと言います。speaker "Bob" で保存します。

この記憶はもう Bob のものです。検索で返るたびにそう表示されます。

meアシスタントが金曜までに要約を送ると約束します。自分の言葉は speaker "me" で保存します。

自分の発言も記憶になり、"me" は話者の上限に数えられません。

ask後日:「Bob は締め切りについて何と言ってた?」speaker "Bob" で検索します。

Bob の言葉だけが返ります。ある人の言葉が別の人の言葉として返ることはありません。

ルールは 3 つ

  • "me" はアシスタント自身です。登録もカウントもされません。予約語で小文字のみ有効です。speaker: "Me""ME" は暗黙変換されず、400 invalid_request_error を返します。
  • 上限は登録時に数えます。ストアあたりまず 50 人。超過登録は 400 invalid_request_error を返し、エラー本文に speaker_limit: 50 が入ります。未登録の名前で store しても 400 で何も保存されません。未登録の名前で検索をフィルタすると 404 not_found_error です。メッセージ文字列ではなくステータスとフィールドで分岐してください。上限は引き上げる予定です。
  • ラベルはすべての読み取りに付きます。検索結果、recall の長期コンテキスト、エングラムの結果のすべてが話者を伴うので、モデルは手にしている言葉が誰のものか常に分かります。検索に speaker を渡せばその人の言葉だけが返り、supersede では話者が引き継がれ、forget では一緒に消えます。
  • 名前は Unicode で、どの言語でも使えます。さくら、Иван、하늘 はすべて有効な話者で、帰属の挙動はどの言語でも同一です。マッチングはトリムと Unicode 正規化の後の完全一致なので、Bobbob は別人です。名前は 80 文字までです。
errors - verbatim
# POST /speakers past the limit
{ "type": "error",
  "error": { "type": "invalid_request_error",
             "message": "This store already has 50 registered speakers, ...",
             "speaker_limit": 50 } }

# store with an unregistered name → 400, nothing stored
{ "type": "error",
  "error": { "type": "invalid_request_error",
             "message": "speaker 'Bob' is not registered in this store. Register it first: ...",
             "speaker": "Bob" } }

# search filtered by an unregistered name → 404
{ "type": "error",
  "error": { "type": "not_found_error",
             "message": "speaker 'Bob' is not registered in this store.",
             "speaker": "Bob" } }

スコープの注意を二つ。speaker は add / store に付きます。add_turn はやり取りを丸ごと記憶し、人単位のラベルとフィルタは speaker を明示して保存した記憶から得られます。また、セッションパッセージ(expand)は複数の記憶の合成であるためラベルは付きません。speaker フィルタは常に原子単位のラベル付き記憶を返します。 また、保存済みの記憶と意味が十分に近い書き込みは破棄されます。文字列の一致ではなく意味で判定します。その store は status "duplicate" と明示的な note を返し、何も保存せず、話者も付きません。既存の記憶と一点だけ異なる新しい事実(「ピーナッツアレルギー」の次の「甲殻類アレルギー」)も同じ規則で破棄されるため、保存されたと仮定せず status を確認してください。

私たちはこの機能を厳しくテストしています。本文に名前が一切ない記憶を保存し、人単位で想起します。帰属は単語の一致ではなく話者の記録から来るため、どの言語でも同じように動作します。

使い方

mem.add_speaker("Bob", user_id="alice")  # once per person; "me" needs no registration
mem.add("Bob said the deadline moved to Tuesday", user_id="alice", speaker="Bob")
mem.add("I promised the summary by Friday", user_id="alice", speaker="me")
hits = mem.search("what did Bob say about the deadline?", user_id="alice", speaker="Bob")
mem.list_speakers(user_id="alice")
mem.remove_speaker("Bob", user_id="alice")  # memories stay, the tag goes
await mem.addSpeaker("Bob", "alice");  // once per person; "me" needs no registration
await mem.add("Bob said the deadline moved to Tuesday", "alice", { speaker: "Bob" });
await mem.add("I promised the summary by Friday", "alice", { speaker: "me" });
const hits = await mem.search("what did Bob say about the deadline?", "alice", 10, { speaker: "Bob" });
await mem.listSpeakers("alice");
await mem.removeSpeaker("Bob", "alice");  // memories stay, the tag goes
mem.add_speaker("Bob", "alice").await?;  // once per person; "me" needs no registration
mem.add("Bob said the deadline moved to Tuesday", "alice", json!({"speaker": "Bob"})).await?;
mem.add("I promised the summary by Friday", "alice", json!({"speaker": "me"})).await?;
let hits = mem.search_with("what did Bob say about the deadline?", "alice", 10, json!({"speaker": "Bob"})).await?;
mem.list_speakers("alice").await?;
mem.remove_speaker("Bob", "alice").await?;  // memories stay, the tag goes
curl -X POST https://api.wontopos.com/api/v1/memory/speakers \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","speaker":"Bob"}'   # once per person

curl -X POST https://api.wontopos.com/api/v1/memory/store \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","content":"Bob said the deadline moved to Tuesday","metadata":{"speaker":"Bob"}}'

curl -X POST https://api.wontopos.com/api/v1/memory/search \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what did Bob say about the deadline?","speaker":"Bob"}'

curl "https://api.wontopos.com/api/v1/memory/speakers?user_id=alice" -H "X-API-Key: $WOS_API_KEY"

curl -X DELETE https://api.wontopos.com/api/v1/memory/speakers \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","speaker":"Bob"}'   # memories stay, the tag goes
response
{ "memories": [
    { "content": "Bob said the deadline moved to Tuesday",
      "speaker": "Bob", ... } ] }
GET /speakers
{ "user_id": "alice",
  "speakers": [ { "speaker": "Bob", "memories": 2, "created_at": "2026-07-10T04:20:39Z" } ],
  "count": 1, "limit": 50 }

一覧はストアが知っている人を、人ごとの記憶数と上限とともに表示します。解除は登録だけを消します。その人の記憶は残り、名前タグだけが外れます。

特定の話者の記憶を読む

by_speaker は、クエリなしで特定の人物の発言を新しい順に返します。"me" はアシスタント自身の発言を返します。ページングは画像と同じカーソル方式で、next_beforenext_skip_ids をそのまま返送します。

page = mem.by_speaker("Bob", limit=50)
page["memories"], page["chunks"]
const page = await mem.bySpeaker("Bob", undefined, { limit: 50 });
let page = mem.by_speaker("Bob", None, 50, None, None).await?;
curl -X POST https://api.wontopos.com/api/v1/memory/by-speaker \
  -H "X-API-Key: $WOS_KEY" \
  -d '{"user_id":"alice","speaker":"Bob","limit":50}'
フィールド動作
memories記憶を新しい順に返します。形式は検索が返すものと同じです。
chunksそれらの記憶の背後にある文単位の断片で、削除で実際に取り除かれる対象です。通常は記憶の件数より多くなります。削除を確定させる前に提示するための値です。points_to_delete としても報告されます。
next_beforenext_skip_ids と組で使う次のページ用のカーソルです。記憶はタイムスタンプが重複しうるため、両方が必要です。
ここでの speaker は保存時に書き込まれたタグであり、本文に対する検索ではありません。話者なしで保存された記憶は検索では見つかりますが、"me" を含め by_speaker では取得できません。

話者の一覧取得、閲覧、削除

mem.list_speakers()                    # who is registered
mem.by_speaker("Bob")                 # what Bob said, newest first
mem.remove_speaker("Bob")             # unregister; the memories stay
await mem.listSpeakers();
await mem.bySpeaker("Bob");
await mem.removeSpeaker("Bob");
mem.list_speakers(None).await?;
mem.by_speaker("Bob", None, None, None, None).await?;
mem.remove_speaker("Bob", None).await?;
curl -X GET    .../api/v1/memory/speakers   -d '{"user_id":"alice"}'
curl -X POST   .../api/v1/memory/by-speaker -d '{"user_id":"alice","speaker":"Bob"}'
curl -X DELETE .../api/v1/memory/speakers   -d '{"user_id":"alice","speaker":"Bob"}'
開発者

画像

記憶は画像を保持できます。エンジンが画像そのものをインデックスするため、レコードにキャプション、タイトル、代替テキストがない場合でも、任意の言語のテキストクエリで一致します。

Tablet 2 以降で対応しています。画像を実装していないエンジンは、素の 404 を返すのではなく機能名を挙げて未対応であることを伝えるため、機能がないのか記憶がないのかを区別できます。JPEG、PNG、GIF、WebP に対応します。

保存する

通常の add 呼び出しに image オブジェクトを渡します。content は空でも構いません。その場合、画像だけで検索できます。

mem.add("at the beach", image={"data": b64})   # caption + image
mem.add("", image={"data": b64})               # the image IS the memory
// the image rides in the 4th argument; the 3rd is metadata
await mem.add("at the beach", undefined, {}, { image: { data: b64 } });
await mem.add("", undefined, {}, { image: { data: b64 } });   // the image IS the memory
let img = json!({"image": {"data": b64}});
mem.add_with("at the beach", None, json!({}), img.clone()).await?;
mem.add_with("", None, json!({}), img).await?;
curl -X POST https://api.wontopos.com/api/v1/memory/store \
  -H "X-API-Key: $WOS_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","content":"","image":{"data":"<base64>"}}'

data は必須です。data:image/jpeg;base64, の接頭辞と、base64openssl が付ける改行は、どちらも自動で取り除かれます。

フィールド動作
data画像の Base64 です。必須。サイズの上限は SDK の定数ではなくサーバー側の設定で、/healthmemory.images.max_bytes として報告します。
reference元データの自前のコピーが置かれている場所です。文字列として保存されるだけで、こちらから取得することはありません。
taken_atRFC3339 形式で、通常は EXIF から取得します。event_date が空の場合はこの値で埋められるため、記憶はアップロードされた日時ではなく撮影された日時で並びます。

検索する

画像専用の検索はありません。searchrecall が、テキストと同じランキングの中で画像も返します。

保存済みの画像を扱う

data, mime = mem.get_image(memory_id=mid)
page       = mem.list_images(limit=50)      # page["count"] = store total
mem.forget_image(memory_id=mid, preview=True)
const { bytes, contentType } = await mem.getImage(undefined, mid);
const page = await mem.listImages(undefined, { limit: 50 });
await mem.forgetImage(undefined, mid, { preview: true });
let (bytes, mime) = mem.get_image(None, mid).await?;
let page = mem.list_images(None, 50, None, None).await?;
mem.forget_image(None, mid, true).await?;
# original bytes — the one call on this plane that is not JSON
curl -X POST   .../api/v1/memory/image  -d '{"user_id":"alice","memory_id":"m_1"}'
curl -X POST   .../api/v1/memory/images -d '{"user_id":"alice","limit":50}'
curl -X DELETE .../api/v1/memory/image  -d '{"user_id":"alice","memory_id":"m_1","preview":true}'
呼び出し動作
get_image元のバイト列を (bytes, content_type) として返します。型はアップロード時の名前ではなくバイト列から判定されます。画像のない記憶では、空の値を返すのではなく例外を送出します。
list_images新しい順に 1 ページ分と、ページサイズではなくストア全体の件数である count を返します。ページングはカーソル方式で、next_beforenext_skip_ids をそのまま返送します。画像はタイムスタンプが重複しうるため、両方が必要です。
forget_image画像を削除し、テキストは残します。キャプションなしで保存された画像は、それ自体が記憶そのものであるため、その場合は記憶も削除されます。

forget_imagepreview=True を渡すと、何も変更せずに memory_kept を取得できます。iter_images はページングを代行します。

画像の料金

画像もテキストと同じ単位のトークンで計算します。トークン = ピクセル面積 / 556.7。長辺が 1,568 px を超えると 1,568 px 基準で数えるため、2,500 px の画像と 1,568 px の画像は同じ値になります。

画像換算基準トークン
700 × 700as sent881
1000 × 1000as sent1,797
1568 × 1568as sent4,417
1920 × 10801568 × 8822,485
2500 × 18751568 × 11763,313
2500 × 25001568 × 15684,417

画像 1 枚あたりの上限は 4,417 トークンです。呼び出し前にこの上限を残高から確保し、終了後に実測値で請求します。実測値が上限を超えることはありません。

大きすぎる画像も小さすぎる画像も 400 で拒否されます。こちらでリサイズは行いません。短辺と長辺のどちらも 700 px 以上、長辺は 2,500 px 以下である必要があります。700 px 未満では埋め込みの課金が一律の下限額になるため、それより小さい画像でも保存コストは同じです。送信前にリサイズしてください。エラーには受け取ったサイズと必要なサイズの両方が示されます。

返る枚数

既定は 1、1 レスポンスあたり最大 5 枚です。5 枚で 20,000 トークン近くになります。

フィールド動作
max_images0 から 5 まで。1 つのレスポンスが含められる画像の枚数です。既定は 1 です。0 はテキストのみを返します。範囲外の値は丸められるのではなく拒否されます。
画像に英語のキャプションを付けると、英語のクエリは改善し、他の言語のクエリは低下します。14 言語の平均で recall@5 が 11.4 ポイント下がります。利用者が複数の言語で検索する場合は、キャプションなしで画像を保存してください。
開発者

verify

verify を指定すると、検索で追加のパスを実行できます。各パスは以前のパスが返した結果を除外するため、2 回目のパスでは 1 回目が到達しなかった記憶に届きます。

Tablet 2 以降で対応しています。実装していないエンジンに要求した場合は呼び出し前に拒否されるため、何もしなかったパスに課金されることはありません。

searchrecall で指定する 0-3 の整数です。追加のパス数を指すため、3 では 4 回の取得が行われます。既定値は 0 です。

hits = mem.search("what did I eat", verify=3)
# the SDKs hand back the memories; `verify_used` is on the HTTP response (curl tab)
const hits = await mem.search("what did I eat", undefined, 10, { verify: 3 });
// the SDKs hand back the memories; `verify_used` is on the HTTP response (curl tab)
let hits = mem.search_opts("what did I eat", None, 10,
                            &SearchOpts { verify: Some(3), ..Default::default() }).await?;
// the SDKs hand back the memories; `verify_used` is on the HTTP response (curl tab)
curl -X POST https://api.wontopos.com/api/v1/memory/search \
  -H "X-API-Key: $WOS_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what did I eat","verify":3}'
# → {"memories":[…], "verify_used":1}

このループで言語モデルは動作しません

各パスはすでに返された id を持ち回り、エンジンはそれらを除外してその先を検索します。クエリは書き換えられないため、同一のリクエストに対する結果は決定的であり、モデルの認証情報も関与しません。追加のパスを使うかどうかは呼び出し側のコードが決めます。

送信する値と返る値

呼び出し動作
verify0 から 3 まで。許可する追加パスの数です。範囲外の値は暗黙に丸められるのではなく 400 で拒否されます。
verify_used実際に実行された追加パスの数です。要求した値より小さくなることがあります。

新しい結果が返らないパスが出た時点で打ち切られ、使われなかったパスは課金されません。後続のパスが失敗した場合は、そこまでに集まった結果が返ります。

最初のパスにすでに答えが含まれていた場合、追加のパスは精度を下げることがあります。LongMemEval-S の single-session-user 問題では 4.2 ポイント低下します。効果は 1 回の取得が外れる頻度に比例するため、大きなストアほど大きくなります。
アドオン · Beta

MCP - AI ツールのための記憶

WOS の本体は API と SDK です。MCP サーバーはその上のアドオン。同じ記憶を、自分で作っていないツールに差し込みます - Claude Code、Claude Desktop、Cursor。

インストール 1 行で、エージェントは 9 つの記憶ツールを受け取り自分で使います。記憶はアカウントにあるので、あるツールが書いたものを他のすべてのツールが呼び出せます - SDK で作った自前のエージェントも含めて。

これで何ができるか

  • プロジェクトを覚えている Claude Code。決定、バグ修正、好み - 次のセッションで説明し直すことなくそのまま呼び出されます。
  • ChatGPT で始めて Claude で続ける。同じストア、同じ記憶 - 会話はやり直しではなく、ツールをまたいで続きます。
  • 自作エージェントも同じ記憶の中に。Claude Code が学んだことを SDK エージェントが呼び出し、エージェントが保存したものを Claude Code が呼び返します。

Claude Code、Claude Desktop、Cursor、Windsurf などあらゆる MCP ホストで動作。ChatGPT は Actions + OpenAPI 仕様で同じ記憶に届きます。

インストール

claude mcp add wontopos --env WONTOPOS_API_KEY=wos-live-... -- npx -y wontopos-mcp

エージェントは 9 つのツールを受け取ります - recall · remember · search · update · forget · list_memories · engram · stats · create_store - 各説明にいつ使うかまで書かれているので、自分で判断して使います。

アドオン自体は無料で npm に公開されています - 課金はこのツールが呼ぶ API 使用量に通常料金のみ。Node 18+ とコンソールで作った API キーが必要です。

開発者ページを開く

アドオン

OpenAPI 仕様

API の完全な機械可読マップ - すべてのエンドポイント、リクエスト、レスポンス、エラー。

OpenAPI は HTTP API を機械が読めるファイルで記述する業界標準の形式です。

https://api.wontopos.com/openapi.json

これで何ができるか

Postman: File → Import → URL を貼り付け - すべてのエンドポイントがクリック可能なコレクションになります。ChatGPT: GPT を作り、Action に同じ URL を貼り付け。コード生成: openapi-generator -i .../openapi.json -g go で未提供言語のクライアントも作れます。

Postman にインポート、未提供言語のクライアント生成、ChatGPT Actions の接続、CI での契約チェックに。テストが実際のルートに固定しているため、ずれることはありません。

アドオン

llms.txt

AI が読めるテキスト 1 枚に収めた API の全体。

llms.txt はウェブの慣習です:サイトルートに置くプレーンテキスト 1 枚で、AI に製品について必要なすべてを伝えます。

https://wontopos.com/llms.txt

IDE やコーディングエージェントに入れれば、WOS の上での作り方をすぐ理解します - 認証、エンドポイント、パターン、エラーまで。リリースごとに更新。

OpenAPI 仕様と同じ事実を、別の読者に:仕様はツール向けの精密な構造、このファイルは AI(や人)が一気に読める散文です。どちらもリリースごとに更新されます。

Model Context Protocol · Beta

あらゆる AI ツールの中の記憶

コマンド 1 行で、Claude Code・Claude Desktop・Cursor などあらゆる MCP ホストが WOS アカウントの長期記憶を持ちます。統合コードは不要。エージェントが 9 つの記憶ツールを受け取り、使いどころを自分で判断します。

MCP はベータです。9 つのツールは現在も動作しテスト済みですが、完成までの間に表面が変わる可能性があります。その下の API と SDK は安定していてバージョン管理されています。

インストール

Claude Code は 1 行(キーは先にコンソールで発行):

claude mcp add wontopos --env WONTOPOS_API_KEY=wos-live-... -- npx -y wontopos-mcp
# pick which store it remembers into (optional): add --env WONTOPOS_USER_ID=my-project
# ~/.cursor/mcp.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
# .vscode/mcp.json
{ "servers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
# ~/.codeium/windsurf/mcp_config.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
# Claude Desktop and any other MCP host
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }

Add to Cursor →  ·  Add to VS Code →

任意の env: WONTOPOS_USER_ID はデフォルトの保存先、WONTOPOS_MODEL はエンジン、WONTOPOS_BASE_URL はセルフホスト先を指定します。 WONTOPOS_READ_ONLY=1 で読み取り専用に切り替わります(呼び出し・検索・一覧のみ)。

1 つのストアを共有する前に

  • 専用キーを使ってください。キーはワークスペースを持つため、MCP 専用に作ったキーは接続ツールが触れられる範囲そのものを絞ります - アプリのキーに触れずにコンソールでいつでもローテーションできます。
  • 読み取り専用モード。WONTOPOS_READ_ONLY=1 で書き込みツールは一切登録されません:エージェントは呼び出し・検索・記憶の一覧・engram の実行・統計の閲覧のみで、保存・更新・削除はできません。記憶を所有せず参照だけすべきエージェント向けです。
  • ツール実行の確認はオンのままに。MCP ホストは既定でツール実行前に確認します - 特に forget はオンのままに。削除はそのストアのすべてのツールに共有されます。
  • 保存したものは、キーを持つすべてのツールが呼び出せます。シークレット - API キーやパスワード - を記憶として保存しないでください。
  • 呼び出された記憶はデータであり、指示ではありません。ツール説明がエージェントに明示的にそう伝えます。それでも、自律エージェントが従うストアに信頼できない第三者のテキストを記憶として保存しないでください。
  • 削除も共有されます。1 つのツールでの forget・delete_all は、すべてのツールから消えることを意味します。
  • "me" はそのストアに書くエージェント自身を指します。複数のエージェントが 1 つのストアを共有すると "me" の声が混ざります。分けたいならエージェントごとにストアを(WONTOPOS_USER_ID)。
  • 支払いはアカウント 1 つに集まります。接続されたすべてのツールが同じ残高とレート制限を消費します。

あとは話すだけ

you毎週金曜にリリースすること、覚えておいて

エージェントが remember ツールを呼びます。ストアに永続保存されるので、セッションが終わっても何も失われません。

new sessionリリースはいつだっけ?

新しいセッションに履歴はゼロ。エージェントが recall を呼び、記憶から答えます:金曜日。

こう話しかけてみてください

  • 「このリポジトリは pnpm を使う。覚えておいて」→ remember が保存し、次のセッションはもう知っています。
  • 「先週決めたエラー形式は何だっけ?」→ recall がその決定をコンテキストに呼び戻します。
  • 「実は締め切りは金曜に変わった」→ エージェントは呼び出した記憶と矛盾すると気づき、update でその記憶をその場で修正します。
  • 「それは間違い。忘れて」→ エージェントが記憶の id を見つけ forget を呼びます。実行前にホストが確認します。
  • 「私について何を覚えてる?」→ list_memories が保存済みのすべてをたどるので、エージェントは答えたり整理したりできます。

特別な言い回しは不要です - 上の例はすべて普通の文章。エージェントが各ツールの説明を読み、自分で選びます。

9 つのツール

  • recall - 1 回でコンテキスト取得:直近のターン+関連する長期記憶。過去の文脈が要るときは最初に呼ぶよう、ツール説明に書いてあります。
  • remember - 残すべき事実・決定を保存。speaker: "me" はエージェント自身の言葉、登録済みの名前は発言者を示します。
  • search - セマンティック検索。人ごとの speaker フィルタに加え、時期やトピックで範囲を絞る filters(「6 月に何を決めた?」)— 意味だけでは絞れない軸が「いつ」だ。
  • update - 事実が変わった記憶を新しい内容で置き換えます。削除せず履歴を残します。
  • forget - id で記憶を 1 件削除。
  • list_memories - 保存されている内容をページ送りで一覧します。「私について何を覚えてる?」に答えたり、整理したりするのに使います。
  • engram - 検索 1 回では足りないときに、組み込みのマルチホップ・パイプライン(deep_recall, timeline, gather)を実行します。
  • stats - ストアにどれだけ入っているかを見ます。整理の前や、書き込みが実際に入ったかの確認に使います。
  • create_store - ストアは明示的。エンドユーザー・プロジェクト・エージェントごとに 1 つ。

SDK と MCP、どう違う?

  • SDK はあなたが書くアプリの中に入ります。いつ保存し何を呼び出すかをあなたのコードが正確に決めます - 決定的で、型があり、バージョン管理されます。プロダクトを作るなら SDK です。
  • MCP はあなたが作っていない AI ツールに差し込みます。記憶をいつ使うかはエージェントがツール説明を見て判断 - コード 0 行。Claude Code・Claude Desktop・Cursor に、あるいは完成済みアシスタントに記憶を付けるときに。

下は同じ API、同じストアです - SDK で作ったアプリと MCP でつないだ Claude Code セッションが 1 つの記憶を共有します。二者択一ではなく、面ごとに選ぶものです。

あらゆるツールをまたぐ 1 つの記憶

記憶はツールではなくアカウントに属します。ChatGPT(Actions + OpenAPI 仕様)から書いたストアが Claude Code でも自作エージェントでも呼び出せ、その逆も可能。あるツールで始めた会話が別のツールで続きます。

そして 1 つのストアだから、Claude Code で使っていた記憶をそのまま自作エージェントとの会話に引き継げます。同じキー・同じストアの SDK エージェントは Claude Code が学んだことをすべて呼び出せ、エージェントが保存したものは次のセッションの Claude Code が呼び出します。

ローカルで stdio 動作(npx wontopos-mcp)。この方式ではキーはあなたの環境に留まり、MCP セッションの一部として当社に送信されることはありません。TypeScript SDK のラッパーなので、自動リトライ・リダイレクト拒否・キーのマスキングがそのまま適用されます。
Model Context Protocol · Beta

Claude Code

代表的な経路です。ターミナルのコマンド 1 行で、すべてのセッションが記憶と共に始まります。

  1. コンソールで API キーを作成。キーはワークスペースを持つため、キー 1 つ = 記憶空間 1 つです。
  2. サーバーを登録。--scope user を付ければ全プロジェクトで使え、付けなければ現在のプロジェクトのみです。
  3. 確認:Claude Code 内で /mcp を実行すると、wontopos が 9 つのツールと共に表示されるはずです。
  4. 自動化のコツ:CLAUDE.md に「過去の文脈が要るときは先に wontopos recall を呼ぶ」と 1 行書けば、頼まなくても毎セッションが記憶から始まります。
claude mcp add wontopos --scope user \
  --env WONTOPOS_API_KEY=wos-live-... -- npx -y wontopos-mcp
# pick a store (optional): add --env WONTOPOS_USER_ID=my-project
Model Context Protocol · Beta

Claude Desktop

下のブロックを claude_desktop_config.json に追加し(設定 → 開発者 → Edit Config)、アプリを再起動すると 9 つのツールが現れます。注意:ウェブ・モバイルの claude.ai はリモート MCP サーバーが必要で、WOS はまだ提供していません - デスクトップアプリが対応経路です。

# claude_desktop_config.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
Model Context Protocol · Beta

Cursor

下のブロックを ~/.cursor/mcp.json に追加するか、ワンクリックボタンを押して Cursor を再起動してください。エージェントが 9 つのツールを取り込みます。

# ~/.cursor/mcp.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }

Add to Cursor →

Model Context Protocol · Beta

VS Code

VS Code(Copilot エージェントモード)はプロジェクトの .vscode/mcp.json から MCP サーバーを読みます。下のブロックを追加するか、ワンクリックボタンを押してください。

# .vscode/mcp.json
{ "servers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }

Add to VS Code →

Model Context Protocol · Beta

Windsurf

Windsurf(Cascade)は ~/.codeium/windsurf/mcp_config.json を読みます。下のブロックを追加してリロードすると、同じ 9 つのツールが現れます。

# ~/.codeium/windsurf/mcp_config.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
Model Context Protocol · Beta

ChatGPT

ChatGPT の MCP コネクタはリモートサーバーのみ対応のため、現在の対応経路はカスタム GPT の Action です:GPT を作り、Action を追加し、下の OpenAPI 仕様 URL を貼り付けて API キーを認証ヘッダーに設定します。その GPT は他のツールと同じ記憶を呼び出します。

# GPT → Configure → Actions → Import from URL
https://api.wontopos.com/openapi.json
# Authentication: API Key · Header name: X-API-Key

同じストア、同じ記憶です。ChatGPT が Action で保存したものを Claude Code が MCP で呼び出し、その逆も可能です。

Model Context Protocol · Beta

Gemini CLI

Gemini CLI は ~/.gemini/settings.json から MCP サーバーを読みます。下のブロックを追加して CLI を再起動すると、同じ 9 つのツールがそこにも現れます。

# ~/.gemini/settings.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
Python SDK

Python - 全メソッドを 3 グループで。

書き込み、読み取り、削除。以下の例はすべて 2026-08-01 に本番 API に対して実行したもので、レスポンスは原文のままです。

pip install wontopos
from wontopos import Client

mem = Client(api_key="wos-live-...")  # or read from an env var

モデルを選ぶ

API キーはどの記憶か(あなたのアカウント)を決め、モデルはどのエンジンが読むかを決めます。すべてのモデルはひとつの記憶を共有するため、あるモデルで保存して別のモデルで呼び出せます。クライアントにデフォルトを設定し、個別の呼び出しは model= を渡して上書きします。

mem = Client(api_key="wos-live-...", model="tablet-1")  # default engine
mem.recall("...", user_id="alice")                  # tablet-1
mem.recall("...", user_id="alice", model="scroll-1")  # or pick a model per call

list_models

カタログ - model に渡せる id と、それぞれの提供状況です。memory: "shared" のモデルは同じストアを読み、"isolated" は独自のストアを持ちます。API キーは不要です。

mem.list_models()
実際のレスポンス
[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"},
 {"id": "tablet-2", "name": "Tablet 2", "available": true, "memory": "shared"},
 {"id": "scroll-1", "name": "Scroll 1", "available": true, "memory": "shared"},
 {"id": "scroll-1.2", "name": "Scroll 1.2", "available": true, "memory": "shared"}]

ping

接続と API キーが有効かを一行で確認します。

mem.ping()   # True, or raises AuthenticationError / PaymentRequiredError

上のカタログは、常に現時点で利用できるモデルを反映しています - それ以外の id を渡すと明確なエラーが返ります。新しいモデルは出荷と同時に自動で現れます。

書き込み

add

記憶を 1 件保存します。取り込み時に埋め込み化 - LLM 呼び出しはなく、支払いは埋め込み分だけです。

mem.add("she prefers tea over coffee", user_id="alice")
mem.add("I promised the summary by Friday", user_id="alice", speaker="me")  # its own words - no registration needed
実際のレスポンス
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

add_turn

会話ターン 1 件(ユーザー + アシスタント)を、短期記憶と長期記憶に同時に保存します。

mem.add_turn("hi", "hello!", user_id="alice")
実際のレスポンス
{"status": "ok"}

speaker

すべての記憶に、誰の発言かを載せられます。人は一度登録し、その後は名前を speaker として渡してください。"me"(アシスタント自身の言葉)は登録不要です。検索にも speaker を指定すれば、その人の発言だけを取り出せます。

mem.add_speaker("Bob", user_id="alice")  # once per person; "me" needs no registration
mem.add("I promised to send the report on Friday", user_id="alice", speaker="me")
mem.add("Bob said the deadline moved to Tuesday", user_id="alice", speaker="Bob")
mem.search("what did Bob say about deadlines?", user_id="alice", speaker="Bob")
response
[{"content": "Bob said the deadline moved to Tuesday", "speaker": "Bob", ...}]
話者はストアと同じく明示的です。先に人を登録し、その名前で保存します。タイプミスが静かに新しい人になることはありません。ストアあたりまず 50 人まで登録でき(順次拡大予定)、"me" は登録も数えられることもありません。

add_bulk

大きなテキストの塊を一括投入します。サーバー側でチャンク化・埋め込み化されるため、既存履歴のインポートに最適です。

mem.add_bulk("Alice moved to Brooklyn in March. She works at a design studio downtown.", user_id="alice")
実際のレスポンス
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}

update

事実が変わったとき。古い記憶は superseded とマークされ(履歴として保持)、新しい記憶がリコールでその座を引き継ぎます。

mem.update("576700aa-...", "she switched to coffee this year", user_id="alice")
実際のレスポンス
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
 "old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}

読み取り

search

セマンティック検索で、関連度の高い順に返します。純粋な埋め込みでキーワードマッチングがないため、どの言語からでもどの記憶にも届きます。SDK は memories 配列を直接返します。以下は生の HTTP ボディです。 セルフレーンを持つモデル(Scroll 1.2 以降)はふたつのレーンで応答し、SDK は両方を統合して返すため、配列が max_results より多くなることがあります。要求した数ではなく、受け取った配列を基準にプロンプト長を見積もってください。

r = mem.search("what does she drink?", user_id="alice", limit=1)
実際のレスポンス(HTTP ボディ)
[{
   "id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624",
   "content": "she prefers tea over coffee",
   "category": "general",
   "time_bucket": "2026-06",
   "importance": 0.3,
   "similarity": 0.6316057443618774,
   "is_superseded": false,
   "superseded_by": null,
   "created_at": "2026-07-10T04:20:39.688276876Z"
 }]
フィールド意味
similarityクエリとの生の埋め込み類似度(0–1)。
is_supersededupdate() によって置き換えられた事実なら true。
search_msサーバー側の検索所要時間。

recall

1 回の往復で、LLM に必要なものがすべて返ります - 結果をそのままプロンプトに貼るだけです。どれだけ保存していても、上限のある固定サイズのコンテキストです。

ctx = mem.recall("what does she drink?", user_id="alice")
実際のレスポンス(形 - リストは省略)
{"short_term":  {"count": 2, "turns": [{"role": "user", "content": "hi", ...}]},
 "long_term":   {"count": 4, "memories": [{"content": "she prefers tea over coffee",
                                           "similarity": 0.63, ...}]},
 "context":     {"count": 4, "around_top_memory": [
                  "[match] she prefers tea over coffee",
                  "[after] Alice moved to Brooklyn in March. ..."]},
 "instruction": "Use short_term for recent context, long_term for relevant
                 past memories, context for surrounding conversation of the
                 most relevant memory."}

history

直近の会話ターン(短期記憶)を、古い順に返します。

turns = mem.history("alice")
実際のレスポンス(HTTP ボディ)
{"count": 2, "turns": [
   {"role": "user",      "content": "hi",     "timestamp": "2026-07-10T04:20:40.989011337Z"},
   {"role": "assistant", "content": "hello!", "timestamp": "2026-07-10T04:20:40.989013416Z"}
 ], "user_id": "alice"}

stats

1 ユーザーの記憶数を返します。

mem.stats("alice")
実際のレスポンス
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}

get

id で記憶を1件取得します - add や list_memories が返した id です。保存した原文とメタデータのみを返し、ベクトルは返しません。他のストアの id や、削除・無効化された記憶は 404 になります。

m = mem.get("alice", memory_id="576700aa-...")
response
{"id": "576700aa-...", "content": "she prefers tea over coffee",
 "category": "general", "created_at": "2026-07-10T04:20:39Z", "event_date": null,
 "is_superseded": false, "superseded_by": null}
実際のレスポンス
{"memory": {"id": "8bd090de-...", "content": "the office moved to the seventh floor in June",
  "category": "general", "created_at": "2026-07-31T18:20:30.531518060+00:00", "event_date": null,
  "is_superseded": false, "superseded_by": null}, "user_id": "docs_livetest"}

list_memories

ストアに保存された記憶を一覧します。保存した原文とメタデータのみを返し、ベクトルは含みません。カーソルでページ送りします - 応答の next_cursor を次の呼び出しに渡します。

page = mem.list_memories("alice", limit=100)
response
{"count": 2, "next_cursor": null, "memories": [
   {"id": "576700aa-...", "content": "she prefers tea over coffee",
    "category": "general", "created_at": "2026-07-10T04:20:39Z", "event_date": null,
 "is_superseded": false, "superseded_by": null}
 ]}

iter_memories · export_memories

カーソル管理なしで全ての記憶を巡回するか、ストア全体を一度に取得します。

for m in mem.iter_memories("alice"):   # every page, no cursor bookkeeping
    print(m["id"], m["content"])
everything = mem.export_memories("alice")   # the whole store as a list

削除

delete

id を指定して記憶を 1 件削除します。

mem.delete("alice", memory_id="576700aa-...")
実際のレスポンス
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}

delete_all

1 ユーザーのすべてを消去します - 1 回の呼び出しで、GDPR 対応です。

mem.delete_all("alice")
実際のレスポンス
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}

エラーと信頼性

すべての失敗は型付きエラーです。個別(レート制限・認証・支払い)に捕捉するか、基底の WosError でまとめて捕捉できます。

from wontopos import PaymentRequiredError, NotFoundError
try:
    mem.add("...", user_id="alice")
except NotFoundError:
    mem.create_store("alice")   # store didn't exist yet
except PaymentRequiredError:
    top_up()                       # out of credit - don't retry

rate_limit

呼び出し直後に残り上限を読み、限界に達する前に速度を落とします。

mem.search("...", user_id="alice")
rl = mem.rate_limit   # {"limit": 150, "remaining": 3, "reset": ...}

search_self

自己記憶モデル(Scroll 1.2+)で 1 回の呼び出しから 2 つのレーンを受け取ります: 他者の発言とエージェント自身の発言を分けて返すので、読み手が話者を取り違えません。

r = mem.search_self("what did I promise?", user_id="alice")
r["memories"]       # what others said / general memories
r["self_memories"]  # the agent's OWN words (speaker "me")

list_engrams

選択中のモデルが実行できるエングラムとデリバリーフォームをサービスに問い合わせます。名前をハードコードすると、新しいエングラムが出た瞬間からそのコードには見えなくなります。

cat = mem.list_engrams()
[e["name"] for e in cat["engrams"]]   # ask, never hard-code

filters

検索をストアの一部に絞り込みます。ランキングの前に適用されるため、フィルタ内での最良の一致が返ります - 上位 N を後から絞ったものではありません。

mem.search("what did we decide", user_id="alice", filters={
    "categories": ["work"],
    "event_from": "2026-01-01",   # when it HAPPENED
})
キー: categories · event_from / event_to(内容がいつ起きたか - metadata.event_date)· time_from / time_to(いつ保存されたか)· min_importance。一覧にないキーは拒否ではなく破棄されるため、綴り間違いは黙って検索範囲を広げます。

idempotency_key

書き込みの再送を安全にします。再試行が自分側で起きるときに使います - 落ちて再実行されたジョブ、再配信するキュー。

mem.add("she prefers tea", "alice", idempotency_key=f"import:{row.id}")
キーは保存する対象から作ってください(import:row-42)。定数は使わないこと - 異なる 2 つの書き込みに同じキーを使うと最初の応答が再生され、2 つ目は黙って失われます。形式: [A-Za-z0-9._:-] の 1〜128 文字。

with_timeout / with_retries

すでに作ったクライアントに触れずに、呼び出し箇所だけを調整します。大きなバックフィルにはタイムアウトを延ばしたクローンを、自前の再試行ループの中では再試行を切ったクローンを使います。

mem.with_timeout(120).add_bulk(big_blob, "alice")  # this slow call only
mem.with_retries(0).add("...", "alice")              # you retry, not the SDK
TypeScript SDK

TypeScript - 全メソッドを 3 グループで。

書き込み、読み取り、削除。以下の例はすべて 2026-08-01 に本番 API に対して実行したもので、レスポンスは原文のままです。

npm install wontopos
import { Client } from "wontopos";

const mem = new Client({ apiKey: "wos-live-..." });

モデルを選ぶ

API キーはどの記憶か(あなたのアカウント)を決め、モデルはどのエンジンが読むかを決めます。すべてのモデルはひとつの記憶を共有するため、あるモデルで保存して別のモデルで呼び出せます。コンストラクタでデフォルトを設定し、個別の呼び出しは withModel() で上書きします。

const mem = new Client({ apiKey: "wos-live-...", model: "tablet-1" });  // default
mem.recall("...", "alice");                          // tablet-1
mem.withModel("scroll-1").recall("...", "alice");  // or pick a model per call

listModels

カタログ - model に渡せる id と、それぞれの提供状況です。memory: "shared" のモデルは同じストアを読み、"isolated" は独自のストアを持ちます。API キーは不要です。

await mem.listModels();
実際のレスポンス
[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"},
 {"id": "tablet-2", "name": "Tablet 2", "available": true, "memory": "shared"},
 {"id": "scroll-1", "name": "Scroll 1", "available": true, "memory": "shared"},
 {"id": "scroll-1.2", "name": "Scroll 1.2", "available": true, "memory": "shared"}]

ping

接続と API キーが有効かを一行で確認します。

await mem.ping();   // true, or throws AuthenticationError / PaymentRequiredError

上のカタログは、常に現時点で利用できるモデルを反映しています - それ以外の id を渡すと明確なエラーが返ります。新しいモデルは出荷と同時に自動で現れます。

書き込み

add

記憶を 1 件保存します。取り込み時に埋め込み化 - LLM 呼び出しはなく、支払いは埋め込み分だけです。

await mem.add("she prefers tea over coffee", "alice");
await mem.add("I promised the summary by Friday", "alice", { speaker: "me" });  // its own words - no registration needed
実際のレスポンス
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

addTurn

会話ターン 1 件(ユーザー + アシスタント)を、短期記憶と長期記憶に同時に保存します。

await mem.addTurn("hi", "hello!", "alice");
実際のレスポンス
{"status": "ok"}

speaker

すべての記憶に、誰の発言かを載せられます。人は一度登録し、その後は名前を speaker として渡してください。"me"(アシスタント自身の言葉)は登録不要です。検索にも speaker を指定すれば、その人の発言だけを取り出せます。

await mem.addSpeaker("Bob", "alice");  // once per person; "me" needs no registration
await mem.add("I promised to send the report on Friday", "alice", { speaker: "me" });
await mem.add("Bob said the deadline moved to Tuesday", "alice", { speaker: "Bob" });
await mem.search("what did Bob say about deadlines?", "alice", 10, { speaker: "Bob" });
話者はストアと同じく明示的です。先に人を登録し、その名前で保存します。タイプミスが静かに新しい人になることはありません。ストアあたりまず 50 人まで登録でき(順次拡大予定)、"me" は登録も数えられることもありません。

addBulk

大きなテキストの塊を一括投入します。サーバー側でチャンク化・埋め込み化されるため、既存履歴のインポートに最適です。

await mem.addBulk("Alice moved to Brooklyn in March. She works at a design studio downtown.", "alice");
実際のレスポンス
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}

update

事実が変わったとき。古い記憶は superseded とマークされ(履歴として保持)、新しい記憶がリコールでその座を引き継ぎます。

await mem.update("576700aa-...", "she switched to coffee this year", "alice");
実際のレスポンス
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
 "old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}

読み取り

search

セマンティック検索で、関連度の高い順に返します。純粋な埋め込みでキーワードマッチングがないため、どの言語からでもどの記憶にも届きます。SDK は memories 配列を直接返します。以下は生の HTTP ボディです。 セルフレーンを持つモデル(Scroll 1.2 以降)はふたつのレーンで応答し、SDK は両方を統合して返すため、配列が max_results より多くなることがあります。要求した数ではなく、受け取った配列を基準にプロンプト長を見積もってください。

const r = await mem.search("what does she drink?", "alice", 1);
実際のレスポンス(HTTP ボディ)
[{
   "id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624",
   "content": "she prefers tea over coffee",
   "category": "general",
   "time_bucket": "2026-06",
   "importance": 0.3,
   "similarity": 0.6316057443618774,
   "is_superseded": false,
   "superseded_by": null,
   "created_at": "2026-07-10T04:20:39.688276876Z"
 }]
フィールド意味
similarityクエリとの生の埋め込み類似度(0–1)。
is_supersededupdate() によって置き換えられた事実なら true。
search_msサーバー側の検索所要時間。

recall

1 回の往復で、LLM に必要なものがすべて返ります - 結果をそのままプロンプトに貼るだけです。どれだけ保存していても、上限のある固定サイズのコンテキストです。

const ctx = await mem.recall("what does she drink?", "alice");
実際のレスポンス(形 - リストは省略)
{"short_term":  {"count": 2, "turns": [{"role": "user", "content": "hi", ...}]},
 "long_term":   {"count": 4, "memories": [{"content": "she prefers tea over coffee",
                                           "similarity": 0.63, ...}]},
 "context":     {"count": 4, "around_top_memory": [
                  "[match] she prefers tea over coffee",
                  "[after] Alice moved to Brooklyn in March. ..."]},
 "instruction": "Use short_term for recent context, long_term for relevant
                 past memories, context for surrounding conversation of the
                 most relevant memory."}

history

直近の会話ターン(短期記憶)を、古い順に返します。

const turns = await mem.history("alice");
実際のレスポンス(HTTP ボディ)
{"count": 2, "turns": [
   {"role": "user",      "content": "hi",     "timestamp": "2026-07-10T04:20:40.989011337Z"},
   {"role": "assistant", "content": "hello!", "timestamp": "2026-07-10T04:20:40.989013416Z"}
 ], "user_id": "alice"}

stats

1 ユーザーの記憶数を返します。

await mem.stats("alice");
実際のレスポンス
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}

get

id で記憶を1件取得します - add や list_memories が返した id です。保存した原文とメタデータのみを返し、ベクトルは返しません。他のストアの id や、削除・無効化された記憶は 404 になります。

const m = await mem.get("alice", "576700aa-...");
response
{"id": "576700aa-...", "content": "she prefers tea over coffee",
 "category": "general", "created_at": "2026-07-10T04:20:39Z", "event_date": null,
 "is_superseded": false, "superseded_by": null}

listMemories

ストアに保存された記憶を一覧します。保存した原文とメタデータのみを返し、ベクトルは含みません。カーソルでページ送りします - 応答の next_cursor を次の呼び出しに渡します。

const page = await mem.listMemories("alice", { limit: 100 });
response
{"count": 2, "next_cursor": null, "memories": [
   {"id": "576700aa-...", "content": "she prefers tea over coffee",
    "category": "general", "created_at": "2026-07-10T04:20:39Z", "event_date": null,
 "is_superseded": false, "superseded_by": null}
 ]}

iterMemories · exportMemories

カーソル管理なしで全ての記憶を巡回するか、ストア全体を一度に取得します。

for await (const m of mem.iterMemories("alice")) console.log(m.id, m.content);
const everything = await mem.exportMemories("alice");

削除

delete

id を指定して記憶を 1 件削除します。

await mem.delete("alice", "576700aa-...");
実際のレスポンス
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}

deleteAll

1 ユーザーのすべてを消去します - 1 回の呼び出しで、GDPR 対応です。

await mem.deleteAll("alice");
実際のレスポンス
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}

エラーと信頼性

すべての失敗は型付きエラーです。個別(レート制限・認証・支払い)に捕捉するか、基底の WosError でまとめて捕捉できます。

import { NotFoundError, PaymentRequiredError } from "wontopos";
try {
  await mem.add("...", "alice");
} catch (e) {
  if (e instanceof NotFoundError) await mem.createStore("alice");
  else if (e instanceof PaymentRequiredError) topUp();   // out of credit
  else throw e;
}

rateLimit

呼び出し直後に残り上限を読み、限界に達する前に速度を落とします。

await mem.search("...", "alice");
const rl = mem.rateLimit;   // { limit: 150, remaining: 3, reset: ... }

searchSelf

自己記憶モデル(Scroll 1.2+)で 1 回の呼び出しから 2 つのレーンを受け取ります: 他者の発言とエージェント自身の発言を分けて返すので、読み手が話者を取り違えません。

const { memories, self_memories } = await mem.searchSelf("what did I promise?", "alice");
// memories = what others said · self_memories = the agent's OWN words

listEngrams

選択中のモデルが実行できるエングラムとデリバリーフォームをサービスに問い合わせます。名前をハードコードすると、新しいエングラムが出た瞬間からそのコードには見えなくなります。

const { engrams, forms } = await mem.listEngrams();  // ask, never hard-code

filters

検索をストアの一部に絞り込みます。ランキングの前に適用されるため、フィルタ内での最良の一致が返ります - 上位 N を後から絞ったものではありません。

await mem.search("what did we decide", "alice", 10, {
  filters: { categories: ["work"], event_from: "2026-01-01" },  // when it HAPPENED
});
キー: categories · event_from / event_to(内容がいつ起きたか - metadata.event_date)· time_from / time_to(いつ保存されたか)· min_importance。一覧にないキーは拒否ではなく破棄されるため、綴り間違いは黙って検索範囲を広げます。

idempotencyKey

書き込みの再送を安全にします。再試行が自分側で起きるときに使います - 落ちて再実行されたジョブ、再配信するキュー。

await mem.add("she prefers tea", "alice", {}, { idempotencyKey: `import:${row.id}` });
キーは保存する対象から作ってください(import:row-42)。定数は使わないこと - 異なる 2 つの書き込みに同じキーを使うと最初の応答が再生され、2 つ目は黙って失われます。形式: [A-Za-z0-9._:-] の 1〜128 文字。

withTimeout / withRetries

すでに作ったクライアントに触れずに、呼び出し箇所だけを調整します。大きなバックフィルにはタイムアウトを延ばしたクローンを、自前の再試行ループの中では再試行を切ったクローンを使います。

await mem.withTimeout(120_000).addBulk(bigBlob, "alice");  // this slow call only
await mem.withRetries(0).add("...", "alice");            // you retry, not the SDK
Rust SDK

Rust - 全メソッドを 3 グループで。

書き込み、読み取り、削除。以下の例はすべて 2026-08-01 に本番 API に対して実行したもので、レスポンスは原文のままです。

cargo add wontopos
use wontopos::Client;

let mem = Client::new("wos-live-...");

モデルを選ぶ

API キーはどの記憶か(あなたのアカウント)を決め、モデルはどのエンジンが読むかを決めます。すべてのモデルはひとつの記憶を共有するため、あるモデルで保存して別のモデルで呼び出せます。with_model() でデフォルトを設定し、もう一度チェーンすれば個別の呼び出しだけ上書きできます。

let mem = Client::new("wos-live-...").with_model("tablet-1");  // default
mem.recall("...", "alice").await?;                       // tablet-1
mem.with_model("scroll-1").recall("...", "alice").await?;  // or pick a model per call

list_models

カタログ - with_model に渡せる id と、それぞれの提供状況です。memory: "shared" のモデルは同じストアを読み、"isolated" は独自のストアを持ちます。API キーは不要です。

mem.list_models().await?;
実際のレスポンス
[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"},
 {"id": "tablet-2", "name": "Tablet 2", "available": true, "memory": "shared"},
 {"id": "scroll-1", "name": "Scroll 1", "available": true, "memory": "shared"},
 {"id": "scroll-1.2", "name": "Scroll 1.2", "available": true, "memory": "shared"}]

ping

接続と API キーが有効かを一行で確認します。

mem.ping().await?;   // Ok(true), or Err whose .kind() is Auth / PaymentRequired

上のカタログは、常に現時点で利用できるモデルを反映しています - それ以外の id を渡すと明確なエラーが返ります。新しいモデルは出荷と同時に自動で現れます。

書き込み

add

記憶を 1 件保存します。取り込み時に埋め込み化 - LLM 呼び出しはなく、支払いは埋め込み分だけです。

mem.add("she prefers tea over coffee", "alice", json!({})).await?;
mem.add("I promised the summary by Friday", "alice", json!({"speaker": "me"})).await?;  // its own words - no registration needed
実際のレスポンス
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

add_turn

会話ターン 1 件(ユーザー + アシスタント)を、短期記憶と長期記憶に同時に保存します。

mem.add_turn("hi", "hello!", "alice").await?;
実際のレスポンス
{"status": "ok"}

speaker

すべての記憶に、誰の発言かを載せられます。人は一度登録し、その後は名前を speaker として渡してください。"me"(アシスタント自身の言葉)は登録不要です。検索にも speaker を指定すれば、その人の発言だけを取り出せます。

mem.add_speaker("Bob", "alice").await?;  // once per person; "me" needs no registration
mem.add("I promised to send the report on Friday", "alice", json!({"speaker": "me"})).await?;
mem.add("Bob said the deadline moved to Tuesday", "alice", json!({"speaker": "Bob"})).await?;
mem.search_with("what did Bob say about deadlines?", "alice", 10, json!({"speaker": "Bob"})).await?;
話者はストアと同じく明示的です。先に人を登録し、その名前で保存します。タイプミスが静かに新しい人になることはありません。ストアあたりまず 50 人まで登録でき(順次拡大予定)、"me" は登録も数えられることもありません。

add_bulk

大きなテキストの塊を一括投入します。サーバー側でチャンク化・埋め込み化されるため、既存履歴のインポートに最適です。

mem.add_bulk("Alice moved to Brooklyn in March...", "alice", "general").await?;
実際のレスポンス
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}

update

事実が変わったとき。古い記憶は superseded とマークされ(履歴として保持)、新しい記憶がリコールでその座を引き継ぎます。

mem.update("576700aa-...", "she switched to coffee this year", "alice").await?;
実際のレスポンス
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
 "old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}

読み取り

search

セマンティック検索で、関連度の高い順に返します。純粋な埋め込みでキーワードマッチングがないため、どの言語からでもどの記憶にも届きます。SDK は memories 配列を直接返します。以下は生の HTTP ボディです。 セルフレーンを持つモデル(Scroll 1.2 以降)はふたつのレーンで応答し、SDK は両方を統合して返すため、配列が max_results より多くなることがあります。要求した数ではなく、受け取った配列を基準にプロンプト長を見積もってください。

let r = mem.search("what does she drink?", "alice", 1).await?;
実際のレスポンス(HTTP ボディ)
[{
   "id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624",
   "content": "she prefers tea over coffee",
   "category": "general",
   "time_bucket": "2026-06",
   "importance": 0.3,
   "similarity": 0.6316057443618774,
   "is_superseded": false,
   "superseded_by": null,
   "created_at": "2026-07-10T04:20:39.688276876Z"
 }]
フィールド意味
similarityクエリとの生の埋め込み類似度(0–1)。
is_supersededupdate() によって置き換えられた事実なら true。
search_msサーバー側の検索所要時間。

recall

1 回の往復で、LLM に必要なものがすべて返ります - 結果をそのままプロンプトに貼るだけです。どれだけ保存していても、上限のある固定サイズのコンテキストです。

let ctx = mem.recall("what does she drink?", "alice").await?;
実際のレスポンス(形 - リストは省略)
{"short_term":  {"count": 2, "turns": [{"role": "user", "content": "hi", ...}]},
 "long_term":   {"count": 4, "memories": [{"content": "she prefers tea over coffee",
                                           "similarity": 0.63, ...}]},
 "context":     {"count": 4, "around_top_memory": [
                  "[match] she prefers tea over coffee",
                  "[after] Alice moved to Brooklyn in March. ..."]},
 "instruction": "Use short_term for recent context, long_term for relevant
                 past memories, context for surrounding conversation of the
                 most relevant memory."}

history

直近の会話ターン(短期記憶)を、古い順に返します。

let turns = mem.history("alice").await?;
実際のレスポンス(HTTP ボディ)
{"count": 2, "turns": [
   {"role": "user",      "content": "hi",     "timestamp": "2026-07-10T04:20:40.989011337Z"},
   {"role": "assistant", "content": "hello!", "timestamp": "2026-07-10T04:20:40.989013416Z"}
 ], "user_id": "alice"}

stats

1 ユーザーの記憶数を返します。

mem.stats("alice").await?;
実際のレスポンス
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}

get

id で記憶を1件取得します - add や list_memories が返した id です。保存した原文とメタデータのみを返し、ベクトルは返しません。他のストアの id や、削除・無効化された記憶は 404 になります。

let m = mem.get("alice", "576700aa-...").await?;
response
{"id": "576700aa-...", "content": "she prefers tea over coffee",
 "category": "general", "created_at": "2026-07-10T04:20:39Z", "event_date": null,
 "is_superseded": false, "superseded_by": null}

list_memories

ストアに保存された記憶を一覧します。保存した原文とメタデータのみを返し、ベクトルは含みません。カーソルでページ送りします - 応答の next_cursor を次の呼び出しに渡します。

mem.list_memories("alice", 100, None).await?;
response
{"count": 2, "next_cursor": null, "memories": [
   {"id": "576700aa-...", "content": "she prefers tea over coffee",
    "category": "general", "created_at": "2026-07-10T04:20:39Z", "event_date": null,
 "is_superseded": false, "superseded_by": null}
 ]}

list_all_memories

カーソル管理なしで全ての記憶を巡回するか、ストア全体を一度に取得します。

let all = mem.list_all_memories("alice").await?;   // every page, collected

削除

delete

id を指定して記憶を 1 件削除します。

mem.delete("alice", "576700aa-...").await?;
実際のレスポンス
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}

delete_all

1 ユーザーのすべてを消去します - 1 回の呼び出しで、GDPR 対応です。

mem.delete_all("alice").await?;
実際のレスポンス
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}

エラーと信頼性

すべての失敗は型付きエラーです。個別(レート制限・認証・支払い)に捕捉するか、基底の WosError でまとめて捕捉できます。

use wontopos::ErrorKind;
match mem.search("...", "alice", 10).await {
    Ok(hits) => { /* use hits */ }
    Err(e) if e.kind() == ErrorKind::NotFound => { mem.create_store("alice").await?; }
    Err(e) if e.is_rate_limited() => { /* back off */ }
    Err(e) => return Err(e),
}

rate_limit

呼び出し直後に残り上限を読み、限界に達する前に速度を落とします。

mem.search("...", "alice", 10).await?;
let rl = mem.rate_limit();   // Some(RateLimit { remaining: Some(3), .. })

search_self

自己記憶モデル(Scroll 1.2+)で 1 回の呼び出しから 2 つのレーンを受け取ります: 他者の発言とエージェント自身の発言を分けて返すので、読み手が話者を取り違えません。

let r = mem.search_self("what did I promise?", "alice", 10).await?;
// r.memories = what others said · r.self_memories = the agent's OWN words

list_engrams

選択中のモデルが実行できるエングラムとデリバリーフォームをサービスに問い合わせます。名前をハードコードすると、新しいエングラムが出た瞬間からそのコードには見えなくなります。

let cat = mem.list_engrams().await?;  // ask, never hard-code

filters

検索をストアの一部に絞り込みます。ランキングの前に適用されるため、フィルタ内での最良の一致が返ります - 上位 N を後から絞ったものではありません。

mem.search_with("what did we decide", "alice", 10, json!({"filters": {
    "categories": ["work"], "event_from": "2026-01-01"   // when it HAPPENED
}})).await?;
キー: categories · event_from / event_to(内容がいつ起きたか - metadata.event_date)· time_from / time_to(いつ保存されたか)· min_importance。一覧にないキーは拒否ではなく破棄されるため、綴り間違いは黙って検索範囲を広げます。

add_idempotent

書き込みの再送を安全にします。再試行が自分側で起きるときに使います - 落ちて再実行されたジョブ、再配信するキュー。

mem.add_idempotent("she prefers tea", "alice", json!({}), &format!("import:{}", row.id)).await?;
キーは保存する対象から作ってください(import:row-42)。定数は使わないこと - 異なる 2 つの書き込みに同じキーを使うと最初の応答が再生され、2 つ目は黙って失われます。形式: [A-Za-z0-9._:-] の 1〜128 文字。

with_timeout / with_retries

すでに作ったクライアントに触れずに、呼び出し箇所だけを調整します。大きなバックフィルにはタイムアウトを延ばしたクローンを、自前の再試行ループの中では再試行を切ったクローンを使います。

mem.with_timeout(120).add_bulk(big_blob, "alice", "general").await?;
mem.with_retries(0).add("...", "alice", json!({})).await?;
curl

curl - インストール不要、同じメソッド。

インストールする SDK はなく、任意の HTTP クライアントで動きます。キーを一度設定すれば、SDK がラップしているのと同じエンドポイントを呼べます。ベース URL は https://api.wontopos.com、認証は X-API-Key、入出力は JSON です。

# set your key once (never hard-code it)
export WOS_API_KEY="wos-live-..."

書き込み

store

記憶を 1 件保存します。取り込み時に埋め込み化 - LLM 呼び出しなし。

curl -X POST https://api.wontopos.com/api/v1/memory/store \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","content":"she prefers tea over coffee"}'
実際のレスポンス
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

store-turn

会話ターン 1 件(ユーザー + アシスタント)を、短期・長期記憶に同時に保存します。

curl -X POST https://api.wontopos.com/api/v1/memory/store-turn \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","user_msg":"hi","assistant_msg":"hello!"}'
実際のレスポンス
{"status": "ok"}

speaker

すべての記憶に、誰の発言かを載せられます。人は一度登録し、その後は名前を speaker として渡してください。"me"(アシスタント自身の言葉)は登録不要です。検索にも speaker を指定すれば、その人の発言だけを取り出せます。

curl -X POST https://api.wontopos.com/api/v1/memory/speakers \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","speaker":"Bob"}'   # once per person

curl -X POST https://api.wontopos.com/api/v1/memory/store \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","content":"I promised to send the report on Friday","metadata":{"speaker":"me"}}'

curl -X POST https://api.wontopos.com/api/v1/memory/store \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","content":"Bob said the deadline moved to Tuesday","metadata":{"speaker":"Bob"}}'

curl -X POST https://api.wontopos.com/api/v1/memory/search \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what did Bob say about deadlines?","speaker":"Bob"}'
話者はストアと同じく明示的です。先に人を登録し、その名前で保存します。タイプミスが静かに新しい人になることはありません。ストアあたりまず 50 人まで登録でき(順次拡大予定)、"me" は登録も数えられることもありません。

supersede

事実が変わったとき - 古い記憶は superseded とマークされ、新しい記憶がリコールでその座を引き継ぎます。

curl -X POST https://api.wontopos.com/api/v1/memory/supersede \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","old_memory_id":"576700aa-...","new_content":"she switched to coffee this year"}'
実際のレスポンス
{"new_memory_id": "07e94433-...", "old_memory_id": "576700aa-...", "status": "superseded"}

bulk-store

長い履歴を 1 回の呼び出しで取り込みます - サーバー側でチャンク分割と埋め込みを行います。

curl -X POST https://api.wontopos.com/api/v1/memory/bulk-store \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","content":"...a long history...","category":"general"}'
実際のレスポンス
{"elapsed_secs": 0.138589761, "status": "ok", "stored": 1, "total_chunks": 1}

Idempotency-Key

書き込みの再送を安全にします。再試行が自分側で起きるときに使います - 落ちて再実行されたジョブ、再配信するキュー。

# same key + same body = the FIRST response is replayed, nothing is stored twice
curl -X POST https://api.wontopos.com/api/v1/memory/store \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: import:row-42" \
  -d '{"user_id":"alice","content":"she prefers tea over coffee"}'
キーは保存する対象から作ってください(import:row-42)。定数は使わないこと - 異なる 2 つの書き込みに同じキーを使うと最初の応答が再生され、2 つ目は黙って失われます。形式: [A-Za-z0-9._:-] の 1〜128 文字。

読み取り

search

セマンティック検索で、関連度の高い順に。純粋な埋め込みなので、どの言語からでもどの記憶にも届きます。

curl -X POST https://api.wontopos.com/api/v1/memory/search \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what does she drink?","max_results":1}'
実際のレスポンス
{"memories": [{"id": "576700aa-...", "content": "she prefers tea over coffee",
   "similarity": 0.63, "is_superseded": false}], "search_ms": 315, "total_found": 1}

search + filters

検索をストアの一部に絞り込みます。ランキングの前に適用されるため、フィルタ内での最良の一致が返ります - 上位 N を後から絞ったものではありません。

curl -X POST https://api.wontopos.com/api/v1/memory/search \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what did we decide",
       "filters":{"categories":["work"],"event_from":"2026-01-01","event_to":"2026-06-30"}}'
キー: categories · event_from / event_to(内容がいつ起きたか - metadata.event_date)· time_from / time_to(いつ保存されたか)· min_importance。一覧にないキーは拒否ではなく破棄されるため、綴り間違いは黙って検索範囲を広げます。

get

store や list が返した id で記憶を 1 件読みます - 原文とメタデータのみ、ベクトルは含みません。

curl -X POST https://api.wontopos.com/api/v1/memory/get \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","memory_id":"576700aa-f0e0-4c26-99a0-10e2d5b0d624"}'

list

ストア全体をカーソル単位でページ送りします。閲覧やエクスポートに使います。

curl -X POST https://api.wontopos.com/api/v1/memory/list \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","limit":100}'   # pass next_cursor back for the next page
実際のレスポンス
{"count": 3, "memories": [{"id": "1a1cfc47-...", "content": "...", "category": "general",
   "created_at": "2026-07-31T18:04:51.937117314+00:00", "event_date": null, "is_superseded": false}],
 "next_cursor": "722c08e5-8998-4882-979e-d71995b5b4af", "user_id": "docs_livetest"}

recall

1 回の往復で、短期 + 長期 + コンテキスト + instruction が返ります。そのままプロンプトに貼ってください。

curl -X POST https://api.wontopos.com/api/v1/memory/recall \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what does she drink?"}'
実際のレスポンス(形)
{"short_term": {"count": 2, "turns": [...]},
 "long_term":  {"count": 4, "memories": [{"content": "she prefers tea over coffee", "similarity": 0.63}]},
 "context":    {"count": 4, "around_top_memory": ["[match] she prefers tea over coffee"]},
 "instruction": "Use short_term for recent context, long_term for relevant past memories..."}

削除

forget

id を指定して記憶を 1 件削除するか、id を省略してユーザーのすべてを削除します(GDPR)。

curl -X POST https://api.wontopos.com/api/v1/memory/forget \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice"}'  # omit memory_id = delete all
実際のレスポンス
{"memories_deleted": 1, "status": "deleted", "user_id": "alice"}

全エンドポイントとボディフィールド →

Rust のみのバリアント

Python と TypeScript では、これらを省略可能な引数として受け取ります。安定版の Rust には既定引数もキーワード引数もないため、最後に確定させるビルダーではなく、それぞれが個別のメソッドになっています。

mem.add_with(text, None, json!({}), extra)   // add + extra body fields
mem.search_opts(q, None, 10, &opts)            // search + verify / max_images
mem.search_with(q, None, 10, extra)            // search + any other field
mem.recall_with(q, None, extra)                // recall + extra
mem.search_self_with(q, None, 10, extra)       // self lane + extra
mem.engram_with(name, q, None, extra)          // engram + extra
mem.update_idempotent(old, new, None, key)     // update + Idempotency-Key
mem.add_turn_idempotent(u, a, None, key)
mem.add_bulk_idempotent(text, None, cat, key)
mem.revisions_page(None, "revised", 20, None, None)
mem.list_all_images(None, None)              // = iter_images, collected

list_all_images は、他の 2 つの SDK が使う名前に合わせて iter_images としても公開されています。それらのドキュメントから来た読者は、まずその名前を入力するためです。

エングラム

Engrams

モデルが呼び出せるリコールツールです - それぞれが、同じ記憶に対する異なる検索戦略になっています。ひとつだけ使っても、複数を同時に実行しても構いません。

提供中。以下の汎用エングラムは LLM を使わない検索パイプラインなので、Tablet 1 以上のすべてのティアで動きます。別のモデルモードである Memoir と Archive は、下の専用セクションで扱います。

エングラムは定期的に追加されます - このリストは増えていきます。

Memoir & Archive Scroll 1.2+

これは呼び出し可能なツールではなく、配信のフォームです。Scroll 1.2 以降では呼び出しごとに form: "memoir" または form: "archive" を選ぶと、通常の検索を含むそのリコールに、その流儀で書かれた時間が付いて返ります。

全エングラム エングラム

Time_awareness Scroll 1.2+

呼び出しごとに選ぶ配信フォームです。フォーム対応モデル(Scroll 1.2 以降)への任意の呼び出しに form - memoir または archive - を渡すと、そのレスポンスがその流儀で描画されて返ります: 通常の検索、recall、任意のエングラムまで。SDK では tz と同じく form フィールド、HTTP では X-WOS-Form ヘッダーです。Memoir は人が思い出すように読め、Archive は正確な記録を保ちます - 違いが最も表れるのは、時間の書き方です。

Memoir

form: "memoir"
人のように思い出す · 物語として

何が起き、その瞬間が次にどうつながったかを、人が思い出すときのやわらかな時間感覚とともに語ります - リストではなく、体験として読めます。

Archive

form: "archive"
記録として保つ · 正確な時間

マッチを正確な記録として返します - 精密な経過時間と絶対的なアンカーを備え、モデルがそのまま読み取れる構造です。

すでに保存された記憶を描画するのであって、記憶を作るのではありません。各記憶は user_id の下でのひとつの store / add 呼び出しです(その user_id がその人のストアそのものです)。まず保存してください。その後はどのリコールも - 下の通常検索も含めて - 時間タグ付きで返ります。保存の方法は クイックスタート を参照してください。
# the memoir form on a plain search — and on recall, the LLM's one-call context
r   = mem.search("what does Alice drink?", user_id="alice", model="scroll-1.2", form="memoir", tz=9)
ctx = mem.recall("what does Alice drink?", user_id="alice", model="scroll-1.2", form="memoir", tz=9)
# every memory's .time reads "a couple weeks ago" (archive → "2 weeks ago (Jun 09)") — the LLM sees human time
// the memoir form on search — and on recall, the LLM's one-call context
const s = await mem.withModel("scroll-1.2").search("what does Alice drink?", "alice", 10, { form: "memoir", tz: 9 });
const ctx = await mem.withModel("scroll-1.2").recall("what does Alice drink?", "alice", { form: "memoir", tz: 9 });
// form on search AND recall — the _with helpers merge extra fields into the body
let s = mem.with_model("scroll-1.2").search_with("what does Alice drink?", "alice", 10, json!({"form": "memoir", "tz": 9})).await?;
let ctx = mem.with_model("scroll-1.2").recall_with("what does Alice drink?", "alice", json!({"form": "memoir", "tz": 9})).await?;
# same X-WOS-Form header on /search, /recall, or /engram/run
curl -X POST https://api.wontopos.com/api/v1/memory/recall \
  -H "X-API-Key: wos-live-..." -H "X-WOS-Model: scroll-1.2" -H "X-WOS-Form: memoir" -H "X-WOS-Timezone: 9" \
  -d '{"user_id":"alice","query":"what does Alice drink?"}'
# every memory comes back with a "time" field; use X-WOS-Form: archive for exact time

tz は呼び出し側の UTC オフセット(時間単位)です - これにより "this morning" のような表現や午前 4 時の日付境界が、その人の現地時間で決まります。省略すると UTC、HTTP では X-WOS-Timezone ヘッダーです。地域別の目安: 米国東部 -5、米国中部 -6、米国西部 -8 · 英国 / リスボン 0 · 中央ヨーロッパ +1 · 東ヨーロッパ +2 · インド +5.5 · 中国 / シンガポール +8 · 韓国 / 日本 +9 · シドニー +10。(標準時基準です - 夏時間で +1 になる地域もあるため、ユーザーが実際に使っている値を渡してください。)

同じ検索を 2 つのフォームで - 記憶は同一で、変わるのは time だけです:

結果 · form: memoir
{ "count": 3, "memories": [
  { "content": "Alice prefers tea over coffee", "time": "a couple weeks ago" },
  { "content": "met Alice at the cafe downtown",  "time": "yesterday afternoon" },
  { "content": "Alice moved to Brooklyn",          "time": "about half a year ago" }
] }
結果 · form: archive
{ "count": 3, "memories": [
  { "content": "Alice prefers tea over coffee", "time": "2 weeks ago (Jun 09)" },
  { "content": "met Alice at the cafe downtown",  "time": "yesterday at 14:00" },
  { "content": "Alice moved to Brooklyn",          "time": "6 months ago (Dec 2025)" }
] }
経過時間MemoirArchive
3 分a few minutes ago3 minutes ago
14 分about 15 minutes ago14 minutes ago
30 分half an hour ago30 minutes ago
50 分about an hour ago50 minutes ago
2 時間a couple hours ago2 hours ago, at 13:10
8 時間this morning8 hours ago, at 07:10
昨日の午後yesterday afternoonyesterday at 14:00
昨夜last night17 hours ago, at 22:00
2 日a couple days ago2 days ago (Tue 15:10)
6 日several days ago6 days ago (Fri 15:10)
9 日about a week agolast week (Jun 16)
16 日a couple weeks ago2 weeks ago (Jun 09)
35 日about a month agolast month (May 21)
60 日a couple months ago2 months ago (Apr 2026)
180 日about half a year ago6 months ago (Dec 2025)
380 日about a year agolast year (Jun 2025)
800 日a couple years ago2 years ago (Apr 2024)
1500 日about 4 years ago4 years ago (May 2022)

上の値はすべてレンダラーの実際の出力です。「昨日」の 2 行を見てください。Memoir は昨日の午後と昨夜を分けます - 一日とはひと眠りのことだからです。一方 Archive は時計の時刻ひとつで書き、昼と夜の線を引きません。

各モードの時間の読み方

Memoir - 人が実際に口にする言い方。直近はかなり鮮明なまま(15 分ほど前30 分前)、遡るほど言い回しが広がっていきます - 2 週間ほど前半年ほど前数年前 - 記憶そのものが、距離とともにゆるんでいくように。一日の中では時計を捨てて目印で語ります:今朝昨夜昨日の午後。そして一日はカレンダーの一目盛りではなく、ひと眠りです。境界は現地時間の午前 4 時ごろにあり、夜更かしはまだ同じ夜のままで、もう明日にはなりません。

Archive - 正確に、常にアンカーとともに。すべての行に、正確な経過時間と、モデルが計算に使える絶対的な基準が付きます。近いほどアンカーは細かくなります。今日は時計(8 時間前、07:10)、今週は曜日と時計(2 日前(火 15:10))、今月は日付(先週(6 月 16 日))、それより前は月と年(6 か月前(2025 年 12 月))。曖昧にならず、間違えません。

Memoir と Archive はレスポンスのすべてのリコール - 通常の検索、recall、エングラム - をその流儀で描画します。モデルのティア(Tablet → Scroll → Book)はエンジンがどこまでやるかを決め、フォーム(memoir / archive)は時間の書き方を決めます。Scroll 1.2 以降で利用できます。
全エングラム エングラム

deep_recall

マルチホップリコール。まずクエリで検索し、トップマッチの内容でもう一度検索します - 単発の検索では届かない、つながったコンテキストを引き込みます。記憶同士が参照し合う場面(人物 → そのプロジェクト → 詳細)に最適です。最大約 12 件を返します。

out = mem.engram("deep_recall", "what should I know about Alice?", user_id="alice")
const out = await mem.engram("deep_recall", "what should I know about Alice?", "alice");
let out = mem.engram("deep_recall", "what should I know about Alice?", "alice").await?;
curl -X POST https://api.wontopos.com/api/v1/engram/run \
  -H "X-API-Key: wos-live-..." -H "Content-Type: application/json" \
  -d '{"name":"deep_recall","user_id":"alice","query":"what should I know about Alice?"}'
レスポンス
{ "engram": "deep_recall", "hops": 2, "count": 12,
  "memories": [ ... ],
  "usage": { "input_tokens": 200, "output_tokens": 589 } }
課金はトークン単位です - すべての呼び出しが usage(入力 + 出力)を返し、API の他の部分と同じトークナイザーで数えます。エングラムごとの隠れた料金はありません。複数を同時に使いたいときは並行して呼んでください - 各エングラムは独立したリクエストです。
全エングラム エングラム

timeline

時系列リコール。関連度ではなく、出来事が起きた時点の新しい順に記憶を返します。「いつ X した?」という質問、履歴、順序の把握に。最大 15 件を返します。

events = mem.engram("timeline", "project milestones", user_id="alice")
const events = await mem.engram("timeline", "project milestones", "alice");
let events = mem.engram("timeline", "project milestones", "alice").await?;
curl -X POST https://api.wontopos.com/api/v1/engram/run \
  -H "X-API-Key: wos-live-..." -H "Content-Type: application/json" \
  -d '{"name":"timeline","user_id":"alice","query":"project milestones"}'
レスポンス
{ "engram": "timeline", "hops": 1, "count": 15,
  "memories": [ ... ],
  "usage": { "input_tokens": 100, "output_tokens": 736 } }
課金はトークン単位です - すべての呼び出しが usage(入力 + 出力)を返し、API の他の部分と同じトークナイザーで数えます。エングラムごとの隠れた料金はありません。複数を同時に使いたいときは並行して呼んでください - 各エングラムは独立したリクエストです。
全エングラム エングラム

gather

広域収集。検索したうえで、上位 3 件のマッチの周辺を展開します - deep_recall より広い網です。人物・プロジェクト・トピックに関連するものすべてを、1 回の呼び出しで集めるのに使います。最大約 18 件を返します。

related = mem.engram("gather", "everything about Project Atlas", user_id="alice")
const related = await mem.engram("gather", "everything about Project Atlas", "alice");
let related = mem.engram("gather", "everything about Project Atlas", "alice").await?;
curl -X POST https://api.wontopos.com/api/v1/engram/run \
  -H "X-API-Key: wos-live-..." -H "Content-Type: application/json" \
  -d '{"name":"gather","user_id":"alice","query":"everything about Project Atlas"}'
レスポンス
{ "engram": "gather", "hops": 4, "count": 18,
  "memories": [ ... ],
  "usage": { "input_tokens": 400, "output_tokens": 637 } }
課金はトークン単位です - すべての呼び出しが usage(入力 + 出力)を返し、API の他の部分と同じトークナイザーで数えます。エングラムごとの隠れた料金はありません。複数を同時に使いたいときは並行して呼んでください - 各エングラムは独立したリクエストです。
全エングラム エングラム

equilibrium

ドリフト補正。 セマンティック検索は会話が続くほど狭くなります。クエリが現在の状態を運ぶため、同じ状態の記憶ばかりが集まり、次のターンはさらにそちらへ傾きます。このエングラムは、クエリが支配しない三つの軸で結果を広げ直します。時間方向への分散、クエリから離れた連想、そしてストアの中身のある部分です。返答が繰り返しになったり平板になったときに使います。特定の事実を探すなら、クエリに近いままの deep_recall か gather が適しています。最大12件を返します。

wide = mem.engram("equilibrium", "how have things been lately?", user_id="alice")
const wide = await mem.engram("equilibrium", "how have things been lately?", "alice");
let wide = mem.engram("equilibrium", "how have things been lately?", "alice").await?;
curl -X POST https://api.wontopos.com/api/v1/engram/run \
  -H "X-API-Key: wos-live-..." -H "Content-Type: application/json" \
  -d '{"name":"equilibrium","user_id":"alice","query":"how have things been lately?"}'
レスポンス
{ "engram": "equilibrium", "hops": 3, "count": 12,
  "memories": [ ... ],
  "usage": { "input_tokens": 300, "output_tokens": 293 } }
課金はトークン単位です - すべての呼び出しが usage(入力 + 出力)を返し、API の他の部分と同じトークナイザーで数えます。エングラムごとの隠れた料金はありません。複数を同時に使いたいときは並行して呼んでください - 各エングラムは独立したリクエストです。
全エングラム エングラム

tone_stabilizer

自分の声。 会話が長くなると、アシスタントは普段の話しぶりから外れます。返答が伸び、報告書のようになり、直近の空気を帯びます。通常の自己想起はこれを悪化させます。現在の状態に近いものを探すため、直近の発言をあたかも人格であるかのように返してしまうからです。このエングラムは、その区間より前の自分の発言を返します。speaker me で保存された発言が必要で、無ければ推測せず空を返します。最大10件を返します。

# store the assistant's turns as speaker "me", then pull its own register back
mem.add("I keep answers short unless you ask for detail.", user_id="alice", speaker="me")
mine = mem.engram("tone_stabilizer", "how do I usually answer?", user_id="alice")
await mem.add("I keep answers short unless you ask for detail.", "alice", { speaker: "me" });
const mine = await mem.engram("tone_stabilizer", "how do I usually answer?", "alice");
mem.add("I keep answers short unless you ask for detail.", "alice", json!({"speaker": "me"})).await?;
let mine = mem.engram("tone_stabilizer", "how do I usually answer?", "alice").await?;
curl -X POST https://api.wontopos.com/api/v1/engram/run \
  -H "X-API-Key: wos-live-..." -H "Content-Type: application/json" \
  -d '{"name":"tone_stabilizer","user_id":"alice","query":"how do I usually answer?"}'
レスポンス
{ "engram": "tone_stabilizer", "hops": 2, "count": 10,
  "memories": [ { "content": "I keep answers short unless you ask for detail.", "speaker": "me" }, ... ],
  "usage": { "input_tokens": 200, "output_tokens": 442 } }
課金はトークン単位です - すべての呼び出しが usage(入力 + 出力)を返し、API の他の部分と同じトークナイザーで数えます。エングラムごとの隠れた料金はありません。複数を同時に使いたいときは並行して呼んでください - 各エングラムは独立したリクエストです。
HTTP API

すべてのエンドポイントを、ひとつのベース URL で。

SDK は不要で、任意の HTTP クライアントで動きます。ベース URL は https://api.wontopos.com、認証は X-API-Key ヘッダー、入出力は JSON です。記憶の操作は POST、ストアの管理は /collection への POST / GET / DELETE です。ストアが先に存在しないと(ストア を参照)、ストア内の操作は 404 を返します。

ヘッダー

ヘッダーはたらき
X-API-Keyすべての呼び出しに必須です。コンソールで発行した、あなたのキーです。
X-WOS-Model任意です。どのエンジンが答えるかを選びます。省略するとアカウントの既定値が使われます。GET /api/v1/models でこのキーが選べるモデルの一覧が得られ、それより前のエンジンが対応できないエンドポイントは 501 を返し、そのモデルの名前を伝えます。
Idempotency-Key書き込みでは任意です。同じキーで同じ本文なら、再保存せず最初の応答を再生します - 下の注記を参照してください。

エンドポイント

エンドポイント用途ボディフィールド
POST /api/v1/memory/collectionストアを作成user_id
GET /api/v1/memory/collectionsストアを一覧(なし)
DELETE /api/v1/memory/collectionストアとその記憶を削除user_id
/api/v1/memory/store記憶を 1 件保存user_id · content · metadata? (event_date · speaker) · image?
/api/v1/memory/store-turn会話ターンを保存user_id · user_msg · assistant_msg
POST /api/v1/memory/speakers話者を登録(明示的、50 人まで)user_id · speaker
GET /api/v1/memory/speakers登録済み話者の一覧 + 記憶数user_id
DELETE /api/v1/memory/speakers話者の登録解除(記憶は残る)user_id · speaker
/api/v1/memory/by-speakerある人が言ったこと、新しい順(「me」= エージェント自身)user_id · speaker · limit? · before? · skip_ids?
POST /api/v1/memory/image画像メモリの元データuser_id · memory_id
DELETE /api/v1/memory/image画像だけ削除し、キャプションは残すuser_id · memory_id · preview?
/api/v1/memory/imagesストアの画像、新しい順(+ 全体件数)user_id · limit? · before? · skip_ids?
/api/v1/memory/lineage1 件のメモリの編集履歴、古い順user_id · memory_id
/api/v1/won/revisionsストアがどれだけ書き換えられたか。無料user_id · include? · limit? · before? · skip_ids?
/api/v1/memory/revisions同じ呼び出しを memory 側の名前で。無料user_id · include? · limit? · before? · skip_ids?
/api/v1/memory/bulk-storeテキストの塊を一括投入user_id · content · category? · timestamp?
/api/v1/memory/searchセマンティック検索user_id · query · max_results? · speaker? · cache_control? · filters? · verify? · max_images?
/api/v1/memory/recall短期 + 長期 + コンテキストuser_id · query · limit? · context_limit?
/api/v1/memory/getid で記憶 1 件user_id · memory_id
/api/v1/memory/listストアをページ送りで一覧user_id · limit? · cursor?
/api/v1/memory/history直近のターンuser_id
/api/v1/memory/stats記憶数user_id
/api/v1/memory/supersede変わった事実を置き換えuser_id · old_memory_id · new_content
/api/v1/memory/forget1 件(または全件)を削除user_id · memory_id? (省略 = 全件削除)
GET /api/v1/engramこのモデルが実行できるエングラム(なし)
POST /api/v1/engram/runエングラムを 1 つ実行name · user_id · query · form? · tz?
GET /api/v1/models利用可能なモデル(なし)
書き込みは Idempotency-Key ヘッダーを受け付けます。同じキーで同じ本文なら再保存せず最初の応答を再生し(10 分)、同じキーで異なる本文なら 422 を返します。キャッシュされるのは 2xx のみなので、失敗した呼び出しはすぐに再試行できます。
# create the store once (stores are explicit)
curl -X POST https://api.wontopos.com/api/v1/memory/collection \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice"}'

# store a memory
curl -X POST https://api.wontopos.com/api/v1/memory/store \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","content":"she prefers tea over coffee"}'

# recall - one call, ready for your prompt
curl -X POST https://api.wontopos.com/api/v1/memory/recall \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what does alice drink?"}'
実際のレスポンス - store
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}
利用ティア

機能は全員同じ。
ティアは上限を引き上げるだけ。

どのティアもフルエンジンで動きます - 同じリコール品質、同じ言語対応、すべてのメソッド。ティアは累計クレジット購入額の増加に応じて Tier 5 まで自動で上がり、申請も営業とのやり取りも不要です。Enterprise(Tier 6)だけが例外です。

支出上限

各ティアには、暦月ごとに支出できる上限があります。累計クレジット購入額が次のしきい値に達すると、即座に昇格します。

利用ティアクレジット購入額月間支出上限
Tier 1$5$100
Tier 2$40$500
Tier 3$200$1,000
Tier 4$400$5,000
Tier 5$1,000$25,000
Tier 6 - Enterpriseお問い合わせ無制限

レート制限

レート制限はアカウント単位です - アカウント内のすべての API キーがひとつの制限を共有し、ティアに応じて拡大します。超過すると retry-after ヘッダー付きの 429 が返るので、バックオフ(1s → 2s → 4s)して再試行してください。すべてのエンドポイントは冪等性に配慮した設計のため、再試行しても安全です。

ティア毎分リクエスト数
Tier 1150
Tier 2300
Tier 3600
Tier 41,500
Tier 53,000
Tier 6 - Enterpriseカスタム

Enterprise(Tier 6)にはカスタムレート制限、SLA、専任サポート、オプションのセルフホストライセンスが付きます - お問い合わせください

無料の呼び出し

料金がまったくかからないエンドポイントがいくつかあります - Won にまとめてあります。価格の代わりに上限が二つあります。

  • エンドポイントごとに毎分 10 リクエストです。無料エンドポイントはそれぞれ独自のバケットを持つため、一方を消費しても他方は消費されません。
  • 共有で毎時 300 リクエストです。すべての無料エンドポイントが、アカウントごとに 1 つの時間あたり枠を共有します。

どちらも通常の利用で到達することはなく、いずれも上記の有料側の上限には影響しません。

料金は使用量ベースです。トークンに加えて、リクエストあたり一律 $0.0001。Tablet は入力 100 万トークンあたり $2、出力 100 万トークンあたり $3。ストレージは上限なしで無料です。この価格設定の理由もご覧ください。
エラーと制限

何かがうまくいかないとき。

エラーは JSON エンベロープで返ります。安定した type、人が読めるメッセージ、そして問題の報告時に添えられる request_id が含まれます。

実際のレスポンス - 無効なキー(HTTP 401)
{"type": "error", "error": {
   "type": "authentication_error",
   "message": "Invalid or revoked API key.",
   "request_id": "063f8b83-eee2-4383-a5cf-11e4bcd29d7c"
 }}
HTTP意味対処
400不正なボディ(フィールドの欠落・型違い)メッセージに該当フィールドが明記されます - 修正して再試行してください。
401API キーが無効または失効しているキーを確認し、コンソールで新しいキーを発行してください。
402残高不足、カード未登録、またはティア上限コンソールでチャージするか、カードを登録してください。レスポンスに balance_centsfloor_cents が入っているので、どちらに止められたのか判別できます。
404該当する記憶・ストア・画像がないid を確認してください。get_image は、記憶が存在していても画像を持たない場合にも 404 を返します。
409その名前は既に使われているストア名とワークスペース名はアカウント内で一意です - 別の名前を選んでください。
413リクエストボディが 10MB 超過Base64 はエンコード元のファイルより約 33% 大きくなるので、画像は縮小してからエンコードしてください。
429レート制限を超過SDK が既にバックオフとジッターを入れて再試行し、Retry-After も守っています。これを受け取ったということは再試行が尽きたということです - 自前のループで包むのではなく、同時実行数を下げてください。
501このモデルのエンジンがそのエンドポイントを実装していない画像と修正履歴にはより新しいエンジンが必要です。GET /api/v1/models でどのモデルが何に対応しているか分かります。
5xxサーバー側の問題バックオフしつつ再試行してください。ただし闇雲にではありません。この API はすべての呼び出しが POST で、サーバーが既にあなたのリクエストを保存し終えている可能性があるため、SDK は 5xx を自動では再試行しません。送り直すときは冪等キーを付けて二重書き込みを防ぎ、お問い合わせの際は request_id を添えてください。

すべてのエラーは WosError であり、ステータスごとに専用のクラスもあります - BadRequestError, AuthenticationError, PaymentRequiredError, NotFoundError, ConflictError, RateLimitError, ServerError, APIConnectionError。数値を比較するのではなく、扱いたいものを捕捉してください。

# SDK error handling (Python)
from wontopos import Client, WosError, RateLimitError, PaymentRequiredError

try:
    mem.search("...", user_id="alice")
except PaymentRequiredError: ...      # 402 - top up
except RateLimitError: ...            # 429 - the SDK already retried; slow down
except WosError as e: ...            # e.status, e.message, e.request_id
except (ValueError, TypeError): ...    # never left the client

一部の誤りは私たちに届くことすらありません。API キー、ストア id、冪等キー、画像はリクエストを送り出す前に検査され、そのときは WosError ではなく ValueErrorTypeError が発生します。except WosError だけではこれらを捕捉できません。

キーの安全。キーは作成時に一度だけ表示され、私たちの側にはハッシュとしてのみ保存されます。環境変数に保管し、漏えいした場合はコンソールで失効させてください - 失効は即時です。

レート制限はアカウント単位で、すべてのキーが共有し、ティアに応じて拡大します - 利用ティア を参照してください。アカウントの使用状況はコンソールで確認できます。

開発者

lineage

1 つの記憶の背後にある編集の連なりを、古い順に返します。revisions がストア全体の変化量を示すのに対し、こちらは 1 つの事実に何が起きたかを示します。

Tablet 2 以降で対応しています。読み取り専用です。revisions とは異なり、記憶の内容を返すため通常どおり課金される呼び出しです。

連なりに含まれる任意の記憶の id を渡します。置き換えられた版は削除されずに保持されるため、現在の事実だけを返す検索からでも遡ることができます。

chain = mem.lineage(memory_id=mid)["chain"]
for step in chain:
    print(step["changed_at"], step["action"], step["content"])
const { chain } = await mem.lineage(undefined, mid);
for (const step of chain) {
  console.log(step.changed_at, step.action, step.content);
}
let r = mem.lineage(None, mid).await?;
for step in r["chain"].as_array().unwrap_or(&vec![]) {
    println!("{} {} {}", step["changed_at"], step["action"], step["content"]);
}
curl -X POST https://api.wontopos.com/api/v1/memory/lineage \
  -H "X-API-Key: $WOS_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","memory_id":"m_9"}'
200
{ "memory_id": "m_9", "count": 3, "truncated": false,
  "chain": [
    { "memory_id": "m_3", "content": "lives in Seoul",
      "created_at": "2026-03-02T…", "changed_at": "2026-06-11T…",
      "action": "replaced", "confidence": 0.94,
      "superseded_by": "m_7", "is_current": false },
    { "memory_id": "m_7", "content": "moved to Busan",   … },
    { "memory_id": "m_9", "content": "Haeundae, specifically",
      "changed_at": null, "superseded_by": null, "is_current": true }
  ] }
フィールド動作
chain各版を古い順に返します。それぞれ記憶と同じフィールドに加えて、以下の 4 つを持ちます。
changed_atこの版が置き換えられた日時 (RFC3339) です。まだ有効な間は null になります。
actionこの連結点で何が起きたか、つまり置き換えた側がこの版とどう関係していたかを示します。
confidenceその関係についてエンジンがどれだけ確信していたかを 0-1 で示します。
is_currentまだ有効な 1 つの版で true になります。連なりごとにちょうど 1 つです。
truncated連なりがサービスの辿る長さを超えていた場合に true になります。返される段は、その場合も最も古いものです。

用途

用途は 2 つです。1 つはデバッグで、記憶が現在の内容になっている理由を確認できます。もう 1 つはアシスタント自身の履歴の参照です。3 回訂正された事実は 1 回だけ書かれた事実とは性質が異なり、それは連なりからしか分かりません。

Won

Won は、記憶を読む側のためのものです。

この API の大半は記憶答えます。Won は記憶について答えます。ストアがどれだけ書き換えられてきたか、どこまで信頼してよいかです。記憶に書かれている人ではなく、それを読む側、たいていはあなたが作っているアシスタントのためのものです。

Wontopos は Won + Topos、記憶が住まうひとつの場所という意味です。Won はその場所のうち、記憶を返すのではなく記憶について報告する部分です。これらの呼び出しは無料で、読み取り専用で、検索には一切触れません。尋ねてもあなたのユーザーの負担は増えず、憶えられている内容も変わりません。

現時点で提供しているもの

現時点では 1 つの呼び出しのみです。

呼び出し動作
POST /won/revisionsこのストアのうち、書き込み後にどれだけが変更されたかを返します。2 つの数値と、それを説明する 2 つの文が返ります。

実行例

生の件数ではなく比率を使ってください。40 件中 3 件と 40 件中 30 件では扱いが異なります。

r = mem.revisions()
# {"revised": 3, "total": 40, "counts": "…", "excludes": "…"}

if r["revised"] / r["total"] > 0.1:
    system += "Some of what you remember here has been corrected since."
const r = await mem.revisions();
// { revised: 3, total: 40, counts: "…", excludes: "…" }

if (r.revised / r.total > 0.1) {
  system += "Some of what you remember here has been corrected since.";
}
let r = mem.revisions(None).await?;
let (rev, tot) = (r["revised"].as_f64().unwrap_or(0.0),
                r["total"].as_f64().unwrap_or(1.0));
if rev / tot > 0.1 { /* say so in the system prompt */ }
curl -X POST https://api.wontopos.com/api/v1/won/revisions \
  -H "X-API-Key: $WOS_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice"}'

# → {"user_id":"alice","revised":3,"total":40,
#     "counts":"memories a transform has touched (supersede, update, retract, image removed)",
#     "excludes":"deletions — a deleted memory leaves nothing to count"}

呼び出し側がモデルであることが多いため、countsexcludes はフラグではなく文で返されます。削除は数に含まれません。

料金と上限

ルール
料金なし。無料の呼び出しは課金処理を通らないため、トークン課金もリクエストごとの手数料も発生せず、使用量も記録されません。
毎分アカウントごとかつエンドポイントごとに 毎分 10 回です。あるエンドポイントの毎分の枠を消費しても、他のエンドポイントの枠は消費されません。
毎時アカウントごとに 毎時 300 回で、すべての無料の呼び出しで共有されます。こちらはパスを区別しないため、無料エンドポイントが増えても 1 アカウントが使える総量は増えません。
有料トラフィックとの関係双方向に独立しています。これらの呼び出しが検索を遅くすることはなく、検索がこれらの枠を使い切ることもありません。同一アカウントのキーはバケットを共有するため、キーを増やしても枠は増えません。

どちらの上限も 429 を返し、秒単位の Retry-After と、どちらの上限に達したかを示すメッセージを含みます。

429 rate_limit_error
Retry-After: 41

{ "error": { "type": "rate_limit_error",
    "message": "This endpoint is free and limited to 10 requests per
                minute, counted per endpoint. Retry in 41s." } }
同じ呼び出しは /api/v1/memory/revisions でも応答します。Won のインターフェースができる前に公開されたクライアント向けです。ハンドラも予算も同一で、別枠ではありません。新しいコードでは Won のアドレスを使用してください。
Won · revisions

ストアがどれだけ書き換えられてきたか

revisionsrevisedtotal で答えます。ストアの記憶のうち、書かれたあとで変えられたものが何件あるかです。大事なことを記憶に委ねる前に、あるいは思い出した事実が今ユーザーの言っていることと噛み合わないときに、尋ねてみる価値があります。十のうち三つの事実が置き換わったストアは、誰も手を入れていないストアほどには信頼できません。

Tablet 2 以降で対応しています。HTTP API から、Python・TypeScript・Rust の SDK から、そして MCP ツールとして呼び出せます。それより前のエンジンは 501 を返し、対応できないモデルの名前を伝えます。

件数

変換が触れたものを数えます - 置き換え、更新、撤回、そして削除された画像です。

mem.revisions()
# {"revised": 3, "unrevised": 37, "total": 40, …}
await mem.revisions();
mem.revisions(None).await?;
curl -X POST https://api.wontopos.com/api/v1/won/revisions \
  -H "X-API-Key: $WOS_KEY" -d '{"user_id":"alice"}'
フィールド意味
revised変換が触れた記憶の数です。
unrevised書き込み以降、何も変更が加えられていない記憶です。revised + unrevised は常に total と一致します。個別に数えた値ではなく導出値のため、同時に書き込みがあっても 3 つの値が食い違うことはありません。
totalストアに入っている記憶の数です。
counts / excludes数値が何を対象としているかを、フラグではなく平文の文で示します。呼び出し側はモデルであることが多いためです。

一覧を読む

件数だけでなく記憶そのものを取得するには include を渡します。省略した場合は件数のみが返り、こちらは低コストの呼び出しです。

page = mem.revisions(include="revised", limit=20)
page["memories"], page["matched"], page["has_more"]
const page = await mem.revisions(undefined, { include: "revised", limit: 20 });
let page = mem.revisions_page(None, "revised", 20, None, None).await?;
curl -X POST https://api.wontopos.com/api/v1/won/revisions \
  -H "X-API-Key: $WOS_KEY" \
  -d '{"user_id":"alice","include":"revised","limit":20}'
フィールド動作
include"revised" または "unrevised" を指定します。それ以外の値は件数へのフォールバックではなく 400 で拒否されます。入力ミスで一覧が黙って落ちると、空のストアと見分けがつかなくなるためです。
limit5 から 20 まで、既定は 20 です。範囲外の値や型の誤りは、丸められるのではなく拒否されます。
matchedページのサイズではなく、このページの背後にある総行数です。
ordered_byサービス側が並び順を明示します。最後に編集された順ではなく、保存された順で新しいものからです。
next_beforenext_skip_ids と組で使う次のページ用のカーソルです。両方をそのまま返送してください。id はページをまたいで蓄積されます。
一覧は記憶が変更された日時ではなく、保存された日時で並びます。「最後に編集された順」と想定する呼び出し側はページを読み違えるため、レスポンス側でどちらであるかを明示しています。
削除は数えません。消された記憶には数えるものが残らないため、大きく削り込まれたストアでも revised は低いまま出ます。この数字が示すのは、どれだけ書き換えられたかであって、どれだけ失われたかではありません。
スケール

コンテキストウィンドウを超えて。

WOS は 1.4M トークンの履歴 - どの LLM のコンテキストウィンドウよりもはるかに大きな規模 - からリコールし、それでも約 1,470 トークンの引き締まったスライスだけを返します。

エージェントの記憶は、プロンプトに収まる量に縛られません。すべてを保持し、履歴がどれだけ大きくなっても、重要なものだけを取り出します。

プライバシー

プライベートで、あなたのもの。

データはあなたのストアに留まります。学習にも閲覧にも再利用にも使いません - 取り出せるように整理するだけです。

  • BYOK。LLM キーはリクエストごとに送られ、保存されることはありません。
  • 隔離。記憶はアカウント単位、さらに user_id 単位でスコープされます。
  • GDPR 削除 & セルフホスト。1 回の呼び出しでユーザーを消去でき、必要ならエンジンを自社環境で運用できます。