mcpField
A
agenthold

agenthold

ローカル
github.comAI・エージェント216 週間前に更新

agenthold について

agentholdは、複数のAIエージェントで共有するバージョン付き状態とリソース調整のためのMCPサーバー兼Pythonライブラリです。バージョン確認で書き込み競合を検出し、排他的な取得や待機で同じリソースへの同時作業を防ぎます。

主な機能

  1. 01バージョン確認による書き込み競合の検出
  2. 02共有リソースの取得と解放
  3. 03リソース状態の確認と利用可能になるまでの待機
  4. 04解放・移動されたリソースの結果記録
  5. 05高度な状態ツールまたはPythonライブラリの利用

活用シーン

  1. 01同じコードベースを編集するエージェントの調整
  2. 02共有値への同時更新による上書きの防止
  3. 03取得中のファイルが利用可能になるまでエージェントを待機

MCP 対応

MCPクライアントに調整とバージョン付き状態のツールを提供し、標準では5つの調整ツールを備えます。高度な操作も選択でき、Pythonライブラリとして直接利用することもできます。

設定

ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。

mcp.json · 共通設定
{
"mcpServers": {
"edobusy-agenthold": {
"command": "uvx",
"args": [
"agenthold"
]
}
}
}
起動コマンドuvx agenthold

ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。

パッケージ
pypiagenthold0.10.0

ツール

ツール一覧はまだ取得されていません

ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。

詳細

agenthold

AI エージェント同士が互いのデータを気づかないうちに上書きするのを防ぎます。

2 つのエージェントが同じ値を更新すると、後から書き込んだ方が先の変更をひっそりと消してしまいます。エラーも例外もなく、データだけが間違った状態になり、システムはそのまま動き続けます。agenthold は、競合検出機能を備えたバージョン管理済みの共有状態をエージェントに提供する MCP サーバーです。エージェントの作業メモリ向けの git と考えてください。

CI PyPI version PyPI Downloads Coverage 80%+ Python 3.11+ Ruff Glama score License: MIT


問題点

2 つのエージェントが同じ値を同時に更新すると、後から書き込んだ方が先の変更を気づかないうちに上書きします。例外は発生しません。値は誤ったものになり、システムはそのまま動き続けます。

agenthold がない場合:気づかないうちに予算超過する問題

2 つのエージェントが $10,000 の予算を読み取り、それぞれ独立に割り当てます。確約した合計は $15,000 です。予算オブジェクトは何も警告しません。これは読み取り・変更・書き込みの競合です。それぞれのエージェントは、読み取り後に何も変更されていないことを前提に書き込んでいます。


仕組み

agenthold は、Postgres の UPDATE ... WHERE version = N や DynamoDB の条件付き書き込みで使われるのと同じ仕組みである、**楽観的同時実行制御(OCC)**によってこの問題を解決します。

agenthold に保存されるすべての値にはバージョン番号が付いています。エージェントが書き込む際は、読み取ったバージョンを渡します。読み取り後に保存済みのバージョンが変わっていた場合、現在の値を含む ConflictError とともに書き込みは拒否されます。エージェントは再読み込みし、再計算してから再試行します。

agenthold を使った場合:競合を防止した割り当て

敗者となったエージェントは競合を検出し、実際に残っている予算($2,000)を再読み込みして、割り当てを調整します。確約した合計は常に正確に $10,000 となります。すべての書き込みが追跡されます。

OCC は、次の理由からエージェントのワークフローに適しています。

  • エージェントは読み取りと書き込みの間に作業(ネットワーク呼び出し、LLM 推論)を行います。その作業中ずっとデータベースのロックを保持することはできません。
  • 競合はまれです。毎回の読み取りでロックを取得するより、一度再試行する方が低コストです。
  • 再試行のロジックはシンプルで明示的であり、完全にエージェントの制御下にあります。

あらゆるエージェントフレームワークで動作

