Skip to main content
Glama

img.png

recall.select

最小限のエージェント記憶システム - 任意のエージェントに1つのURLを与えるだけで、ほぼゼロセットアップで長期記憶を獲得できます。Qdrant + FastMCP + FastAPI/Bootstrap 上に構築されています。

完全な設計と段階的なビルド計画については docs/specs/initial_specification.md を、注目すべき変更の実行記録については docs/specs/changelog.md を参照してください。

仕組み

記憶はベクトルとして保存されます。各記憶ストアは Qdrantコレクション であり、(user, project) ペアに 1対1 でマッピングされます。これらのベクトルに関するメタデータ(ユーザー、APIキー、プロジェクト、コレクションごとの使用量/制限統計)は MongoDB に保存されます。

flowchart LR
    agent[Agent] --> web[FastAPI / MCP]
    web <-->qdrant[Qdrant]
    web <--> mongo[MongoDB]
    web <--> embed[Embedding API]

Qdrantコレクションは 遅延的に 作成されます。最初の記憶が (user, project) ペアに保存されるまで、Qdrantには何も触れません。

Related MCP server: Firmament Open Brain

アーキテクチャ

  • app/main.py - FastAPIアプリ。Bootstrapのランディングページを提供し、起動時にMongoのインデックスが存在することを確認します(コールド/リモートDBに対して耐性があります)。

  • app/mcp_server.py - メモリリンクの背後にあるMCPサーバー。エージェントのMCPクライアントは {PUBLIC_BASE_URL}/m/{key} を指します(Streamable HTTP、ステートレス、JSONレスポンス)。パス内のAPIキーが完全な認証情報であり、ツールをキー所有者のデフォルトプロジェクトにスコープします。基本ツール: store_memory / recall_memory / delete_memory。セマンティックレイヤーツール(vector_semantics.py を参照): link_memories / unlink_memories / annotate_memory / memory_connections / recall_connected - 接続されたエージェントはクライアント側で関係推論を行い(明示的な要求があった場合のみ)、これらは結果を取り込むかトラバースします。同じキーは、キーレスな /mcp エンドポイントに対して Authorization: Bearer として送信することもでき、シークレットをURL/ログから隠すことができます。{...}/m/{key}.md(app/api/connect.py 内)は、対応するセットアップ手順(両方の形式)を提供します。

  • app/dependencies.py - コアDIコンテナ(injector)。共有シングルトン(Qdrantクライアント、Mongoクライアント/DB、リモートエンベッダー)を構築します。FastAPIの依存関係(app/api/deps.py)と起動時は、クライアントを自分で構築するのではなく、app_container から解決します。

  • app/services/ - サービスレイヤー(HTTP/ルートコードはなく、I/Oのみ):

    • qdrant_store.py - Qdrantクライアント + ensure_collection/upsert_memory/search/delete_memory、およびセマンティックレイヤーが必要とするポイントレベルのプリミティブ(neighbors、scroll_points、retrieve_points、set_payload)。

    • vector_semantics.py - ベクトルメモリユーティリティレイヤー: ストアを意味のグラフとして扱います。各ポイントのペイロード内の予約済み _semantics 名前空間には、deixisアンカー(所有者、保存時刻。保存時に書き込まれる)、クライアントが抽出したエンティティ、およびクライアントが宣言した型付き関係(upsert_relations はそれらを検証して保存します - サーバー側でLLM呼び出しはありません)が保持されます。宣言された関係には2つの品質ヘッジがあります: confidence(0-1]、エッジのトラバーサル強度をスケーリング)と valid_till(ISO 8601; 期限切れのエッジはすべての読み取りパスで無視されるため、古い構造は自動的に廃止されます)。衛生管理: remove_relations は間違ったエッジを削除し(upsert_relations の修正版)、memory.delete_memory は prune_relations_to を呼び出すため、メモリの削除後もぶら下がったエッジは残りません。 プラグイン可能な レンズ(topical/temporal/entity/declared)は型付きエッジを導出します。その上に semantic_graph(マルチグラフ)、spreading_activation(接続による検索)、concept_clusters(創発的オントロジー)、infer_relation(宣言された真実が優先され、その後に幾何学的ヒューリスティック)が配置されます。パフォーマンスメモ: 入力エッジのルックアップ(relations_of(include_incoming=True))は現在、境界付きスクロールアンドスキャンです。逆トラバーサルがホットになった場合の修正は、Qdrantの ペイロードインデックス を _semantics.relations[].target に作成し(create_payload_index、キーワードスキーマ)、スキャンの代わりにフィルタリングクエリを使用することです - 同じストア、インデックスのみ。スキーマは何も変わりません。

    • mongo.py - Mongoクライアント、get_db()、および ensure_indexes()(一意の複合インデックスで1対1の (user, project) ルールを強制)。

    • users.py - add_user、get_user、get_user_by_email、update_user。

    • api_keys.py - ユーザーにバインドされたキー。SHA-256ハッシュとして保存(平文は add_api_key から一度だけ返され、永続化されません): add_api_key、delete_api_key、delete_user_keys、list_api_keys、get_labeled_key、get_by_key(提示されたトークンをハッシュ化し、ダイジェストと照合; MCP認証ゲートで record_use=True とすると last_used_at がスタンプされます)。保存時、各キーは非秘密の表示ヒントも保持します - key_prefix + key_last4。masked() によって rs_ab12…wxyz としてレンダリングされるため、シークレットを再公開することなくキーをリスト表示して区別できます。

    • projects.py - add_project、get_project、list_projects、update_project、delete_project。

    • collections.py - (user, project) ↔ Qdrantコレクション のレジストリ。collection_name(user_id, project_id) は内部命名標準(rs_{user}_{project})です。制限と統計のために points_count/calls_count を追跡します。

    • collection_provisioning.py - 両面の create_collection / destroy_collection ステップ。コレクションは、Mongoレジストリ行 と そのバッキングQdrantコレクションの両方が存在する場合にのみ存在します。これは collections レジストリと qdrant_store を1つのアトミックで冪等な操作に構成し、2つのストアが同期しなくなることがないようにします。作成は遅延的であるため、唯一の作成呼び出し元は最初のメモリ書き込み(memory.store_memory)です。コレクションAPIの削除は destroy_collection を使用します。

    • embeddings.py - Embedder 抽象化; embeddings_remote.py - 具体的なテキスト→ベクトルバックエンド(リモート埋め込みAPI、例: DeepInfra)。

    • monobank.py - 最小限のMonobankアクワイアリングクライアント(create_invoice、fetch_invoice_status)とWebhook認証(fetch_pubkey / verify_signature、生のボディに対するECDSA-SHA256)。mcp-api.netのマーチャントトークンを再利用します。recall.selectは独自の請求書/リダイレクト/Webhookを所有します。

    • billing.py - プランカタログと、Monobankの invoiceId をキーとする支払い記録。チェックアウト時に record_pending。apply_webhook は、success 時に購入者の tier を 1回だけ 切り替えます(リトライ/重複に対して冪等)。reconcile はWebhookが見逃したものを解決します(以下参照)。ティアは 時間制限付き です: grant_tier はエンタイトルメントが付与される唯一の場所(支払い済み請求書または所有者の好意)であり、tier_expires_at と tier_grants の監査行を書き込みます。effective_tier(user) はすべてのチェックが読み取る必要があるものです。保存された paid_2x の日付が過ぎている場合は無料アカウントになります。また、ティアごとの許容量の単一の真実の情報源でもあります: call_allowance(tier) / project_allowance(tier)(None = 無制限; 未知のティアは無料にフォールバック)。

    • usage.py - 月間呼び出しメーターと価格モデルゲート。受け入れられた store/recall/delete のたびに、(user, カレンダー月) ごとの usage 行にカウントされます。check_call_allowed は、ティアの月間 call_allowance が使い果たされると呼び出しを拒否し、QuotaExceeded を発生させます。memory.py で強制され(MCPツールとHTTPメモリAPIの両方がカバーされます)、app/main.py によって HTTP 429 にマッピングされます。MCPトランスポートはツールエラーとして表面化します。全期間の collections.calls_count とは別です。

    • account.py - サインインした /account ページが表示する読み取り専用スナップショット(プラン、月間使用量、プロジェクトごとの保存数、マスクされた形式と作成日/最終使用日付のAPIキーリスト)。billing/usage/projects/collections/api_keys から構成されます。

    • docs.py - 公開 /docs 統合ガイドのコンテンツ。MCPクライアント設定を1か所で構築します(mcp_config / mcp_config_json)。ドキュメントページ と app/api/connect.py のキーごとの .md の両方で再利用されるため、2つがずれることはありません。INTEGRATIONS はガイドレジストリです(エントリを追加することでページを追加)。

