Configuration System
March 13, 2026 · View on GitHub
Version: 2.3
Last Updated: December 28, 2025
Location: Sources/ConfigurationSystem/
Overview
The Configuration System provides centralized management of all SAM settings, preferences, and runtime configuration. It replaces UserDefaults with a robust JSON-based configuration system, ensuring atomic writes, proper backups, and organized storage of all application state.
Key Responsibilities:
- File-based configuration management (JSON)
- Working directory configuration
- System prompt management
- Application preferences
- Endpoint configuration
- Performance monitoring
- Build-time configuration
Design Philosophy:
- JSON-first (no UserDefaults except for simple flags)
- Atomic writes with temp file → rename pattern
- Organized directory structure in Application Support
- Codable-based type safety
- Centralized configuration access
Directory Structure
~/Library/Application Support/SAM/
├── conversations/ # Conversation JSON files
│ ├── backups/ # Automatic backups
│ ├── {UUID}/ # Per-conversation directories
│ │ ├── conversation.json
│ │ ├── tasks.json
│ │ └── .vectorrag/
│ └── active-conversation.json # Current conversation ID
├── system-prompts/ # System prompt templates
├── endpoints/ # API endpoint configurations
├── preferences/ # Application preferences
└── backups/ # Configuration backups
Core Components
ConfigurationManager
File: ConfigurationManager.swift
Type: Singleton (@MainActor)
Purpose: Central hub for all file-based configuration operations
Key Features:
- Generic save/load for any Codable type
- Atomic write operations (temp → rename)
- Directory structure management
- File existence checking
- Configuration deletion
- Automatic directory creation
Public Interface:
@MainActor
public class ConfigurationManager: ObservableObject {
public static let shared = ConfigurationManager()
// Directory URLs
public let configurationDirectory: URL // ~/Library/Application Support/SAM/
public let conversationsDirectory: URL // .../conversations/
public let systemPromptsDirectory: URL // .../system-prompts/
public let endpointsDirectory: URL // .../endpoints/
public let preferencesDirectory: URL // .../preferences/
public let backupsDirectory: URL // .../backups/
// Generic configuration operations
func save<T: Codable>(_ object: T, to filename: String, in directory: URL) throws
func load<T: Codable>(_ type: T.Type, from filename: String, in directory: URL) throws -> T
func exists(_ filename: String, in directory: URL) -> Bool
func delete(_ filename: String, in directory: URL) throws
func listFiles(in directory: URL, withExtension ext: String) throws -> [String]
}
Atomic Write Pattern:
// 1. Write to temporary file
let tempURL = fileURL.appendingPathExtension("tmp")
try data.write(to: tempURL)
// 2. Atomic rename (replaces existing file)
_ = try FileManager.default.replaceItem(
at: fileURL,
withItemAt: tempURL,
backupItemName: nil,
options: [],
resultingItemURL: nil
)
Why Atomic Writes:
- Prevents corruption if app crashes during write
- Ensures file is always in valid state
- OS-level atomic operation (all or nothing)
WorkingDirectoryConfiguration
File: WorkingDirectoryConfiguration.swift
Type: Singleton (ObservableObject)
Purpose: Manage conversation working directory paths
Key Features:
- Configurable base path (default:
~/SAM) - Per-conversation subdirectories
- Path normalization and validation
- Automatic directory creation
- Persistent storage via UserDefaults
Public Interface:
public class WorkingDirectoryConfiguration: ObservableObject {
public static let shared = WorkingDirectoryConfiguration()
@Published public private(set) var basePath: String // e.g., "~/SAM"
public var expandedBasePath: String // Expands ~ to full path
func updateBasePath(_ newPath: String)
func resetToDefault()
func buildPath(subdirectory: String) -> String
}
Usage Example:
let config = WorkingDirectoryConfiguration.shared
let workDir = config.buildPath(subdirectory: "My Conversation")
// Result: "/Users/andrew/SAM/My Conversation/"
Path Building Logic:
- Replace
/in subdirectory name with-(safety) - Append to base path
- Expand
~to full user path - Ensure trailing slash
EndpointConfigurationManager
File: EndpointConfigurationManager.swift
Type: Singleton (@MainActor)
Purpose: Manage API endpoint configurations
Key Responsibilities:
- Load/save endpoint configurations
- Validate endpoint settings
- Provide default configurations
- Handle multiple providers (OpenAI, Anthropic, GitHub, etc.)
Configuration Structure:
public struct EndpointConfiguration: Codable, Identifiable {
public let id: UUID
public var name: String
public var provider: String // "openai", "anthropic", "github_copilot"
public var baseURL: String?
public var apiKey: String?
public var defaultModel: String?
public var isActive: Bool
public var customHeaders: [String: String]?
}
Public Interface:
@MainActor
public class EndpointConfigurationManager: ObservableObject {
public static let shared = EndpointConfigurationManager()
@Published public var endpoints: [EndpointConfiguration] = []
@Published public var activeEndpoint: EndpointConfiguration?
func loadEndpoints() async throws
func saveEndpoints() async throws
func addEndpoint(_ endpoint: EndpointConfiguration) async throws
func updateEndpoint(_ endpoint: EndpointConfiguration) async throws
func deleteEndpoint(id: UUID) async throws
func setActiveEndpoint(_ endpoint: EndpointConfiguration) async throws
}
SystemPromptConfiguration
File: SystemPromptConfiguration.swift
Purpose: Define system prompt structure and build SAM's comprehensive system prompt
Prompt Structure:
public struct SystemPromptConfiguration: Codable, Identifiable {
public let id: UUID
public var name: String
public var content: String
public var priority: Int // Determines load order
public var isActive: Bool
public var category: String? // e.g., "personality", "tools", "safety"
public var variables: [String: String]? // Template variables
}
Manager Implementations:
Two implementations exist:
- SimpleSystemPromptManager - Basic string-based prompts
- EnhancedSystemPromptManager - Component-based system with priority
Key System Prompt Sections (December 2025 Updates):
Multi-Step Task Guidance: The system prompt includes todo workflow instructions:
- "MUST use the todo_operations tool to plan and track progress"
- "ALWAYS mark exactly ONE todo 'in-progress' before starting work"
- "ALWAYS mark a todo 'completed' immediately after finishing"
- "Update todos frequently - the user sees your progress through the todo list"
Workflow Continuation Protocol:
The buildWorkflowContinuationProtocol() function provides mandatory status signal guidance:
- Status signals are MANDATORY for any multi-step task (with OR without todo list)
- Format:
{"status": "continue"}for ongoing,{"status": "complete"}for done - Prevents agents from ending without explicit continuation or completion
XML Tag Structure (for Claude/Anthropic models):
When usesXMLTags is true, the system prompt uses proper layering:
<instructions>- Main system prompt<context>- Todo state and user query- Model-specific adjustments for optimal behavior
ApplicationPreferencesManager
File: ApplicationPreferencesManager.swift
Type: Singleton (@MainActor)
Purpose: Manage application-wide preferences
Preferences Structure:
public struct ApplicationPreferences: Codable {
// UI Preferences
public var theme: Theme
public var fontSize: Double
public var showLineNumbers: Bool
// Voice Preferences
public var voiceEnabled: Bool
public var voiceLanguage: String
// Model Preferences
public var defaultModel: String?
public var temperatureDefault: Double
// Performance
public var enablePerformanceMonitoring: Bool
public var maxMemoryUsage: Int64
}
PerformanceMonitor
File: PerformanceMonitor.swift
Purpose: Runtime performance tracking and reporting
Monitored Metrics:
- CPU usage (user + system time)
- Memory usage (resident size)
- GPU utilization (via Metal)
- Model load times
- Inference latency
- Token generation rate
Public Interface:
@MainActor
public class PerformanceMonitor: ObservableObject {
@Published public var cpuUsage: Double = 0.0
@Published public var memoryUsage: Int64 = 0
@Published public var gpuUtilization: Double = 0.0
func startMonitoring()
func stopMonitoring()
func recordModelLoad(modelId: String, duration: TimeInterval)
func recordInference(duration: TimeInterval, tokens: Int)
}
ModelConfigurationManager
File: ModelConfiguration.swift
Type: Singleton
Purpose: Centralized model metadata and configuration system
Key Features:
- Model Metadata: Context windows, prompt formats, provider defaults
- Pricing Information: Cost per million tokens (input/output)
- Format Specifications: Delta mode, XML tags, stateful markers
- Runtime Configuration: Provider-specific behavior settings
ModelConfig Structure:
public struct ModelConfig: Codable {
public let provider: String // "anthropic", "openai", etc.
public let promptFormat: PromptFormat // System prompt, XML tags
public let providerDefaults: ModelProviderDefaults // Delta mode, streaming
public let supportsStatefulMarker: Bool // For conversational state
public let contextWindow: Int // Maximum context tokens
public let requiresAlternatingMessages: Bool // Claude requirement
// Pricing (per million tokens)
public let costPerMillionInputTokens: Double?
public let costPerMillionOutputTokens: Double?
/// Computed cost display string
public var costDisplayString: String {
// Returns: "0x" (free), "\$0.10/\$0.40" (paid), or "-" (unknown)
}
}
public struct PromptFormat: Codable {
public let systemPromptKey: String // "system", "systemInstruction"
public let useXMLTags: Bool // Claude optimization
}
public struct ModelProviderDefaults: Codable {
public let deltaMode: String // "cumulative" or "incremental"
public let supportsStreaming: Bool
}
Storage Location:
Sources/ConfigurationSystem/Resources/model_config.json
Configuration File Structure:
{
"model_configurations": {
"gemini-2.5-pro": {
"provider": "gemini",
"prompt_format": {
"system_prompt_key": "systemInstruction",
"use_xml_tags": false
},
"provider_defaults": {
"delta_mode": "cumulative",
"supports_streaming": true
},
"supports_stateful_marker": false,
"context_window": 2097152,
"requires_alternating_messages": false,
"cost_per_million_input_tokens": 1.25,
"cost_per_million_output_tokens": 10.0
}
}
}
Public Interface:
public class ModelConfigurationManager {
public static let shared = ModelConfigurationManager()
/// Get configuration for model (tries exact match, then base name)
public func getConfiguration(for modelName: String) -> ModelConfig?
/// Get context window size
public func getContextWindow(for modelName: String) -> Int?
/// Get cost display string (e.g., "\$0.10/\$0.40", "0x")
public func getCostDisplayString(for modelName: String) -> String?
}
Pricing Display Format:
The cost display uses a smart formatting system:
- Free models:
"0x"(zero multiplier) - Paid models:
"\$0.10/\$0.40"(input/output per million tokens) - Unknown:
nilor"-"
Cost formatting rules:
- Whole numbers:
$2/\$12(no decimals) - Decimals < 1:
$0.10/\$0.40(two decimals) - Decimals ≥ 1:
$1.25/\$10.0(one decimal)
Supported Models:
The system includes configurations for:
- Gemini Models: gemini-2.5-pro, gemini-2.5-flash, gemini-2.0-flash, etc.
- Claude Models: claude-3-opus, claude-3-sonnet, claude-3-haiku
- OpenAI Models: gpt-4, gpt-3.5-turbo, etc.
- Other Providers: DeepSeek, GitHub Copilot models
Usage Example:
// Get cost for display in UI
let manager = ModelConfigurationManager.shared
if let cost = manager.getCostDisplayString(for: "gemini-2.5-pro") {
print("Cost: \(cost)/1M") // "Cost: \$1.25/\$10.0/1M"
}
// Get context window
if let contextSize = manager.getContextWindow(for: "gemini-2.5-flash") {
print("Context: \(contextSize) tokens") // "Context: 1048576 tokens"
}
Fallback Strategy:
When querying model configurations:
- Try exact model name match (e.g.,
"gemini/gemini-2.5-pro") - Try base model name (strip provider prefix:
"gemini-2.5-pro") - Return
nilif not found
This allows flexible lookups regardless of provider prefix.
BuildConfiguration
File: BuildConfiguration.swift
Purpose: Compile-time configuration flags
Configuration Flags:
public enum BuildConfiguration {
public static var isDebug: Bool {
#if DEBUG
return true
#else
return false
#endif
}
public static var isRelease: Bool {
!isDebug
}
}
Other Components
AIInstructionsScanner
Purpose: Scan for .github/copilot-instructions.md and similar AI context files
LocationManager
Purpose: Manage geographic location data (if needed for location-aware features)
PersonalityManager & PersonalityTrait
Purpose: Manage AI personality configurations and behavioral traits
PromptComponentLibrary
Purpose: Reusable prompt components for system prompt composition
ToolResultStorage
Purpose: Store and retrieve tool execution results
TodoReminderInjector
Purpose: Inject todo list context into conversation messages
File: TodoReminderInjector.swift
Provides todo list state injection for multi-step workflows, ensuring agents maintain awareness of task progress.
Key Features:
- Injects on EVERY request when todos exist (not periodically)
- Progress rules included to remind agent to update todos
- Conversation-scoped via effectiveScopeId
- Stateless design for thread safety
Injection Trigger:
// Returns true when todos exist for this conversation
func shouldInjectReminder(
conversationId: UUID?,
effectiveScopeId: UUID?,
responseCount: Int
) async -> Bool {
let todos = TodoManager.shared.getTodos(scopeId: effectiveScopeId ?? conversationId)
return !todos.isEmpty
}
Reminder Format:
<todo_context>
## Current Todo List State
[Status counts: X completed, Y in-progress, Z not-started]
[Formatted todo items with status and descriptions]
## Progress Rules (CRITICAL)
- Before beginning ANY todo: mark it in-progress FIRST
- After completing ANY todo: mark it completed IMMEDIATELY
- Update todos FREQUENTLY - user sees progress through the list
- ONE todo can be in-progress at a time
</todo_context>
Usage in System Prompt: The reminder is injected as context immediately before the user's message for maximum salience.
Configuration Patterns
Loading Configuration
let config = try ConfigurationManager.shared.load(
MyConfiguration.self,
from: "my-config.json",
in: ConfigurationManager.shared.preferencesDirectory
)
Saving Configuration
try ConfigurationManager.shared.save(
myConfig,
to: "my-config.json",
in: ConfigurationManager.shared.preferencesDirectory
)
Checking Existence
if ConfigurationManager.shared.exists(
"my-config.json",
in: ConfigurationManager.shared.preferencesDirectory
) {
// Load existing config
} else {
// Create default config
}
Error Handling
public enum ConfigurationError: LocalizedError {
case saveFailure(String)
case loadFailure(String)
case fileNotFound(String)
case deleteFailure(String)
case invalidConfiguration(String)
public var errorDescription: String? {
switch self {
case .saveFailure(let message):
return "Failed to save configuration: \(message)"
case .loadFailure(let message):
return "Failed to load configuration: \(message)"
case .fileNotFound(let path):
return "Configuration file not found: \(path)"
case .deleteFailure(let message):
return "Failed to delete configuration: \(message)"
case .invalidConfiguration(let message):
return "Invalid configuration: \(message)"
}
}
}
Data Flow
flowchart TB
App[Application] -->|Load Config| CM[ConfigurationManager]
CM -->|Read JSON| FS[File System]
FS -->|Return Data| CM
CM -->|Decode| Config[Configuration Object]
Config -->|Update| App
App -->|Save Config| CM
CM -->|Encode| JSON[JSON Data]
CM -->|Write Temp| Temp[temp.json.tmp]
CM -->|Atomic Rename| Final[config.json]
WDC[WorkingDirectoryConfiguration] -->|Build Path| Path[Conversation Path]
Path -->|Create Directory| FS
style CM fill:#4A90E2
style Config fill:#7ED321
style Temp fill:#F5A623
style Final fill:#50E3C2
Integration with Other Subsystems
ConversationEngine
- Loads/saves conversation JSON files via ConfigurationManager
- Uses WorkingDirectoryConfiguration for conversation directories
- Stores conversation backups in
backups/directory
APIFramework
- Uses EndpointConfigurationManager for provider settings
- Stores API keys and endpoint configurations
- Validates configurations before API calls
MCPFramework
- Stores tool configurations
- Manages MCP server connection settings
- Persists tool execution results via ToolResultStorage
Best Practices
1. Always Use ConfigurationManager
// ❌ WRONG: Direct UserDefaults
UserDefaults.standard.set(value, forKey: "my-setting")
// ✅ RIGHT: ConfigurationManager with Codable type
try ConfigurationManager.shared.save(
settings,
to: "settings.json",
in: ConfigurationManager.shared.preferencesDirectory
)
2. Define Codable Structs
// Define type-safe configuration
struct MySettings: Codable {
var option1: String
var option2: Int
}
3. Handle Errors Gracefully
do {
let config = try ConfigurationManager.shared.load(...)
} catch ConfigurationError.fileNotFound {
// Use defaults
let config = MySettings.default
} catch {
logger.error("Failed to load config: \(error)")
}
4. Validate After Loading
let config = try ConfigurationManager.shared.load(...)
guard config.isValid() else {
throw ConfigurationError.invalidConfiguration("Missing required fields")
}
File Locations
Application Support
~/Library/Application Support/SAM/
├── conversations/ # Managed by ConversationManager
├── system-prompts/ # System prompt templates
├── endpoints/ # API endpoint configs
├── preferences/ # App preferences
└── backups/ # Automatic backups
User Defaults (Minimal Use)
Only for simple boolean flags:
voiceListeningModevoiceSpeakingModeworkingDirectory.basePath
Everything else uses JSON files via ConfigurationManager
Future Enhancements
Planned
- Configuration versioning and migration
- Configuration validation schemas
- Import/export of configurations
- Configuration profiles (dev, prod, test)
- Encrypted configuration storage for sensitive data
Under Consideration
- Remote configuration sync
- Configuration templates
- Configuration change notifications
- Configuration history/versioning
See Also
- API Framework - Uses endpoint configurations
- Conversation Engine - Uses conversation storage
- Shared Topics - Shared topic models
- Working Directory Flow