- [Overview](#overview)
ContextBank Object API
Table of Contents
Overview
The ContextBank is a singleton object that manages TPipe’s global context window system, enabling context sharing between pipes, pipelines, and parallel operations through a keyed banking system.
object ContextBank
Public Properties
swapMutex
val swapMutex: Mutex = Mutex()
Mutex for managing context window swapping operations. Ensures thread-safe access when changing the active banked context window.
bankMutex
val bankMutex: Mutex = Mutex()
Mutex for managing bank access operations. Ensures thread-safe access when reading from or writing to the context bank storage.
Public Functions
Current Context Access
getBankedContextWindow(): ContextWindow
Retrieves direct reference to the currently active banked context window.
Behavior: Returns the actual object reference without copying. Warning: Not safe for use in coroutines or multi-threaded environments due to potential race conditions. Use copyBankedContextWindow() for concurrent access.
copyBankedContextWindow(): ContextWindow?
Creates a deep copy of the currently active banked context window.
Behavior: Uses serialization/deserialization for deep copying to ensure thread safety. Returns null if serialization fails. Recommended for use within coroutines and concurrent operations.
updateBankedContext(newContext: ContextWindow)
Updates the currently active banked context window.
Behavior: Direct assignment without thread safety. Warning: Not safe for concurrent use. Use updateBankedContextWithMutex() for thread-safe updates.
updateBankedContextWithMutex(newContext: ContextWindow)
Thread-safe update of the currently active banked context window.
Behavior: Uses bankMutex to ensure exclusive access during update. Recommended method for updating banked context in concurrent environments.
Bank Management
emplace(key: String, window: ContextWindow, mode: StorageMode)
Stores or replaces a context window with explicit storage mode control.
Parameters:
key: The identifier for storing the context windowwindow: The ContextWindow to storemode: Storage mode controlling memory and disk persistence behavior
Storage Modes:
StorageMode.MEMORY_ONLY: Store only in memory, no disk persistenceStorageMode.MEMORY_AND_DISK: Store in both memory and disk (default behavior)StorageMode.DISK_ONLY: Store only on disk, load on-demand without cachingStorageMode.DISK_WITH_CACHE: Store on disk with LRU memory cache and automatic eviction
Example:
// Disk-only for large, infrequently accessed contexts
ContextBank.emplace("large-context", window, StorageMode.DISK_ONLY)
// Cached disk storage with automatic eviction
ContextBank.emplace("hot-data", window, StorageMode.DISK_WITH_CACHE)
emplace(key: String, window: ContextWindow, persistToDisk: Boolean = false)
Backward compatible storage method using boolean flag.
Parameters:
key: The identifier for storing the context windowwindow: The ContextWindow to storepersistToDisk: When true, stores to both memory and disk; when false, memory only
Behavior: Maps to storage modes: persistToDisk=true → MEMORY_AND_DISK, persistToDisk=false → MEMORY_ONLY. Maintained for backward compatibility with existing code.
emplaceWithMutex(key: String, window: ContextWindow, mode: StorageMode)
Thread-safe storage with explicit storage mode control.
Parameters:
key: The identifier for storing the context windowwindow: The ContextWindow to storemode: Storage mode controlling memory and disk persistence behavior
Behavior: Uses bankMutex to ensure exclusive access during bank modification. Recommended method for updating bank contents in concurrent environments.
emplaceWithMutex(key: String, window: ContextWindow, persistToDisk: Boolean = false)
Thread-safe backward compatible storage method.
Parameters:
key: The identifier for storing the context windowwindow: The ContextWindow to storepersistToDisk: When true, stores to both memory and disk; when false, memory only
Behavior: Uses bankMutex to ensure exclusive access. Maintained for backward compatibility.
getContextFromBank(key: String, copy: Boolean = true): ContextWindow
Retrieves a context window from the bank by key, respecting storage mode.
Behavior:
- Page Lock Check: Returns empty
ContextWindow()if page is locked via ContextLock - Storage Mode Aware: DISK_ONLY loads without caching, DISK_WITH_CACHE caches with eviction
- With
copy = true(default): Returns deep copy via serialization for thread safety - With
copy = false: Returns direct reference for performance but without thread safety - Missing key: Returns empty
ContextWindow()if key doesn’t exist - Automatic Disk Loading: Loads from disk if file exists but not in memory
Example:
// Set disk-only mode
ContextBank.emplace("data", window, StorageMode.DISK_ONLY)
// Loads from disk without caching in memory
val loaded = ContextBank.getContextFromBank("data")
Storage Mode Management
setStorageMode(key: String, mode: StorageMode)
Sets the storage mode for a specific key.
Parameters:
key: The context bank keymode: The storage mode to apply
Example:
ContextBank.setStorageMode("my-key", StorageMode.DISK_ONLY)
getStorageMode(key: String): StorageMode
Gets the storage mode for a specific key.
Returns: The storage mode, or MEMORY_AND_DISK if not set (default for backward compatibility)
setStorageModeWithMutex(key: String, mode: StorageMode)
Thread-safe version of setStorageMode().
Memory Eviction
evictFromMemory(key: String): Boolean
Removes a context window from memory without deleting the disk file.
Returns: true if the key was in memory and was removed, false otherwise
Example:
// Free memory while keeping disk file
ContextBank.evictFromMemory("large-context")
evictFromMemoryWithMutex(key: String): Boolean
Thread-safe version of evictFromMemory().
evictAllFromMemory()
Removes all context windows from memory without deleting disk files.
evictAllFromMemoryWithMutex()
Thread-safe version of evictAllFromMemory().
evictTodoListFromMemory(key: String): Boolean
Removes a TodoList from memory without deleting the disk file.
evictTodoListFromMemoryWithMutex(key: String): Boolean
Thread-safe version of evictTodoListFromMemory().
evictAllTodoListsFromMemory()
Removes all TodoLists from memory without deleting disk files.
evictAllTodoListsFromMemoryWithMutex()
Thread-safe version of evictAllTodoListsFromMemory().
Cache Configuration
configureCachePolicy(config: CacheConfig)
Configures cache behavior for DISK_WITH_CACHE storage mode.
Parameters:
config: Cache configuration with memory limits and eviction policy
Example:
ContextBank.configureCachePolicy(
CacheConfig(
maxMemoryBytes = 50 * 1024 * 1024, // 50MB
maxEntries = 100,
evictionPolicy = EvictionPolicy.LRU
)
)
Eviction Policies:
EvictionPolicy.LRU: Least Recently Used (default)EvictionPolicy.LFU: Least Frequently UsedEvictionPolicy.FIFO: First In First OutEvictionPolicy.MANUAL: No automatic eviction
getCacheStatistics(): CacheStatistics
Returns current cache statistics.
Returns: CacheStatistics with:
memoryEntries: Number of entries in memorydiskOnlyEntries: Number of disk-only entriestotalMemoryBytes: Total memory usagecacheHitRate: Cache hit rate (0.0 to 1.0)
Example:
val stats = ContextBank.getCacheStatistics()
println("Memory entries: ${stats.memoryEntries}")
println("Cache hit rate: ${stats.cacheHitRate}")
clearCache()
Clears all DISK_WITH_CACHE entries from memory.
Behavior: Only removes cached entries, does not affect MEMORY_ONLY or MEMORY_AND_DISK entries.
Bank Management
Context Swapping
swapBank(key: String, copy: Boolean = true)
Swaps the active banked context window with one from the bank.
Behavior:
- With
copy = true(default): Creates deep copy of banked context for safety - With
copy = false: Uses direct reference for performance - Missing key: Uses empty
ContextWindow()if key doesn’t exist - Thread safety: Warning: Not safe for concurrent use, use
swapBankWithMutex()instead
swapBankWithMutex(key: String)
Thread-safe context window swapping operation.
Behavior: Uses both bankMutex and swapMutex for comprehensive thread safety. Always performs copying for safety. Recommended method for context swapping in concurrent environments.
Thread-Safe Operations
ContextBank provides two tiers of thread-safe access:
Legacy mutex methods use the global bankMutex and are suitable for simple concurrent access:
emplaceWithMutex(): UsesbankMutexfor bank modificationsupdateBankedContextWithMutex(): UsesbankMutexfor banked context updatesswapBankWithMutex(): Uses bothbankMutexandswapMutexfor context swapping
Suspend methods use per-page mutexes so that operations on unrelated page keys can proceed concurrently without blocking each other. These are the recommended approach for coroutine-based code:
getContextFromBankSuspend(key: String, copy: Boolean = true, skipRemote: Boolean = false): ContextWindow
Suspend-safe retrieval of a context window. Acquires the page-level mutex for key only, so reads on other keys are not blocked.
val context = ContextBank.getContextFromBankSuspend("user-profile")
emplaceSuspend(key: String, window: ContextWindow, mode: StorageMode, skipRemote: Boolean = false)
Suspend-safe storage with explicit storage mode. Acquires the page-level mutex for key.
ContextBank.emplaceSuspend("user-profile", updatedContext, StorageMode.MEMORY_AND_DISK)
mutateContextWindowSuspend(key: String, mode: StorageMode, skipRemote: Boolean, block: (ContextWindow) -> Unit): ContextWindow
Atomic read-modify-write on a context window. Loads the current value, applies block, and stores the result — all while holding the page mutex. This prevents lost updates when multiple coroutines modify the same key.
val updated = ContextBank.mutateContextWindowSuspend("session-state", StorageMode.MEMORY_AND_DISK) { window ->
window.addText("New session event at ${System.currentTimeMillis()}")
}
withContextWindowReferenceSuspend(key: String, skipRemote: Boolean, block: (ContextWindow) -> Unit): ContextWindow
Similar to mutateContextWindowSuspend but intended for metadata-only mutations that should not immediately persist to disk. The block receives the shared reference and can modify it in place while the page mutex is held.
ContextBank.withContextWindowReferenceSuspend("counters") { window ->
val count = (window.metadata["requestCount"] as? Int ?: 0) + 1
window.metadata["requestCount"] = count
}
Other Suspend Methods
| Method | Description |
|---|---|
getBankedContextWindowSuspend(copy) | Suspend-safe access to the active banked context |
updateBankedContextSuspend(newContext) | Suspend-safe update of the active banked context |
swapBankSuspend(key, copy) | Suspend-safe context window swapping |
deletePersistingBankKeySuspend(key) | Suspend-safe deletion of a persisted bank key |
deleteContextWindowSuspend(key) | Suspend-safe deletion of a context window |
deleteTodoListSuspend(key) | Suspend-safe deletion of a todo list |
contextWindowExistsSuspend(key) | Suspend-safe existence check for a context window |
todoListExistsSuspend(key) | Suspend-safe existence check for a todo list |
getPageKeysSuspend(skipRemote) | Suspend-safe listing of all page keys |
getTodoListKeysSuspend(skipRemote) | Suspend-safe listing of all todo list keys |
getPagedTodoListSuspend(key, copy) | Suspend-safe retrieval of a todo list |
emplaceTodoListSuspend(key, todoList, ...) | Suspend-safe storage of a todo list |
configureCachePolicySuspend(config) | Suspend-safe cache policy configuration |
clearCacheSuspend() | Suspend-safe cache clearing |
When to use which:
- Use
*WithMutex()methods when calling from blocking code or simple coroutine contexts - Use
*Suspend()methods when calling from coroutine-heavy code where per-page concurrency matters (e.g., manifold workers operating on different page keys simultaneously)
Utilities
clearBankedContext()
Resets the currently active banked context window to empty state.
Behavior: Replaces banked context with new empty ContextWindow(). Useful for cleanup operations and conditional logic that checks for context presence.
TodoList Integration
ContextBank provides storage and retrieval for TodoList objects, enabling task management across pipes and pipelines.
getPagedTodoList(key: String, copy: Boolean = true): TodoList
Retrieves a TodoList from ContextBank storage.
Parameters:
key: The page key identifying the TodoListcopy: Iftrue, returns a deep copy; iffalse, returns direct reference
Behavior:
- Checks in-memory storage for the key
- If not found in memory and disk persistence is enabled, attempts to load from
~/.tpipe/TPipe-Default/memory/todo/<key>.todo - Returns the TodoList if found, or an empty TodoList if not found
- When
copy = true, uses serialization for deep copying to ensure thread safety
Example:
// Get a copy (safe for concurrent use)
val todoList = ContextBank.getPagedTodoList("my-tasks")
// Get direct reference (faster but not thread-safe)
val todoListRef = ContextBank.getPagedTodoList("my-tasks", copy = false)
emplaceTodoList(key: String, todoList: TodoList, writeToDisk: Boolean, overwrite: Boolean)
Stores a TodoList in ContextBank.
Parameters:
key: Unique identifier for this TodoListtodoList: The TodoList object to storewriteToDisk: Iftrue, persists to~/.tpipe/TPipe-Default/memory/todo/<key>.todooverwrite: Iftrue, replaces existing TodoList with same key; iffalse, merges with existing
Behavior:
- Stores TodoList in memory under the specified key
- If
writeToDisk = true, serializes and saves to disk for persistence across runs - If
overwrite = falseand a TodoList exists with this key, merges the new list with the existing one - Creates the todolist directory if it doesn’t exist
Example:
val todoList = TodoList()
// ... populate tasks ...
ContextBank.emplaceTodoList(
key = "code-review",
todoList = todoList,
writeToDisk = true,
overwrite = true
)
emplaceTodoListWithMutex(key: String, todoList: TodoList, writeToDisk: Boolean, overwrite: Boolean)
Thread-safe version of emplaceTodoList().
Behavior: Identical to emplaceTodoList() but uses mutex locking to ensure thread safety. Use this when multiple coroutines or threads might save TodoLists simultaneously.
Example:
runBlocking {
ContextBank.emplaceTodoListWithMutex(
key = "shared-tasks",
todoList = todoList,
writeToDisk = true,
overwrite = true
)
}
TodoList Storage Location:
TodoLists are stored in the TPipe configuration directory:
- Path:
~/.tpipe/TPipe-Default/memory/todo/<key>.todo - Format: JSON serialization of TodoList object
- Persistence: Survives application restarts when
writeToDisk = true
See TodoList API for complete TodoList documentation and usage examples.
Retrieval Functions
Retrieval functions enable lazy-loading of context windows from external sources like databases, APIs, or remote services. When a key with a bound retrieval function is requested, the function executes automatically to fetch the data.
RetrievalFunction Type
typealias RetrievalFunction = suspend (String) -> ContextWindow?
A suspending function that takes a context bank key and returns a ContextWindow if retrieval succeeds, or null if it fails.
Parameters:
key: The context bank key being requested
Returns: ContextWindow? - The retrieved context window, or null on failure
registerRetrievalFunction(key: String, function: RetrievalFunction)
Binds a retrieval function to a specific context bank key.
Parameters:
key: The context bank key to bind the function tofunction: The suspending function that will fetch the context window
Behavior:
- When
getContextFromBank(key)is called and the key is not in memory/disk, the retrieval function executes - The function result is cached in memory after successful retrieval
- Thread-safe: uses
ConcurrentHashMapfor function storage - Retrieval functions are not persisted across application restarts
Example:
import com.TTT.Context.ContextBank
import com.TTT.Context.ContextWindow
// Register database retrieval function
ContextBank.registerRetrievalFunction("user-profile") { key ->
val data = database.query("SELECT * FROM profiles WHERE key = ?", key)
if (data != null)
{
val context = ContextWindow()
context.addText(data.toString())
context
}
else null
}
// Later, when accessed, automatically fetches from database
val profile = ContextBank.getContextFromBank("user-profile")
removeRetrievalFunction(key: String)
Removes a retrieval function binding from a key.
Parameters:
key: The context bank key to unbind
Behavior:
- Removes the retrieval function for the specified key
- Does not affect any cached context windows already loaded
- Thread-safe operation
Example:
// Remove retrieval function
ContextBank.removeRetrievalFunction("user-profile")
// Now getContextFromBank() will return empty context if not in memory/disk
Retrieval Function Use Cases
Database-Backed Context:
import com.TTT.Context.ContextBank
import com.TTT.Context.ContextWindow
// Register retrieval for database-backed lorebook
ContextBank.registerRetrievalFunction("knowledge-base") { key ->
val entries = database.getLoreBookEntries(key)
val context = ContextWindow()
entries.forEach { entry ->
context.addLoreBookEntry(
key = entry.triggerKey,
value = entry.content,
weight = entry.weight
)
}
context
}
API-Backed Context:
import com.TTT.Context.ContextBank
import com.TTT.Context.ContextWindow
import com.TTT.Util.httpGet
import com.TTT.Util.deserialize
// Register retrieval from REST API
ContextBank.registerRetrievalFunction("external-data") { key ->
try
{
val response = httpGet("https://api.example.com/context/$key")
deserialize<ContextWindow>(response)
}
catch (e: Exception)
{
println("Failed to retrieve context: ${e.message}")
null
}
}
Remote Memory Integration:
import com.TTT.Context.ContextBank
import com.TTT.Context.MemoryClient
// Register retrieval from remote memory server
ContextBank.registerRetrievalFunction("remote-context") { key ->
MemoryClient.getContextWindow(key)
}
// Now local ContextBank transparently fetches from remote server
val context = ContextBank.getContextFromBank("remote-context")
Computed Context:
import com.TTT.Context.ContextBank
import com.TTT.Context.ContextWindow
// Register dynamic context generation
ContextBank.registerRetrievalFunction("system-stats") { key ->
val context = ContextWindow()
val runtime = Runtime.getRuntime()
val stats = """
Memory: ${runtime.totalMemory() / 1024 / 1024} MB
Free: ${runtime.freeMemory() / 1024 / 1024} MB
Processors: ${runtime.availableProcessors()}
""".trimIndent()
context.addText(stats)
context
}
Retrieval Function Behavior
Execution Flow:
getContextFromBank(key)is called- Check if key exists in memory → return if found
- Check if key exists on disk → load and return if found
- Check if retrieval function is registered for key → execute if found
- Cache result in memory after successful retrieval
- Return empty
ContextWindow()if all methods fail
Performance Considerations:
- Retrieval functions execute only on cache miss
- Results are cached in memory after first retrieval
- Use
evictFromMemory(key)to force re-retrieval on next access - Retrieval functions are suspending for async I/O operations
Error Handling:
import com.TTT.Context.ContextBank
import com.TTT.Context.ContextWindow
// Retrieval function with error handling
ContextBank.registerRetrievalFunction("resilient-data") { key ->
var retries = 3
var lastError: Exception? = null
while (retries > 0)
{
try
{
return@registerRetrievalFunction fetchFromExternalSource(key)
}
catch (e: Exception)
{
lastError = e
retries--
if (retries > 0) kotlinx.coroutines.delay(1000)
}
}
println("Failed to retrieve $key after 3 attempts: ${lastError?.message}")
null
}
Write Back Functions
Write back functions are the write-side complement to retrieval functions. When a context bank key with a bound write back function is written via emplace or emplaceWithMutex, the function executes to persist the data to an external destination such as a database, API, or remote service.
WriteBackFunction Type
typealias WriteBackFunction = suspend (String, ContextWindow) -> Boolean
A suspending function that receives the context bank key and the context window being written, and returns true if the write succeeded.
Parameters:
key: The context bank key being writtenwindow: The context window to persist
Returns: true if the write back was successful, false otherwise
registerWriteBackFunction(key: String, function: WriteBackFunction)
Binds a write back function to a specific context bank key.
Parameters:
key: The context bank key to bind the function tofunction: The suspending function that will persist the context window
Behavior:
- The function is called after the context window is stored locally
- Write back functions are stored in a
ConcurrentHashMapand are thread-safe - Write back functions are not persisted across application restarts
Example:
import com.TTT.Context.ContextBank
import com.TTT.Context.ContextWindow
// Register a write back function that saves to a database
ContextBank.registerWriteBackFunction("user-profile") { key, window ->
try
{
database.save(key, serialize(window))
true
}
catch (e: Exception)
{
println("Failed to write back $key: ${e.message}")
false
}
}
// When this key is written, the write back function fires automatically
val context = ContextWindow()
context.addText("Updated profile data")
ContextBank.emplaceWithMutex("user-profile", context, persistToDisk = true)
removeWriteBackFunction(key: String)
Removes a write back function binding from a key.
Parameters:
key: The context bank key to unbind
Example:
ContextBank.removeWriteBackFunction("user-profile")
clearWriteBackFunctions()
Removes all registered write back functions.
ContextBank.clearWriteBackFunctions()
Write Back Function Use Cases
Database Sync:
ContextBank.registerWriteBackFunction("session-state") { key, window ->
database.upsert("context_windows", key, serialize(window))
true
}
Remote Memory Sync:
ContextBank.registerWriteBackFunction("shared-context") { key, window ->
val result = MemoryClient.putContextWindow(key, window)
result is MemoryOperationResult.Success
}
Audit Logging:
ContextBank.registerWriteBackFunction("sensitive-data") { key, window ->
auditLog.record("context_write", key, System.currentTimeMillis())
true
}
Retrieval + Write Back Pattern
Combine retrieval and write back functions for full bidirectional sync with an external store:
// Read from database on cache miss
ContextBank.registerRetrievalFunction("synced-context") { key ->
database.load(key)?.let { deserialize<ContextWindow>(it) }
}
// Write back to database on every update
ContextBank.registerWriteBackFunction("synced-context") { key, window ->
database.save(key, serialize(window))
true
}
Key Behaviors
Thread Safety Model
ContextBank provides three tiers of thread-safe access:
- Non-mutex methods (e.g.,
emplace(),getContextFromBank()) — fastest, but require external synchronization in concurrent environments - Legacy mutex methods (e.g.,
emplaceWithMutex()) — use the globalbankMutexfor simple thread safety - Suspend methods (e.g.,
emplaceSuspend(),mutateContextWindowSuspend()) — use per-page mutexes so operations on unrelated keys proceed concurrently
The suspend methods are the recommended approach for coroutine-based code. They use ConcurrentHashMap-backed per-page locks internally, meaning two coroutines writing to different page keys will never block each other.
Disk persistence uses atomic temp-file replacement with sidecar lock files (MemoryPersistence) to prevent corruption from concurrent writes or crashes during file I/O.
Copy vs Reference Strategy
Most operations offer choice between copying (safe but slower) and direct references (fast but potentially unsafe). Copying uses serialization for deep copying, ensuring complete isolation between contexts.
Page-Based Organization
The banking system uses string keys as “pages” to organize different context domains. This enables:
- Context Isolation: Different pipelines can use separate pages
- Context Sharing: Multiple operations can access the same page
- Context Switching: Active context can be swapped between pages
Fallback Behavior
Operations gracefully handle missing keys by returning empty ContextWindow() objects rather than throwing exceptions. This ensures robust operation even with invalid page keys.
Global State Management
As a singleton, ContextBank maintains global state across the entire TPipe system. This enables:
- Cross-Pipeline Context: Multiple pipelines can share context
- Persistent Context: Context survives individual pipe/pipeline execution
- Centralized Management: Single point of control for all global context
Storage Mode System
ContextBank supports four storage modes for fine-grained memory and disk management:
MEMORY_ONLY:
- Fastest access, no disk I/O
- Data lost on application restart
- Use for temporary or frequently accessed data
MEMORY_AND_DISK (Default):
- Fast access with persistence
- Backward compatible with existing code
- Use for important data needing both speed and durability
DISK_ONLY:
- Most memory efficient
- Loads from disk on every access without caching
- Use for large, infrequently accessed contexts
DISK_WITH_CACHE:
- Balanced approach with automatic eviction
- Configurable cache policies (LRU/LFU/FIFO)
- Use for large datasets where hot data should be cached
Example Usage:
// Configure cache for memory-constrained environment
ContextBank.configureCachePolicy(
CacheConfig(
maxMemoryBytes = 50 * 1024 * 1024,
maxEntries = 100,
evictionPolicy = EvictionPolicy.LRU
)
)
// Store large context to disk only
val largeContext = ContextWindow()
// ... populate ...
ContextBank.emplace("large-data", largeContext, StorageMode.DISK_ONLY)
// Access when needed (loads from disk)
val loaded = ContextBank.getContextFromBank("large-data")
// Monitor cache performance
val stats = ContextBank.getCacheStatistics()
println("Cache hit rate: ${stats.cacheHitRate}")
Next Steps
- ContextLock API - Continue into context access control.