diff --git a/en/plugin/certified-plugins.mdx b/en/plugin/certified-plugins.mdx
index f968d77a5..29b174379 100644
--- a/en/plugin/certified-plugins.mdx
+++ b/en/plugin/certified-plugins.mdx
@@ -93,7 +93,45 @@ A declaration is an application, not approval. It grants no badge and does not c
- Do not read and cache installation configuration in `initialize()`.
- Reach storage, files, credentials, logs, models and other platform capabilities through the SDK Host APIs. Authority is resolved per call; do not cache it yourself.
- Durable tenant state belongs in Host services. Runners use `ctx.state_get()` / `ctx.state_set()` and friends (this requires `ctx.context.available_apis.state` to be `true`). In-process caches may hold artifact-level public data only; if you must cache tenant data, the key has to include the complete installation identity and must be cleaned up when the installation is removed.
-- Tenant-bearing background tasks and threads are forbidden. Once a handler returns, that invocation's context and run-associated calls are no longer valid.
+- Do not start tenant-bearing background tasks or threads: the SDK does **not** track ownership of work you create. If you must detach work, keep a registry keyed by the installation binding and stop it in `on_installation_revoked()`. Once a handler returns, that invocation's context and run-associated calls are no longer valid.
+
+### One installation, many usages
+
+A Workspace installs each plugin once, but that single installation is reused by **several product objects**:
+
+| Component kind | What reuses it | What isolates it |
+| --- | --- | --- |
+| Runner in an Agent or Pipeline | every processor | `ctx.state.*` is already per-processor (scopes carry `binding_identity`, derived from the Agent UUID) |
+| KnowledgeEngine / Parser behind a knowledge base | every knowledge base | **your plugin must isolate by knowledge-base ID itself** |
+| Tool / Command / EventListener / Page | several processors, conversations or page requests | read per call; never accumulate on the instance |
+
+The platform storage key is `(instance, Workspace, owner type, plugin, key)` and carries **no processor or knowledge-base dimension**. So whenever your plugin persists or caches anything, it must put the product object's identity into the key:
+
+- **Config**: read per call (`ctx.config`, or the task-local `self.get_plugin_config()`); never cache it on the instance.
+- **Persistence**: keys must include the product identity (processor identity / knowledge-base ID / conversation identity).
+- **Process-local caches**: keys must include both the full installation binding **and** the product identity, and must be released when the installation is revoked (below).
+- **Locks and fences**: key them by `(installation, product object)`. **A failure in one processor or knowledge base must not block the others of the same installation.**
+- **Failure grading**: only a dispatched mutation with an unknown outcome should enter a state that needs manual reconciliation; deterministic validation errors and pre-dispatch cancellation must not affect later calls.
+- **Logs and telemetry**: a diagnostic failure must not affect the work, and tenant data must not be written into a process-wide singleton.
+
+### When an installation is revoked
+
+Under shared placement one object graph serves every installation of the artifact, so **revoking one installation does not destroy it**. The plugin must release binding-keyed process-local state itself:
+
+```python
+class MyPlugin(BasePlugin):
+ def __init__(self) -> None:
+ super().__init__()
+ self._caches: dict[str, Cache] = {}
+
+ async def on_installation_revoked(self, binding) -> None:
+ self._caches.pop(binding.installation_uuid, None)
+```
+
+- **Fires for** uninstall, disable, Workspace removal and upgrade (the superseded binding is revoked). It runs only under shared placement; dedicated workers are shut down, so their process-local state disappears with the process.
+- **There is no active invocation**, so Host APIs (storage, config, binding lookups) are unavailable — you can only release process-local state, and the installation is identified by the argument.
+- Exceptions are logged and **never block revocation**.
+- It requires an SDK version that provides `BasePlugin.on_installation_revoked`; on older versions the hook is simply never called.
### Allowed / forbidden
diff --git a/en/plugin/dev/components/command.mdx b/en/plugin/dev/components/command.mdx
index 5e6d03787..f46f5dcfc 100644
--- a/en/plugin/dev/components/command.mdx
+++ b/en/plugin/dev/components/command.mdx
@@ -207,6 +207,17 @@ Command processing supports returning multiple messages, so please use `yield` s
For specific return values, please refer to the definition of `CommandReturn`: [langbot_plugin.api.entities.builtin.command.context.CommandReturn](https://github.com/langbot-app/langbot-plugin-sdk/tree/main/src/langbot_plugin/api/entities/builtin/command/context.py)
+## One installation, many usages
+
+A single command is triggered **concurrently by several conversations**:
+
+- Read config per call; never read it in `initialize()` and cache it on the instance.
+- Never leave command arguments, return values or conversation data on instance fields or module-level variables; `ExecuteContext` is valid only for the current execution.
+- Concurrent triggering means instance fields get interleaved writes — do not use them to hold "current conversation" style state. Data that must survive calls belongs in Host storage, keyed with the conversation identity.
+- If you keep per-installation process-local caches, release them in `on_installation_revoked(binding)`.
+
+See [Certified plugins and shared runtime](/en/plugin/certified-plugins) for the full specification.
+
## What's Next
You have learned the basic information about command registration and command execution. Next, you can:
diff --git a/en/plugin/dev/components/event-listener.mdx b/en/plugin/dev/components/event-listener.mdx
index 298cfd1f7..9fe045ca0 100644
--- a/en/plugin/dev/components/event-listener.mdx
+++ b/en/plugin/dev/components/event-listener.mdx
@@ -99,6 +99,17 @@ class EventContext(pydantic.BaseModel):
Event registration is implemented through the `@self.handler` decorator, with the decorator parameter being the event type. All monitorable events are defined in `langbot_plugin.api.entities.builtin.events`.
+## One installation, many usages
+
+The same event listener is called **concurrently by several Workspaces and several events**:
+
+- Never accumulate state per event on the instance (counters, last event, conversation maps); concurrent callbacks will overwrite each other.
+- A callback takes its data only from the current `EventContext`; never reuse a context or its fields across calls.
+- Data that must survive calls belongs in Host storage, keyed with the installation identity and the event-related identity (Workspace, conversation, and so on).
+- If you keep per-installation process-local state, release it in `on_installation_revoked(binding)`.
+
+See [Certified plugins and shared runtime](/en/plugin/certified-plugins) for the full specification.
+
## What's Next
You have learned the basic information about event listeners. Next, you can:
diff --git a/en/plugin/dev/components/knowledge-engine.mdx b/en/plugin/dev/components/knowledge-engine.mdx
index 8c8ccd008..901ec2b7b 100644
--- a/en/plugin/dev/components/knowledge-engine.mdx
+++ b/en/plugin/dev/components/knowledge-engine.mdx
@@ -467,6 +467,18 @@ async def get_knowledge_file_stream(self, storage_path: str) -> bytes:
file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path)
```
+## One installation, many usages
+
+A single knowledge engine is used by **several knowledge bases** at once, and the platform storage key `(instance, Workspace, owner type, plugin, key)` carries no knowledge-base dimension:
+
+- Persistence and cache keys must include the **knowledge-base ID**, otherwise vectors and document state of different knowledge bases overwrite each other.
+- Locks and reconciliation state must be keyed by `(installation, knowledge base)`; **a failure while ingesting one knowledge base must not block ingestion or retrieval of the others under the same installation**.
+- Only a dispatched mutation with an unknown outcome should enter a state that needs manual reconciliation; deterministic validation errors and pre-dispatch cancellation must not affect later calls.
+- Telemetry and diagnostic failures must not affect the work: never let a log or report failure interrupt ingestion or retrieval, and never write tenant data into a process-wide singleton.
+- If you keep per-installation process-local caches or state, release them in `on_installation_revoked(binding)`.
+
+See [Certified plugins and shared runtime](/en/plugin/certified-plugins) for the full specification.
+
## Testing the Knowledge Engine
After creation, execute the command `lbp run` in the plugin directory to start debugging. Then in LangBot:
diff --git a/en/plugin/dev/components/knowledge-retriever.mdx b/en/plugin/dev/components/knowledge-retriever.mdx
index 23e96fb30..4f0c598d6 100644
--- a/en/plugin/dev/components/knowledge-retriever.mdx
+++ b/en/plugin/dev/components/knowledge-retriever.mdx
@@ -253,6 +253,18 @@ using_rerank = self.config.get('using_rerank', False)
search_mode = self.config.get('search_mode', 'embedding')
```
+## One installation, many usages
+
+A single knowledge retriever is used by **several knowledge bases**, and the platform storage key `(instance, Workspace, owner type, plugin, key)` carries no knowledge-base dimension:
+
+- Any cache, index or intermediate result used for retrieval must be isolated by **knowledge-base ID**, with the ID in the key; never keep it on the instance.
+- Key retrieval locks and reconciliation state by `(installation, knowledge base)`; a retrieval failure in one knowledge base must not make retrieval unavailable for the others under the same installation.
+- `RetrievalContext` is valid only for a single retrieval; never store it or its results on instance fields for reuse across calls.
+- Knowledge-base data that must survive calls belongs in Host storage, with a key that includes both the installation identity and the knowledge-base ID.
+- If you cache connections, indexes or any other process-local state per installation, release them in `on_installation_revoked(binding)`.
+
+See [Certified plugins and shared runtime](/en/plugin/certified-plugins) for the full specification.
+
## Testing the Retriever
After creation, execute the command `lbp run` in the plugin directory to start debugging. Then in LangBot:
diff --git a/en/plugin/dev/components/page.mdx b/en/plugin/dev/components/page.mdx
index f95516b59..1b1349842 100644
--- a/en/plugin/dev/components/page.mdx
+++ b/en/plugin/dev/components/page.mdx
@@ -236,6 +236,17 @@ await langbot.api('/entries', { id: '...' }, 'DELETE');
Full example code is available at [FAQManager plugin](https://github.com/langbot-app/langbot-plugin-demo/tree/main/FAQManager).
+## One installation, many usages
+
+The same Page is called by **many page requests** (different requests and different Workspaces under one installation):
+
+- Never leave the previous request's data on the instance; take the endpoint, method, body and current installation information from the current `PageRequest` / binding every time.
+- Page data always lives in installation-scoped Host storage; `self.plugin` is a process-level shared object and must not hold tenant data.
+- Requests from different Workspaces share the same Page instance, so instance fields get interleaved; data that must survive requests belongs in Host storage.
+- If you keep per-installation process-local caches, release them in `on_installation_revoked(binding)`.
+
+See [Certified plugins and shared runtime](/en/plugin/certified-plugins) for the full specification.
+
## Notes
- Pages run inside a `sandbox="allow-scripts allow-forms"` iframe and cannot open popups or navigate the parent page
diff --git a/en/plugin/dev/components/parser.mdx b/en/plugin/dev/components/parser.mdx
index 04f1ec0f2..bb3d6a933 100644
--- a/en/plugin/dev/components/parser.mdx
+++ b/en/plugin/dev/components/parser.mdx
@@ -200,6 +200,17 @@ result = await self.plugin.invoke_parser(
# result is a dict containing text, sections, metadata
```
+## One installation, many usages
+
+A single Parser is used by **several knowledge bases** (different knowledge bases of one installation upload files of different MIME types), and the platform storage key carries no knowledge-base dimension:
+
+- Parse caches and temporary files must be isolated by **knowledge-base ID**, with the ID in the key; never cache them on the instance.
+- `ParseContext` and intermediate parse results are valid only for a single parse; never store them on instance fields or module-level variables.
+- A parse failure affects only that upload of the current knowledge base; it must not make parsing unavailable for the other knowledge bases.
+- If you cache models, parsers or temporary directories per installation, release them in `on_installation_revoked(binding)` and make sure temporary files can be cleaned up.
+
+See [Certified plugins and shared runtime](/en/plugin/certified-plugins) for the full specification.
+
## Testing the Parser
After creation, execute the command `lbp run` in the plugin directory to start debugging. Then in LangBot:
diff --git a/en/plugin/dev/components/runner.mdx b/en/plugin/dev/components/runner.mdx
index 688a014fb..df5b4d9aa 100644
--- a/en/plugin/dev/components/runner.mdx
+++ b/en/plugin/dev/components/runner.mdx
@@ -190,6 +190,18 @@ After installing the plugin, add this configuration under **Plugin processor** o
Plugin subscriptions run independently of Agent/Pipeline routes. An event can trigger multiple processors, and one failure does not prevent the others from running. Bots using the same configuration share settings and runtime state; create separate configurations for different settings. Expose optional behavior, such as replies, through component configuration to avoid duplicate responses.
+## One installation, many usages
+
+A Workspace installs each plugin once, but that installation is reused by **several processors** (Agents and Pipelines). A Runner must keep their data apart on its own:
+
+- `ctx.state.*` is already per-processor: scopes carry `binding_identity` (derived from the Agent UUID), so different processors of the same installation never overwrite each other.
+- The platform storage key is `(instance, Workspace, owner type, plugin, key)` and carries **no processor dimension**; any key you write into Host storage or caches yourself must therefore also include the processor or conversation identity.
+- `RunnerContext` (`ctx`) is valid only for a single run. Never keep it or its fields on the instance for reuse across calls; persist session state that must survive calls via `ctx.state_get()` / `ctx.state_set()`.
+- Several processors share the same Runner instance, so never accumulate the current run's data on instance fields. If your runner must hold cross-call state in-process (for example a long-lived external session process), do not declare shared runtime — stay dedicated.
+- If you keep per-installation process-local caches, release them in `on_installation_revoked(binding)`.
+
+See [Certified plugins and shared runtime](/en/plugin/certified-plugins) for the full specification.
+
## Next Steps
- See the [`RunnerContext` API reference](/en/plugin/dev/apis/agent-run#runner).
diff --git a/en/plugin/dev/components/tool.mdx b/en/plugin/dev/components/tool.mdx
index 5ac07dc85..e3095a854 100644
--- a/en/plugin/dev/components/tool.mdx
+++ b/en/plugin/dev/components/tool.mdx
@@ -169,6 +169,17 @@ Now execute the command `lbp run` in the plugin directory to start debugging. Co
+## One installation, many usages
+
+A single Tool is called by **several processors and conversations** (one event can trigger several plugin processors):
+
+- Read config per call (`self.get_plugin_config()` or `ctx.config`); never read it in `initialize()` and cache it on the instance.
+- Never leave call arguments, results or conversation data on instance fields or module-level variables; take the conversation identity from the current call's `session` / `query_id`.
+- Concurrent calls share the same instance, so instance fields get interleaved writes. Data that must survive calls belongs in Host storage, keyed with the corresponding processor or conversation identity.
+- If you keep per-installation process-local caches, release them in `on_installation_revoked(binding)`.
+
+See [Certified plugins and shared runtime](/en/plugin/certified-plugins) for the full specification.
+
## What's Next
You have learned the basic usage of tools. Next, you can:
diff --git a/ja/plugin/certified-plugins.mdx b/ja/plugin/certified-plugins.mdx
index b2f434deb..2d4885759 100644
--- a/ja/plugin/certified-plugins.mdx
+++ b/ja/plugin/certified-plugins.mdx
@@ -93,7 +93,45 @@ execution:
- `initialize()` でインストール設定を読み取り、保持しないでください。
- ストレージ、ファイル、資格情報、ログ、モデルなどのプラットフォーム機能は SDK の Host API 経由で利用します。権限は呼び出しごとに解決されるため、自分でキャッシュしないでください。
- 呼び出しをまたいで保持する必要があるテナント状態は Host サービスに置きます。Runner は `ctx.state_get()` / `ctx.state_set()` などを使用します(`ctx.context.available_apis.state` が `true` である必要があります)。プロセス内キャッシュは artifact レベルの公開データのみ許可されます。テナントデータをキャッシュする場合は、キーに完全な installation 識別子を含め、インストール削除時に解放できるようにしてください。
-- テナントデータを伴うバックグラウンドタスクやスレッドは禁止です。ハンドラーが戻った時点で、その呼び出しのコンテキストと実行関連の呼び出しは無効になります。
+- テナントデータを伴うバックグラウンドタスクやスレッドを開始しないでください。SDK は自分で作成した処理の所有権を**追跡しません**。どうしてもハンドラーから切り離す場合は、installation binding をキーにした登録表を保ち、`on_installation_revoked()` で停止してください。ハンドラーが戻った時点で、その呼び出しのコンテキストと実行関連の呼び出しは無効になります。
+
+### 1 つの installation、複数の用途
+
+1 つの Workspace でプラグインをインストールできるのは 1 回ですが、その installation は**複数のプロダクトオブジェクト**から再利用されます。
+
+| コンポーネント種別 | 再利用するもの | 分離のされ方 |
+| --- | --- | --- |
+| Agent / パイプラインの Runner | 各プロセッサ | `ctx.state.*` は既にプロセッサ単位(スコープに `binding_identity`(Agent UUID 由来)を含む) |
+| ナレッジベースの KnowledgeEngine / Parser | 各ナレッジベース | **プラグイン自身がナレッジベース ID で分離する必要があります** |
+| Tool / Command / EventListener / Page | 複数のプロセッサ、会話、ページリクエスト | 呼び出しごとに読み取り、インスタンスに蓄積しない |
+
+プラットフォームのストレージキーは `(instance, Workspace, owner type, plugin, key)` で、**プロセッサやナレッジベースの次元を含みません**。したがって永続化やキャッシュを行う場合は、キーにプロダクトオブジェクトの識別子を含めてください。
+
+- **設定**:呼び出しごとに読み取る(`ctx.config`、または task-local な `self.get_plugin_config()`)。インスタンスにキャッシュしない。
+- **永続化**:キーにプロダクトの識別子(プロセッサ ID / ナレッジベース ID / 会話 ID)を含める。
+- **プロセス内キャッシュ**:キーに完全な installation binding **と** プロダクトの識別子の両方を含め、installation が取り消されたときに解放する(下記)。
+- **ロックとフェンス**:`(installation, プロダクトオブジェクト)` 単位で記録する。**1 つのプロセッサやナレッジベースの失敗が、同じ installation の他を遮断してはいけません。**
+- **失敗の切り分け**:手動対账が必要な状態に入るのは、送出済みで結果不明の変更だけにします。決定的な検証エラーや送出前のキャンセルは後続に影響させない。
+- **ログとテレメトリ**:診断の失敗が業務に影響してはいけませんし、テナントデータをプロセス全体のシングルトンに書かないでください。
+
+### installation が取り消されたとき
+
+共有配置では 1 つのオブジェクトグラフが同じ artifact の全 installation にサービスするため、**1 つの installation を取り消してもオブジェクトグラフは破棄されません**。プラグインが binding 単位のプロセス内状態を自分で解放する必要があります。
+
+```python
+class MyPlugin(BasePlugin):
+ def __init__(self) -> None:
+ super().__init__()
+ self._caches: dict[str, Cache] = {}
+
+ async def on_installation_revoked(self, binding) -> None:
+ self._caches.pop(binding.installation_uuid, None)
+```
+
+- **呼ばれる場面**:アンインストール、無効化、Workspace の削除、および**アップグレード**(旧 binding が取り消される)。共有配置でのみ呼ばれます。専用ワーカーは停止されるため、プロセス内状態はプロセスとともに消えます。
+- **呼び出し中は有効な invocation がありません**。Host API(ストレージ、設定、binding 参照)は利用できず、解放できるのはプロセス内状態だけです。installation は引数で渡されます。
+- 例外はログに記録され、**取り消し処理を妨げません**。
+- `BasePlugin.on_installation_revoked` を提供する SDK バージョンが必要です。それ以前のバージョンではこのフックは呼ばれません。
### 許可 / 禁止の対照表
diff --git a/ja/plugin/dev/components/command.mdx b/ja/plugin/dev/components/command.mdx
index db9523a2e..d289ea78d 100644
--- a/ja/plugin/dev/components/command.mdx
+++ b/ja/plugin/dev/components/command.mdx
@@ -207,6 +207,17 @@ yield CommandReturn(
具体的な戻り値については、`CommandReturn`の定義を参照してください: [langbot_plugin.api.entities.builtin.command.context.CommandReturn](https://github.com/langbot-app/langbot-plugin-sdk/tree/main/src/langbot_plugin/api/entities/builtin/command/context.py)
+## 1 つの installation、複数の用途
+
+1 つのコマンドは**複数の会話から同時に**起動されます:
+
+- 設定は呼び出しごとに読み取ってください。`initialize()` で読み取ってインスタンスにキャッシュしないでください。
+- コマンド引数、戻り値、会話データをインスタンスフィールドやモジュールレベルの変数に残さないでください。`ExecuteContext` は今回の実行でのみ有効です。
+- 同時起動ではインスタンスフィールドの書き込みが交錯します。「現在の会話」のような状態をそこに保存しないでください。呼び出しをまたいで保持するデータは Host ストレージに置き、キーに会話の識別子を含めてください。
+- installation 単位でプロセス内キャッシュを保持する場合は、`on_installation_revoked(binding)` で解放してください。
+
+完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
+
## 次のステップ
コマンド登録とコマンド実行の基本情報を学びました。次は:
diff --git a/ja/plugin/dev/components/event-listener.mdx b/ja/plugin/dev/components/event-listener.mdx
index e1cd24f8b..6dbcad4c8 100644
--- a/ja/plugin/dev/components/event-listener.mdx
+++ b/ja/plugin/dev/components/event-listener.mdx
@@ -99,6 +99,17 @@ class EventContext(pydantic.BaseModel):
イベント登録は`@self.handler`デコレータを通じて実装され、デコレータのパラメータはイベントタイプです。監視可能なすべてのイベントは`langbot_plugin.api.entities.builtin.events`で定義されています。
+## 1 つの installation、複数の用途
+
+同じイベントリスナーは**複数の Workspace、複数のイベントから同時に**呼び出されます:
+
+- イベント単位の状態(カウンタ、直近のイベント、会話マップなど)をインスタンスに蓄積しないでください。同時実行のコールバックが互いを上書きします。
+- コールバックは現在の `EventContext` からのみデータを取得し、コンテキストやそのフィールドを呼び出しをまたいで再利用しないでください。
+- 呼び出しをまたいで保持するデータは Host ストレージに置き、キーに installation の識別子とイベントに関連する識別子(Workspace、会話など)を含めてください。
+- installation 単位でプロセス内状態を保持する場合は、`on_installation_revoked(binding)` で解放してください。
+
+完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
+
## 次のステップ
イベントリスナーの基本情報を学びました。次は:
diff --git a/ja/plugin/dev/components/knowledge-engine.mdx b/ja/plugin/dev/components/knowledge-engine.mdx
index 7f1a9f1a4..7394f4bc2 100644
--- a/ja/plugin/dev/components/knowledge-engine.mdx
+++ b/ja/plugin/dev/components/knowledge-engine.mdx
@@ -468,6 +468,18 @@ async def get_knowledge_file_stream(self, storage_path: str) -> bytes:
file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path)
```
+## 1 つの installation、複数の用途
+
+1 つの RAG エンジンは**複数のナレッジベース**から同時に使われ、プラットフォームのストレージキー `(instance, Workspace, owner type, plugin, key)` にはナレッジベースの次元がありません:
+
+- 永続化とキャッシュのキーには**ナレッジベース ID** を含めてください。含めないと、別々のナレッジベースのベクトルやドキュメント状態が互いを上書きします。
+- ロックと「要対账」状態は `(installation, ナレッジベース)` 単位で記録してください。**1 つのナレッジベースの取り込み失敗が、同じ installation の他のナレッジベースの取り込みや検索を遮断してはいけません。**
+- 手動対账が必要な状態に入るのは、送出済みで結果不明の変更だけにしてください。決定的な検証エラーや送出前のキャンセルは後続の呼び出しに影響させないでください。
+- テレメトリや診断の失敗は業務に影響させないでください。ログや送信の失敗で取り込みや検索を中断せず、テナントデータをプロセス全体のシングルトンに書き込まないでください。
+- installation 単位でプロセス内キャッシュや状態を保持する場合は、`on_installation_revoked(binding)` で解放してください。
+
+完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
+
## RAGエンジンのテスト
作成後、プラグインディレクトリでコマンド`lbp run`を実行してデバッグを開始します。その後、LangBotで:
diff --git a/ja/plugin/dev/components/knowledge-retriever.mdx b/ja/plugin/dev/components/knowledge-retriever.mdx
index c44e0055f..a14d6373c 100644
--- a/ja/plugin/dev/components/knowledge-retriever.mdx
+++ b/ja/plugin/dev/components/knowledge-retriever.mdx
@@ -253,6 +253,18 @@ using_rerank = self.config.get('using_rerank', False)
search_mode = self.config.get('search_mode', 'embedding')
```
+## 1 つの installation、複数の用途
+
+1 つのナレッジリトリーバーは**複数のナレッジベース**から使われ、プラットフォームのストレージキー `(instance, Workspace, owner type, plugin, key)` にはナレッジベースの次元がありません:
+
+- 検索に使うキャッシュ、インデックス、中間結果は**ナレッジベース ID** で分離し、キーにナレッジベース ID を含めてください。インスタンスに置かないでください。
+- 検索のロックと「要対账」状態は `(installation, ナレッジベース)` 単位で記録してください。あるナレッジベースの検索失敗が、同じ installation の他のナレッジベースの検索を利用不能にしてはいけません。
+- `RetrievalContext` は 1 回の検索でのみ有効です。インスタンスフィールドに保存して呼び出しをまたいで再利用しないでください。
+- 呼び出しをまたいで保持するナレッジベースのデータは Host ストレージに置き、キーに installation の識別子とナレッジベース ID の両方を含めてください。
+- installation 単位で接続、インデックス、その他のプロセス内状態をキャッシュする場合は、`on_installation_revoked(binding)` で解放してください。
+
+完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
+
## リトリーバーのテスト
作成後、プラグインディレクトリでコマンド`lbp run`を実行してデバッグを開始します。その後、LangBotで:
diff --git a/ja/plugin/dev/components/page.mdx b/ja/plugin/dev/components/page.mdx
index a6ae6ba3f..21a5c8aa1 100644
--- a/ja/plugin/dev/components/page.mdx
+++ b/ja/plugin/dev/components/page.mdx
@@ -236,6 +236,17 @@ await langbot.api('/entries', { id: '...' }, 'DELETE');
完全なサンプルコードは[FAQManagerプラグイン](https://github.com/langbot-app/langbot-plugin-demo/tree/main/FAQManager)を参照してください。
+## 1 つの installation、複数の用途
+
+同じ Page は**複数のページリクエスト**から呼び出されます(同じ installation の別々のリクエスト、別々の Workspace):
+
+- 前回のリクエストのデータをインスタンスに残さないでください。エンドポイント、メソッド、body、現在の installation の情報は、毎回現在の `PageRequest` / binding から取得します。
+- ページのデータは常に installation スコープの Host ストレージに置いてください。`self.plugin` はプロセスレベルの共有オブジェクトであり、テナントデータを保存できません。
+- 別々の Workspace のリクエストは同じ Page インスタンスを共有するため、インスタンスフィールドの使用が交錯します。リクエストをまたいで保持するデータは Host ストレージに置いてください。
+- installation 単位でプロセス内キャッシュを保持する場合は、`on_installation_revoked(binding)` で解放してください。
+
+完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
+
## 注意事項
- ページは`sandbox="allow-scripts allow-forms"`のiframe内で実行され、ポップアップを開いたり親ページをナビゲートすることはできません
diff --git a/ja/plugin/dev/components/parser.mdx b/ja/plugin/dev/components/parser.mdx
index 85fa14cea..e9ec46f14 100644
--- a/ja/plugin/dev/components/parser.mdx
+++ b/ja/plugin/dev/components/parser.mdx
@@ -201,6 +201,17 @@ result = await self.plugin.invoke_parser(
# resultはtext、sections、metadataを含むdict
```
+## 1 つの installation、複数の用途
+
+1 つの Parser は**複数のナレッジベース**から使われます(同じ installation の別々のナレッジベースが異なる MIME タイプのファイルをアップロードします)。プラットフォームのストレージキーにはナレッジベースの次元がありません:
+
+- 解析キャッシュと一時ファイルは**ナレッジベース ID** で分離し、キーにナレッジベース ID を含めてください。インスタンスにキャッシュしないでください。
+- `ParseContext` と解析の中間結果は 1 回の解析でのみ有効です。インスタンスフィールドやモジュールレベルの変数に保存しないでください。
+- 解析の失敗が影響するのは現在のナレッジベースのそのアップロードだけです。他のナレッジベースの解析を利用不能にしてはいけません。
+- installation 単位でモデル、パーサー、一時ディレクトリをキャッシュする場合は、`on_installation_revoked(binding)` で解放し、一時ファイルを確実に削除できるようにしてください。
+
+完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
+
## パーサーのテスト
作成後、プラグインディレクトリでコマンド`lbp run`を実行してデバッグを開始します。その後、LangBotで:
diff --git a/ja/plugin/dev/components/runner.mdx b/ja/plugin/dev/components/runner.mdx
index 466365010..4a51360d1 100644
--- a/ja/plugin/dev/components/runner.mdx
+++ b/ja/plugin/dev/components/runner.mdx
@@ -190,6 +190,18 @@ async with ctx.reply_stream() as reply:
プラグインの購読は Agent/パイプラインのルートと独立して実行されます。同じイベントを複数のプロセッサーが処理でき、一つが失敗しても他の実行を妨げません。同じ設定を使用するボットは設定内容と実行状態を共有します。個別に設定する場合は、新しい設定を作成してください。返信などの任意の動作はコンポーネントの設定として提供し、重複返信を避けてください。
+## 1 つの installation、複数の用途
+
+1 つの Workspace でプラグインをインストールできるのは 1 回ですが、その installation は**複数のプロセッサ**(Agent / パイプライン)から再利用されます。Runner はプロセッサ間でデータが混ざらないよう自分で保証する必要があります:
+
+- `ctx.state.*` はすでにプロセッサ単位で分離されています。スコープに `binding_identity`(Agent UUID 由来)が含まれるため、同じ installation の別プロセッサが互いを上書きしません。
+- プラットフォームのストレージキーは `(instance, Workspace, owner type, plugin, key)` で、**プロセッサの次元を含みません**。したがって自分で Host ストレージやキャッシュに書くキーには、プロセッサまたは会話の識別子も含めてください。
+- `RunnerContext`(`ctx`)は 1 回の実行でのみ有効です。インスタンスに保存して呼び出しをまたいで再利用しないでください。呼び出しをまたいで保持する会話状態は `ctx.state_get()` / `ctx.state_set()` を使います。
+- 複数のプロセッサは同じ Runner インスタンスを共有するため、現在の実行のデータをインスタンスフィールドに蓄積しないでください。長期常駐の外部セッションプロセスのように、プロセス内に呼び出しをまたぐ状態を持つ必要がある Runner は、共有実行を宣言せず専用のままにしてください。
+- installation 単位でプロセス内キャッシュを保持する場合は、`on_installation_revoked(binding)` で解放してください。
+
+完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
+
## 次のステップ
- [`RunnerContext` API リファレンス](/ja/plugin/dev/apis/agent-run#runner)を確認します。
diff --git a/ja/plugin/dev/components/tool.mdx b/ja/plugin/dev/components/tool.mdx
index 8a9ab8c77..60431db70 100644
--- a/ja/plugin/dev/components/tool.mdx
+++ b/ja/plugin/dev/components/tool.mdx
@@ -169,6 +169,17 @@ httpx
+## 1 つの installation、複数の用途
+
+1 つの Tool は**複数のプロセッサと会話**から呼び出されます(1 つのイベントで複数のプラグインプロセッサが起動し得ます):
+
+- 設定は呼び出しごとに読み取ってください(`self.get_plugin_config()` または `ctx.config`)。`initialize()` で読み取ってインスタンスにキャッシュしないでください。
+- 呼び出し引数、戻り値、会話データをインスタンスフィールドやモジュールレベルの変数に残さないでください。会話の識別子は現在の呼び出しの `session` / `query_id` から取得します。
+- 同時呼び出しは同じインスタンスを共有するため、インスタンスフィールドは書き込みが交錯します。呼び出しをまたいで保持するデータは Host ストレージに置き、対応するプロセッサまたは会話の識別子をキーに含めてください。
+- installation 単位でプロセス内キャッシュを保持する場合は、`on_installation_revoked(binding)` で解放してください。
+
+完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
+
## 次のステップ
ツールの基本的な使い方を学びました。次は:
diff --git a/zh/plugin/certified-plugins.mdx b/zh/plugin/certified-plugins.mdx
index e2966d4df..7518dfd53 100644
--- a/zh/plugin/certified-plugins.mdx
+++ b/zh/plugin/certified-plugins.mdx
@@ -93,7 +93,45 @@ execution:
- 不得在 `initialize()` 中读取并缓存安装配置。
- 存储、文件、凭据、日志、模型等平台能力必须通过 SDK 的 Host API 访问;权限由当前调用自动解析,不需要也不应该自己缓存。
- 需要跨调用保留的租户数据必须存放在 Host 服务中。Runner 使用 `ctx.state_get()` / `ctx.state_set()` 等接口(需要 `ctx.context.available_apis.state` 为 `true`)。进程内缓存只允许存放制品级公共数据;如果确实要缓存租户数据,键必须包含完整安装标识,并能在安装被移除时清理。
-- 禁止发起携带租户数据的后台任务或线程。处理函数返回后,本次调用的上下文和运行关联调用即失效。
+- 不要发起携带租户数据的后台任务或线程:SDK **不跟踪**你自己创建的任务的所有权。如果确实要脱离处理函数运行,请以安装绑定为键维护一份登记表,并在 `on_installation_revoked()` 中停止它们。处理函数返回后,本次调用的上下文和运行关联调用即失效。
+
+### 一个安装,多份用法
+
+一个 workspace 内每个插件只安装一次,但同一份安装会被**多个产品对象**复用:
+
+| 组件类型 | 复用它的是 | 隔离靠什么 |
+| --- | --- | --- |
+| Agent / 流水线中的 Runner | 每个处理器 | `ctx.state.*` 已按处理器隔离(作用域含 `binding_identity`,由 Agent UUID 派生) |
+| 知识库用的 KnowledgeEngine / Parser | 每个知识库 | **需要插件自己按知识库 ID 隔离** |
+| Tool / Command / EventListener / Page | 多个处理器、会话或页面请求 | 依赖调用时读取,不能在实例上累积 |
+
+平台的插件存储键是 `(实例, 工作区, 组件类型, 插件名, key)`,**不包含处理器或知识库维度**。所以只要你的插件要持久化或缓存,就必须自己把产品对象的身份拼进键里:
+
+- **配置**:每次调用读取(`ctx.config`,或 task-local 的 `self.get_plugin_config()`),不要缓存到实例。
+- **持久化**:键必须包含产品对象身份(处理器身份 / 知识库 ID / 会话身份)。
+- **进程内缓存**:键必须同时包含完整安装绑定**和**产品对象身份,并在安装被撤销时释放(见下)。
+- **锁与栅栏**:必须按 `(安装, 产品对象)` 记。**一个处理器或知识库的失败,不得阻断同一安装下的其他对象。**
+- **失败分级**:只有"已经派发、结果未知"的变更才应进入需要人工对账的状态;确定性的校验错误和调用前的取消不应影响后续调用。
+- **日志与遥测**:诊断数据的失败不得影响业务,也不要把租户数据写进进程级单例。
+
+### 安装被撤销时
+
+共享模式下,一个进程内只有一个对象图,服务同一制品的所有安装。所以**某个安装被撤销时对象图不会销毁**,插件必须自己释放按安装绑定记的进程内状态:
+
+```python
+class MyPlugin(BasePlugin):
+ def __init__(self) -> None:
+ super().__init__()
+ self._caches: dict[str, Cache] = {}
+
+ async def on_installation_revoked(self, binding) -> None:
+ self._caches.pop(binding.installation_uuid, None)
+```
+
+- **触发时机**:卸载、停用、移除工作区,以及**升级**(旧绑定被撤销)。只有共享模式会调用;专属模式下进程会被关停,进程内状态随进程消失。
+- **调用时没有调用上下文**,Host API(存储、配置、绑定查询)不可用——只能释放进程内状态,安装身份由参数给出。
+- 抛出异常会被记录,**不影响撤销流程**。
+- 需要带 `BasePlugin.on_installation_revoked` 的 SDK 版本;旧版本上这个钩子只是永远不会被调用。
### 允许 / 禁止对照
diff --git a/zh/plugin/dev/components/command.mdx b/zh/plugin/dev/components/command.mdx
index 7857c7a32..f76a57431 100644
--- a/zh/plugin/dev/components/command.mdx
+++ b/zh/plugin/dev/components/command.mdx
@@ -207,6 +207,17 @@ yield CommandReturn(
具体的返回值请参考`CommandReturn`的定义:[langbot_plugin.api.entities.builtin.command.context.CommandReturn](https://github.com/langbot-app/langbot-plugin-sdk/tree/main/src/langbot_plugin/api/entities/builtin/command/context.py)
+## 一个安装,多份用法
+
+一个命令会被**多个会话并发触发**:
+
+- 配置每次调用读取,不要在 `initialize()` 中读取后缓存到实例。
+- 不要把命令参数、返回值或会话数据留在实例字段或模块级变量上;`ExecuteContext` 只在本次执行内有效。
+- 并发触发意味着实例字段会被交织写入,不要用它保存「当前会话」之类的状态;需要跨调用保留的数据放 Host 存储,键包含会话身份。
+- 如果按安装维护了进程内缓存,请在 `on_installation_revoked(binding)` 中释放。
+
+以上约束的完整说明见[认证插件与共享运行](/zh/plugin/certified-plugins)。
+
## 接下来做什么
您已经了解了命令注册和命令执行的基本信息,接下来可以:
diff --git a/zh/plugin/dev/components/event-listener.mdx b/zh/plugin/dev/components/event-listener.mdx
index 5e1847348..8e71419d1 100644
--- a/zh/plugin/dev/components/event-listener.mdx
+++ b/zh/plugin/dev/components/event-listener.mdx
@@ -99,6 +99,17 @@ class EventContext(pydantic.BaseModel):
事件注册通过装饰器`@self.handler`实现,装饰器参数为事件类型。可监听的事件均在`langbot_plugin.api.entities.builtin.events`中定义。
+## 一个安装,多份用法
+
+同一个事件监听器会被**多个工作区、多个事件并发调用**:
+
+- 不要在实例上按事件累积状态(计数、最近事件、会话映射等),并发回调会互相覆盖。
+- 回调只从当前 `EventContext` 取数据,不要跨调用复用上下文或其字段。
+- 需要跨调用保留的数据放 Host 存储,键包含安装标识与事件相关的身份(工作区、会话等)。
+- 如果按安装维护了进程内状态,请在 `on_installation_revoked(binding)` 中释放。
+
+以上约束的完整说明见[认证插件与共享运行](/zh/plugin/certified-plugins)。
+
## 接下来做什么
您已经了解了事件监听器的基本信息,接下来可以:
diff --git a/zh/plugin/dev/components/knowledge-engine.mdx b/zh/plugin/dev/components/knowledge-engine.mdx
index de2968ef5..8d4117693 100644
--- a/zh/plugin/dev/components/knowledge-engine.mdx
+++ b/zh/plugin/dev/components/knowledge-engine.mdx
@@ -468,6 +468,18 @@ async def get_knowledge_file_stream(self, storage_path: str) -> bytes:
file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path)
```
+## 一个安装,多份用法
+
+一个 RAG 引擎会被**多个知识库**同时选用,而平台的存储键 `(实例, 工作区, 组件类型, 插件名, key)` 不含知识库维度:
+
+- 持久化与缓存的键必须包含**知识库 ID**,否则不同知识库的向量、文档状态会互相覆盖。
+- 锁与「待对账」状态必须按 `(安装, 知识库)` 记;**一个知识库摄取失败不得阻断同一安装下其他知识库的摄取与检索**。
+- 只有「已经派发、结果未知」的变更才应进入需要人工对账的状态;确定性的校验错误与派发前的取消不应影响后续调用。
+- 遥测与诊断上报失败不得影响业务:日志/上报出错不要中断摄取或检索,也不要把租户数据写进进程级单例。
+- 如果按安装维护了进程内缓存或状态,请在 `on_installation_revoked(binding)` 中释放。
+
+以上约束的完整说明见[认证插件与共享运行](/zh/plugin/certified-plugins)。
+
## 测试 RAG 引擎
创建完成后,在插件目录执行命令 `lbp run`,启动调试。然后在 LangBot 中:
diff --git a/zh/plugin/dev/components/knowledge-retriever.mdx b/zh/plugin/dev/components/knowledge-retriever.mdx
index cc4a566db..54830fb19 100644
--- a/zh/plugin/dev/components/knowledge-retriever.mdx
+++ b/zh/plugin/dev/components/knowledge-retriever.mdx
@@ -254,6 +254,18 @@ using_rerank = self.config.get('using_rerank', False)
search_mode = self.config.get('search_mode', 'embedding')
```
+## 一个安装,多份用法
+
+一个知识检索器会被**多个知识库**选用,平台的存储键 `(实例, 工作区, 组件类型, 插件名, key)` 不含知识库维度:
+
+- 检索所需的缓存、索引或中间结果必须按**知识库 ID** 隔离,键里要带上知识库 ID,不能放到实例上。
+- 检索的锁与「待对账」状态按 `(安装, 知识库)` 记,一个知识库检索失败不得让同一安装下其他知识库的检索不可用。
+- `RetrievalContext` 只在单次检索内有效,不要把它或检索结果保存到实例字段跨调用复用。
+- 需要跨调用保留的知识库数据存到 Host 存储,键同时包含安装标识与知识库 ID。
+- 如果按安装缓存了连接、索引或其他进程内状态,请在 `on_installation_revoked(binding)` 中释放。
+
+以上约束的完整说明见[认证插件与共享运行](/zh/plugin/certified-plugins)。
+
## 测试检索器
创建完成后,在插件目录执行命令 `lbp run`,启动调试。然后在 LangBot 中:
diff --git a/zh/plugin/dev/components/page.mdx b/zh/plugin/dev/components/page.mdx
index d5b9a9ac9..d79ef3e5c 100644
--- a/zh/plugin/dev/components/page.mdx
+++ b/zh/plugin/dev/components/page.mdx
@@ -236,6 +236,17 @@ await langbot.api('/entries', { id: '...' }, 'DELETE');
完整示例代码请参考 [FAQManager 插件](https://github.com/langbot-app/langbot-plugin-demo/tree/main/FAQManager)。
+## 一个安装,多份用法
+
+同一个 Page 会被**多次页面请求**调用(同一安装下的不同请求、不同工作区):
+
+- 不要把上一次请求的数据留在实例上;端点、方法、body 与当前安装信息一律从当前 `PageRequest` / 绑定取。
+- 页面数据一律按安装作用域存放(installation-scoped Host 存储);`self.plugin` 是进程级共享对象,不能保存租户数据。
+- 不同工作区的请求会共享同一个 Page 实例,实例字段会被交织使用;需要跨请求保留的数据放 Host 存储。
+- 如果按安装维护了进程内缓存,请在 `on_installation_revoked(binding)` 中释放。
+
+以上约束的完整说明见[认证插件与共享运行](/zh/plugin/certified-plugins)。
+
## 注意事项
- 页面运行在 `sandbox="allow-scripts allow-forms"` 的 iframe 中,不能弹窗或导航父页面
diff --git a/zh/plugin/dev/components/parser.mdx b/zh/plugin/dev/components/parser.mdx
index 83f413fc8..04a449968 100644
--- a/zh/plugin/dev/components/parser.mdx
+++ b/zh/plugin/dev/components/parser.mdx
@@ -201,6 +201,17 @@ result = await self.plugin.invoke_parser(
# result 为 dict,包含 text、sections、metadata
```
+## 一个安装,多份用法
+
+一个 Parser 会被**多个知识库**选用(同一安装下不同知识库会上传不同 MIME 类型的文件),而平台的存储键不含知识库维度:
+
+- 解析缓存与临时文件必须按**知识库 ID** 隔离,键里要带上知识库 ID,不要缓存到实例上。
+- `ParseContext` 与解析中间结果只在单次解析内有效,不要保存到实例字段或模块级变量。
+- 解析失败只影响当前知识库的这次上传,不要让其他知识库的解析不可用。
+- 如果按安装缓存了模型、解析器或临时目录,请在 `on_installation_revoked(binding)` 中释放,并确保临时文件可被清理。
+
+以上约束的完整说明见[认证插件与共享运行](/zh/plugin/certified-plugins)。
+
## 测试解析器
创建完成后,在插件目录执行命令 `lbp run`,启动调试。然后在 LangBot 中:
diff --git a/zh/plugin/dev/components/runner.mdx b/zh/plugin/dev/components/runner.mdx
index f94a824ad..71b0f2c68 100644
--- a/zh/plugin/dev/components/runner.mdx
+++ b/zh/plugin/dev/components/runner.mdx
@@ -190,6 +190,18 @@ async with ctx.reply_stream() as reply:
插件处理器与 Agent/流水线路由独立执行,同一事件可以触发多个插件处理器,某个处理器失败不会阻止其他处理。使用同一配置的机器人会共享设置和运行状态;需要分别设置时,创建不同的配置。可选功能应通过组件配置提供,例如是否回复消息,避免与其他处理器重复回复。
+## 一个安装,多份用法
+
+一个工作区内每个插件只安装一次,但这份安装会被**多个处理器**(Agent / 流水线)复用。Runner 需要自己保证处理器之间不串数据:
+
+- `ctx.state.*` 已经按处理器隔离:其作用域包含 `binding_identity`(由 Agent UUID 派生),同一安装下的不同处理器不会互相覆盖。
+- 平台的插件存储键是 `(实例, 工作区, 组件类型, 插件名, key)`,**不含处理器维度**;所以你自己写进 Host 存储或缓存的键,必须额外包含处理器或会话身份。
+- `RunnerContext`(`ctx`)只在单次运行内有效,不要把它或它的字段保存到实例上跨调用复用;需要跨调用保留的会话状态用 `ctx.state_get()` / `ctx.state_set()`。
+- 多个处理器会共享同一个 Runner 实例,不要在实例字段上累积当前运行的数据;如果运行器必须依赖进程内持有的跨调用状态(例如长期驻留的外部会话进程),就不要声明共享运行,保持专属。
+- 如果按安装维护了进程内缓存,请在 `on_installation_revoked(binding)` 中释放。
+
+以上约束的完整说明见[认证插件与共享运行](/zh/plugin/certified-plugins)。
+
## 接下来做什么
- 查看 [`RunnerContext` 上下文 API](/zh/plugin/dev/apis/agent-run#runner)。
diff --git a/zh/plugin/dev/components/tool.mdx b/zh/plugin/dev/components/tool.mdx
index 3d93be191..7c031e487 100644
--- a/zh/plugin/dev/components/tool.mdx
+++ b/zh/plugin/dev/components/tool.mdx
@@ -170,6 +170,17 @@ httpx
+## 一个安装,多份用法
+
+一个 Tool 会被**多个处理器与会话**调用(同一事件可以触发多个插件处理器):
+
+- 配置每次调用读取(`self.get_plugin_config()` 或 `ctx.config`),不要在 `initialize()` 中读取后缓存到实例。
+- 不要把调用参数、返回结果或会话数据留在实例字段或模块级变量上;会话身份从当前调用的 `session` / `query_id` 取。
+- 并发调用会共享同一个实例,实例字段会被交织写入;需要跨调用保留的数据放 Host 存储,键包含对应处理器或会话身份。
+- 如果按安装维护了进程内缓存,请在 `on_installation_revoked(binding)` 中释放。
+
+以上约束的完整说明见[认证插件与共享运行](/zh/plugin/certified-plugins)。
+
## 接下来做什么
您已经了解了工具的基本用法,接下来可以: