Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@
## 2026-06-29 - Optimize D1 Queries by Batching
**Learning:** Found N+1 query pattern on the hub stats page (`hub/src/routes/admin/stats.ts`) when iterating over the list of inbound peers to query `events_pulled_24h` for each peer sequentially using `.first()`. This introduces significant roundtrip latency and resource overhead as the number of peers grows. D1 supports batching queries, which can collapse N queries into a single HTTP roundtrip. D1's returned `batch` types can be peculiar, particularly when running against tests using Miniflare or test shims where row result structure might differ across platforms.
**Action:** Replaced sequential queries in `Promise.all` with `db.batch()` to combine the multiple `db.prepare(...).bind(...)` calls into a single execution, and normalized the output structure parsing. This significantly reduces N+1 overhead and improves performance scaling as the peer count increases.
## 2026-07-28 - Optimize N+1 Query in Agent Thread Context Parsing
**Learning:** In `workers/agent/index.ts`, when preparing the thread context to check for prompt injections and auto-trigger agent workflows, the codebase was mapping over `threadEmails` (retrieved via `stub.getEmails`) and calling `stub.getEmail` inside a `Promise.all` for each email in the thread to get the full body. This caused an N+1 query issue for large threads, generating many separate roundtrips to the Durable Object.
**Action:** Replaced the `Promise.all` loop with a single call to the existing helper function `getFullThread(stub, emailData.threadId)`. This leverages the DO `getThreadEmails` RPC method, which runs a single combined DO SQL query (batching the fetches), significantly reducing latency and compute overhead for deep conversation threads.
23 changes: 5 additions & 18 deletions workers/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from "../../shared/folders";
import { isPromptInjection, verifyDraft } from "../lib/ai";
import {
getFullThread,
getMailboxStub,
stripHtmlToText,
textToHtml,
Expand Down Expand Up @@ -437,24 +438,10 @@ export class EmailAgent extends AIChatAgent<any> {
thread_id: emailData.threadId,
})) as EmailMetadata[];
if (threadEmails.length > 1) {
const fullThread = await Promise.all(
threadEmails.map(async (e) => {
const full = (await stub.getEmail(e.id)) as EmailFull | null;
const text = full?.body ? stripHtmlToText(full.body) : "";
return {
id: e.id,
sender: e.sender,
recipient: e.recipient,
subject: e.subject,
date: e.date,
folder_id: e.folder_id,
body_text: text,
};
}),
);
// ⚑ Bolt: Optimize sorting by replacing `new Date(b.date).getTime()` with `Date.parse(b.date)`
fullThread.sort((a, b) => Date.parse(a.date) - Date.parse(b.date));
threadContext = fullThread
// ⚑ Bolt: Eliminate N+1 query overhead by replacing Promise.all mapping of `stub.getEmail`
// with a single call to `getFullThread`, which fetches the entire thread context in one DO roundtrip.
const fullThreadData = await getFullThread(stub, emailData.threadId);
threadContext = fullThreadData.messages
.map(
(e) =>
`[${e.date}] ${e.sender} β†’ ${e.recipient} (${e.folder_id}): ${e.body_text.substring(0, 500)}`,
Expand Down
Loading