Skip to content

Commit e488d9a

Browse files
committed
improvement(search): share one character budget across the documents of a scaled read call
1 parent 9ff3c55 commit e488d9a

4 files changed

Lines changed: 53 additions & 17 deletions

File tree

‎apps/sim/lib/knowledge/mcp/server.protocol.test.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,20 @@ describe('Search MCP protocol', () => {
100100
expect(read.isError).not.toBe(true)
101101
expect(mocks.liveRead.mock.lastCall?.[0].input).toMatchObject({
102102
limit: 8,
103-
scaleWindowWithLimit: true,
103+
scaledWindow: { documentsInCall: 1 },
104104
})
105105
expect(read.content).toEqual([
106106
{ type: 'text', text: expect.stringContaining('Read evidence') },
107107
])
108+
const batchRead = await client.callTool({
109+
name: 'read_document',
110+
arguments: { documentIds: [documentId, `${documentId}b`], limit: 8 },
111+
})
112+
expect(batchRead.isError).not.toBe(true)
113+
expect(mocks.liveRead.mock.lastCall?.[0].input).toMatchObject({
114+
limit: 8,
115+
scaledWindow: { documentsInCall: 2 },
116+
})
108117
const offsetless = await client.callTool({
109118
name: 'search',
110119
arguments: {

‎apps/sim/lib/knowledge/mcp/server.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ export function createKnowledgeMcpServer(context: KnowledgeMcpContext): McpServe
298298
...options,
299299
documentId: id,
300300
organizationId,
301-
scaleWindowWithLimit: true,
301+
scaledWindow: { documentsInCall: documentId ? 1 : batch.length },
302302
resultSecretRegistry: registry,
303303
signal,
304304
},

‎apps/sim/lib/sim-search/live/application.test.ts‎

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,25 +1210,37 @@ describe('authorized live retrieval', () => {
12101210
return { length: chunk.content.length, next: read.next }
12111211
}
12121212

1213+
const scaled = (documentsInCall: number) => ({ scaledWindow: { documentsInCall } })
1214+
12131215
it('returns a proportionally longer window when a scaling caller asks for more chunks', async () => {
1214-
const { length, next } = await readWindow({ limit: 8, scaleWindowWithLimit: true })
1216+
const { length, next } = await readWindow({ limit: 8, ...scaled(1) })
12151217
expect(length).toBe(21_333)
12161218
expect(next).toEqual({ startChunkIndex: 0, startOffset: 21_333 })
12171219
})
12181220

12191221
it('continues a longer window from where the previous one ended', async () => {
1220-
const { length, next } = await readWindow({
1221-
limit: 8,
1222-
scaleWindowWithLimit: true,
1223-
startOffset: 21_333,
1224-
})
1222+
const { length, next } = await readWindow({ limit: 8, ...scaled(1), startOffset: 21_333 })
12251223
expect(length).toBe(30_000 - 21_333)
12261224
expect(next).toBeNull()
12271225
})
12281226

12291227
it.each([
1230-
['at the default limit', { limit: 3, scaleWindowWithLimit: true }],
1231-
['below the default limit', { limit: 1, scaleWindowWithLimit: true }],
1228+
[5, 1, 13_333],
1229+
[8, 2, 20_000],
1230+
[8, 3, 13_333],
1231+
[8, 4, 10_000],
1232+
])(
1233+
'shares one call budget: limit %i across %i documents reads %i characters each',
1234+
async (limit, documentsInCall, expected) => {
1235+
expect((await readWindow({ limit, ...scaled(documentsInCall) })).length).toBe(expected)
1236+
}
1237+
)
1238+
1239+
it.each([
1240+
['at the default limit', { limit: 3, ...scaled(1) }],
1241+
['below the default limit', { limit: 1, ...scaled(1) }],
1242+
['when the call budget is spread over five documents', { limit: 8, ...scaled(5) }],
1243+
['when the call budget is spread over ten documents', { limit: 8, ...scaled(10) }],
12321244
['for a caller that does not scale', { limit: 8 }],
12331245
])('keeps the standard window %s', async (_, extra) => {
12341246
expect((await readWindow(extra)).length).toBe(8_000)

‎apps/sim/lib/sim-search/live/application.ts‎

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -291,15 +291,27 @@ const PREVIEW_CHARACTERS = 1800
291291
const READ_WINDOW_CHARACTERS = 8000
292292
/** The read limit {@link READ_WINDOW_CHARACTERS} is sized for, which is the default limit. */
293293
const READ_WINDOW_LIMIT = 3
294+
/**
295+
* Characters the documents of one scaled read call share. It keeps the call's result, with about
296+
* a thousand characters of metadata per document, under the 50,000-character size at which MCP
297+
* clients such as Claude Code save a tool result to a file rather than show it.
298+
*/
299+
const SCALED_READ_CALL_CHARACTERS = 40_000
294300

295301
/**
296302
* Characters one read returns. A live document is one chunk, so a caller that scales the window
297-
* gets a longer one when it asks for more chunks than the default; at or below the default, and
298-
* for every other caller, the window stays the standard page.
303+
* gets a longer one when it asks for more chunks than the default, bounded by an even share of
304+
* the call's budget across the documents it reads, and never shorter than the standard page. At
305+
* or below the default limit, and for every other caller, the window is the standard page.
299306
*/
300-
function readWindowCharacters(input: Pick<LiveReadInput, 'limit' | 'scaleWindowWithLimit'>) {
301-
if (!input.scaleWindowWithLimit || input.limit <= READ_WINDOW_LIMIT) return READ_WINDOW_CHARACTERS
302-
return Math.floor((READ_WINDOW_CHARACTERS * input.limit) / READ_WINDOW_LIMIT)
307+
function readWindowCharacters({
308+
limit,
309+
scaledWindow,
310+
}: Pick<LiveReadInput, 'limit' | 'scaledWindow'>) {
311+
if (!scaledWindow || limit <= READ_WINDOW_LIMIT) return READ_WINDOW_CHARACTERS
312+
const scaled = Math.floor((READ_WINDOW_CHARACTERS * limit) / READ_WINDOW_LIMIT)
313+
const share = Math.floor(SCALED_READ_CALL_CHARACTERS / Math.max(1, scaledWindow.documentsInCall))
314+
return Math.max(READ_WINDOW_CHARACTERS, Math.min(scaled, share))
303315
}
304316

305317
/**
@@ -992,8 +1004,11 @@ export type LiveReadInput = ResourceOwner & {
9921004
documentId: string
9931005
filters?: WorkspaceSearchFilters
9941006
limit: number
995-
/** Lengthens the window for a limit above the default, as {@link readWindowCharacters} sizes it. */
996-
scaleWindowWithLimit?: boolean
1007+
/**
1008+
* Lengthens the window for a limit above the default, sharing one call's budget across the
1009+
* documents it reads, as {@link readWindowCharacters} sizes it.
1010+
*/
1011+
scaledWindow?: { documentsInCall: number }
9971012
startChunkIndex?: number
9981013
startOffset?: number
9991014
resultSecretRegistry: ResolvedSecretTraceRegistry

0 commit comments

Comments
 (0)