公開ページ(app/main.py から提供、Bootstrap + Jinja、app/translations/*.yml によるi18n): / ランディング、/plans、/account(サインイン済み)、および /docs/integrations ガイド。FastAPIの組み込みAPIドキュメントは /docs から /api/docs(/api/redoc、/api/openapi.json)に移動され、公開サイトが /docs を所有します。

支払いはHTTPレイヤーを介して app/api/payments.py で処理されます: POST /api/me/checkout(サインイン済み)は請求書を作成し、Monobankの pay_url を返します。検証済みの POST /webhooks/monobank はティアを付与します。GET /payment/success|fail は化粧的なブラウザ戻りページです(エンタイトルメントはWebhook駆動であり、これらではありません)。

エンタイトルメントはWebhookだけに依存しません。 Monobankは各ステータス変更を1回だけ送信し、再送信しないため、再起動やプロキシの不具合でコールバックが失われると、支払った顧客が古いティアのままになり、こちら側で気付く手段がありません。そのため、アプリはプルも行います: PAYMENT_RECONCILE_MINUTES ごとにバックグラウンドスイープ(billing.reconcile_with_monobank、app/main.py のライフスパンで開始)が、5分経過してもまだ処理中の各支払いの実際のステータスを取得し、それを 同じ apply_webhook 遷移にプッシュします。プッシュとプルは互いに冪等です - どちらかが先に到達するとティアが付与され、もう一方は何もしません。payments の行はチェックアウトが 開始 されたときに書き込まれるため、created は「支払いページを開いた」ことを意味し、「支払った」ではありません。/admin/payments はその区別を明示的に表示します。

購入は1か月分(SUBSCRIPTION_DAYS)であり、永久ではありません: grant_tier は tier_expires_at をスタンプし、同じバックグラウンドループは期限切れのアカウントを無料に戻します(downgrade_expired)。アカウントページにはプランの有効期限が表示されます。まだ自動更新はありません - ユーザーが再度購入し、クレジットが残っている間に購入すると、ウィンドウが 延長 され、再開されるわけではありません。エンタイトルメントは effective_tier を通じて読み取られるため、期限切れの付与は、スイープが保存されたフィールドを書き換える前でもすぐに支払いを停止します。

自動更新がないため、アプリは 尋ねます: billing.renewal_state(user) は /account のプロンプトを駆動します - プランの最後の RENEWAL_WARNING_DAYS(7)日間の警告とワンクリック更新ボタン、および「プランが終了しました。更新してください」というプロンプトが LAPSED_PROMPT_DAYS(30)日間表示されます(ダウングレードスイープは lapsed_tier / tier_lapsed_at を記録するため、ページは何が期限切れになったかをまだ表示できます)。更新は、プランページが使用するのと同じ /api/me/checkout に投稿し、保持していたプランに事前設定されます。まだメールはありません - プロンプトは訪問したユーザーにのみ届きます。

すべてのCRUD関数はオプションの db=/client= 引数を取るため、ライブバックエンドなしでテストで駆動できます。

設定

環境変数で設定します(ローカルの .env は自動的に読み込まれます。コミットしないでください - .env.example を参照):

変数

デフォルト値

目的

MONGODB_URI

(必須)

リモートのマネージドMongoDB接続文字列。

MONGODB_DB

recall_select

データベース名。

QDRANT_URL

http://qdrant:6333

Qdrantエンドポイント(内部Composeネットワーク)。

QDRANT_API_KEY

(ローカルではなし、本番では必須)

アプリとQdrant間で共有されるシークレット。ComposeはQDRANT__SERVICE__API_KEYをこの値に設定し、アプリはすべてのリクエストでこれを送信します。これはqdrant.recall.selectダッシュボードへの唯一のゲートであり、ダッシュボード自体には認証がありません。

VECTOR_SIZE

768

すべてのコレクションのベクトル次元数。リモートの埋め込みAPIには(dimensionsパラメータ経由で)このサイズのベクトルを返すよう要求され、両者の同期が保たれます。

EMBEDDING_API_KEY

(必須)

リモート埋め込みAPIのAPIキー。

EMBEDDING_BASE_URL

https://api.deepinfra.com/v1

OpenAI互換の埋め込みAPIベースURL。

GOOGLE_CLIENT_ID

(サインインに必須)

Google OAuth 2.0 WebクライアントID。

GOOGLE_CLIENT_SECRET

(サインインに必須)

Google OAuth 2.0クライアントシークレット。

SESSION_SECRET

(開発用フォールバック)

セッションクッキーに署名します。本番環境では安定した値を設定してください。

PUBLIC_BASE_URL

http://localhost:8000

公開オリジン。メモリリンクとOAuthリダイレクトURIを構築します。

FORWARDED_ALLOW_IPS

172.25.0.0/16 (compose) / 127.0.0.1 (uvicorn)

uvicornが信頼するX-Forwarded-Proto/-Forヘッダーの送信元ピア。Composeではcaddy_netサブネットにデフォルト設定され、リダイレクトでhttpsスキームが維持され、ログに実際のクライアントIPが記録されるようにします。そのネットワークが再作成された場合はdocker network inspect caddy_netで確認してください。

MONOBANK_API_KEY

(支払いに必須)

Monobankアクワイアリングマーチャントトークン。mcp-api.netプラットフォームと共有 - 同一マーチャント、単一アカウント。請求書はreferenceで区別されます。

MONOBANK_REDIRECT_URL

{PUBLIC_BASE_URL}/payment/success

購入者が支払い後にブラウザがリダイレクトされるURL。

MONOBANK_WEBHOOK_URL

{PUBLIC_BASE_URL}/webhooks/monobank

サーバー間コールバックで、ティアを付与します。公開アクセス可能である必要があります。

MONOBANK_WEBHOOK_VERIFY

1

マーチャント公開鍵に対してWebhookのX-Signを検証します。金銭が絡む場合は有効にしておいてください。ローカル開発時のみ0に設定します。

PAYMENT_RECONCILE_MINUTES

15

Monobankから処理中の支払いの実際のステータスを取得する間隔(分)。これにより、Webhookが失われても支払い中の顧客が取り残されることを防ぎます。0でスイープを無効化します。

ADMIN_SECRET

(未設定 - エリア無効)

/adminのオーナー管理エリアをアンロックします。未設定の場合、すべての/adminルートは404を返します。

ADMIN_SESSION_HOURS

12

アンロックされた管理セッションが再ロックされるまでの時間。

オーナー管理エリア(/admin)

サポートやユーザーの視点確認のために、任意のユーザーの個人エリアへの読み取り専用ウィンドウです。ADMIN_SECRETを設定し(生成: python -c "import secrets; print(secrets.token_urlsafe(32))")、Webコンテナを再作成した後、{PUBLIC_BASE_URL}/adminを開き、セッションごとに一度キーを入力します。/admin/usersはすべてのアカウントを一覧表示します(メール、名前、ユーザーIDで検索可能)。各行から、そのユーザーのプラン、今期間の使用量、メモリカウント付きのプロジェクト、マスクされた形式のメモリリンクを開くことができます。

境界は意図的に設定されています。キーはPOSTで送信され(URLパラメータではないため、履歴やアクセスログに残りません)、間違った推測を繰り返すとクライアントは5分間ロックアウトされます。セッションはADMIN_SESSION_HOURS後に自動的に再ロックされ、このエリアのルートは何も書き込まず、メモリテキストやキーシークレットを公開しません。オーナーはアカウントの構造を見ることができても、その内容は見ることができません。ADMIN_SECRETが未設定の場合、このエリアはまったく存在しません。

認証(Googleサインイン)

サインインはメモリリンクへのゲートです。ユーザーはGoogleでサインインし、メモリリンクをコピーをクリックしてデフォルトのプロジェクト、コレクション、APIキーをプロビジョニングし、エージェントに渡すURLを取得します。シークレットは一度だけ表示されます(ハッシュのみが保存されます)。その後、ランディングページはリンクをマスクして表示し(GET /api/me/link経由)、ボタンは明示的で確認が必要な「新しいリンクを取得」に変わります。再生成は古いリンクを無効にし、サイレントに行われることはありません。キーは/accountで管理されます。マスクされたリスト、作成日/最終使用日、ラベル付き作成(一度だけ表示)、取り消し。Google認証情報を設定するには:

  1. Google Cloud Console → APIs & Services → OAuth同意画面 - 設定します(外部。未検証の間はテストユーザーとしてメールアドレスを追加)。

  2. 認証情報 → 認証情報を作成 → OAuthクライアントID → Webアプリケーション。

  3. 承認済みのリダイレクトURIを追加: {PUBLIC_BASE_URL}/auth/callback - 例: ローカル開発ではhttp://localhost:8000/auth/callback、本番ではhttps://recall.select/auth/callback(ローカルでテストする場合は両方を追加)。

  4. クライアントIDとクライアントシークレットを.envにコピーします(GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET)。安定したSESSION_SECRETを設定します(python -c "import secrets; print(secrets.token_urlsafe(48))")。

ローカルでの実行

Docker Composeを使用したフルスタック(Web + Qdrant):

cp .env.example .env   # then fill in MONGODB_URI
docker compose up --build
# open http://localhost:8000

または、独自のQdrant/Mongoに対してアプリのみを実行:

pip install -e ".[dev]"
uvicorn app.main:app --reload

テスト

pip install -e ".[dev]"
pytest

CRUDテストはインメモリMongo(mongomock)に対して実行され、Qdrant/埋め込みクライアントは偽装されています。ライブバックエンドは不要です。

デプロイ

./deploy/deploy.sh

同じコマンドが2つの場所から機能します。実行された場所を検出します:

  • 開発マシンから(またはエージェントのボックスから): ローカルコミットをプッシュし、recall-server SSHエイリアス経由でサーバー上でデプロイを実行します。

  • サーバー上で(setti@setti-server:~/recall_select$ ./deploy/deploy.sh): その場でデプロイ。SSHホップはありません。

どちらのパスも同じワーカーを実行します - deploy/_server_deploy.sh: masterのgit同期、Composeスタック(FastAPI web + Qdrant)の再ビルド、共有Caddyプロキシのリロード(recall.selectの自動HTTPS)、古いイメージの削除。MongoDBはリモート/マネージドであるため、認証/MONGODB_URI環境変数(.env参照)がサーバー上に存在している必要があります。

サーバー上で実行する人は、リポジトリへのGitHubプルアクセス(~/.ssh内の認証済みSSHキー)とdockerグループのメンバーシップが必要です。これはclaude-agentとsettiの両方で当てはまります。ワーカーはリポジトリをgitのsafe.directoryとして自動登録するため、リポジトリの所有者でないデプロイ担当者が「疑わしい所有権」によってブロックされることはありません。

自動デプロイ(CI)

masterへのすべてのプッシュは、GitHub Actions(.github/workflows/deploy.yml)を介して自動デプロイされます。上記と同じフローですが、人間ではなくCIによってトリガーされます。ジョブはサーバーにSSH接続し、deploy/_server_deploy.shを標準入力にパイプするため、プッシュされたコミット自身のデプロイロジックが実行されます。デプロイは直列化され(concurrency)、Run workflowボタン(workflow_dispatch)を使用してオンデマンドでデプロイすることもできます。

一度だけの設定 - Settings → Secrets and variables → Actionsの下に追加:

シークレット

必須

目的

DEPLOY_SSH_KEY

はい

公開鍵がデプロイユーザーの~/.ssh/authorized_keysにある秘密鍵。

DEPLOY_HOST / DEPLOY_USER

はい

サーバーアドレスとデプロイを実行するSSHユーザー。

DEPLOY_PORT

いいえ

SSHポート(デフォルト22)。

DEPLOY_KNOWN_HOSTS

いいえ

サーバーのホストキーを固定します。未設定の場合、CIはssh-keyscan経由で初回使用時に信頼します。

アプリのシークレット(MONGODB_URI、OAuthなど)はサーバーの.envに残ります。CIがそれらを見ることはありません。

ライセンス

GNU Affero General Public License v3.0 に基づいてライセンスされています。ネットワークサービスとして修正版を実行する場合、AGPL はそのソースコードをユーザーに提供することを要求します。Copyright © 2026 Sergii Setti.

Maintenance

ActivityStale
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables each user to have a private, per-user memory store that persists across AI tools, allowing them to store, recall, search, list, and delete memories via MCP.
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to store and semantically retrieve durable memories across sessions via MCP or REST, with tools for remembering, recalling, asking, updating, and forgetting memories.
    4 npm
    MIT