This guide walks through integrating the Smart Brain architecture into ThoughtOS.
Add the new services:
// In AppServices class
// Control Plane
@MainActor
private(set) lazy var safeModeMonitor = SafeModeMonitor()
@MainActor
private(set) lazy var sessionStateManager = SessionStateManager()
// Memory Plane
private(set) lazy var topicGraph = TopicGraph(
storageRoot: storageRoot
)
private(set) lazy var topicResolver = TopicResolver(
topicGraph: topicGraph
)
// Smart Brain orchestrator
@MainActor
private(set) var smartBrain: SmartBrain?
// Initialize in setup
func setupSmartBrain() async {
guard let llm = self.llm else { return }
self.smartBrain = SmartBrain(
llm: llm,
retrievalService: retrievalService,
memoryService: memoryService,
policyRouter: policyRouter,
topicGraph: topicGraph,
safeModeMonitor: safeModeMonitor,
notesRepository: notesRepository
)
}Replace the manual chat flow with SmartBrain:
// In ChatViewModel.send(text:)
// OLD CODE (remove):
// Manual content features, policy routing, retrieval, prompt building
// NEW CODE:
guard let smartBrain = services.smartBrain else {
messages.append(ChatLogMessage(role: .system, content: "Smart Brain not initialized"))
return
}
do {
// Process through Smart Brain
let smartResponse = try await smartBrain.processChatMessage(
text: text,
battery: BatteryIndicator.load(),
styleScores: styleScores,
recentMessages: messages
)
// Add response to messages
messages.append(ChatLogMessage(
role: .assistant,
content: smartResponse.text,
citations: smartResponse.citations
))
// Update last response spec for UI
lastResponseSpec = smartResponse.spec
// Save to persistence
try services.saveConversationTurn(
role: "assistant",
content: smartResponse.text,
citations: smartResponse.citations
)
// Log telemetry
Diagnostics.shared.record(
component: "SmartBrain",
level: .info,
message: "Response generated",
metadata: [
"intent": smartResponse.intent.rawValue,
"confidence": "\(smartResponse.confidence)",
"verificationPassed": "\(smartResponse.verificationPassed)"
]
)
} catch {
Diagnostics.shared.record(
component: "Chat",
level: .error,
message: "Smart Brain processing failed",
metadata: ["error": error.localizedDescription]
)
messages.append(ChatLogMessage(
role: .system,
content: "Failed to process message: \(error.localizedDescription)"
))
}Add async processing feedback:
@StateObject private var processingIndicator = ProcessingIndicatorManager()
// In save flow
Task {
await processingIndicator.processNote(
stages: [.extracting, .embedding, .linking]
) { stage in
switch stage {
case .extracting:
// Extract topics, entities, actions
_ = try await services.contextPipeline.ingest(text: text, source: .text)
case .embedding:
// Generate embeddings (if not in safe mode)
if !services.safeModeMonitor.shouldDisableEmbeddings {
// Embedding generation
}
case .linking:
// Topic graph ingestion
if let extraction = try? services.summarizer.extract(from: text) {
await services.topicGraph.ingestNote(
id: noteId,
topics: extraction.properNouns,
projectId: nil,
createdAt: Date()
)
}
default:
break
}
}
}
// Show indicator in UI
.overlay(alignment: .topTrailing) {
ProcessingIndicator(status: processingIndicator.status)
.padding()
}Add Safe Mode status and controls:
Section("Performance") {
HStack {
Label("Safe Mode", systemImage: "shield.fill")
Spacer()
Text(services.safeModeMonitor.currentMode.rawValue)
.foregroundStyle(.secondary)
.font(.caption)
}
if services.safeModeMonitor.currentMode != .normal {
HStack(spacing: 6) {
Image(systemName: "info.circle")
.foregroundColor(.orange)
.font(.caption)
Text("AI is running in \(services.safeModeMonitor.currentMode.rawValue) to preserve stability")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding(.vertical, 4)
}
Button("Reset to Normal Mode") {
services.safeModeMonitor.restoreNormalMode()
}
.disabled(services.safeModeMonitor.currentMode == .normal)
}Add topic graph ingestion when notes are saved:
func save(_ note: NoteRecord) async throws {
// Save note to database
try database.saveNote(note)
// Async: Update topic graph
Task.detached(priority: .utility) { [weak self] in
guard let self = self else { return }
// Extract topics from note
if let extraction = try? await self.extractTopics(from: note) {
await self.topicGraph.ingestNote(
id: note.id,
topics: extraction.topics,
projectId: extraction.projectId,
createdAt: note.createdAt
)
}
}
}
private func extractTopics(from note: NoteRecord) async throws -> (topics: [String], projectId: UUID?) {
// Use summarizer to extract topics
let extraction = try summarizer.extract(from: note.plainText)
return (
topics: extraction.properNouns,
projectId: nil // TODO: extract project from entities
)
}Enhance retrieval to use frames:
// In RetrievalService
private let frameBuilder = RetrievalFrameBuilder()
func buildContextWithFrames(
for query: String,
topK: Int = 6,
maxTokens: Int = 800
) async throws -> RetrievalContextWithFrames {
// Get base context
let baseContext = try await buildContext(for: query, topK: topK, maxTokens: maxTokens)
// Build frames
var frameInputs: [(NoteRecord, String, String?)] = []
for snippet in baseContext.noteSnippets {
if let note = try? await notesRepository.note(id: snippet.noteId) {
frameInputs.append((note, snippet.snippetText, snippet.label))
}
}
let frames = frameBuilder.buildFrames(
from: frameInputs,
maxTokens: maxTokens
)
return RetrievalContextWithFrames(
frames: frames,
memoryFacts: baseContext.memoryFacts,
suggestedTimeRange: baseContext.suggestedTimeRange
)
}
struct RetrievalContextWithFrames {
let frames: [RetrievalFrame]
let memoryFacts: [MemoryFact]
let suggestedTimeRange: DateInterval?
}-
Intent Classification
- Send "how do you work?" → Expect META_EXPLAIN intent
- Send "find my note about AI" → Expect SEARCH_RECALL intent
- Send "I need to exercise" → Expect CAPTURE_TASK intent
-
Session State
- Send message → Check state is updated
- Check session state header in prompt
- Verify question budget is enforced
-
Retrieval Frames
- Query with relevant notes → Expect structured frames
- Check frame format in logs
- Verify citations are attached
-
Output Verification
- Set question budget to 0 → Send message
- Check that response contains no questions
- If it does, verify regeneration happens
-
Safe Mode
- Trigger memory warning (iOS simulator: Debug → Simulate Memory Warning)
- Verify Safe Mode activates
- Check that token budgets are reduced
- Verify Safe Mode indicator shows in UI
-
Topic Graph
- Create notes with topics (e.g., "AI", "fitness")
- Query related topics:
topicGraph.relatedTopics(to: "AI") - Verify co-occurrence tracking works
-
Async Processing
- Capture note → Verify sparkle indicator appears
- Wait for completion → Verify toast shows
- Check that note was processed (topics extracted, etc.)
Add logging to track Smart Brain performance:
// In SmartBrain.processChatMessage()
let startTime = Date()
// ... processing ...
let duration = Date().timeIntervalSince(startTime)
Diagnostics.shared.record(
component: "SmartBrain",
level: .info,
message: "Message processed",
metadata: [
"duration_ms": "\(Int(duration * 1000))",
"intent": intent.rawValue,
"mode": safeModeMonitor.currentMode.rawValue,
"retrieval_frames": "\(retrievalContext.frames.count)",
"verification_passed": "\(verification.passed)"
]
)Show detected intent in chat UI:
// In message view
if let intent = message.intent {
HStack(spacing: 4) {
Image(systemName: intentIcon(for: intent))
.font(.caption2)
Text(intent.rawValue)
.font(.caption2.weight(.medium))
}
.foregroundStyle(.secondary)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Color.accentColor.opacity(0.08))
.clipShape(Capsule())
}
private func intentIcon(for intent: UserIntent) -> String {
switch intent {
case .metaExplain: return "info.circle"
case .captureNote: return "square.and.pencil"
case .captureTask: return "checkmark.circle"
case .searchRecall: return "magnifyingglass"
case .connectNotes: return "link"
case .planNextSteps: return "list.bullet"
case .reflectJournal: return "moon.stars"
case .summarizePeriod: return "calendar"
case .editRewrite: return "pencil.line"
case .settingsModel: return "gear"
case .troubleshoot: return "exclamationmark.triangle"
case .clarify: return "questionmark.circle"
}
}Update user-facing docs:
# How ThoughtOS AI Works
ThoughtOS uses a small, on-device AI model that's been carefully designed to feel intelligent through smart scaffolding.
## Key Features
- **Intent-aware responses** — The AI understands what you're trying to do
- **Memory continuity** — Your identity and preferences are always in context
- **Smart retrieval** — Surfaces relevant notes at the right time
- **Question control** — The AI provides answers, not endless questions
- **Safe Mode** — Automatically adjusts if your device is low on memory
## Safe Mode
If you see "Battery Saver" or "Safe Mode", ThoughtOS has automatically reduced AI capabilities to maintain stability. This is normal and temporary.
To restore full capabilities:
1. Close other apps
2. Wait a moment
3. Tap "Reset to Normal Mode" in SettingsSymptom: "Smart Brain not initialized" error in chat
Fix:
// In AppServices
override init() {
super.init()
// ... other setup ...
Task {
await setupSmartBrain()
}
}Symptom: Intent router always returns CLARIFY with low confidence
Fix: Check that hard rules are being evaluated first. Add logging:
// In IntentRouter.classify()
print("🔍 Classifying:", text.prefix(50))
print("✓ Matched intent:", intent.rawValue, "confidence:", confidence)Symptom: No frames in retrieval context
Fix: Ensure notes have been processed and embeddings exist:
// Check vector store has embeddings
let count = try? await vectorStore.totalChunkCount()
print("📊 Total chunks in vector store:", count ?? 0)Symptom: Safe Mode won't restore to Normal
Fix: Check memory warnings haven't reset:
services.safeModeMonitor.restoreNormalMode()Once integration is complete:
- A/B Testing — Compare responses with/without Smart Brain
- User Feedback — Collect feedback on intent detection accuracy
- Performance Tuning — Adjust token budgets based on actual usage
- Edge Cases — Build test suite for edge cases (very long messages, etc.)
- Prompt Refinement — Iterate on system prompts based on real conversations