agenthold は、ツール統合のオープン標準である MCP(Model Context Protocol) を介して接続します。MCP に対応するフレームワークなら、接着コードを一切追加せずに agenthold を利用できます。

フレームワーク接続方法
Claude Desktop / Claude Code標準搭載:mcpServers 設定に追加
Cursor / Continue / Windsurf標準搭載:MCP 設定に追加
LangChain / LangGraphlangchain-mcp-adapters
CrewAIAgent のネイティブな mcps フィールド
OpenAI Agents SDK標準搭載の mcp_servers パラメーター
Google ADK標準搭載の MCP Toolbox
AutoGenautogen_ext.tools.mcp
PydanticAIネイティブな MCP 統合

agenthold はフレームワークではありません。データベースがアプリケーションを支えるのと同じように、オーケストレーション層の下で動作する共有インフラストラクチャです。エージェントは既存のツールとロジックをそのまま使い、agenthold は不足している調整機能を追加します。

まだ MCP を使っていませんか? agenthold は、あらゆるフレームワークから直接呼び出せるPython ライブラリとしても利用できます。StateStore をインポートし、バージョンを確認しながら .get() と .set() を呼び出せば、競合を防止する共有状態を利用できます。


アーキテクチャ

graph LR
    A1["Agent 1
    LangChain, CrewAI, etc."] -->|MCP| S["agenthold
    MCP Server"]
    A2["Agent 2
    Claude, OpenAI, etc."] -->|MCP| S
    A3["Agent 3
    AutoGen, ADK, etc."] -->|MCP| S
    S --> DB[("SQLite
    WAL mode")]
    DB -->|version 3| S
    S -->|"conflict! retry"| A2

書き込みにはすべてバージョン番号が付与されます。エージェントが読み取ってから保存済みのバージョンが変更されている場合、書き込みは拒否され、エージェントは最新のデータで再試行します。これは、Postgres の条件付き更新や DynamoDB の条件付き書き込みで使われているものと同じ仕組みです。


クイックスタート

1. インストール
pip install agenthold
# or
uv pip install agenthold
2. MCP クライアントの設定に追加
{
  "mcpServers": {
    "agenthold": {
      "command": "agenthold",
      "args": ["--db", "/path/to/state.db"]
    }
  }
}
3. 完了

エージェントは自動的に連携します。CLAUDE.md も、システムプロンプトの変更も、名前空間の設計も必要ありません。

エージェントが接続すると、自己説明型の 5 つのツール agenthold_register、agenthold_claim、agenthold_release、agenthold_status、agenthold_wait が表示されます。ツールの説明には、それぞれをいつ、どのように使うかが記載されています。MCP クライアントがサーバーの指示を含める場合、サーバーの指示によってプロトコルが補強されます。


リソース

agenthold は正規 URI でリソースを識別します。ツールは次のいずれの形式も受け付けます。

  • パスのみ(例:"src/main.py")— default という名前のワークスペース、または設定済みのワークスペースが 1 つだけの場合はそのワークスペースを基準に解決されます。
  • 明示的な URI — ファイルには "file://<workspace>/<path>"、不透明なリソースには "custom://<name>" を使います。

同等の入力(./src/main.py、src\main.py、src//main.py、ワークスペース内の絶対パス)はすべて同じ内部 URI に正規化されるため、異なる省略表記を使うエージェントがいてもキー空間が分割されることはありません。パストラバーサル(..)とドットセグメント(.)は境界で拒否されます。

大文字と小文字を区別しないファイルシステム(Windows および macOS のデフォルト)では、ファイルリソースは大文字と小文字を区別せずに照合されます。そのため、src/Main.py と src/main.py は同じリソースとして解決され、2 つのエージェントが同一の物理ファイルを同時に確保することはできません。これは実行中のプラットフォームに従います。custom:// の名前は常に大文字と小文字が区別されます。

--workspace name=path(繰り返し指定可能)でワークスペースを設定します。フラグを指定しない場合、agenthold は現在の作業ディレクトリに default という単一のワークスペースを作成します。複数のワークスペースを設定すると、1 つの agenthold プロセスで複数の独立したコードベースをまたいで調整できます。


ツール

agenthold は、デフォルトで 5 つの調整ツールを公開します。

agenthold_register

自身を登録して、一意のエージェント ID を受け取ります。agenthold_claim または agenthold_release を使う前に、一度呼び出す必要があります。

{ "name": "editor-agent", "model": "claude-sonnet-4-6" }
{
  "status": "registered",
  "agent_id": "agent-a1b2c3d4",
  "name": "editor-agent",
  "registered_at": "2026-03-18T10:00:00+00:00"
}

agenthold_claim

リソースを変更する前に、排他的アクセス権を確保します。登録済みの agent_id が必要です。

{ "resource": "intro.md", "agent_id": "agent-a1b2c3d4" }

Claimed(排他的アクセス権を保持しています):

{ "status": "claimed", "resource": "file://default/intro.md", "version": 1 }

リソースに重要な過去の履歴(削除、移動、放棄、または期限切れ)がある場合、レスポンスには previous_outcome、previous_holder、previous_outcome_at、および前の保持者が行った操作を説明する hint も含まれます。previous_outcome: "moved" の場合、moved_to は新しいリソース URI です。

Busy(別のエージェントがこのリソースを操作中です):

{
  "status": "busy",
  "resource": "file://default/intro.md",
  "held_by": "agent-e5f6g7h8",
  "claimed_at": "2026-03-18T10:00:00+00:00",
  "hint": "Another agent holds this resource. Work on a different resource, or call agenthold_wait to be notified when it becomes available."
}

Already claimed(すでにこのリソースの権利を保持しています。べき等です):

{ "status": "already_claimed", "resource": "file://default/intro.md", "version": 1 }

agenthold_release

行った操作を明示する結果を指定して、保持している権利を解放します。結果は解放状態のレコードに保存され、次の取得者に表示されるため、古い前提に基づいて操作することを防げます。登録済みの agent_id が必要です。

{
  "resource": "intro.md",
  "agent_id": "agent-a1b2c3d4",
  "outcome": "modified"
}
{ "status": "released", "resource": "file://default/intro.md", "version": 2, "outcome": "modified" }

結果:released(デフォルト — ライフサイクルに関する特別な変更なし)、modified(その場で変更)、created(以前は存在しなかった)、deleted(このリソースの場所には存在しなくなった)、moved(移動 — 新しいリソースを指定する moved_to も渡します)。

名前を変更する場合(mv old new):両方のパスを取得し、ディスク上で名前を変更してから、ソースを outcome: "moved", moved_to: "new" で解放し、宛先を outcome: "created" で解放します。


agenthold_status

リソースが利用可能か、現在権利を取得されているかを確認します。登録は不要です。

{ "resource": "intro.md" }

Available:

{ "status": "available", "resource": "file://default/intro.md" }

前の保持者が重要な結果(deleted、moved、abandoned、expired)を宣言していた場合、レスポンスには previous_outcome、previous_holder、previous_outcome_at、および hint も含まれます。moved の場合、moved_to は新しいリソース URI です。

Claimed:

{
  "status": "claimed",
  "resource": "file://default/intro.md",
  "held_by": "agent-e5f6g7h8",
  "agent_name": "editor-agent",
  "agent_model": "claude-sonnet-4-6",
  "claimed_at": "2026-03-18T10:00:00+00:00",
  "version": 3
}

agenthold_wait

権利を取得されているリソースが利用可能になるまで待機します。保持者が解放するか、タイムアウトするまで、エージェントのターンをブロックします。

{ "resource": "intro.md", "timeout_seconds": 30 }

Available(リソースが解放されました):

{ "status": "available", "resource": "file://default/intro.md", "elapsed_seconds": 2.4 }

重要な結果を伴う解放を受けて待機が終了した場合、レスポンスには previous_outcome、previous_holder、previous_outcome_at、任意の moved_to、および hint も含まれます。移動または削除されたリソースを編集するために待機していたエージェントは、そのパスが利用可能になった理由を把握できます。

Timeout:

{
  "status": "timeout",
  "resource": "file://default/intro.md",
  "held_by": "writer-2",
  "elapsed_seconds": 30.2,
  "hint": "The resource was not released within the timeout. Try working on a different resource, or call agenthold_wait again with a longer timeout."
}

高度なツール

カスタムの調整プロトコル向けに、agenthold は --tools advanced を指定すると、8 つの低レベルプリミティブを公開します。

agenthold_get · agenthold_set · agenthold_list · agenthold_history · agenthold_delete · agenthold_watch · agenthold_clear_namespace · agenthold_export

これらを使うと、完全な OCC 競合検出機能を備えたバージョン管理状態ストアに対して、エージェントが直接、読み取り、書き込み、監視を行えます。このモードでは、サーバーからの指示は送信されません。

高度なツールのリファレンス全文を見る →


競合検出

expected_version を使った読み取り・変更・書き込みのパターンは、agenthold の中核です。標準的な再試行ループは次のとおりです。

from agenthold.store import StateStore
from agenthold.exceptions import ConflictError

store = StateStore("./state.db")

record = store.get("campaign", "budget")   # read once before doing work
do_expensive_work()                         # LLM call, API request, etc.

while True:
    new_value = compute_new_value(record.value)
    try:
        store.set(
            "campaign", "budget", new_value,
            updated_by="my-agent",
            expected_version=record.version,
        )
        break   # write succeeded
    except ConflictError:
        record = store.get("campaign", "budget")   # re-read and retry

これが機能する理由: バージョン番号が契約となります。読み取り後に保存済みのバージョンが進んでいた場合、別のエージェントが先に書き込みを行っています。現在の値を取得し、再計算して、もう一度試します。再試行回数は同時に書き込むエージェントの数によって制限されます。実際には、エージェント間で複数回競合することはほとんどありません。

ロックを使わない理由: ロックにはリース機構が必要です(ロックを保持したままエージェントがクラッシュした場合はどうなるでしょうか)。また、読み取りのたびに遅延が生じ、エージェントのワークフローに伴う長い I/O 待ちとも相性がよくありません。OCC では、実際に競合が発生した場合にのみコストがかかります。


Python ライブラリとして使用する

from agenthold.store import StateStore
from agenthold.exceptions import ConflictError

store = StateStore("./state.db")

# Write a value (first write, no conflict check needed)
store.set("order-1234", "status", "received", updated_by="intake-agent")

# Read it back; always get the version number too
record = store.get("order-1234", "status")
print(record.value)    # "received"
print(record.version)  # 1

# Write with conflict detection; pass the version you read
try:
    store.set(
        "order-1234", "status", "processing",
        updated_by="fulfillment-agent",
        expected_version=record.version,  # rejected if another agent wrote first
    )
except ConflictError as e:
    # Another agent wrote between your read and write.
    # e.detail has the current version, value, and who wrote it.
    record = store.get("order-1234", "status")
    # ... recalculate and retry

実際の使用例

複数のエージェントによるセッションでは、調整は自動的に行われます。エージェントのツール呼び出しは次のようになります。

Agent A: agenthold_register(name="writer", model="claude-sonnet-4-6")
         → agent_id: "agent-a1b2c3d4"

Agent A: agenthold_claim(resource="chapter-3.md", agent_id="agent-a1b2c3d4")
         → status: "claimed", resource: "file://default/chapter-3.md"

Agent B: agenthold_claim(resource="chapter-3.md", agent_id="agent-e5f6g7h8")
         → status: "busy", hint: "Work on a different resource..."

Agent A: agenthold_release(resource="chapter-3.md", agent_id="agent-a1b2c3d4", outcome="modified")
         → status: "released", outcome: "modified"

システムプロンプトの調整は不要です。ツールの説明がエージェントをガイドします。

実践例

「変更前」と「変更後」のスクリプトを含む、実践例が2つあります。

注文処理:2つのエージェントが同じ注文レコードを同時に更新します。

uv run python examples/order_processing/without_agenthold.py   # silent overwrite
uv run python examples/order_processing/with_agenthold.py      # conflict detection + retry

予算配分:2つのエージェントが共有のマーケティング予算を使います。

uv run python examples/budget_allocation/without_agenthold.py  # $10k budget → $15k committed
uv run python examples/budget_allocation/with_agenthold.py     # exact allocation, full audit trail

設定

agenthold --db ./state.db                              # standard mode (default)
agenthold --db ./state.db --tools advanced             # advanced mode
agenthold --db ./state.db --claim-ttl 1800             # standard + 30 min TTL
agenthold --workspace myproj=/abs/path                 # named workspace
agenthold --workspace a=/x --workspace b=/y            # multiple workspaces
agenthold --transport http --port 8417                 # serve over HTTP (see below)
フラグデフォルト説明
--db./agenthold.dbSQLite データベースファイルのパスです。プロセス内ストアを使う場合は :memory: を指定します(テスト専用。プロセス終了時にデータは失われます)。
--toolsstandardツールセット:standard(register/claim/release/status/wait)または advanced(get/set/delete/watch/list/history/clear/export)。
--claim-ttlNone (no expiry)非アクティブなエージェントの claim が期限切れになるまでの秒数です。standard モードでのみ適用されます。設定すると、最後のアクティビティからこの値を超えているエージェントが保持する claim は期限切れとして扱われ、他のエージェントが取得できるようになります。--transport http と併用することを推奨します。
--workspace現在の作業ディレクトリにある default という名前のワークスペース1つワークスペースは name=path(例:myproj=/abs/path)または絶対パス(名前はベース名から取得)で設定します。複数回指定できます。複数のワークスペースを設定すると、1つの agenthold プロセスで、同じデータベースを使って別々のコードベース間を調整できます。ツール入力の相対パスは、default という名前のワークスペース、または設定済みのワークスペースが1つだけの場合はそのワークスペースを基準に解決されます。
--transportstdiostdio(エージェントごとにローカルサブプロセスを1つ起動)または http(多数のエージェントが Streamable HTTP 経由で接続する、長時間稼働するサーバーを1つ起動)。
--host127.0.0.1HTTP のバインドアドレスです。--transport http の場合のみ使用します。
--port8417HTTP ポートです。--transport http の場合のみ使用します。
--path/mcpMCP トランスポートがマウントされる HTTP エンドポイントのパスです。--transport http の場合のみ使用します。
--json-responseoffHTTP 経由で SSE ストリームの代わりに JSON レスポンスを返します。--transport http の場合のみ使用します。
--allowed-hostnoneDNS リバインディング対策を有効にし、この Host ヘッダー値を許可します。複数回指定できます。省略すると、対策は無効のままです(localhost で利用しやすい設定)。--transport http の場合のみ使用します。
--auth-tokennoneすべての HTTP リクエストで、このベアラートークンを必須にします(Authorization: Bearer <token>)。複数回指定できます。AGENTHOLD_AUTH_TOKEN 環境変数(カンマ区切り)でも設定できます。環境変数の使用を推奨します。--transport http の場合のみ使用します。

データベースファイルは初回実行時に自動で作成されます。他の SQLite ファイルと同様にバックアップしてください。

HTTP トランスポート

デフォルトでは、agenthold は stdio で通信します。MCP クライアントはエージェントごとに agenthold のサブプロセスを1つ起動し、各サブプロセスは同じマシン上の共有 SQLite ファイルを介して連携します。これは、1台のマシンでローカルに複数のエージェントを実行する場合に最適です。

異なるプロセス、コンテナ、またはホスト上で動作するエージェントを連携させるには、Streamable HTTP 経由で長時間稼働する agenthold サーバーを1つ起動し、すべてのエージェントをそのサーバーに接続します。

agenthold --db ./state.db --transport http --host 127.0.0.1 --port 8417 --claim-ttl 1800

その後、コマンドではなく URL を使って MCP クライアントを接続します。

{
  "mcpServers": {
    "agenthold": {
      "url": "http://127.0.0.1:8417/mcp"
    }
  }
}

5つの標準ツール(および --tools advanced を指定した場合の高度なツールセット)は、HTTP 経由でも同じように動作します。変更されるのはトランスポートだけです。1つのサーバーが長時間にわたって多数のエージェントにサービスを提供するため、エージェントが切断された際に claim を自動的に再取得できるよう、--claim-ttl を設定してください。

認証

すべての HTTP リクエストでベアラートークンを必須にするには、1 つ以上のトークンを設定します。トークンがプロセス一覧に表示されないよう、環境変数の使用をおすすめします。

AGENTHOLD_AUTH_TOKEN=s3cr3t agenthold --db ./state.db --transport http --host 0.0.0.0
# or, less privately:
agenthold --transport http --auth-token s3cr3t --auth-token another-token

クライアントは Authorization ヘッダーでトークンを送信します。

{
  "mcpServers": {
    "agenthold": {
      "url": "http://your-host:8417/mcp",
      "headers": { "Authorization": "Bearer s3cr3t" }
    }
  }
}

有効なトークンがないリクエストは、ストアに到達する前に 401 で拒否されます。トークンは定時間で比較されます。認証が適用されるのは --transport http のみです(stdio は信頼されたローカルトランスポートです)。このリリースの認証は全体に適用されるか、適用されないかのどちらかです。名前空間ごとのスコープ設定はロードマップに含まれています。

AGENTHOLD_AUTH_TOKEN の値はカンマで分割されるため、トークン自体にカンマを含めることはできません(カンマを含むトークンには --auth-token を繰り返し指定してください)。認証が要求されているのに、すべてのトークンが空白の場合、認証なしでサービスを提供するのではなく、サーバーは起動を拒否します。

セキュリティ。 ベアラートークンのプライバシーは、それを運ぶトランスポートの安全性に依存します。実際のデプロイでは、agenthold の前段に TLS(リバースプロキシ)を配置してください。デフォルトのバインドアドレスは 127.0.0.1(ローカルホストのみ)です。--auth-token を指定せずにローカルホスト以外へバインドすると、起動時に警告が表示されます。DNS リバインディング(Host ヘッダー)保護を有効にするには、--allowed-host <host>(複数回指定可能)を渡してください。これは認証ではなく、--auth-token の代わりにはならない点に注意してください。

長時間稼働するサーバー。 agenthold は現時点ではアイドル状態のエージェントレコードを削除しないため、エージェントの入れ替わりが激しい状態でサーバーを非常に長時間稼働させると、エージェントのメタデータが蓄積されます。切断されたエージェントのクレームを回復できるよう --claim-ttl を設定し、必要に応じて定期的に再起動してください。自動削除はロードマップに含まれています。


ストアの調査

agenthold データベースは単なる SQLite ファイルですが、手動で開く必要はほとんどありません。agenthold コマンドは読み取り専用のインスペクターとしても機能するため、MCP クライアントを使わずに、各エージェントの動作をひと目で確認できます。これらのコマンドは完全に読み取り専用(データベースは query_only で開かれ、スキーマへの書き込みは行われません)なので、--db に誤ったファイルを指定した場合でも、ストアが変更されることはありません。

agenthold agents --db ./state.db          # who is registered
agenthold claims --db ./state.db          # what is currently claimed, and by whom
agenthold claims --db ./state.db --all    # also show freed/moved/deleted claims
agenthold namespaces --db ./state.db      # namespaces + record counts
agenthold keys orders --db ./state.db     # keys in a namespace
agenthold history orders order-1 --db ./state.db   # a key's version history

例:

$ agenthold claims --db ./state.db
RESOURCE                    STATE    BY            SINCE
--------------------------  -------  ------------  ------
custom://chapter-1          claimed  editor-agent  2s ago
file://default/src/main.py  claimed  review-bot    2s ago

すべてのコマンドで --db PATH(デフォルトは ./agenthold.db)と、機械可読な出力を得るための --json を指定できます。引数なしの agenthold(サブコマンドなし)は、これまでどおり MCP サーバーを起動します。インスペクターは機能を追加するものであり、既存の動作を変更しません。


開発

git clone https://github.com/edobusy/agenthold.git
cd agenthold
uv sync --all-extras --dev

テストの実行:

uv run pytest tests/ -v

カバレッジの確認:

uv run pytest tests/ --cov=agenthold --cov-report=term-missing

Lint と型チェック:

uv run ruff check src/ tests/
uv run ruff format src/ tests/
uv run mypy src/

CI は main へのプッシュごとに Python 3.11 と 3.12 で実行されます。詳しいガイドラインについては、CONTRIBUTING.md を参照してください。


技術的な補足(エンジニア向けの設計上の判断)

なぜ SQLite なのか? この用途には SQLite が適しています。依存関係がなく、Python の標準ライブラリに含まれており、あらゆる環境で動作します。WAL モードを有効にしているため、読み取り専用の操作(エクスポート、監視)がプロセス間で書き込みをブロックしません。書き込みトランザクションでは BEGIN IMMEDIATE を使って事前に書き込みロックを取得するため、複数の agenthold プロセスが同じデータベースファイルを共有している場合でも、OCC の競合検出が正しく機能します。busy_timeout は 5 秒に設定されているため、2 つ目の書き込み処理はすぐに失敗せず、待機します。この規模では、Postgres を使っても運用上の依存関係が増えるだけで、メリットはありません。ストレージバックエンドは明確なインターフェース(StateStore)の背後にあり、必要になれば Postgres に置き換えられます。意図的にシンプルなツールを選ぶことは、制約ではありません。

なぜ悲観的ロックではなく OCC なのか? ロックは保持者が解放する必要があるため、クラッシュ、タイムアウト、古いロック保持者への対処が必要になります。競合がまれな場合、この複雑さに見合う価値はありません。OCC では、実際に競合が発生した場合にのみコストがかかります。追加の読み取りが 1 回と、再試行が 1 回です。エージェントが読み取りと書き込みの間にかなりの処理(LLM の推論、API 呼び出し、ツールの実行)を行うマルチエージェントのワークフローでは、OCC が適切な選択です。

バージョン管理で保証されること: 各キーのバージョンは 1 から始まり、書き込みのたびに正確に 1 ずつ増加します。state_history テーブルは追記専用で、ライブレコードの更新前にすべての書き込みを記録するため、2 つの書き込みの間にクラッシュしても履歴の整合性が保たれます。削除時もライブレコードを削除する前に state_history に墓標エントリ(event_type: "delete")を書き込むため、キーのライフサイクル全体を履歴で確認できます。順序の保証はキー単位であり、異なるキーのバージョンは任意の順序で更新される可能性があります。

本番規模に対応する場合の変更点: 主な変更点は 1 つです。同時書き込みのスループット、レプリケーション、マネージドホスティングを改善するため、SQLite を Postgres に置き換えます。StateStore インターフェースは、この変更を局所的に行えるよう、すでに設計されています。ネットワークトランスポートと認証はすでに用意されています。agenthold は MCP SDK の StreamableHTTPSessionManager を介して Streamable HTTP を提供するため、リモートエージェントはローカルプロセスではなくネットワーク経由で接続します。また、--auth-token でベアラートークンによるアクセス制御を行います(認証を参照)。本番環境では、リバースプロキシの前段に TLS を設定してください。また、トークンの名前空間/テナントごとのスコープ設定はまだ実装されていない点に注意してください。


ライセンス

MIT。LICENSE を参照してください。


mcp-name: io.github.edobusy/agenthold

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。