memwright について
Attestorは、アクセス制御、来歴管理、監査可能な削除を必要とするマルチエージェントシステム向けのセルフホスト可能なメモリーサービスです。Pythonライブラリ、RESTサイドカー、MCPサーバーとして動作し、決定論的な検索と過去時点の再現に対応します。
主な機能
- 01AgentContextによるロールベースアクセス制御
- 02メモリーの来歴記録と任意のEd25519署名
- 03エージェントごとの予算と書き込み上限
- 04LLMによる採点を使わない決定論的検索
- 05時間指定の再現とテナント分離
- 06監査可能な削除と永続的なガバナンスジョブ
活用シーン
- 01信頼レベルの異なるエージェント間でのメモリー共有
- 02過去の時点でシステムが保持していた情報の再現
- 03監査可能なデータ削除要件への対応
- 04セルフホスト型マルチエージェントメモリーレイヤーの運用
MCP 対応
AttestorはMCPサーバーとして動作し、PythonライブラリやRESTサイドカーと同じAPIを提供します。MCPインターフェースはストアを直接呼び出し、独自の認証に依存します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
uvx memwrightPG_CONNECTION_STRINGPostgreSQL connection string for pgvector (default: postgresql://memwright:memwright@localhost:5432/memwright)NEO4J_PASSWORDシークレットNeo4j database password (default: memwright)OPENROUTER_API_KEYシークレットOpenRouter API key for embeddings (required for semantic search)OPENAI_API_KEYシークレットOpenAI API key for embeddings (alternative to OpenRouter)ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
Attestor
マルチエージェントの本番メッシュに向けた、ガバナンスを備えたメモリです。
Attestorはもはや「コーディングアシスタントのためのメモリ」ではありません。3つのことが起きました。エディターのベンダーがメモリ機能を無料で提供し、ベンチマーク競争はどのLLMが質問に答えるかを競うものになり、メモリレイヤーについての本格的な議論は、まったく別の問題を扱うようになりました。つまり、多くのエージェントが1つのメモリを共有するとき、書き込みを許可されるのは誰か、忘却を許可されるのは誰か、そして後からそれを証明できるか、という問題です。Attestorは今、そのためのものです。
多くのエージェントが1つのメモリを共有するとき、想起は簡単な部分です。難しいのは、誰が書き込めるのか、誰が忘れられるのか、回答が何をもとに作られたのか、そして後からそれらすべてを証明できるかどうかです。Attestorはセルフホスト可能なメモリサービスで、こうした特性をポリシー文書ではなくコードで強制します。AgentContextレイヤーでのロールベースアクセス制御(Ed25519による署名も任意で可能)を各メモリに付与し、エージェントごとのトークン予算と書き込みクォータを設定します。LLMを使わない決定論的なランキング経路、過去の任意の時点を指定してクエリできる時間的な置き換え、3つすべてのストレージロールにおける厳格なテナント分離、宣言型の保持ポリシーを備えた、監査可能で監査を最優先とするユーザー忘却処理も提供します。さらに、ガバナンスジョブは永続的なTemporalワークフローとして実行され、ポリシーに従って再試行され、クラッシュ後に再開し、オペレーターUIで確認でき、読み取り経路には入りません。Pythonライブラリ、RESTサイドカー、または同じAPIを備えたMCP サーバーとして動作します。ロールの強制はAgentContextの保証であり、プロセス内で実行されます。RESTとMCPの各インターフェイスはストアを直接呼び出し、それぞれ独自の認証に依存します。
ctx = AgentContext(agent_id="reviewer-1", role=AgentRole.REVIEWER, namespace="acme-prod")
ctx.add_memory("...") # PermissionError: REVIEWER lacks 'write'
mem.recall("...", context=ctx, as_of=last_friday) # what the mesh believed then — nothing is deleted
pip install attestor
| バージョン | 4.1.11(安定版。新規構築向けの再構築で、v3からの移行パスはありません) |
| PyPI | attestor |
| インポート | attestor |
| ライブサイト | https://attestor.dev/ |
| リポジトリ | https://github.com/bolnet/attestor |
| ライセンス | MIT |
Surendra Singh が設計・構築。15年にわたる本番システムの規律をメモリレイヤーに取り入れ、マルチエージェントAI向けの監査可能なインフラを構築しています。関連プロジェクト:
claude-finance(Claudeを活用した金融分析)・private-equity(PE × AIワークショップ)。AIインフラのシニアICを採用中であれば、ご連絡ください。
概要
Attestorは、セキュリティレビュー、規制当局、顧客、あるいは午前3時にページャーで呼び出されたエンジニアなど、説明責任を果たす相手がいるエージェントメッシュのためのメモリレイヤーです。すべてのメモリはPostgresに一度だけ保存され(信頼できる唯一の情報源)、そこからPinecone(ベクトル)とNeo4j(グラフ)に派生データが作られます。読み取りと書き込みはすべて、アイデンティティ、ロール、名前空間、予算を保持するAgentContextを経由し、ストアには過去の任意の状態を再構築できるだけの履歴が保持されます。
強制される内容
以下の各保証は、ロードマップ上の予定ではなく、コードの特性です。ファイル参照は、強制を実装する箇所を示しています。
| 保証内容 | コードの動作 | 場所 |
|---|---|---|
| 役割ベースのアクセス制御、6 つの役割 | ORCHESTRATOR = 読み取り + 書き込み + 忘却、PLANNER / EXECUTOR / RESEARCHER = 読み取り + 書き込み、REVIEWER / MONITOR = 読み取り専用です。read_only=True は独立したキルスイッチであり、役割にかかわらず書き込み + 忘却を無効にします。違反があると、バックエンドにアクセスする前に PermissionError が発生します。 | attestor/context.py — ROLE_PERMISSIONS、_require_permission |
| すべてのメモリに由来情報を付与 | すべての書き込みに agent_id、session_id、namespace、役割が含まれます。子コンテキストでは agent_trail が蓄積されるため、「誰の代理で、どのエージェントがこれを述べたのか」を確認できます。source_episode_id はエピソードの統合処理で設定され、すべての書き込みで設定されるわけではありません。置き換えによって削除されることはありません。行が削除されるのは、明示的な forget_user()(事前に監査)、保持 delete ポリシー(事後に監査)、または compact()(監査なし)の場合に限られます。id ‖ agent_id ‖ t_created ‖ content_hash への Ed25519 署名は任意で付与でき、verify_memory() で検証できます。 | attestor/context.py、attestor/identity/signing.py |
| エージェントごとの予算と書き込みクォータ | token_budget は、1 つの AgentContext に対して recall() がまとめる内容の上限であり、書き込みを制限するものではありません。max_writes_per_agent はコンテキスト内のメモリカウンターで、新しいコンテキストを作成するとリセットされます。set_quota() はユーザーごとの 1 日の書き込み上限を設定し、add() で適用されます。エージェントごとではなく、ユーザーごとの設定です。 | attestor/context.py、attestor/core/quota_service.py、attestor/core/agent_memory.py |
| 決定論的な読み取りパス | ランキングはベクトル → BM25 → RRF → グラフ → MMR → 予算への適合の順で行われ、デフォルトではスコアリングに LLM を使用しません。同じクエリには同じ順序で結果を返し、ユニットテストと再生が可能です。クエリ拡張レーン(HyDE、マルチクエリ)は候補を追加する YAML フラグであり、スコアリングには関与しません。任意のリランカー(bge、cohere、llm プロバイダー)で候補を再スコアリングできますが、デフォルトでは無効です。 | attestor/retrieval/orchestrator/core.py、attestor/retrieval/scorer.py、attestor/retrieval/reranker.py |
時間的な置き換え、as_of による再生 | 矛盾がある場合、古い事実を新しい事実で置き換えます。置き換えによって削除されることはありません。すべての事実には、有効時間(valid_from、valid_until)とトランザクション時間(t_created、t_expired)が付与されます。recall(as_of=...) は、ドキュメントのハイドレーション時と BM25 レーンでトランザクション時間に基づいて候補をフィルタリングします。Pinecone レーンでは as_of を無視し、警告を記録します。 | attestor/temporal/manager.py、attestor/store/schema.sql、attestor/store/pinecone_backend.py |
| 3 つすべての役割における厳格なテナント分離 | テナントはユーザーです。Postgres:attestor.current_user_id をキーとする行レベルセキュリティを使用し、namespace をメタデータに付与します。Pinecone:1 つのインデックスを使用し、すべての呼び出しで namespace= により分割します。Neo4j:namespace プロパティと複合 (key, namespace) 制約を使用し、名前空間の範囲内でトラバーサルします。PageRank のプロジェクションはグローバルで、ランキングシグナルとしてのみ使用されます。 | attestor/store/schema.sql、attestor/store/pinecone_backend.py、attestor/store/neo4j_backend.py |
| 監査可能なユーザー忘却と保持 | forget_user() は削除前に、RLS の適用対象外で規約により追記専用の forget_audit 行を書き込み、その後、ドキュメント → 状態 → ベクトル → グラフの順に処理します。バックエンドごとの件数を報告するため、一部のみ成功した場合も可視化され、黙って処理されることはありません。宣言型の保持ポリシー(archive / delete。名前空間、カテゴリ、レイヤー、タグ、経過時間でフィルタリング)は、先に適用され、その後 forget_audit 行を書き込みます。監査を先に行うのは forget_user() のみです。データ主体アクセス要求向けに完全エクスポートが可能です。 | attestor/compliance/retention.py、attestor/core/agent_memory.py — export_user、purge_user、deletion_audit_log |
| 永続的なガバナンスジョブ | 統合、派生状態の修復(attestor durable rebuild)、監査を先に行うユーザー忘却サーガ、保持およびセッションの定期処理は、明示的な再試行ポリシーとオペレーター UI を備えた Temporal ワークフローとして実行されます。忘却サーガは監査行を書き込み、ドキュメント行を削除した後、ベクトル、グラフ、状態の処理を並行して実行します。各バックエンドを個別に再試行し、一部のみ成功した状態は失敗として報告します。オプトインの追加機能(attestor[durable])です。読み取りパスと Claude Code フックがこれに触れることはありません。 | attestor/durable/、docs/INSTALL.md(第 04 章) |
評価方法
最も近いのは、時間的ナレッジグラフを用いたメモリレイヤー(Zep、Graphiti)です。本番環境のメッシュで有用な比較基準は、ベンチマーク精度ではありません。メモリレイヤー自体が、ロール、予算、テナンシー、来歴、監査可能な削除を強制するのか、それともそのすべてを呼び出し元のアプリケーションに委ねるのかです。上の表が Attestor の回答です。同じチェックリストを使い、ファイルへの参照も添えて、ほかの選択肢も評価してください。
想定用途
- 信頼レベルの異なる多数の LLM 駆動型エージェントが同じメモリに書き込むマルチエージェント製品
- 時点を指定した再構成と、削除についての説明責任が必要な規制対象システム
- セルフホスト型のデプロイ — お使いの VPC、Postgres、Pinecone、Neo4j
想定していない用途
- 単一のコーディングアシスタント向けの個人用メモリ — 下記の Claude Code プラグインは、同じエンジンを試すためのデモであり、製品ではありません
- 汎用ベクトルデータベース、またはチャンク化/再ランキング/オーケストレーションを行う RAG フレームワーク
- LLM エージェントのランタイム — Attestor はメモリのバックエンドです。エージェントのループはご自身で用意してください
1 つのコマンドでローカル環境から試せます。
pipx install attestor && attestor quickstartを実行すると、Docker で Postgres + Pinecone Local + Neo4j が起動し、Claude Code MCP サーバーとフックが設定されるため、実際のプロジェクトでガバナンスレイヤーの動作を確認できます。詳しくは、以下のクイックスタートとClaude Code へのインストールをご覧ください。
クイックスタート
1. インストール
pip install attestor # or: pipx install attestor
またはコンテナを取得(内部構造の調査に適したイメージで、python:3.12-slim の上に単一レイヤーを重ねています。現在は linux/amd64 に対応しています):
docker pull ghcr.io/bolnet/attestor:latest # recommended — anonymous pull, mirrored to all registries below
同じイメージは以下にもミラーされています。
| レジストリ | イメージの取得先 |
|---|---|
| GHCR | ghcr.io/bolnet/attestor:latest |
| Docker Hub | bolnet2025/attestor:latest |
| Quay | quay.io/bolnet/attestor:latest |
| AWS ECR Public | public.ecr.aws/m6h5j7o3/attestor:latest |
| GCP AR | us-central1-docker.pkg.dev/coral-marker-452616-n4/attestor/attestor:latest |
(内部用の Azure ACR ミラー memwright.azurecr.io/attestor もありますが、非公開です。Azure のお客様は、上記の公開レジストリのいずれかから az acr import を使用してください。)
イメージのデフォルトのエントリーポイントは attestor mcp(stdio 経由の MCP サーバー)です。本番環境で使用する場合は、環境変数を使ってコンテナを外部の Postgres + Neo4j に接続してください(または attestor/infra/local/docker-compose.yml を使って連携させてください)。attestor doctor、attestor api などを実行するには、エントリーポイントを上書きしてください。
2. ローカルスタックを起動する — 1 つのコマンドで、質問はゼロ
attestor quickstart
attestor quickstart は、対話なしでローカル環境全体をインストールし、各手順をすべて表示します。~/.attestor/{config.toml,attestor.yaml,.env} を書き込み、Docker で3 つのロールからなるローカルスタックを起動し、ローカルの Ollama bge-m3 埋め込みモデル(クラウドキー不要)を使用し、Claude Code MCP サーバー(./.mcp.json)とライフサイクルフックを設定して、attestor doctor を実行します。
前提条件: Docker が起動していること、および Ollama で bge-m3 が提供されていること(ollama pull bge-m3)。quickstart は事前チェックを実行してポート/ツールをスキャンし、不足しているものがあれば知らせます。プロンプトは一切表示されません。
| コンテナ | ロール | ポート | 用途 |
|---|---|---|---|
| Postgres 16 | ドキュメント | 5432 | 信頼できる唯一の情報源 — コンテンツ、タグ、エンティティ、ts、来歴。user_id による RLS 分離 |
| Pinecone Local | ベクトル | 5080-5089 | 高密度埋め込み、名前空間ごとの分離、プレーン gRPC(HTTPS なし) |
| Neo4j 5 + GDS | グラフ | 7687 | エンティティノードと型付きエッジ、PageRank / BFS / Leiden |
後で元に戻すには、attestor teardown を実行します(質問はゼロ。デフォルトではデータボリュームを保持します。--purge を指定するとボリュームも消去し、--dry-run を指定するとプレビューを表示します)。
Claude Code では、同じインストールを会話形式で実行できます:/plugin marketplace add bolnet/attestor → /plugin install attestor(その後、有効化します)。次に /attestor:install-attestor を実行すると、attestor quickstart が実行されます。クラウド/マネージドバックエンド(Neon / RDS / Cloud SQL、Pinecone Cloud、Neo4j AuraDB)と代替埋め込みモデル(Pinecone Inference llama-text-embed-v2、Voyage voyage-4、OpenAI text-embedding-3)は、~/.attestor/attestor.yaml(唯一の信頼できる情報源)で設定します。詳しくは docs/INSTALL.md を参照してください。
attestor doctor(最後に自動実行されます。または任意のタイミングで実行できます)は、4 つのサブシステムすべてをチェックします:ドキュメントストア(Postgres)、ベクトルストア(Pinecone)、グラフストア(Neo4j)、検索パイプライン。停止できない唯一の必須依存関係はドキュメントストア(Postgres)です。一時的なベクトルプローブの失敗は隠蔽されず、レスポンストレースに表示されます(retrieval/orchestrator/debug.py — vector_error フィールド)。
3. 使用する
from attestor import AgentMemory, AgentContext, AgentRole
mem = AgentMemory() # picks up env / ~/.attestor.toml automatically
ctx = AgentContext(
agent_id="researcher-1",
role=AgentRole.RESEARCHER,
namespace="acme-prod",
)
mem.add(
content="Alice is the engineering manager",
entity="alice",
category="role",
context=ctx,
)
results = mem.recall(query="who runs engineering?", context=ctx)
for r in results:
print(r.score, r.memory.content)
SOLO モード(設定不要)。 v4 では、
AgentMemory().add('foo')を実行すると、シングルトンのlocalユーザー、Inbox プロジェクト(metadata.is_inbox=true)、および日次セッションが自動的にプロビジョニングされます。そのため、上記のスニペットは ID を設定しなくても、新しいデータベースですぐに動作します(core.py:179-209)。マルチテナントの本番環境では、実際のnamespaceを持つ明示的なAgentContextを渡してください。
4. スモークベンチマークを実行する(任意)
小規模な LongMemEval スライスに対して、インストール全体が正常に動作することを確認します。デフォルト値は configs/attestor.yaml から取得されます:Pinecone Inference llama-text-embed-v2(1024-D)埋め込みモデルと Pinecone ベクトルストア、回答モデル openai/gpt-5.5、2 つのジャッジ(openai/gpt-5.5 + anthropic/claude-sonnet-4-6)、parallel=2。
set -a && source .env && set +a # OPENROUTER_API_KEY, PINECONE_API_KEY, NEO4J_PASSWORD
.venv/bin/python scripts/lme_smoke_local.py --n 2 --yes
すべてのモデルとパラメーターは YAML から取得されます。ベンチマーク用ハーネスの全容については、以下の § ベンチマーク を参照してください。
ベンチマーク
すべてのベンチマーク(スモーク、単一スライス、全件スイープ、合成的な上書き)は、2 つの YAML ファイルから設定値を読み込みます:
| ファイル | 内容 |
|---|---|
configs/attestor.yaml | スタック — 埋め込みモデル、モデル、検索機能、データベース、レジストリ、クラウド |
configs/bench.yaml | ベンチマーク専用 — バリアント、カテゴリの反復順序、目標スコア、出力パス |
2 つのファイルのキーは重複していてはなりません。CI テスト tests/test_config_no_duplicate_keys.py がこれを検証します。ベンチマークのローダー(attestor.bench_config.get_bench)は重複があるとクラッシュします。1 回限りの上書き(1 回のベンチマーク実行で別のモデルを使うなど)には、環境変数または CLI フラグを使用してください。bench.yaml にキーを重複して記述してはいけません。
LongMemEval とは
LongMemEval(Wu et al.、2024 — ICLR '25 で発表)は、メモリ拡張型チャットアシスタント向けの標準ベンチマークです。長期にわたる複数セッションの会話履歴から事実を想起しなければ答えられない質問に、AI システムが正しく回答できるかを測定します。これは、Attestor が対応するために構築されたまさにそのシナリオです。
500 問、6 つの推論カテゴリ、3 種類のハイスタックサイズ。 3 種類すべてで質問は同じです。変わるのは、答えを含むセッションの周囲にあるノイズだけです。
| バリアント | トークン数 / 問 | セッション数 | 測定内容 |
|---|---|---|---|
oracle | 約 3~15k | 正解データ 1~3 件 | 推論の上限 — 完璧な検索結果を得た場合に、回答システムがどこまでできるかを測定します。ここでスコアが低い場合、プロンプトまたは LLM に問題があります(検索では改善できません)。 |
s(Standard / Small) | 約 115k | 約 50 | 公開リーダーボード — 標準的な比較基準です。単一の Claude/GPT コンテキストウィンドウに収まるため、Attestor の検索性能を「すべてを長いコンテキストに詰め込むだけ」というベースラインと比較します。 |
m(Plus / Medium) | 100 万以上 | 約 500 | 純粋な検索性能 — どのコンテキストウィンドウにも収まらないサイズです。メモリレイヤーが必須となり、長いコンテキストによる近道は使えません。 |
注目すべき数値は LME-S です。 トークンコストを 30 分の 1 に抑えながら、長いコンテキストのベースラインから 5% 以内のスコアを達成するメモリレイヤー、というのが訴求ポイントです。
6 つの推論カテゴリ(整備済み LME-S、合計 500 問 — 整備済み分割には abstention の内訳がありません。この点は、合成 supersession suite がカバーします):
| カテゴリ | N | テスト内容 |
|---|---|---|
multi-session | 133 | 事実が複数のセッションにまたがる — 時間を通じてエンティティを追跡する必要があります |
temporal-reasoning | 133 | 日付の計算(「2 週間前」「X より前」)— Attestor の双時点レイヤーはこの内訳向けに構築されています |
knowledge-update | 78 | 置き換え — 新旧両方の事実がある場合、新しい事実を優先する必要があります |
single-session-user | 70 | 1 つのセッションで、ユーザーが事実を述べています |
single-session-assistant | 56 | 1 つのセッションで、アシスタントが事実を述べています |
single-session-preference | 30 | 1 つのセッションでのユーザーの好み |
Attestor にこのベンチマークを使う理由: temporal-reasoning と knowledge-update の内訳では、Attestor を一般的な RAG と区別する機能、つまり双時点の想起、矛盾時の置き換え、イベント時刻とトランザクション時刻の区別を直接テストします。これらの内訳で高スコアを達成することは、規制対象 AI/監査/コンプライアンスにおける訴求ポイントになります。
公開済みの Attestor の数値については、docs/bench/ を参照してください。ベンチマーク成果物は lme-{variant}-{category}-{date}.{report,summary}.json として保存されます。以下の Reporting セクションでは、成果物を表として表示する方法を説明します。
LongMemEval データセットのダウンロード(ベンチマーク実行前に一度だけ)
すべての lme_*.sh スクリプトでは、xiaowu0162/longmemeval-cleaned が HuggingFace で公開している整備済み LongMemEval 分割を使用します。初回使用時に自動ダウンロードされますが、何が起きるかを把握しておくとよいでしょう。
キャッシュの場所(初回呼び出し時に作成):
~/.cache/attestor/longmemeval/
(XDG_CACHE_HOME を設定している場合は $XDG_CACHE_HOME/attestor/longmemeval/ です。)
バリアントとディスク上のサイズ:
| バリアント | ファイル名 | サイズ | トークン数 / 問 | 用途 |
|---|---|---|---|---|
oracle | longmemeval_oracle.json | 約 5 MB | 約 3~15k | 推論の上限 — 最も低コストのスモークテスト |
s | longmemeval_s_cleaned.json | 約 250 MB | 約 115k | 公開リーダーボード(標準) |
m | longmemeval_m_cleaned.json | 約 2 GB | 100 万以上 | 検索を必須にします(長いコンテキストによる近道は使えません) |
オプション A — 自動ダウンロード(推奨)
ベンチマークコマンドを実行するだけです。初回の呼び出しでダウンロードしてキャッシュし、以降の呼び出しではディスクから読み込みます。
# Will download longmemeval_oracle.json (~5 MB) the first time
.venv/bin/python scripts/lme_smoke_local.py --n 2 --yes --variant oracle
# Will download longmemeval_s_cleaned.json (~250 MB) the first time
scripts/bench/lme_run.sh knowledge-update
各バリアントのダウンロードコストがかかるのは一度だけです。初回実行時にインターネット接続が不安定な場合は、キャッシュディレクトリ内の不完全なファイルを削除して再実行してください。
オプション B — キャッシュを事前に温める(オフライン/CI)
ベンチ当日までに、使用する予定のすべてのバリアントを事前に取得します。
.venv/bin/python -c "
from attestor.longmemeval import load_or_download
for v in ('oracle', 's', 'm'):
samples = load_or_download(variant=v)
print(f'{v}: {len(samples)} samples')
"
期待される出力:
oracle: 500 samples
s: 500 samples
m: 500 samples
オプション C — 手動ダウンロード(ファイアウォールで保護された環境)
実行環境から huggingface.co に接続できない場合は、ネットワークに接続されたマシンでファイルを取得し、手動でキャッシュディレクトリに配置します。
mkdir -p ~/.cache/attestor/longmemeval
cd ~/.cache/attestor/longmemeval
# pick the variants you need
curl -L -o longmemeval_oracle.json \
https://huggingface.co/datasets/xiaowu0162/longmemeval-cleaned/resolve/main/longmemeval_oracle.json
curl -L -o longmemeval_s_cleaned.json \
https://huggingface.co/datasets/xiaowu0162/longmemeval-cleaned/resolve/main/longmemeval_s_cleaned.json
curl -L -o longmemeval_m_cleaned.json \
https://huggingface.co/datasets/xiaowu0162/longmemeval-cleaned/resolve/main/longmemeval_m_cleaned.json
ベンチハーネスはこれらのファイル名を完全一致で確認します。名前を変更しないでください。
データセットが読み込み可能か確認する
ダウンロード後(自動または手動)、ローダーが問題なくデータセットを読み込めることを確認します。
.venv/bin/python -c "
from attestor.longmemeval import load_or_download
from collections import Counter
samples = load_or_download(variant='s')
cnt = Counter(s.question_type for s in samples)
print(f'Loaded {len(samples)} samples')
for cat, n in sorted(cnt.items(), key=lambda x: -x[1]):
print(f' {cat}: {n}')
"
クリーニング済みの s バリアントで期待される結果(質問 500 件、カテゴリ 6 個 — 注:クリーニング済みの分割には棄権用のスライスはありません):
Loaded 500 samples
multi-session: 133
temporal-reasoning: 133
knowledge-update: 78
single-session-user: 70
single-session-assistant: 56
single-session-preference: 30
件数が一致しない場合、ファイルが途中で切れています。再ダウンロードしてください。
クイックスモーク(1 分以内、0.10 ドル以内)
コミットする前、またはより大規模な実行を始める前に、パイプラインが最初から最後まで動作することを確認します。
.venv/bin/python scripts/lme_smoke_local.py --n 2 --yes --variant oracle
oracle は最も低コストのバリアントです(正解セッションのみで、妨害用のヘイスタックはありません)。スキーマは自動的に再適用されます。実行間でデータベースの内容を保持する場合は、--skip-schema を指定してください。
単一カテゴリ — scripts/bench/lme_run.sh
# all 6 categories, current variant from bench.yaml (default: s)
scripts/bench/lme_run.sh
# one slice — full
scripts/bench/lme_run.sh knowledge-update
# one slice — capped at N samples (smoke)
scripts/bench/lme_run.sh knowledge-update 10
# one slice on a different variant (oracle = cheapest, m = ~1M tokens)
scripts/bench/lme_run.sh knowledge-update "" oracle
有効な --category の値:single-session-user、single-session-assistant、single-session-preference、multi-session、temporal-reasoning、knowledge-update。各カテゴリでテストする内容とサンプル数については、上記のLongMemEval についてを参照してください。
各実行では、次の 2 つのファイルが保存されます。
docs/bench/lme-{variant}-{category}-{YYYYMMDD}.report.json # full LMERunReport
docs/bench/lme-{variant}-{category}-{YYYYMMDD}.summary.json # BenchmarkSummary
全カテゴリの実行 — scripts/bench/lme_all.sh
bench.yaml の lme.categories リストを順番に処理します。スライスの追加や削除は、スクリプトではなく YAML の編集で行います。
# All 6 slices, current variant
scripts/bench/lme_all.sh
# All 6 slices, capped at 10 samples each (smoke)
scripts/bench/lme_all.sh 10
# All 6 slices on Oracle variant
scripts/bench/lme_all.sh "" oracle
いずれかのスライスが失敗すると、スクリプトはそのエラーを記録して次に進みます。
レポート — scripts/bench/lme_report.py
docs/bench/lme-*.summary.json をすべて集約して 1 つの Markdown テーブルを作成し、(variant, category) ごとに最新のファイルを選びます。
.venv/bin/python scripts/bench/lme_report.py # latest-per-slice
.venv/bin/python scripts/bench/lme_report.py --variant s # filter to LME-S
.venv/bin/python scripts/bench/lme_report.py \
--markdown-out docs/bench/LME-S.md # also write file
.venv/bin/python scripts/bench/lme_report.py --trend # progression over time
デフォルトモード(スライスごとに最新のファイルを使用):
| Variant | Category | Score | N | Date | Answer | Judges |
| ------- | -------- | -----:| -:| ---- | ------ | ------ |
| s | knowledge-update | 87.5% | 78 | 20260429 | openai/gpt-5.4-mini | openai/gpt-5.5, anthropic/claude-sonnet-4-6 |
トレンドモード(--trend)では、docs/bench/trend.csv(ベンチ実行ごとに 1 行追加され、lme_run.sh によって自動入力されます)を読み込み、Δ 列で推移を表示します。
| Variant | Category | Date | N | Score | Δ | SHA | Features | Run |
| ------- | -------- | ---- | -:| -----:| -:| --- | -------- | --- |
| s | knowledge-update | 20260429 | 78 | 80.0% | | a126e7a | | bench |
| s | knowledge-update | 20260430 | 78 | 88.0% | +8.0 | badcf1b | multi_query | bench |
| s | knowledge-update | 20260501 | 78 | 91.5% | +3.5 | xxxxxxx | multi_query,hyde | bench |
Features 列には、実行ごとに有効だった検索/回答生成のフラグが正確に記録されるため、どの設定がどの改善につながったのかを一目で確認できます。
検索+回答機能のフラグ
5つの独立した機能を、configs/attestor.yaml のブール値フラグで有効にできます。デフォルトではすべて無効です。ベンチマーク実行ごとに1つ選び、効果を測定して、有効にした状態でリリースする機能を決定してください。
| フラグ | 機能 | 効果 | コスト増加分 |
|---|---|---|---|
retrieval.multi_query | 質問をN個の言い換えに書き換え、N+1個のベクトルレーンをRRFで統合します | +6~10%(文献値);LME-Sの時間的推論スモークテストでは−10ポイントの退行 | リコールごとに小規模LLM呼び出し1回+追加ベクトル検索N回 |
retrieval.hyde | イベントを記述する仮想文書の埋め込み(temperature=0)を生成し、並列のベクトルレーンとして埋め込みます | LME-Sの時間的推論で実測+10ポイント(30問のスモークテストで70%→80%→96.7%、BM25ハイブリッド使用時) | リコールごとに小規模LLM呼び出し1回+追加ベクトル検索1回 |
retrieval.temporal_prefilter | 「2週間前」などを正規表現で検出し、ベクトル検索の前にイベント時刻の範囲を絞り込みます | +1.5%(文献値);LME-Sの疑問詞形式のアンカー質問では0ポイント | 無料(正規表現のみ、LLM呼び出しなし) |
self_consistency | 回答生成器がtemperatureでK=5個のサンプルを生成し、合意を得た回答を選びます | +3~6%(文献値) | 回答生成コストの5倍 |
critique_revise | 回答→批評→条件付きで修正します | +3~5%(文献値) | 最悪の場合、回答生成コストの約3倍 |
このリリースでは、multi_query と hyde は同時に使用できません(両方のフラグが有効な場合は、警告をログに記録したうえで multi_query が優先されます)。回答生成側でも、self_consistency と critique_revise は同様に同時に使用できません。検索側と回答生成側の組み合わせ(例:hyde + self_consistency)は問題ありません。
HyDE v2プロンプト(attestor/retrieval/hyde.py)は、回答形式ではなくイベントを記述するスニペットを生成するため、埋め込みは質問形式のクエリではなく、元データ形式の会話ターンに近い位置に配置されます。これが、LME-Sの時間的推論で実測+10ポイントの効果を生んだ要因です。再実行時に結果が決定論的になるよう、temperature=0 に固定されています。
上記に正直な否定的結果も記録しています。multi_query と temporal_prefilter は、文献の数値からLME-Sの時間的推論スライスへの一般化ができませんでした。multi_query の言い換えは質問形式のままであり、RRFによって限界的なヒットが薄められます。temporal_prefilter のヒューリスティックなアンカーは、疑問詞形式の質問(「何日前に…」)には役立ちませんでした。適切なツールはHyDEでした。機能ごとの手法と診断アーティファクトは docs/bench/pinecone-lme-temporal-diagnostic-{baseline,mq3,hyde,hyde-bm25}-20260429.json にあります。
複数のベクトルDBに対応した診断ハーネス — experiments/pinecone_lme_temporal_diagnostic.py は、--baseline/--multi-query/--hyde/--bm25-hybrid/--temporal-prefilter/--category フラグを使い、Pinecone Localに対して検索のみのLME-S診断を実行します。回答生成器も評価者もなく、純粋なrecall@Kの上限を測定します。--skip-ingest を指定すると、データが格納済みの名前空間を再利用できるため、検索フラグの反復検証を高速に行えます(30問で約60秒、新規取り込みでは約50分)。
単一機能のベンチマーク方法: configs/attestor.yaml で対象機能の enabled: true に切り替え、ベンチマークスライスを実行し、すべて無効にした同日実行のベースラインと比較します。傾向テーブルの Δ 列に差分が表示されます。
合成置換テストスイート — python -m evals.knowledge_updates
人手で厳選した50件のケースで、矛盾のカテゴリは10種類(数値、分類、時間、好み、エンティティ、場所、意図、関係、数、二値ステータス)、各カテゴリ5件です。各ケースでは、2つのセッションを取り込みます(Session 1で事実を述べ、Session 5でそれに矛盾する内容を述べます)。そのうえで、新しい事実を回答すべき質問を行います。指標は、検索結果のトップ1に新しい事実が表示されるケースの割合です。
# All 50 cases — ~5 min, ~$0.50 worth of embedding calls
.venv/bin/python -m evals.knowledge_updates
# Smoke — first 5 cases
.venv/bin/python -m evals.knowledge_updates --limit 5
# Custom fixtures
.venv/bin/python -m evals.knowledge_updates --fixtures my_cases.json
出力:
docs/bench/knowledge-updates-{YYYYMMDD}.report.json # per-case verdicts (new_wins | stale_wins | miss | ambiguous)
docs/bench/knowledge-updates-{YYYYMMDD}.summary.json # aggregate score + per-category breakdown
目標スコア(bench.yaml で設定可能):新しい事実の勝率 92%。これを下回る場合は、attestor/retrieval/scorer.py の置換信頼度減衰の重みを調整する必要があります。
コストと実行時間のガイド
回答者と判定者の reasoning_effort=high、parallel=2、OpenRouter の料金に基づく概算です。
| 実行 | N | 経過時間 | コスト |
|---|---|---|---|
| クイックスモークテスト | 2 oracle | 約1分 | < $0.10 |
knowledge-update のスライス | 78 | 約30〜60分 | 約$3〜5 |
temporal-reasoning のスライス | 133 | 約50〜100分 | 約$5〜8 |
| LME-S 500q 全体 | 500 | 約75〜180分 | 約$20〜30 |
| 合成 supersession | 50 | 約5分 | 約$0.50(埋め込みのみ) |
コストを抑えるには、configs/attestor.yaml の models.reasoning_effort.{answerer,judge} を high から medium または low に変更してください。
設定早見表 — configs/bench.yaml
bench:
lme:
variant: s # s | m | oracle
cache_dir: ~/.cache/attestor/lme
output_dir: docs/bench
sample_limit: null # null = full dataset; int = truncate
category: null # null = all 7; or single slice name
categories: [...] # iteration order for lme_all.sh
variants_to_run: [...] # for full size matrix
knowledge_updates:
fixtures_path: evals/knowledge_updates/fixtures.json
n_cases: 50
target_score: 0.92
categories: [numeric, categorical, ...]
report:
headline_slice: abstention
trend_csv: docs/bench/trend.csv
markdown_path: docs/bench/LME-S.md
アーキテクチャ
バイテンポラル — 過去の任意の状態を再現
各メモリには、2つの時間軸があります。
| 軸 | カラム | 意味 |
|---|---|---|
| イベント時刻 | valid_from, valid_until | その事実が 現実世界で 真である期間 |
| トランザクション時刻 | t_created, t_expired | その行が ストアに 登録された期間 |
さらに、superseded_by チェーンがあります。古い事実が削除されることはなく、いつまでもクエリ可能です(attestor/temporal/manager.py:30-66)。
# What did we believe on March 1?
mem.recall(query="who runs engineering?", as_of="2026-03-01T00:00:00Z", context=ctx)
# Show me everything we knew about Alice between Feb and Apr
mem.recall(query="alice", time_window=("2026-02-01", "2026-04-01"), context=ctx)
as_of と time_window は、オーケストレーターとドキュメントストアを通じてエンドツーエンドで伝播します。書き込み時の自動 supersession は core.py:add() に組み込まれています(core.py:762, 784-785)。add のたびに、テンポラルマネージャーは同じ (entity, category, namespace) を持ち、内容が異なるアクティブな行を見つけて superseded とマークし、valid_until=now を設定して、superseded_by=<new_id> でリンクします。現在の検出方式は、ルールベースの文字列一致です。
テナント分離 — Postgres の行レベルセキュリティ
すべてのテナントテーブル(users、projects、sessions、episodes、memories、user_quotas、deletion_audit)には、attestor.current_user_id セッション変数をキーとする tenant_isolation_* ポリシーが設定されています。値が空または未設定の場合はフェイルクローズとなり、どの行も表示されません(attestor/store/schema.sql:311-327)。
率直な説明。 制御を担うのは Python ではなく Postgres です。
attestor/context.py:49-56のAgentRole列挙型は、来歴を示すためにメモリへ引き継がれるメタデータであり、Python で操作を制限するものでは ありません。実際にアクセスを制御するのは RLS です。これはメモリバックエンドにとって適切なアーキテクチャですが、Python のコードだけを読む場合は知っておくとよいでしょう。
検索パイプライン — セマンティック優先の6ステップ
attestor/retrieval/orchestrator/core.py は、すべてのクエリに対して同じ6つのステップを実行します。
- ベクトルの上位K件を取得 — Pinecone のコサイン類似度で k=50(自己完結型のデプロイ向けに pgvector をオプトインのフォールバックとして引き続き利用可能)
- グラフで絞り込み — 各候補のエンティティから質問内のエンティティまで Neo4j で深さ ≤ 2 の BFS を実行し、ホップごとに関連度ボーナスを加算(0ホップ=+0.30、1ホップ=+0.20、2ホップ=+0.10、到達不能=−0.05)。「ソフト」ではなく離散的です。
- トリプルを注入 — 型付きエッジの事実(
uses、authored-by、supersedes)を合成メモリとして注入 - MMR で再ランク付け — λ=0.7
- 信頼度の減衰と時間的ブースト — 新しいデータは順位が上がり、古く信頼度の低い行は下がります
- 予算に合わせて調整 — スコア順を維持する貪欲法で、呼び出し元のトークン予算内に格納
各呼び出しは JSONL トレースを logs/attestor_trace.jsonl に書き込みます(ATTESTOR_TRACE=0 で無効化できます)。
非同期検索 — 監査を弱めずにレイテンシを削減
独立した検索ステップは asyncio.gather を使って並行実行しますが、8つの監査不変条件はいずれも緩和されません。 信頼性を犠牲にして速度を得るのではなく、両方を実現します。
| 非同期ステップ | レイテンシの改善 | 維持される監査不変条件 |
|---|---|---|
| HyDE LLM 呼び出し ‖ 元の質問のベクトル埋め込み | HyDE を有効にした検索で −33%(シミュレーションの単体テストでは約600 ms → 約400 ms) | A7 — 生成器は temperature=0.0 に固定します。同じプロンプトと同じモデルなら、同じ仮説文が生成され、同じ RRF 順序になります。T > 0 の場合、非同期化は非決定性のリスクを高めるため、明示的に T=0 に固定します。 |
| レーンごとのベクトル検索を並列実行(HyDE/マルチクエリ) | N に比例(約 N × レーンごとの所要時間 → 最も時間のかかるレーンの所要時間) | 同じ入力であれば、レーン全体の RRF は決定論的です。gather の順序によって順位が崩れることはありません(test_multi_query_async_preserves_RRF_order)。 |
| 自己一貫性の K 分岐(回答生成側) | K=5 のサンプリングで5倍 | 投票による合意は順序に依存しません。回答生成側の変更であり、ドキュメントストアには影響しません。 |
| ベクトル検索 ‖ BM25 ‖ グラフ候補取得 | ベースラインの検索で −20% | A2 recall_started_at の上限 — すべてのストア横断読み取りに、検索開始時に取得した同じ単調タイムスタンプが適用されます。検索中に発生した並行書き込みは読み取り対象になりません。 |
| グラフ BFS ‖ Postgres ドキュメント取得 | 通常 −50 ms | 同じ上限が適用されます。 |
書き込み側は引き続き同期処理です。 すべての add()、update()、置き換え書き込みは、非同期リファクタリングの対象外として明示されています。監査チェーンは書き込みの直列順序に依存し、二時点の t_created 順序は行ごとに線形化可能でなければなりません。非同期化するのは読み取り側だけです。
トレースは引き続き再構築可能です。 すべてのイベントに recall_id、単調増加する seq、および任意の parent_event_id が含まれます。そのため、監査ダッシュボードでは並行する検索をイベントのストリームではなくイベントのツリーとして表示できます。(recall_id, seq) を使うことで、JSONL ログから因果関係の順序を再構築できます。
同じ recall(as_of=X) の再生保証を維持します。 過去の検索は、二時点の列、deletion_audit、トレース JSONL からバイト単位で再現できます。非同期並列処理が変えるのは読み取りのタイミングだけであり、読み取る内容は変わりません。重要なテスト(tests/test_as_of_replay.py)は、すべての非同期 PR のリグレッションゲートに含まれています。
設計の全容と監査不変条件の対応表:docs/plans/async-retrieval/PLAN.md。規約:すべての非同期 PR には、監査保全の根拠を添え、対応する不変条件テスト(tests/async_retrieval/test_audit_invariants_under_async.py)がマージ前に GREEN であることを確認します。
3つのストレージの役割
| 役割 | 用途 | デフォルト | 代替候補 |
|---|---|---|---|
| ドキュメント | 信頼できる唯一の情報源(コンテンツ、タグ、エンティティ、ts、来歴、信頼度) | Postgres 16 | AlloyDB、ArangoDB、DynamoDB、Cosmos DB |
| ベクトル | メモリごとの高密度埋め込み | Pinecone(ローカル Docker/クラウド) | pgvector、AlloyDB ScaNN、ArangoDB、OpenSearch Serverless、Cosmos DiskANN |
| グラフ | エンティティノードと型付きエッジ | Neo4j 5 + GDS | AlloyDB 上の Apache AGE、ArangoDB、Neptune、NetworkX(Azure) |
Postgres が信頼できる唯一の情報源です。Pinecone のベクトルと Neo4j のグラフはいずれも派生状態で、Postgres から再構築できます。ただし、標準インストールではどちらも必須です。ベクトルのコサイン類似度検索は検索パイプラインのステップ1、グラフ展開はステップ2であり、会話の取り込み時には型付きエッジが書き込まれます。停止してはならないのはドキュメントストアだけです。オーケストレーターは、一時的なベクトル検索の失敗を握りつぶさず、レスポンストレースに記録します(vector_error)。
耐久性のあるガバナンスジョブ — 読み取りパスは待たず、ジョブは終了しません
以前は手作業のキュー、スイーパーを約束する docstring、または警告のみの書き込みだったガバナンス処理は、Temporal ワークフロー(attestor/durable/)として実行されます。オプトイン方式です。デフォルトでは durable.enabled: false です。無効の場合、またはサーバーに到達できない場合、すべてのジョブは置き換え前のインプロセスパスにフォールバックします。
| トリガー | ワークフロー | 保証される内容 |
|---|---|---|
| 書き込みごと | DeriveMemory | ベクトルとグラフの導出は冪等で再試行されるため、導出状態が Postgres から気付かないうちに乖離することはありません。 |
| エピソードごと | ConsolidateEpisode | SKIP LOCKED キューとリースを置き換えます。停止または失敗したエピソードは失われず、確認できます。 |
| スケジュールに従って | RetentionSweep、SessionSweep | 適用するのは一度だけの Temporal Schedule により、宣言的な保持処理とアイドルセッションのクリーンアップが実際に実行されます。 |
| オンデマンド | RebuildDerived、ForgetUser | Postgres から Pinecone と Neo4j を再構築します。Forget はサーガとして実行されます。まず監査行を作成し、その後、各バックエンドで個別に再試行と結果を記録します。 |
tests/test_durable_isolation.py で適用される、厳守すべきルールが 2 つあります。想起処理は Temporal に触れず、フックは Temporal を待たないこと(attestor/retrieval/** または attestor/hooks/** 配下のものは attestor.durable や temporalio をインポートできません)、そして ワークフローペイロードには ID のみを含め、メモリ内容は含めないことです。これにより、Postgres が唯一の信頼できる情報源であり続け、Temporal にはジョブの状態だけが保持されます。ドライバーの例外は、ワークフロー履歴に記録される前にサニタイズされます。
attestor quickstart --durable # Temporal server + UI (:8233) via compose profile `durable`
attestor worker # the governance worker; needs durable.enabled: true
attestor durable status # probe the server, list schedules, describe a workflow
attestor durable rebuild # rebuild derived state from Postgres
attestor durable schedules apply # idempotent create-or-update of the retention / session schedules
オプションの BM25 / FTS レーン
トリガーによって維持される content_tsv tsvector と GIN インデックスにより、埋め込みでは見落としやすいクエリ(頭字語、ID、まれな固有名詞)の検索精度が向上します。v4 スキーマが検出されると有効になり、Reciprocal Rank Fusion(RRF、k=60)を介してベクトルレーンと融合します。このカラムがないバックエンドでは、何もせず正常に処理を続行します(core.py:122-130)。
会話の取り込み
会話のターンを監査可能なメモリに変換する、負荷の高い書き込みパスです。core.py:ingest_round(turn) は 4 つのパスを統括します。
turn → extract_user_facts(user_turn) ┐
extract_agent_facts(assistant_turn) ┘ → resolve_conflicts → apply
話者を固定した 2 パス抽出
attestor/extraction/round_extractor.py:216, 258 — ユーザーのターンとアシスタントのターンで別々のプロンプトを使います。ユーザーターンの抽出器はユーザーに帰属する事実のみを出力し、アシスタントターンの抽出器はアシスタントが導入した事実のみを出力します。これにより、話者間での誤った帰属を防ぎます。LongMemEval のスコアで Mem0 を +53.6 上回る差は、この分割によるものです。
4 つの判断による競合解決
attestor/extraction/conflict_resolver.py:40, 98 — 新たに抽出された各事実について、既存の類似メモリを対象に LLM を呼び出し、次のいずれかを返します。
| 判断 | 効果 |
|---|---|
ADD | 新しい情報で、既存の一致項目がない場合 — 新しいメモリを書き込みます |
UPDATE | 同じエンティティと述語で、値がより詳細になった場合 — 既存の ID を維持します |
INVALIDATE | 古いメモリと矛盾する場合 — 置き換え済みとしてマークします(タイムラインで再生されます) |
NOOP | すでに反映済みの場合 — スキップします |
各 Decision には evidence_episode_id が含まれます。すべての置き換えは監査可能です。フェイルセーフとして、単一の事実の解析に失敗した場合はデフォルトで ADD になります。黙って破棄するより、重複に近い行を残す方が適切です。
2 つの書き込みパス、2 つの契約。
mem.add(...)は軽量なルールベースの置き換え(§二時点)を実行します。mem.ingest_round(turn)は 4 つの判断による完全なパイプラインを実行します。会話データにはingest_roundを、競合の判断をすでに済ませている構造化データの書き込みにはaddを選択してください。
スリープ時の統合
mem.consolidate()(core.py:526)は、より強力なモデルを使って最近のエピソードから事実を再抽出し、統合します。現在は Python API からのみ呼び出せます。CLI コマンドはありません。ストリーミング抽出器より新しい事実が必要な場合は、アプリケーションから(cron、systemd timer、ECS scheduled task を使って)スケジュールしてください。
リフレクションエンジン
attestor/consolidation/reflection.py は、1 人のユーザーについて N 件のエピソードを対象に、定期的な統合を実行します。出力は次のとおりです。
stable_preferences— 3 件以上のエピソードに現れるパターンstable_constraints— ユーザーが繰り返し適用するルールchanged_beliefs— 変化した好み(古い内容 → 新しい内容、明示的な無効化を含む)contradictions_for_review— 人間によるレビューの対象としてフラグ付けされ、自動解決はされません
「自動解決しない」という方針は、規制対象のチャットシステムにおける重要な要素です。プロンプトでは明確に指示されています(reflection.py:35-66):「矛盾を自動解決しないでください。人間によるレビューの対象としてフラグ付けしてください。」
Chain-of-Note による読み取り
recall() はリストを返します。recall_as_pack() は、エージェントが実際に推論に利用できる型付きの検索エンベロープを返します。各フィールドは、記憶が矛盾する際に引用、回答の棄権、適切な有効期間の選択を行うために、Chain-of-Note のフローで必要となるものです。
pack = mem.recall_as_pack(query="who runs engineering?", context=ctx)
for entry in pack.memories:
print(entry.id, # cite this in the answer
entry.confidence, # weight or abstain
entry.valid_from, # bi-temporal window for conflict resolution
entry.valid_until,
entry.source_episode_id) # provenance back to the round it came from
agent.send(pack.render_prompt()) # Chain-of-Note prompt, memories interpolated as JSON
ContextPack は frozen=True で、ハッシュ可能かつ JSON シリアライズ可能です。ツール呼び出しにそのまま渡せます。デフォルトのプロンプトには ABSTAIN と CONFLICT の条項が明示されています。そうしなければ、最先端のモデルはどれもデフォルトで捏造してしまいます。
マルチエージェントのプリミティブ
6 つのロール
AgentRole:ORCHESTRATOR、PLANNER、EXECUTOR、RESEARCHER、REVIEWER、MONITOR(attestor/context.py:49-56)。ロールは、出所を追跡できるよう、各メモリのメタデータに引き継がれます。アクセス制御は 2 層で実施されます。
- AgentContext 層 —
ROLE_PERMISSIONSマトリックスにより、ロールごとの書き込み/忘却が制限されます。マトリックス:ORCHESTRATOR = R+W+F、PLANNER/EXECUTOR/RESEARCHER=R+W、REVIEWER/MONITOR=Rのみです。read_only=Trueは独立したキルスイッチです。 - Postgres RLS 層 —
user_idによる行レベルのフィルターです(§テナント分離を参照)。
AgentContext — 引き継ぎ、スクラッチパッド、履歴
orchestrator = AgentContext.from_env(agent_id="orchestrator", namespace="project:acme")
planner = orchestrator.as_agent("planner", role=AgentRole.PLANNER)
executor = planner.as_agent("executor", role=AgentRole.EXECUTOR)
# Each child carries parent_agent_id + accumulating agent_trail.
# All three share the same scratchpad: Dict[str, Any] for typed handoff data.
as_agent() は、parent_agent_id、完全な agent_trail、共有の scratchpad を持つ子コンテキストを作成します。履歴は蓄積されるため、「この回答は、エージェント Y から情報を得たエージェント X によるものだ」と証明するのに役立ちます。
エージェントごとのトークン予算
AgentContext.token_budget(デフォルトは 20 000)が適用されます。recall() は予算を使い切るまで、結果を優先順に詰め込みます(scorer.py:fit_to_budget)。token_budget_used はセッション内の各呼び出しで累積します。
オプションの書き込みクォータ
mem.set_quota(user_id, daily_writes=...) → v4 の user_quotas テーブルに対する add で適用されます(core.py:592-621)。オプションです。未設定の場合は無制限です。
セキュリティとコンプライアンス
行レベルセキュリティ
§テナント分離を参照してください。RLS ポリシーがアクセス制御の役割を担い、Python 層はそれを信頼します。接続ごとに attestor.current_user_id を設定してください。
すべてのメモリにおける出所情報
すべてのメモリには、agent_id、session_id、source_episode_id が含まれます。置き換えチェーン(superseded_by)は永久に保持されます。会話エピソードは、そこから抽出されたメモリとは別に逐語的に保存されます。つまり、どの会話ターンからどの事実が得られたかをいつでも再構成できます。
削除監査ログ
ハード削除(GDPR に基づく消去など)の前に、カスケード処理に先立って deletion_audit に行を書き込みます。削除された内容、日時、理由、実行者が記録されます。これは、それ以外は不変のスキーマに設けられた例外です。
GDPR — エクスポートと消去
mem.export_user(external_id="user-42") # full data export (memories + episodes + sessions + projects)
mem.purge_user(external_id="user-42", # cascading hard delete with audit trail
reason="GDPR right-to-erasure request 2026-04-27")
mem.deletion_audit_log(limit=100) # forensic readback
core.py:557-590。v4 のみです。データ主体のアクセス権に関する Art. 15/Art. 17 の要件を満たす情報をすべて返す/書き込みます。
オプション:Ed25519 による出所情報の署名
設定で有効にします(signing.enabled = true)。各 add の際、attestor は Ed25519 キーを使用して、正規化されたペイロード id || agent_id || t_created || content_hash に署名します。mem.verify_memory(memory_id) は bool を返します(core.py:623-640)。オプションで、デフォルトでは無効です。暗号学的な否認防止が必要な敵対的な書き込みの状況で有効にしてください。
ランタイムトポロジー
3つのモードすべてで同じ API を使用します。変更するのは設定だけです。
| モード | 構成 | 用途 |
|---|---|---|
| A — 埋め込みライブラリ | プロセス内の AgentMemory(config) が Postgres + Neo4j と直接通信 | 単一プロセスのエージェント、スクリプト、ノートブック |
| B — サイドカー | localhost:8080 で attestor api を実行。言語に依存しない HTTP クライアントが、同じ Postgres + Neo4j を共有 | 1台のマシン上の多言語エージェント(Python + TS + Go) |
| C — 共有サービス | 管理対象の Postgres + Neo4j をバックエンドに、エージェントメッシュの前段(App Runner / Cloud Run / Container Apps)に1つの Attestor サービスを配置 | 本番環境のマルチエージェントプラットフォーム |
attestor api --port 8080 # Mode B / C — Starlette ASGI REST (HTTP)
attestor mcp --path ~/.attestor # MCP stdio server (zero-config; for Claude Desktop / Cursor / Windsurf)
attestor serve ~/.attestor # MCP stdio server (positional-path variant; equivalent transport)
バックエンド
| バックエンド | ドキュメント | ベクトル | グラフ | 状態 |
|---|---|---|---|---|
| Postgres + Neo4j (デフォルト) | ✓ | pgvector | Neo4j + GDS | 本番環境対応 |
| ArangoDB | ✓ | ✓ | ✓ | 本番環境対応(1つのエンジンで3つすべての役割を担います) |
| AWS | DynamoDB | OpenSearch Serverless | Neptune | バックエンドコード + Terraform を提供済み |
| Azure | Cosmos DB | Cosmos DiskANN | NetworkX(プロセス内) | バックエンドコードを提供済み。Terraform は近日提供予定 |
| GCP | AlloyDB | AlloyDB ScaNN | AGE on AlloyDB | バックエンドコードを提供済み。Terraform は近日提供予定 |
設定でデフォルトを上書きできます。
# ~/.attestor.toml
backend = "postgres+neo4j" # or "arangodb" | "aws" | "azure" | "gcp"
Terraform のリファレンスは attestor/infra/ にあります。
埋め込み
プロバイダーは次の順序で自動検出されます(attestor/store/embeddings.py:get_embedding_provider)。
- ローカル Ollama
bge-m3— 1024-D、8K コンテキスト —http://localhost:11434に接続できる場合に使用されます - クラウドネイティブ — SDK と認証情報が存在する場合は Bedrock Titan / Vertex / Azure OpenAI
- OpenAI
text-embedding-3-large(ネイティブで 3072-D。スキーマとの互換性を保つにはOPENAI_EMBEDDING_DIMENSIONS=1024を指定してください) - OpenRouter — フェデレーション実行用
ローカル優先の設計です。上書きするには:
export ATTESTOR_DISABLE_LOCAL_EMBED=1 # skip the Ollama probe entirely
export ATTESTOR_EMBEDDING_PROVIDER=openai
export ATTESTOR_EMBEDDING_MODEL=text-embedding-3-large
CLI
attestor --help にすべてのコマンドが表示されます。特に便利なコマンドは次のとおりです。
| コマンド | 用途 |
|---|---|
attestor quickstart | 質問なしでローカルにインストール — バックエンド + 設定 + MCP/フック + 診断 |
attestor teardown | quickstart を元に戻します(コンテナー + 設定 + MCP/フック。--purge を指定するとデータを消去します) |
attestor init | ストアの初期設定を作成します(低レベルのコマンドです。簡単に始めるには quickstart を使用してください) |
attestor doctor | すべてのストアと検索パイプラインのヘルスチェック |
attestor add / recall / search / list | CRUD に準じたメモリー操作 |
attestor timeline | エンティティのタイムライン(双時制マネージャーを使用) |
attestor stats | ストアの統計情報 |
attestor export / import | JSON のダンプ/復元 |
attestor compact | アーカイブ済みのメモリーを削除 |
attestor update / forget | メモリーを変更/アーカイブ |
attestor inspect | データベースの生の状態を確認 |
attestor api | Starlette REST API を起動 |
attestor serve <path> | MCP stdio サーバーを起動(位置引数でパスを指定する形式) |
attestor mcp [--path …] | MCP stdio サーバーを起動(設定不要。Claude Desktop / Cursor / Windsurf のデフォルト) |
attestor ui | ストアの読み取り専用ブラウザー UI |
attestor hook {session-start, post-tool-use, stop} | Claude Code のライフサイクルフックを実行 |
attestor lme / locomo / mab | 組み込みベンチマークランナー(§評価を参照) |
MCP サーバー
attestor mcp(または attestor serve <path>)は、8つのツールを備えた MCP stdio サーバーを公開します。
| ツール | 用途 |
|---|---|
memory_add | 出所情報付きでメモリーを書き込む |
memory_get | ID を指定して1件のメモリーを取得 |
memory_recall | 検索パイプライン全体を実行 |
memory_search | 条件で絞り込んだ一覧(エンティティ/カテゴリ/時刻/名前空間) |
memory_forget | ID を指定してメモリーをアーカイブ |
memory_timeline | エンティティの時系列 |
memory_stats | ストアの統計情報 |
memory_health | 役割ごとのヘルススナップショット — 統合時には最初に呼び出してください |
さらに、MCP リソース(メモリー一覧)と プロンプト(IDE アシスタント向けの定型検索プロンプト)も利用できます。
Hooks(Claude Code)
3 つのライフサイクルフックが attestor/hooks/ に含まれています。
session_start— cwd/リポジトリに基づき、関連するメモリをセッションコンテキストに注入しますpost_tool_use—Write/Edit/Bashから有用な成果物を自動で取得しますstop— 終了時にセッションの要約を書き込みます
インストーラー(次のセクション)を使うか、~/.claude/settings.json で手動設定して有効にします。
Claude Code へのインストール
実行するコマンドは 1 つだけです。 pipx install attestor の後に attestor quickstart を実行します。質問はなく、デフォルトプロファイルが 1 つ設定されます。ローカルバックエンドを起動し、ローカルの Ollama bge-m3 埋め込みモデル(クラウドキー不要)を使用して、MCP サーバー(./.mcp.json)とライフサイクルフックを設定し、attestor doctor を実行して、各手順をすべて表示します。いつでも attestor teardown で元に戻せます。
pipx install attestor && attestor quickstart # install (zero questions)
attestor teardown # uninstall (--purge also wipes data volumes)
前提条件: Docker が起動しており、Ollama が bge-m3 を提供していること(ollama pull bge-m3)。quickstart の事前チェックではこれらを確認し、不足しているものを報告します。質問は表示されません。
Claude Code 内から実行する方法(プラグイン)。 プラグインを一度インストールしてから、プラグインが提供するコマンドを実行します。
/plugin marketplace add bolnet/attestor # one-time
/plugin install attestor # then ENABLE it in the /plugin → Installed menu
/attestor:install-attestor # runs `attestor quickstart` for you
プラグインのコマンドには名前空間が付いています。コマンドは
/install-attestorのように名前空間なしではなく、/attestor:install-attestor(および/attestor:uninstall-attestor)です。インストール直後のプラグインは無効になっている場合があります。/plugin→ Installed メニューで有効にして/reload-pluginsを実行してください。そうしないとコマンドを解決できません。
メモリはプロジェクトごとに自動で分離されます。各作業ディレクトリ(git root、なければ cwd)はハード分離された独自のテナントとなるため、プロジェクト間でメモリが共有されることはありません。設定する名前空間はありません。
ローカルバックエンドは 3 つの Docker コンテナとして起動します(quickstart が実行する、同梱の attestor/infra/local/docker-compose.yml)。
| コンテナ | 種類 | ストレージの役割 |
|---|---|---|
attestor_postgres_document_db | Postgres 16 + pgvector | ドキュメント — 信頼できる唯一の情報源 |
attestor_pinecone_vector_db | Pinecone Local | ベクトル — 埋め込み |
attestor_neo4j_graph_db | Neo4j 5 + GDS | グラフ — PageRank/BFS |
すべてのコンテナ、ボリューム、compose ネットワーク/プロジェクトの名前には
attestor_…が付いているため、docker ps -a \| grep attestor(およびdocker volume ls \| grep attestor)で Attestor が所有するすべてのものを一覧表示できます。
クラウド/マネージドバックエンド(Neon · RDS · Cloud SQL、Pinecone Cloud、Neo4j AuraDB)や、別の埋め込みモデル(Pinecone Inference llama-text-embed-v2、Voyage voyage-4、OpenAI text-embedding-3)は ~/.attestor/attestor.yaml で設定します。詳しくは docs/INSTALL.md をご覧ください。
スキルとしてインストール(2026 年版エージェント SDK)
Attestor には、標準の SKILL.md が skills/attestor-memory/SKILL.md に含まれています。Anthropic(skills-2025-10-02)と OpenAI の Responses API は、再利用可能なエージェントの専門知識を配布するための形式として、YAML フロントマターを含む Markdown ファイルに収束しました。wheel には SKILL.md が含まれているため、2026 年版のあらゆるハーネスで pip install attestor を一度実行すれば、自動検出できるようになります。
このスキルは、6 つの基本プリミティブ(recall、add、timeline、current_facts、forget、audit)と、v4 のエンタープライズ機能(双時性の as_of リプレイ、RBAC ロール、名前空間の分離、来歴の署名、GDPR エクスポート/消去)をエージェントに教えます。すべてのコード例は、attestor.AgentMemory に実際に存在するメソッドを参照しています。また、CI テスト(tests/test_skill_md.py)により、SKILL.md が実際の API から乖離しないようにしています。
独自のホストで契約を固定するには、次のようにします。
pip install attestor
python -c "import attestor, importlib.resources as r; print(r.files('attestor'))" # confirm wheel installed
# Point your agent harness at the bundled SKILL.md or read it directly:
python -c "from pathlib import Path; import attestor; \
print((Path(attestor.__file__).parent.parent / 'skills' / 'attestor-memory' / 'SKILL.md').read_text())"
評価
境界に関する説明。 デュアル LLM ジャッジスタックはベンチマークの仕組みであり、ランタイムの契約ではありません。本番環境での想起は、単一のパイプラインで決定論的に行われます。複数のジャッジが回答を採点するのは評価時のみであり、ユーザー向けの読み取りでは行われません。
| ランナー | ソース | 測定内容 |
|---|---|---|
attestor lme | LongMemEval(Google の長期記憶ベンチマーク) | 長い履歴下での回答精度、蒸留、異なるファミリー間でのデュアルジャッジ照合 |
attestor locomo | LoCoMo | 会話における長期記憶の一貫性 |
attestor mab | MultiAgentBench | マルチエージェントの連携 |
| AbstentionBench(CI ゲート) | 内部 | 既知の未知事項について、回答を控えるべき状況 |
scripts/lme_smoke_local.py | デュアル LLM スモークテスト | インストールの簡易検証(Quick Start §6 を参照) |
スモークテスト用ドライバーは、公開ベンチマークの標準スタックを正確に再現します。環境変数/CLI フラグのすべてのオーバーライド一覧については、--help を参照してください。
プロジェクト構成
attestor/
core.py -- AgentMemory (main public API)
client.py -- MemoryClient (HTTP drop-in for remote Attestor)
context.py -- AgentContext, AgentRole, Visibility
models.py -- Memory, RetrievalResult, ContextPack
cli.py -- attestor CLI entry point
api.py -- Starlette ASGI REST API
longmemeval.py -- LongMemEval benchmark runner (dual-judge)
locomo.py -- LoCoMo runner
doctor_v4.py -- v4 schema + invariant validator
init_wizard.py -- interactive install flow
store/
base.py -- DocumentStore / VectorStore / GraphStore protocols
registry.py -- backend selection
connection.py -- config layering / env resolution
embeddings.py -- provider auto-detect (Ollama / OpenAI / Bedrock / Vertex / Azure)
postgres_backend.py -- pgvector (document + vector roles)
neo4j_backend.py -- Neo4j + GDS (graph role)
arango_backend.py -- all 3 roles in one
aws_backend.py -- DynamoDB + OpenSearch Serverless + Neptune
azure_backend.py -- Cosmos DB DiskANN + NetworkX
gcp_backend.py -- AlloyDB pgvector + AGE + ScaNN
schema.sql -- v4 Postgres schema (RLS, bi-temporal columns, content_tsv)
conversation/
ingest.py -- ingest_round() pipeline
extraction/
round_extractor.py -- 2-pass speaker-locked extraction
conflict_resolver.py -- 4-decision contract (ADD/UPDATE/INVALIDATE/NOOP)
rule_based.py -- deterministic fact extraction (no LLM)
prompts.py -- shared prompt templates
consolidation/
consolidator.py -- sleep-time re-extraction
reflection.py -- cross-thread synthesis (stable patterns + flagged contradictions)
graph/
extractor.py -- entity / relation extraction
retrieval/
orchestrator/ -- 6-step semantic-first pipeline (core.py)
tag_matcher.py
scorer.py -- MMR, confidence decay, entity boost, fit-to-budget
trace.py -- JSONL trace writer
temporal/
manager.py -- timelines, supersession, contradiction detection, as_of replay
identity/
signing.py -- Ed25519 provenance signing (optional)
defaults.py -- SOLO mode auto-provisioning
mcp/
server.py -- MCP server (tools, resources, prompts)
hooks/
session_start.py
post_tool_use.py
stop.py
ui/
app.py -- Starlette read-only viewer
static/, templates/ -- Evidence Board UI
utils/
config.py, tokens.py
infra/
local/ -- Docker Compose (Postgres + Neo4j)
aws_arango/ -- Reference Terraform
tests/ -- Unit tests; live cloud tests env-gated
evals/ -- LongMemEval / LoCoMo / MultiAgentBench / AbstentionBench harnesses
docs/ -- Architecture notes, ADRs
commands/ -- /install-attestor, etc.
scripts/ -- lme_smoke_local.py, etc.
開発
poetry install
poetry run pytest tests/ -q # unit tests, no external services needed
ATTESTOR_LIVE_PG=1 poetry run pytest tests/live -q # live integration (env-gated)
スタイル:black によるフォーマット、isort によるインポート整理、ruff によるリンティング、mypy による型チェックを行います。PEP 8 に準拠し、シグネチャには型注釈を付け、DTO にはデータクラスを使用します。ファイルは小さく保ちます(通常は 200~400 行、最大 800 行)。
知っておくと便利な規約:
- Postgres が信頼できる唯一の情報源です。Neo4j は派生データなので、差異が生じた場合は Postgres から再構築してください。
- ベクターパス/グラフパスで発生した致命的でないエラーは捕捉して記録します。ドキュメントパスがひそかに破損することはありません。
- 設定の優先順位:環境変数 →
~/.attestor.toml→ コード内のオーバーライド。 - 書き込みパスは 2 つあります。構造化データには
add()(軽量なルールベースの置き換え処理)、会話データにはingest_round()(2 パス処理と 4 つの判断からなる完全な契約)を使用します。
ヘルスチェック
連携時には、必ず最初にこれを呼び出してください。
attestor doctor # CLI
mem = AgentMemory()
print(mem.health()) # Python API
// MCP
{ "tool": "memory_health" }
Document Store(Postgres)、Vector Store(pgvector)、Graph Store(Neo4j)、および検索パイプラインを検査します。デフォルトのトポロジーでは、4 つすべてが必須です。グラフ拡張は標準パイプラインのステップ 2 であり、任意の高速化機能ではありません。一時的なベクタープローブの失敗は recall() のトレースに vector_error として表示されるため、呼び出し元は結果の劣化と問題のない結果を区別できます。
ステータスとバージョン管理
- バージョン: 4.1.6(安定版) — PyPI および MCP Registry に
io.github.bolnet/attestorとして公開されています。pip install attestorで最新の 4.1.x がインストールされます(--preフラグは不要です)。 - v3 → v4: テナントの厳格な分離、二時点ファクト、LLM を使わない検索クリティカルパスを備えた、v4 ネイティブの Postgres スキーマ上での新規構築です。自動移行には対応していません。 v3 はアルファ版のみで、本番ユーザーはいませんでした。v3 の DB を削除して再インストールしてください。
- トラックごとの変更履歴全体については、
CHANGELOG.mdを参照してください。
ライセンス
MIT。詳細は LICENSE を参照してください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。