diff --git a/.jules/bolt.md b/.jules/bolt.md index 134f394..091086d 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -1,3 +1,6 @@ ## 2026-04-25 - LLM API Connection Pooling **Learning:** Making repeated API calls to external LLM providers (e.g. OpenRouter, OpenAI) without HTTP Keep-Alive results in 100-200ms of unnecessary TCP/TLS handshake overhead per request. In an agentic loop, this accumulates significantly. **Action:** When initializing HTTP clients (like Axios) for repeated internal/external services, configure them with an `https.Agent` setting `keepAlive: true` to pool connections. +## 2026-06-01 - Agentic Loop Caching Bottleneck +**Learning:** Agentic loops frequently execute the same queries (e.g., in SearchTool.deep), resulting in redundant and expensive external network operations. +**Action:** Always implement a Map-based in-memory cache for agentic tools that perform repeated deterministic queries. Ensure the cache read/write logic remains inside the core execution wrapper to maintain execution flow and standardized logging while instantly returning results for identical queries. diff --git a/dist/src/agents/connector.d.ts b/dist/src/agents/connector.d.ts index e171759..3791989 100644 --- a/dist/src/agents/connector.d.ts +++ b/dist/src/agents/connector.d.ts @@ -7,6 +7,7 @@ export interface ToolDefinition { export declare class AgentConnector { private core; private config; + private client; constructor(core: CoreEngine, config?: { provider?: string; apiKey?: string; diff --git a/dist/src/agents/connector.js b/dist/src/agents/connector.js index d1c7aed..5f9b86d 100644 --- a/dist/src/agents/connector.js +++ b/dist/src/agents/connector.js @@ -1,14 +1,53 @@ "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentConnector = void 0; const axios_1 = __importDefault(require("axios")); +const https = __importStar(require("https")); class AgentConnector { constructor(core, config = {}) { this.core = core; this.config = config; + // Optimize repeated API calls by enabling keep-alive for HTTP/HTTPS connections. + // This avoids expensive TCP/TLS handshakes (~100-200ms per request) on consecutive LLM calls. + this.client = axios_1.default.create({ + httpsAgent: new https.Agent({ keepAlive: true }) + }); } /** * Standard protocol to expose oneshotsx tools to any LLM @@ -39,7 +78,7 @@ class AgentConnector { return "Agent running in simulation mode. No API key provided."; } try { - const response = await axios_1.default.post('https://openrouter.ai/api/v1/chat/completions', { + const response = await this.client.post('https://openrouter.ai/api/v1/chat/completions', { model: 'meta-llama/llama-3.1-70b-instruct', messages: [{ role: 'user', content: message }] }, { diff --git a/dist/src/tools/index.d.ts b/dist/src/tools/index.d.ts index f6fbbb5..bf3b894 100644 --- a/dist/src/tools/index.d.ts +++ b/dist/src/tools/index.d.ts @@ -10,12 +10,14 @@ export declare class ShellTool { export declare class FileTool { private core; constructor(core: CoreEngine); - read(path: string): Promise>; - write(path: string, content: string): Promise>; - patch(path: string, search: string, replace: string): Promise>; + private resolveAndValidatePath; + read(unsafePath: string): Promise>; + write(unsafePath: string, content: string): Promise>; + patch(unsafePath: string, search: string, replace: string): Promise>; } export declare class SearchTool { private core; + private cache; constructor(core: CoreEngine); deep(query: string): Promise>; } diff --git a/dist/src/tools/index.js b/dist/src/tools/index.js index e8ea37b..a73c918 100644 --- a/dist/src/tools/index.js +++ b/dist/src/tools/index.js @@ -37,6 +37,7 @@ exports.SearchTool = exports.FileTool = exports.ShellTool = void 0; const child_process_1 = require("child_process"); const util_1 = require("util"); const fs = __importStar(require("fs/promises")); +const path = __importStar(require("path")); const execAsync = (0, util_1.promisify)(child_process_1.exec); class ShellTool { constructor(core) { @@ -55,23 +56,37 @@ class FileTool { constructor(core) { this.core = core; } - async read(path) { - this.core.log(`Reading file: ${path}`); - return this.core.execute(() => fs.readFile(path, 'utf-8')); + resolveAndValidatePath(unsafePath) { + const resolvedPath = path.resolve(process.cwd(), unsafePath); + if (!resolvedPath.startsWith(process.cwd() + path.sep) && resolvedPath !== process.cwd()) { + throw new Error('Access denied: Invalid path'); + } + return resolvedPath; } - async write(path, content) { - this.core.log(`Writing file: ${path}`); - return this.core.execute(() => fs.writeFile(path, content)); + async read(unsafePath) { + this.core.log(`Reading file: ${unsafePath}`); + return this.core.execute(() => { + const safePath = this.resolveAndValidatePath(unsafePath); + return fs.readFile(safePath, 'utf-8'); + }); + } + async write(unsafePath, content) { + this.core.log(`Writing file: ${unsafePath}`); + return this.core.execute(() => { + const safePath = this.resolveAndValidatePath(unsafePath); + return fs.writeFile(safePath, content); + }); } - async patch(path, search, replace) { - this.core.log(`Patching file: ${path}`); + async patch(unsafePath, search, replace) { + this.core.log(`Patching file: ${unsafePath}`); return this.core.execute(async () => { - const content = await fs.readFile(path, 'utf-8'); + const safePath = this.resolveAndValidatePath(unsafePath); + const content = await fs.readFile(safePath, 'utf-8'); const newContent = content.replace(search, replace); if (content === newContent) { - throw new Error(`Search string not found in ${path}`); + throw new Error(`Search string not found in file`); } - await fs.writeFile(path, newContent); + await fs.writeFile(safePath, newContent); }); } } @@ -79,13 +94,20 @@ exports.FileTool = FileTool; class SearchTool { constructor(core) { this.core = core; + // ⚡ Bolt: Cache search results to prevent redundant API calls during agentic loops + this.cache = new Map(); } async deep(query) { this.core.log(`Initiating deep search for: ${query}`); // This is where real API calls to Google/X/Reddit would go return this.core.execute(async () => { + // ⚡ Bolt: Check cache before making expensive API call + if (this.cache.has(query)) { + this.core.log(`⚡ Cache hit for query: ${query}`); + return this.cache.get(query); + } // Simulated results for the SDK base - return { + const result = { query, timestamp: new Date().toISOString(), findings: [ @@ -93,6 +115,9 @@ class SearchTool { "Trending solutions in 2026" ] }; + // ⚡ Bolt: Store result in cache for future identical queries + this.cache.set(query, result); + return result; }); } } diff --git a/src/tools/index.ts b/src/tools/index.ts index 6af329e..0706e20 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -60,14 +60,23 @@ export class FileTool { } export class SearchTool { + // ⚡ Bolt: Cache search results to prevent redundant API calls during agentic loops + private cache = new Map(); + constructor(private core: CoreEngine) {} async deep(query: string): Promise> { this.core.log(`Initiating deep search for: ${query}`); // This is where real API calls to Google/X/Reddit would go return this.core.execute(async () => { + // ⚡ Bolt: Check cache before making expensive API call + if (this.cache.has(query)) { + this.core.log(`⚡ Cache hit for query: ${query}`); + return this.cache.get(query); + } + // Simulated results for the SDK base - return { + const result = { query, timestamp: new Date().toISOString(), findings: [ @@ -75,6 +84,10 @@ export class SearchTool { "Trending solutions in 2026" ] }; + + // ⚡ Bolt: Store result in cache for future identical queries + this.cache.set(query, result); + return result; }); } }