@@ -2,7 +2,10 @@ import { beforeEach, describe, expect, it, jest } from "bun:test"
22import type { AgentMessage } from "@mariozechner/pi-agent-core"
33import type { BmClient } from "../bm-client.ts"
44import type { BasicMemoryConfig } from "../config.ts"
5- import { BasicMemoryContextEngine } from "./basic-memory-context-engine.ts"
5+ import {
6+ BasicMemoryContextEngine ,
7+ MAX_ASSEMBLE_RECALL_CHARS ,
8+ } from "./basic-memory-context-engine.ts"
69
710function makeConfig (
811 overrides ?: Partial < BasicMemoryConfig > ,
@@ -102,6 +105,31 @@ describe("BasicMemoryContextEngine", () => {
102105 } )
103106 } )
104107
108+ it ( "injects bounded BM recall during assemble when bootstrap found context" , async ( ) => {
109+ const engine = new BasicMemoryContextEngine (
110+ mockClient as unknown as BmClient ,
111+ makeConfig ( ) ,
112+ )
113+
114+ await engine . bootstrap ( {
115+ sessionId : "session-assemble" ,
116+ sessionFile : "/tmp/session-assemble.jsonl" ,
117+ } )
118+
119+ const result = await engine . assemble ( {
120+ sessionId : "session-assemble" ,
121+ messages : makeMessages ( [ { role : "user" , content : "hello" } ] ) ,
122+ } )
123+
124+ expect ( result . messages ) . toEqual (
125+ makeMessages ( [ { role : "user" , content : "hello" } ] ) ,
126+ )
127+ expect ( result . systemPromptAddition ) . toContain ( "## Active Tasks" )
128+ expect ( result . systemPromptAddition ) . toContain ( "Fix auth rollout" )
129+ expect ( result . systemPromptAddition ) . toContain ( "## Recent Activity" )
130+ expect ( result . systemPromptAddition ) . toContain ( "API review" )
131+ } )
132+
105133 it ( "returns a no-op bootstrap result when there is no recall context" , async ( ) => {
106134 mockClient . search . mockResolvedValue ( [ ] )
107135 mockClient . recentActivity . mockResolvedValue ( [ ] )
@@ -120,6 +148,63 @@ describe("BasicMemoryContextEngine", () => {
120148 bootstrapped : false ,
121149 reason : "no recall context found" ,
122150 } )
151+
152+ const result = await engine . assemble ( {
153+ sessionId : "session-3" ,
154+ messages : makeMessages ( [ { role : "user" , content : "hello" } ] ) ,
155+ } )
156+
157+ expect ( result ) . toEqual ( {
158+ messages : makeMessages ( [ { role : "user" , content : "hello" } ] ) ,
159+ estimatedTokens : 0 ,
160+ } )
161+ } )
162+
163+ it ( "keeps assemble recall stable and within the hard bound" , async ( ) => {
164+ mockClient . search . mockResolvedValue ( [
165+ {
166+ title : "Long task" ,
167+ permalink : "long-task" ,
168+ content : "A" . repeat ( 4000 ) ,
169+ file_path : "memory/tasks/long-task.md" ,
170+ } ,
171+ ] )
172+ mockClient . recentActivity . mockResolvedValue ( [
173+ {
174+ title : "Long recent item" ,
175+ permalink : "long-recent-item" ,
176+ file_path : "memory/long-recent-item.md" ,
177+ created_at : "2026-03-09T12:00:00Z" ,
178+ } ,
179+ ] )
180+
181+ const engine = new BasicMemoryContextEngine (
182+ mockClient as unknown as BmClient ,
183+ makeConfig ( {
184+ recallPrompt : "P" . repeat ( 4000 ) ,
185+ } ) ,
186+ )
187+
188+ await engine . bootstrap ( {
189+ sessionId : "session-bounded" ,
190+ sessionFile : "/tmp/session-bounded.jsonl" ,
191+ } )
192+
193+ const first = await engine . assemble ( {
194+ sessionId : "session-bounded" ,
195+ messages : makeMessages ( [ { role : "user" , content : "hello" } ] ) ,
196+ } )
197+ const second = await engine . assemble ( {
198+ sessionId : "session-bounded" ,
199+ messages : makeMessages ( [ { role : "user" , content : "hello" } ] ) ,
200+ } )
201+
202+ expect ( first . systemPromptAddition ) . toBeDefined ( )
203+ expect ( first . systemPromptAddition ?. length ) . toBeLessThanOrEqual (
204+ MAX_ASSEMBLE_RECALL_CHARS ,
205+ )
206+ expect ( first . systemPromptAddition ) . toContain ( "[Basic Memory recall truncated]" )
207+ expect ( second . systemPromptAddition ) . toBe ( first . systemPromptAddition )
123208 } )
124209
125210 it ( "captures only the current turn after prePromptMessageCount" , async ( ) => {
0 commit comments