feat(openclaw): support bankId for static banks (#910)
* feat(openclaw): support exact static bank ids * test(openclaw): use generic static bank id example * feat(openclaw): support bankId static bank configuration --------- Co-authored-by: Aldous the Orchestrator <Aldoustheorchestrator@users.noreply.github.com>
This commit is contained in:
parent
8a2388a48f
commit
0e81d1a25e
6 changed files with 63 additions and 13 deletions
|
|
@ -96,7 +96,8 @@ Optional settings in `~/.openclaw/openclaw.json`:
|
|||
- `embedVersion` - hindsight-embed version (default: `"latest"`)
|
||||
- `bankMission` - Agent identity/purpose stored on the memory bank. Helps the memory engine understand context for better fact extraction during retain. Set once per bank on first use — not a recall prompt.
|
||||
- `dynamicBankId` - Enable per-context memory banks (default: `true`)
|
||||
- `bankIdPrefix` - Optional prefix for bank IDs (e.g. `"prod"` → `"prod-slack-C123"`)
|
||||
- `bankId` - Static bank ID used when `dynamicBankId` is `false`. Can also be set with `HINDSIGHT_BANK_ID`.
|
||||
- `bankIdPrefix` - Optional prefix for bank IDs (e.g. `"prod"` → `"prod-slack-C123"` or `"prod-shared-bank"`)
|
||||
- `dynamicBankGranularity` - Fields used to derive bank ID: `agent`, `channel`, `user`, `provider` (default: `["agent", "channel", "user"]`)
|
||||
- `excludeProviders` - Message providers to skip for recall/retain (e.g. `["slack"]`, `["telegram"]`, `["discord"]`)
|
||||
- `autoRecall` - Auto-inject memories before each turn (default: `true`). Set to `false` when the agent has its own recall tool.
|
||||
|
|
@ -144,7 +145,7 @@ Available isolation fields:
|
|||
- `user` - The user interacting with the agent
|
||||
- `provider` - The message provider (e.g. Slack, Discord)
|
||||
|
||||
Use `bankIdPrefix` to namespace bank IDs across environments (e.g. `"prod"`, `"staging"`). Set `dynamicBankId` to `false` to use a single shared bank for all conversations.
|
||||
Use `bankIdPrefix` to namespace bank IDs across environments (e.g. `"prod"`, `"staging"`). Set `dynamicBankId` to `false` to use a single shared bank for all conversations. In static mode, the plugin uses `bankId`, then `HINDSIGHT_BANK_ID`, then the default `openclaw` bank name.
|
||||
|
||||
### Retention Controls
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ Optional settings in `~/.openclaw/openclaw.json` under `plugins.entries.hindsigh
|
|||
| `llmModel` | provider default | LLM model override used with `llmProvider` |
|
||||
| `llmApiKeyEnv` | provider standard env var | Custom env var name for the provider API key |
|
||||
| `dynamicBankId` | `true` | Enable per-context memory banks |
|
||||
| `bankId` | — | Static bank ID used when `dynamicBankId` is `false`. Can also be set with `HINDSIGHT_BANK_ID`. |
|
||||
| `bankIdPrefix` | — | Prefix for bank IDs (e.g. `"prod"`) |
|
||||
| `dynamicBankGranularity` | `["agent", "channel", "user"]` | Fields used to derive bank ID. Options: `agent`, `channel`, `user`, `provider` |
|
||||
| `excludeProviders` | `[]` | Message providers to skip for recall/retain (e.g. `slack`, `telegram`, `discord`) |
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@
|
|||
"description": "Enable per-user memory banks. When true, memories are isolated by user per channel (e.g., slack-U123, telegram-456789). When false, all users share a single 'openclaw' bank.",
|
||||
"default": true
|
||||
},
|
||||
"bankId": {
|
||||
"type": "string",
|
||||
"description": "Static bank ID used when dynamicBankId is false. Can also be provided via HINDSIGHT_BANK_ID."
|
||||
},
|
||||
"bankIdPrefix": {
|
||||
"type": "string",
|
||||
"description": "Optional prefix for bank IDs (e.g., 'prod' results in 'prod-slack-U123'). Useful for separating environments."
|
||||
|
|
@ -282,6 +286,10 @@
|
|||
"label": "Dynamic Bank IDs",
|
||||
"placeholder": "true (isolate memories per channel)"
|
||||
},
|
||||
"bankId": {
|
||||
"label": "Static Bank ID",
|
||||
"placeholder": "e.g. openclaw, shared-bank (used when dynamicBankId is false)"
|
||||
},
|
||||
"bankIdPrefix": {
|
||||
"label": "Bank ID Prefix",
|
||||
"placeholder": "e.g., prod, staging (optional)"
|
||||
|
|
|
|||
|
|
@ -91,6 +91,17 @@ describe('deriveBankId', () => {
|
|||
expect(bankId).toBe('openclaw');
|
||||
});
|
||||
|
||||
it('should return configured bankId when dynamicBankId is false', () => {
|
||||
const config: PluginConfig = {
|
||||
dynamicBankId: false,
|
||||
bankId: 'shared-bank',
|
||||
bankIdPrefix: 'prod',
|
||||
dynamicBankGranularity: ['provider', 'user'],
|
||||
};
|
||||
const bankId = deriveBankId(ctx, config);
|
||||
expect(bankId).toBe('prod-shared-bank');
|
||||
});
|
||||
|
||||
it('should encode segments to prevent separator collisions', () => {
|
||||
const ctxWithSeparator: PluginHookAgentContext = {
|
||||
agentId: 'a::b',
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ async function lazyReinit(): Promise<void> {
|
|||
const defaultBankId = deriveBankId(undefined, config);
|
||||
client.setBankId(defaultBankId);
|
||||
|
||||
if (config.bankMission && !config.dynamicBankId) {
|
||||
if (config.bankMission && usesStaticBank(config)) {
|
||||
await client.setBankMission(config.bankMission);
|
||||
}
|
||||
|
||||
|
|
@ -222,7 +222,7 @@ if (typeof global !== 'undefined') {
|
|||
getClientForContext: async (ctx: PluginHookAgentContext | undefined) => {
|
||||
if (!client) {return null;}
|
||||
const config = currentPluginConfig || {};
|
||||
if (config.dynamicBankId === false) {
|
||||
if (usesStaticBank(config)) {
|
||||
return client;
|
||||
}
|
||||
const bankId = deriveBankId(ctx, config);
|
||||
|
|
@ -244,7 +244,7 @@ if (typeof global !== 'undefined') {
|
|||
}
|
||||
|
||||
// Set bank mission on first use of this bank (if configured)
|
||||
if (config.bankMission && config.dynamicBankId && !banksWithMissionSet.has(bankId)) {
|
||||
if (config.bankMission && !usesStaticBank(config) && !banksWithMissionSet.has(bankId)) {
|
||||
try {
|
||||
await bankClient.setBankMission(config.bankMission);
|
||||
banksWithMissionSet.add(bankId);
|
||||
|
|
@ -268,6 +268,29 @@ const __dirname = dirname(__filename);
|
|||
// Default bank name (fallback when channel context not available)
|
||||
const DEFAULT_BANK_NAME = 'openclaw';
|
||||
|
||||
function getConfiguredBankId(pluginConfig: PluginConfig): string | undefined {
|
||||
if (typeof pluginConfig.bankId !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const trimmed = pluginConfig.bankId.trim();
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function usesStaticBank(pluginConfig: PluginConfig): boolean {
|
||||
return pluginConfig.dynamicBankId === false;
|
||||
}
|
||||
|
||||
function getDefaultBankId(pluginConfig: PluginConfig): string {
|
||||
return pluginConfig.bankIdPrefix ? `${pluginConfig.bankIdPrefix}-${DEFAULT_BANK_NAME}` : DEFAULT_BANK_NAME;
|
||||
}
|
||||
|
||||
function getStaticBankId(pluginConfig: PluginConfig): string {
|
||||
const configuredBankId = getConfiguredBankId(pluginConfig);
|
||||
const baseBankId = configuredBankId || DEFAULT_BANK_NAME;
|
||||
return pluginConfig.bankIdPrefix ? `${pluginConfig.bankIdPrefix}-${baseBankId}` : baseBankId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip plugin-injected memory tags from content to prevent retain feedback loop.
|
||||
* Removes <hindsight_memories> and <relevant_memories> blocks that were injected
|
||||
|
|
@ -515,12 +538,12 @@ function parseSessionKey(sessionKey: string): { agentId?: string; provider?: str
|
|||
|
||||
export function deriveBankId(ctx: PluginHookAgentContext | undefined, pluginConfig: PluginConfig): string {
|
||||
if (pluginConfig.dynamicBankId === false) {
|
||||
return pluginConfig.bankIdPrefix ? `${pluginConfig.bankIdPrefix}-openclaw` : 'openclaw';
|
||||
return getStaticBankId(pluginConfig);
|
||||
}
|
||||
|
||||
// When no context is available, fall back to the static default bank.
|
||||
if (!ctx) {
|
||||
return pluginConfig.bankIdPrefix ? `${pluginConfig.bankIdPrefix}-openclaw` : 'openclaw';
|
||||
return getDefaultBankId(pluginConfig);
|
||||
}
|
||||
|
||||
const fields = pluginConfig.dynamicBankGranularity?.length ? pluginConfig.dynamicBankGranularity : ['agent', 'channel', 'user'];
|
||||
|
|
@ -768,6 +791,9 @@ async function checkExternalApiHealth(apiUrl: string, apiToken?: string | null):
|
|||
function getPluginConfig(api: MoltbotPluginAPI): PluginConfig {
|
||||
const config = api.config.plugins?.entries?.['hindsight-openclaw']?.config || {};
|
||||
const defaultMission = 'You are an AI assistant helping users across multiple communication channels (Telegram, Slack, Discord, etc.). Remember user preferences, instructions, and important context from conversations to provide personalized assistance.';
|
||||
const envBankId = typeof process.env.HINDSIGHT_BANK_ID === 'string' && process.env.HINDSIGHT_BANK_ID.trim().length > 0
|
||||
? process.env.HINDSIGHT_BANK_ID.trim()
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
bankMission: config.bankMission || defaultMission,
|
||||
|
|
@ -783,6 +809,7 @@ function getPluginConfig(api: MoltbotPluginAPI): PluginConfig {
|
|||
apiPort: config.apiPort || 9077,
|
||||
// Dynamic bank ID options (default: enabled)
|
||||
dynamicBankId: config.dynamicBankId !== false,
|
||||
bankId: envBankId || (typeof config.bankId === 'string' && config.bankId.trim().length > 0 ? config.bankId.trim() : undefined),
|
||||
bankIdPrefix: config.bankIdPrefix,
|
||||
excludeProviders: Array.isArray(config.excludeProviders) ? config.excludeProviders : [],
|
||||
autoRecall: config.autoRecall !== false, // Default: true (on) — backward compatible
|
||||
|
|
@ -855,12 +882,13 @@ export default function (api: MoltbotPluginAPI) {
|
|||
debug(`[Hindsight] Custom bank mission configured: "${pluginConfig.bankMission.substring(0, 50)}..."`);
|
||||
}
|
||||
|
||||
// Log dynamic bank ID mode
|
||||
// Log bank ID mode
|
||||
if (pluginConfig.dynamicBankId) {
|
||||
const prefixInfo = pluginConfig.bankIdPrefix ? ` (prefix: ${pluginConfig.bankIdPrefix})` : '';
|
||||
debug(`[Hindsight] ✓ Dynamic bank IDs enabled${prefixInfo} - each channel gets isolated memory`);
|
||||
} else {
|
||||
debug(`[Hindsight] Dynamic bank IDs disabled - using static bank: ${DEFAULT_BANK_NAME}`);
|
||||
const sourceInfo = getConfiguredBankId(pluginConfig) ? 'configured' : 'default';
|
||||
debug(`[Hindsight] Dynamic bank IDs disabled - using ${sourceInfo} static bank: ${getStaticBankId(pluginConfig)}`);
|
||||
}
|
||||
|
||||
// Detect external API mode
|
||||
|
|
@ -933,7 +961,7 @@ export default function (api: MoltbotPluginAPI) {
|
|||
|
||||
// Note: Bank mission will be set per-bank when dynamic bank IDs are enabled
|
||||
// For now, set it on the default bank
|
||||
if (pluginConfig.bankMission && !pluginConfig.dynamicBankId) {
|
||||
if (pluginConfig.bankMission && usesStaticBank(pluginConfig)) {
|
||||
debug(`[Hindsight] Setting bank mission...`);
|
||||
await client.setBankMission(pluginConfig.bankMission);
|
||||
}
|
||||
|
|
@ -978,7 +1006,7 @@ export default function (api: MoltbotPluginAPI) {
|
|||
|
||||
// Note: Bank mission will be set per-bank when dynamic bank IDs are enabled
|
||||
// For now, set it on the default bank
|
||||
if (pluginConfig.bankMission && !pluginConfig.dynamicBankId) {
|
||||
if (pluginConfig.bankMission && usesStaticBank(pluginConfig)) {
|
||||
debug(`[Hindsight] Setting bank mission...`);
|
||||
await client.setBankMission(pluginConfig.bankMission);
|
||||
}
|
||||
|
|
@ -1070,7 +1098,7 @@ export default function (api: MoltbotPluginAPI) {
|
|||
const defaultBankId = deriveBankId(undefined, reinitPluginConfig);
|
||||
client.setBankId(defaultBankId);
|
||||
|
||||
if (reinitPluginConfig.bankMission && !reinitPluginConfig.dynamicBankId) {
|
||||
if (reinitPluginConfig.bankMission && usesStaticBank(reinitPluginConfig)) {
|
||||
await client.setBankMission(reinitPluginConfig.bankMission);
|
||||
}
|
||||
|
||||
|
|
@ -1098,7 +1126,7 @@ export default function (api: MoltbotPluginAPI) {
|
|||
const defaultBankId = deriveBankId(undefined, reinitPluginConfig);
|
||||
client.setBankId(defaultBankId);
|
||||
|
||||
if (reinitPluginConfig.bankMission && !reinitPluginConfig.dynamicBankId) {
|
||||
if (reinitPluginConfig.bankMission && usesStaticBank(reinitPluginConfig)) {
|
||||
await client.setBankMission(reinitPluginConfig.bankMission);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ export interface PluginConfig {
|
|||
hindsightApiUrl?: string; // External Hindsight API URL (skips local daemon when set)
|
||||
hindsightApiToken?: string; // API token for external Hindsight API authentication
|
||||
dynamicBankId?: boolean; // Enable per-channel memory banks (default: true)
|
||||
bankId?: string; // Static bank ID used when dynamicBankId is false. Can also be set via HINDSIGHT_BANK_ID.
|
||||
bankIdPrefix?: string; // Prefix for bank IDs (e.g. 'prod' -> 'prod-slack-C123')
|
||||
excludeProviders?: string[]; // Message providers to exclude from recall/retain (e.g. ['telegram', 'discord'])
|
||||
autoRecall?: boolean; // Auto-recall memories on every prompt (default: true). Set to false when agent has its own recall tool.
|
||||
|
|
|
|||
Loading…
Reference in a new issue