@@ -272,5 +272,148 @@ export default definePluginEntry({
272272 parameters : Type . Object ( { request_id : Type . String ( ) } ) ,
273273 run : ( client , input ) => client . getRecallTrace ( input . request_id ) ,
274274 } ) ;
275+
276+ // ------------------------------------------------------------------
277+ // Memory-host hooks (fix for #279).
278+ //
279+ // The plugin registers tools above, but without participating in the
280+ // OpenClaw memory-host lifecycle, agents only invoke OB1 if they
281+ // remember to. We register two additive memory hooks so OB1 lands
282+ // in every turn automatically:
283+ //
284+ // 1. registerMemoryPromptSupplement — auto-injects an OB1 discipline
285+ // block into the system prompt (recall-before / writeback-after
286+ // reminder, instruction-vs-evidence semantics, tool list).
287+ // 2. registerMemoryCorpusSupplement — exposes OB1 as a searchable
288+ // corpus so OpenClaw's native recall flow queries OB1 alongside
289+ // whatever other corpora are active.
290+ //
291+ // Both are additive (per the SDK contract), coexist with the active
292+ // exclusive memory plugin, and no-op cleanly when OB1 isn't configured.
293+ // ------------------------------------------------------------------
294+
295+ const OB1_TOOL_NAMES = [
296+ "openbrain_recall" ,
297+ "openbrain_writeback" ,
298+ "openbrain_report_usage" ,
299+ "openbrain_inspect_memory" ,
300+ "openbrain_list_review_queue" ,
301+ "openbrain_review_memory" ,
302+ "openbrain_get_recall_trace" ,
303+ ] as const ;
304+
305+ function isConfigured ( ) : boolean {
306+ const raw = ( ( api as any ) . pluginConfig || { } ) as Record < string , unknown > ;
307+ return typeof raw . endpoint === "string"
308+ && raw . endpoint . length > 0
309+ && typeof raw . workspaceId === "string"
310+ && raw . workspaceId . length > 0 ;
311+ }
312+
313+ if ( typeof ( api as any ) . registerMemoryPromptSupplement === "function" ) {
314+ ( api as any ) . registerMemoryPromptSupplement ( ( params : { availableTools : Set < string > } ) => {
315+ if ( ! isConfigured ( ) ) return [ ] ;
316+ const present = OB1_TOOL_NAMES . filter ( ( t ) => params . availableTools . has ( t ) ) ;
317+ if ( present . length === 0 ) return [ ] ;
318+ return [
319+ "## OB1 Agent Memory" ,
320+ "Long-term governed memory is available via OB1. Use it as a discipline, not a fallback." ,
321+ "" ,
322+ "Workflow:" ,
323+ "- Before meaningful work, call `openbrain_recall` with a task-scoped query." ,
324+ "- Treat returned memories tagged `instruction` as binding rules; `evidence`-tagged ones as supporting context only." ,
325+ "- After meaningful work, call `openbrain_writeback` with compact, provenance-labeled findings (decisions, lessons, constraints, outputs, failures)." ,
326+ "- After acting on recalled memories, call `openbrain_report_usage` with `request_id` and the IDs you used vs. ignored — closes the recall-quality loop." ,
327+ "" ,
328+ `Available tools: ${ present . map ( ( t ) => "`" + t + "`" ) . join ( ", " ) } .` ,
329+ ] ;
330+ } ) ;
331+ }
332+
333+ if ( typeof ( api as any ) . registerMemoryCorpusSupplement === "function" ) {
334+ ( api as any ) . registerMemoryCorpusSupplement ( {
335+ async search ( input : { query : string ; maxResults ?: number ; agentSessionKey ?: string } ) {
336+ if ( ! isConfigured ( ) ) return [ ] ;
337+ let client : AgentMemoryClient ;
338+ try {
339+ client = await clientFromApi ( api ) ;
340+ } catch {
341+ return [ ] ;
342+ }
343+ const limit = Math . min ( Math . max ( input . maxResults ?? 10 , 1 ) , 50 ) ;
344+ let response : any ;
345+ try {
346+ response = await client . recall ( {
347+ query : input . query . slice ( 0 , 2000 ) ,
348+ task_type : "general" ,
349+ limits : { max_items : limit , max_tokens : 4000 } ,
350+ scope : { project_only : false , include_unconfirmed : false , include_stale : false } ,
351+ } ) ;
352+ } catch {
353+ return [ ] ;
354+ }
355+ const memories : any [ ] = Array . isArray ( response ?. memories ) ? response . memories : [ ] ;
356+ return memories . map ( ( m , i ) => {
357+ const policy = m ?. use_policy ?? { } ;
358+ const provenance = policy ?. can_use_as_instruction
359+ ? "instruction"
360+ : policy ?. can_use_as_evidence
361+ ? "evidence"
362+ : undefined ;
363+ return {
364+ corpus : "openbrain" ,
365+ path : `openbrain://memory/${ m ?. id ?? i } ` ,
366+ title : typeof m ?. summary === "string" ? m . summary . slice ( 0 , 80 ) : undefined ,
367+ kind : "memory" ,
368+ score : typeof m ?. score === "number" ? m . score : Math . max ( 0 , 1 - i / Math . max ( memories . length , 1 ) ) ,
369+ snippet : String ( m ?. summary ?? m ?. content ?? "" ) . slice ( 0 , 600 ) ,
370+ id : typeof m ?. id === "string" ? m . id : undefined ,
371+ provenanceLabel : provenance ,
372+ source : "openbrain.agent_memory" ,
373+ sourceType : "openbrain.agent_memory" ,
374+ updatedAt : typeof m ?. updated_at === "string" ? m . updated_at : undefined ,
375+ } ;
376+ } ) ;
377+ } ,
378+ async get ( input : { lookup : string } ) {
379+ if ( ! isConfigured ( ) ) return null ;
380+ const id = input . lookup . replace ( / ^ o p e n b r a i n : \/ \/ m e m o r y \/ / , "" ) ;
381+ if ( ! id ) return null ;
382+ let client : AgentMemoryClient ;
383+ try {
384+ client = await clientFromApi ( api ) ;
385+ } catch {
386+ return null ;
387+ }
388+ let memory : any ;
389+ try {
390+ memory = await client . inspectMemory ( id ) ;
391+ } catch {
392+ return null ;
393+ }
394+ if ( ! memory || typeof memory !== "object" ) return null ;
395+ const policy = ( memory as any ) ?. use_policy ?? { } ;
396+ const provenance = policy ?. can_use_as_instruction
397+ ? "instruction"
398+ : policy ?. can_use_as_evidence
399+ ? "evidence"
400+ : undefined ;
401+ const content = String ( ( memory as any ) ?. content ?? ( memory as any ) ?. summary ?? "" ) ;
402+ return {
403+ corpus : "openbrain" ,
404+ path : input . lookup ,
405+ title : typeof ( memory as any ) ?. summary === "string" ? ( memory as any ) . summary . slice ( 0 , 80 ) : undefined ,
406+ kind : "memory" ,
407+ content,
408+ fromLine : 1 ,
409+ lineCount : content . split ( "\n" ) . length ,
410+ id : typeof ( memory as any ) ?. id === "string" ? ( memory as any ) . id : id ,
411+ provenanceLabel : provenance ,
412+ sourceType : "openbrain.agent_memory" ,
413+ updatedAt : typeof ( memory as any ) ?. updated_at === "string" ? ( memory as any ) . updated_at : undefined ,
414+ } ;
415+ } ,
416+ } ) ;
417+ }
275418 } ,
276419} ) ;
0 commit comments