History Management System in Prompt-Optimizer: Architecture and Implementation
The prompt-optimizer history management system uses a three-layer architecture consisting of an interface/API layer exposing CRUD operations, a business logic layer handling validation and chain management, and platform-specific adapters for browser and Electron environments.
The history management system in the linshenkx/prompt-optimizer repository provides a robust framework for storing, versioning, and retrieving prompt optimization records across web and desktop platforms. Implemented within the packages/core package, this subsystem tracks every prompt iteration as an immutable record while maintaining logical grouping through versioned chains. The architecture abstracts storage concerns through a provider pattern, enabling seamless operation across browser storage APIs and Electron's IPC-based filesystem access.
Three-Layer Architecture Overview
The history subsystem organizes functionality into distinct layers that separate concerns between interface definition, business logic, and platform adaptation.
| Layer | Responsibility | Key File |
|---|---|---|
| API / Service | Defines public contracts via IHistoryManager and IImportExportable interfaces |
packages/core/src/services/history/types.ts |
| Business Logic | Validates records, enforces storage limits, builds chains, and coordinates storage | packages/core/src/services/history/manager.ts |
| Platform Adapters | Browser-side direct storage vs. Electron IPC proxy for main process communication | packages/core/src/services/history/electron-proxy.ts |
All data persistence flows through the storage abstraction (StorageAdapter → IStorageProvider). The system maintains a default limit of 50 records and organizes entries into chains, where each chain represents a series of iterations for a single prompt optimization session.
Core Components and Data Models
Type Definitions and Interfaces (types.ts)
The foundation of the system resides in packages/core/src/services/history/types.ts, which defines the shape of historical data and the manager contract.
export interface PromptRecord {
id: string;
chainId: string;
version: number;
originalPrompt: string;
optimizedPrompt: string;
type: 'optimize' | 'iterate';
timestamp: number;
modelKey: string;
modelName?: string;
templateId: string;
iterationNote?: string;
}
export interface PromptRecordChain {
chainId: string;
rootRecord: PromptRecord;
currentRecord: PromptRecord;
versions: PromptRecord[];
}
export interface IHistoryManager extends IImportExportable {
addRecord(record: PromptRecord): Promise<void>;
getRecords(): Promise<PromptRecord[]>;
createNewChain(record: Omit<PromptRecord, 'chainId' | 'version'>): Promise<PromptRecord>;
addIteration(chainId: string, record: Omit<PromptRecord, 'chainId' | 'version'>): Promise<PromptRecord>;
getChain(chainId: string): Promise<PromptRecordChain | null>;
getAllChains(): Promise<PromptRecordChain[]>;
deleteChain(chainId: string): Promise<void>;
}
The IImportExportable interface enables backup and migration capabilities through standardized importData and exportData methods.
The HistoryManager Business Logic (manager.ts)
The HistoryManager class in packages/core/src/services/history/manager.ts implements the core business rules and state management.
export class HistoryManager implements IHistoryManager {
private readonly storageKey = CORE_SERVICE_KEYS.PROMPT_HISTORY;
private readonly maxRecords = 50;
private readonly storage: StorageAdapter;
private readonly modelManager: IModelManager;
constructor(storageProvider: IStorageProvider, modelManager: IModelManager) {
this.storage = new StorageAdapter(storageProvider);
this.modelManager = modelManager;
}
}
Key responsibilities include:
addRecord– Validates incomingPromptRecordobjects, auto-populatesmodelNameviaModelManager.getModelNameByKey()if missing, and inserts records at the head of the storage array while maintaining the 50-record limit through truncation.createNewChain– Generates a UUID forchainId, assigns version1, and persists the root record.addIteration– Retrieves the target chain, increments the version counter, sets type toiterate, and appends the new record to the chain.getChain/getAllChains– Filters records bychainId, sorts by version ascending, and constructsPromptRecordChainobjects containing root, current, and full version history.deleteChain– Removes all records sharing a commonchainId.- Import/Export – Implements
IImportExportableto clear existing data and bulk-import records while preserving original IDs and validating payload structure.
All storage operations wrap low-level errors in domain-specific exceptions (HistoryStorageError, RecordNotFoundError, RecordValidationError) defined in packages/core/src/services/history/errors.ts.
Electron IPC Proxy (electron-proxy.ts)
When running in the Electron desktop environment, the renderer process cannot access the filesystem directly. The ElectronHistoryManagerProxy class in packages/core/src/services/history/electron-proxy.ts implements IHistoryManager while forwarding calls to the main process via window.electronAPI.history.
export class ElectronHistoryManagerProxy implements IHistoryManager {
async addRecord(record: PromptRecord): Promise<void> {
const safeRecord = safeSerializeForIPC(record);
return this.electronAPI.history.addRecord(safeRecord);
}
// ... other methods follow same pattern
}
The safeSerializeForIPC utility (from packages/core/src/utils/ipc-serialization.ts) strips Vue reactivity proxies and other non-serializable properties to ensure valid JSON payloads across the IPC boundary.
Error Handling Strategy (errors.ts)
The system employs a hierarchical error model in packages/core/src/services/history/errors.ts that extends base HistoryError classes with specific error codes from packages/core/src/constants/error-codes.ts. This architecture enables the UI layer to map technical failures to localized user messages based on structured error codes.
Chain-Based Versioning System
The PromptRecordChain concept organizes history into logical sequences. Each chain originates with a root record (version 1) created via createNewChain. Subsequent optimizations or edits create iteration records with incrementing version numbers via addIteration.
This design maintains immutable snapshots of every prompt state while providing efficient access to the current version and full audit history. The chain structure supports branching visualization and rollback capabilities in the UI layer.
Implementation Examples
Creating a New History Chain
import { createHistoryManager } from '@prompt-optimizer/core';
import { MemoryStorageProvider } from '@prompt-optimizer/core/src/services/storage/memory';
import { ModelManager } from '@prompt-optimizer/core/src/services/model/manager';
const storage = new MemoryStorageProvider();
const modelMgr = new ModelManager();
const history = createHistoryManager(storage, modelMgr);
const rootRecord = await history.createNewChain({
id: crypto.randomUUID(),
originalPrompt: 'Write a haiku about sunrise',
optimizedPrompt: 'Compose a haiku describing sunrise',
type: 'optimize',
modelKey: 'gpt-4',
templateId: 'default',
});
Adding Iterations to Existing Chains
const chainId = rootRecord.chainId;
await history.addIteration({
chainId,
originalPrompt: 'Compose a haiku describing sunrise',
optimizedPrompt: 'Sun climbs, sky blushes – a fleeting gold kiss',
modelKey: 'gpt-4',
templateId: 'default',
iterationNote: 'Made tone more vivid',
});
Retrieving Chain History
const chain = await history.getChain(chainId);
console.log('Root prompt:', chain.rootRecord.originalPrompt);
console.log('Current version:', chain.currentRecord.version);
console.log('Total iterations:', chain.versions.length);
Exporting and Importing Data
// Export for backup
const backupData = await history.exportData();
localStorage.setItem('history_backup', JSON.stringify(backupData));
// Import to restore or migrate
const importedData = JSON.parse(localStorage.getItem('history_backup'));
await history.importData(importedData);
Electron Environment Setup
import { ElectronHistoryManagerProxy } from '@prompt-optimizer/core';
const history = new ElectronHistoryManagerProxy();
// API identical to browser implementation
await history.addRecord({
id: crypto.randomUUID(),
originalPrompt: 'Explain quantum computing',
optimizedPrompt: 'Explain quantum computing to a high school student',
type: 'optimize',
chainId: 'chain-xyz',
version: 1,
timestamp: Date.now(),
modelKey: 'gpt-4',
templateId: 'default',
});
Summary
- The history management system in prompt-optimizer employs a strict three-layer architecture separating interfaces, business logic, and platform adapters.
- PromptRecord objects are immutable snapshots stored in versioned PromptRecordChain groupings with a default limit of 50 total records.
- HistoryManager in
packages/core/src/services/history/manager.tshandles validation, model name resolution, and chain construction. - ElectronHistoryManagerProxy enables desktop operation by serializing calls over IPC using
safeSerializeForIPCto handle Vue reactivity wrappers. - The IImportExportable interface provides portable backup and migration capabilities across storage backends.
- Domain-specific errors in
packages/core/src/services/history/errors.tsprovide structured error codes for UI localization.
Frequently Asked Questions
What is the maximum number of history records stored in prompt-optimizer?
The system enforces a default limit of 50 records, defined by the maxRecords constant in packages/core/src/services/history/manager.ts. When adding new records via addRecord, the manager automatically truncates the oldest entries to maintain this limit, preventing unbounded storage growth in browser and desktop environments.
How does prompt-optimizer handle model name resolution in history records?
When a PromptRecord is saved without an explicit modelName, the HistoryManager performs lazy resolution by querying the injected IModelManager instance via getModelNameByKey(). This lookup occurs during the addRecord validation phase, ensuring the stored record contains the human-readable display name required for UI rendering while maintaining the modelKey for technical references.
Can history data be migrated between different storage backends?
Yes. The IHistoryManager interface extends IImportExportable, which exposes exportData() and importData() methods. These methods serialize the entire history collection to a plain JSON array of PromptRecord objects, allowing users to backup data from browser LocalStorage and restore it into Electron's filesystem storage or vice versa without losing chain relationships or version history.
How does the Electron app access history storage?
In Electron environments, the renderer process instantiates ElectronHistoryManagerProxy instead of the direct HistoryManager. This proxy class implements the same interface but forwards all method calls to the main process via window.electronAPI.history. Before transmission, records pass through safeSerializeForIPC to remove framework-specific proxies, ensuring the IPC payload contains only vanilla JavaScript objects compatible with Electron's structured clone algorithm.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →