@@ -390,6 +390,20 @@ const __dirname = dirname(__filename);
390390// Default bank name (fallback when channel context not available)
391391const DEFAULT_BANK_NAME = "openclaw" ;
392392
393+ // Default granularity fields used by deriveBankId when not explicitly configured.
394+ // This constant is shared between getPluginConfig (normalisation) and deriveBankId
395+ // (fallback) so the skip-reason check and the bank-routing logic always agree.
396+ const DEFAULT_DYNAMIC_BANK_GRANULARITY : Array < "agent" | "provider" | "channel" | "user" > = [
397+ "agent" ,
398+ "channel" ,
399+ "user" ,
400+ ] ;
401+
402+ // Throttle set: log an info-level skip message at most once per (sessionKey) per
403+ // process lifetime so operators can discover silent retention/recall skips without
404+ // flooding the log on every turn.
405+ const loggedSkipSessions = new Set < string > ( ) ;
406+
393407function getConfiguredBankId ( pluginConfig : PluginConfig ) : string | undefined {
394408 if ( typeof pluginConfig . bankId !== "string" ) {
395409 return undefined ;
@@ -794,6 +808,27 @@ function isRetryableIdentitySkipReason(reason: IdentitySkipReason | undefined):
794808 return reason ?. kind === "retryable" ;
795809}
796810
811+ /**
812+ * Log an identity-skip event at info level, throttled to once per session key
813+ * per process lifetime. This makes silent skips visible to operators without
814+ * flooding the log on every turn.
815+ */
816+ function logSkipOnce (
817+ operation : "recall" | "retain" | "dispatch" | "agent_start" ,
818+ sessionKey : string | undefined ,
819+ reason : IdentitySkipReason
820+ ) : void {
821+ if ( ! sessionKey ) return ;
822+ const cacheKey = `${ operation } :${ sessionKey } ` ;
823+ if ( loggedSkipSessions . has ( cacheKey ) ) return ;
824+ loggedSkipSessions . add ( cacheKey ) ;
825+ const hint =
826+ reason . kind === "final"
827+ ? ". If unexpected, set dynamicBankGranularity to ['agent','channel','user'] or use static banking (dynamicBankId: false + bankId: '<name>')"
828+ : "" ;
829+ log . info ( `Skipping ${ operation } on session '${ sessionKey } ': ${ reason . detail } ${ hint } ` ) ;
830+ }
831+
797832function cacheSessionIdentity (
798833 sessionKey : string | undefined ,
799834 resolvedCtx : PluginHookAgentContext | undefined
@@ -886,11 +921,13 @@ export function getIdentitySkipReason(
886921 // exist to keep the default multi-tenant bank from being polluted by CLI/main
887922 // sessions that lack a stable identity. They should NOT fire when the user has
888923 // explicitly opted into a routing scheme that expects those sessions:
889- // - dynamicBankGranularity includes 'agent' → each agent (including 'main')
890- // gets its own bank
924+ // - dynamicBankGranularity includes 'agent' (the default) → each agent
925+ // (including 'main') gets its own bank
891926 // - dynamicBankId === false with a configured bankId → user pinned a single
892927 // named bank and wants every session retained into it
893- const agentBanking = pluginConfig ?. dynamicBankGranularity ?. includes ( "agent" ) ?? false ;
928+ // When dynamicBankGranularity is unset, the default is ["agent","channel","user"]
929+ // which includes "agent", so agentBanking defaults to true to match deriveBankId.
930+ const agentBanking = pluginConfig ?. dynamicBankGranularity ?. includes ( "agent" ) ?? true ;
894931 const staticBanking =
895932 pluginConfig ?. dynamicBankId === false &&
896933 typeof pluginConfig ?. bankId === "string" &&
@@ -994,7 +1031,7 @@ export function deriveBankId(
9941031 const resolvedCtx = resolveSessionIdentity ( ctx ) ;
9951032 const fields = pluginConfig . dynamicBankGranularity ?. length
9961033 ? pluginConfig . dynamicBankGranularity
997- : [ "agent" , "channel" , "user" ] ;
1034+ : DEFAULT_DYNAMIC_BANK_GRANULARITY ;
9981035
9991036 // Validate field names at runtime — typos silently produce 'unknown' segments
10001037 const validFields = new Set ( [ "agent" , "channel" , "user" , "provider" ] ) ;
@@ -1321,7 +1358,7 @@ function getPluginConfig(api: MoltbotPluginAPI): PluginConfig {
13211358 autoRecall : config . autoRecall !== false , // Default: true (on) — backward compatible
13221359 dynamicBankGranularity : Array . isArray ( config . dynamicBankGranularity )
13231360 ? config . dynamicBankGranularity
1324- : undefined ,
1361+ : DEFAULT_DYNAMIC_BANK_GRANULARITY ,
13251362 autoRetain : config . autoRetain !== false , // Default: true
13261363 retainRoles : Array . isArray ( config . retainRoles ) ? config . retainRoles : undefined ,
13271364 retainFormat : config . retainFormat === "text" ? "text" : "json" ,
@@ -1802,6 +1839,7 @@ export default function (api: MoltbotPluginAPI) {
18021839 debug (
18031840 `[Hindsight] before_dispatch marked session ${ sessionKey } to skip this turn: ${ formatIdentitySkipReason ( skipReason ) } `
18041841 ) ;
1842+ logSkipOnce ( "dispatch" , sessionKey , skipReason ) ;
18051843 return ;
18061844 }
18071845 if ( ! resolvedCtx ?. senderId || typeof resolvedCtx . senderId !== "string" ) {
@@ -1830,6 +1868,7 @@ export default function (api: MoltbotPluginAPI) {
18301868 debug (
18311869 `[Hindsight] before_agent_start skipping session ${ sessionKey } : ${ formatIdentitySkipReason ( skipReason ) } `
18321870 ) ;
1871+ logSkipOnce ( "agent_start" , sessionKey , skipReason ) ;
18331872 return ;
18341873 }
18351874 if ( ! resolvedCtx ) {
@@ -1895,6 +1934,7 @@ export default function (api: MoltbotPluginAPI) {
18951934 debug (
18961935 `[Hindsight] Skipping recall for session ${ sessionKeyForCache } : ${ formatIdentitySkipReason ( skipTurnReason ) } `
18971936 ) ;
1937+ logSkipOnce ( "recall" , sessionKeyForCache , skipTurnReason ) ;
18981938 return ;
18991939 }
19001940
@@ -1912,6 +1952,7 @@ export default function (api: MoltbotPluginAPI) {
19121952 debug (
19131953 `[Hindsight] Skipping recall for session ${ sessionKeyForCache } : ${ formatIdentitySkipReason ( identitySkipReason ) } `
19141954 ) ;
1955+ logSkipOnce ( "recall" , sessionKeyForCache , identitySkipReason ) ;
19151956 return ;
19161957 }
19171958
@@ -2124,6 +2165,7 @@ ${memoriesFormatted}
21242165 debug (
21252166 `[Hindsight Hook] Skipping retain for session ${ sessionKeyForLookup } : ${ formatIdentitySkipReason ( skipTurnReason ) } `
21262167 ) ;
2168+ logSkipOnce ( "retain" , sessionKeyForLookup , skipTurnReason ) ;
21272169 if ( sessionKeyForLookup ) {
21282170 skipHindsightTurnBySession . delete ( sessionKeyForLookup ) ;
21292171 }
@@ -2144,6 +2186,7 @@ ${memoriesFormatted}
21442186 debug (
21452187 `[Hindsight Hook] Skipping retain for session ${ sessionKeyForLookup } : ${ formatIdentitySkipReason ( identitySkipReason ) } `
21462188 ) ;
2189+ logSkipOnce ( "retain" , sessionKeyForLookup , identitySkipReason ) ;
21472190 if ( sessionKeyForLookup ) {
21482191 skipHindsightTurnBySession . delete ( sessionKeyForLookup ) ;
21492192 }
0 commit comments