Skip to main content

Shell Development Guide

Learn how to create platform-specific shells that host Soul Kernel on new devices and platforms.

Overview

A Shell is a platform-specific application that:
  • Embeds or connects to Soul Kernel
  • Provides native UI/UX
  • Manages platform resources
  • Handles device capabilities

Architecture Patterns

1. Embedded Pattern (Mobile, Edge)

The kernel runs in-process with the shell:
┌─────────────────┐
│   Shell App     │
│  ┌───────────┐  │
│  │Soul Kernel│  │
│  └───────────┘  │
└─────────────────┘

2. Service Pattern (Desktop, Server)

The kernel runs as a separate service:
┌─────────────┐     gRPC      ┌──────────────┐
│  Shell UI   │◄─────────────►│ Soul Service │
└─────────────┘               └──────────────┘

3. Distributed Pattern (Cloud, IoT)

Multiple shells connect to shared kernel:
┌────────┐  ┌────────┐  ┌────────┐
│Mobile  │  │  Web   │  │Physical│
└───┬────┘  └───┬────┘  └───┬────┘
    └───────────┼───────────┘

         ┌─────────────┐
         │Soul Cluster │
         └─────────────┘

Creating a New Shell

Step 1: Choose Integration Method

Option A: Native Binding (Rust FFI)

Best for: Mobile apps, embedded systems
// Create C bindings
#[no_mangle]
pub extern "C" fn soul_init() -> *mut SoulKernel {
    Box::into_raw(Box::new(SoulKernel::new()))
}

#[no_mangle]
pub extern "C" fn soul_ask(
    kernel: *mut SoulKernel,
    query: *const c_char
) -> *mut c_char {
    // Implementation
}

Option B: gRPC Client

Best for: Desktop apps, web services
// soul_service.proto
service SoulService {
    rpc Initialize(InitRequest) returns (InitResponse);
    rpc Ask(AskRequest) returns (AskResponse);
    rpc Remember(RememberRequest) returns (RememberResponse);
    rpc StreamEvents(EventRequest) returns (stream Event);
}

Option C: WebAssembly

Best for: Web browsers, sandboxed environments
// Compile with wasm-pack
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct WebSoul {
    kernel: SoulKernel,
}

#[wasm_bindgen]
impl WebSoul {
    pub fn new() -> Self {
        Self { kernel: SoulKernel::new() }
    }
    
    pub fn ask(&mut self, query: &str) -> String {
        self.kernel.ask(query)
    }
}

Step 2: Implement Core Features

1. Initialization

// Example: React Native Shell
import { NativeModules } from 'react-native';

const { SoulBridge } = NativeModules;

export class Soul {
    private initialized = false;
    
    async initialize(config: SoulConfig) {
        if (this.initialized) return;
        
        await SoulBridge.initialize({
            modelPath: config.modelPath,
            memoryPath: config.memoryPath,
            skills: config.enabledSkills,
        });
        
        this.initialized = true;
    }
}

2. Communication

// Example: iOS Shell
class SoulManager: ObservableObject {
    @Published var responses: [SoulResponse] = []
    private let kernel: OpaquePointer
    
    func ask(_ query: String) async throws -> String {
        return try await withCheckedThrowingContinuation { continuation in
            soul_ask_async(kernel, query) { response, error in
                if let error = error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume(returning: response)
                }
            }
        }
    }
}

3. Memory Sync

// Example: Android Shell
class SoulSyncManager(context: Context) {
    private val syncWorker = WorkManager.getInstance(context)
    
    fun scheduleSync() {
        val syncRequest = PeriodicWorkRequestBuilder<SoulSyncWorker>(
            15, TimeUnit.MINUTES
        ).setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()
        ).build()
        
        syncWorker.enqueueUniquePeriodicWork(
            "soul-sync",
            ExistingPeriodicWorkPolicy.KEEP,
            syncRequest
        )
    }
}

Step 3: Platform Integration

iOS Specific

// Handle background processing
class SoulBackgroundTask {
    func scheduleAppRefresh() {
        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "io.soulkernel.sync",
            using: nil
        ) { task in
            self.handleBackgroundSync(task)
        }
    }
    
    private func handleBackgroundSync(_ task: BGTask) {
        Soul.shared.sync { success in
            task.setTaskCompleted(success: success)
        }
    }
}

Android Specific

// Handle lifecycle
class SoulLifecycleObserver : DefaultLifecycleObserver {
    override fun onStart(owner: LifecycleOwner) {
        Soul.instance.resume()
    }
    
    override fun onStop(owner: LifecycleOwner) {
        Soul.instance.pause()
    }
}

Unity Specific

// Handle Unity lifecycle
public class SoulBehaviour : MonoBehaviour {
    private SoulClient client;
    
    void Awake() {
        DontDestroyOnLoad(gameObject);
        client = new SoulClient();
    }
    
    void OnApplicationPause(bool pauseStatus) {
        if (pauseStatus) {
            client.Pause();
        } else {
            client.Resume();
        }
    }
}

Step 4: UI/UX Guidelines

Conversation Interface

// Example: React component
function SoulChat() {
    const [messages, setMessages] = useState([]);
    const [input, setInput] = useState('');
    const soul = useSoul();
    
    const sendMessage = async () => {
        const userMessage = { role: 'user', content: input };
        setMessages([...messages, userMessage]);
        
        const response = await soul.ask(input);
        const soulMessage = { role: 'soul', content: response };
        setMessages([...messages, userMessage, soulMessage]);
        
        setInput('');
    };
    
    return (
        <ChatContainer>
            <MessageList messages={messages} />
            <InputBar 
                value={input}
                onChange={setInput}
                onSubmit={sendMessage}
            />
        </ChatContainer>
    );
}

Status Indicators

// Show Soul state
enum SoulState {
    case initializing
    case ready
    case thinking
    case syncing
    case error(String)
}

struct SoulStatusView: View {
    @ObservedObject var soul: Soul
    
    var body: some View {
        HStack {
            statusIcon
            Text(statusText)
                .foregroundColor(statusColor)
        }
    }
    
    var statusIcon: some View {
        switch soul.state {
        case .thinking:
            return ProgressView()
        case .syncing:
            return Image(systemName: "arrow.triangle.2.circlepath")
        // ...
        }
    }
}

Testing Your Shell

Unit Tests

@Test
fun testSoulInitialization() {
    val soul = Soul(mockContext)
    soul.initialize()
    
    assertTrue(soul.isReady)
    assertNotNull(soul.version)
}

Integration Tests

func testSoulConversation() async throws {
    let soul = try await Soul.test()
    
    let response = try await soul.ask("Hello")
    XCTAssertFalse(response.isEmpty)
    XCTAssertTrue(response.contains("Hello"))
}

UI Tests

// Cypress example
describe('Soul Chat', () => {
    it('responds to user input', () => {
        cy.visit('/chat');
        cy.get('[data-testid=chat-input]').type('Hello Soul');
        cy.get('[data-testid=send-button]').click();
        
        cy.get('[data-testid=message-list]')
            .should('contain', 'Hello Soul')
            .and('contain', 'Hello!');
    });
});

Performance Optimization

1. Lazy Loading

// Load kernel only when needed
class LazyKernel {
    private kernel?: SoulKernel;
    
    async getKernel(): Promise<SoulKernel> {
        if (!this.kernel) {
            this.kernel = await import('./soul-kernel');
        }
        return this.kernel;
    }
}

2. Response Caching

class CachedSoul {
    private let cache = NSCache<NSString, SoulResponse>()
    
    func ask(_ query: String) async -> String {
        let key = query as NSString
        
        if let cached = cache.object(forKey: key) {
            return cached.text
        }
        
        let response = await soul.ask(query)
        cache.setObject(response, forKey: key)
        return response.text
    }
}

3. Batch Operations

class BatchedSoul {
    private val pendingQueries = mutableListOf<Query>()
    
    fun askBatched(query: String): Deferred<String> {
        val deferred = CompletableDeferred<String>()
        pendingQueries.add(Query(query, deferred))
        
        if (pendingQueries.size >= BATCH_SIZE) {
            processBatch()
        }
        
        return deferred
    }
}

Distribution

Mobile App Stores

# iOS App Store requirements
- Binary size < 200MB uncompressed
- Privacy manifest for AI features
- Age rating considerations
- Export compliance (encryption)

# Google Play requirements
- APK/AAB size limits
- Privacy policy URL
- Permissions justification
- Content rating

Desktop Distribution

# Example: Tauri config
[tauri]
bundle.identifier = "io.soulkernel.desktop"
bundle.icon = ["icons/icon.png"]
bundle.resources = ["models/*", "skills/*"]

[tauri.bundle.macos]
frameworks = ["SoulKernel.framework"]

Web Deployment

// Service worker for offline support
self.addEventListener('install', (event) => {
    event.waitUntil(
        caches.open('soul-v1').then((cache) => {
            return cache.addAll([
                '/soul-kernel.wasm',
                '/soul-kernel.js',
                '/models/base.onnx',
            ]);
        })
    );
});

Troubleshooting

Common Issues

  1. Kernel Won’t Initialize
    • Check file paths
    • Verify permissions
    • Ensure sufficient memory
  2. Slow Response Times
    • Profile kernel calls
    • Check network latency
    • Optimize model size
  3. Memory Leaks
    • Use proper cleanup
    • Monitor memory usage
    • Test lifecycle events

Debug Tools

# Enable verbose logging
export RUST_LOG=soul_kernel=debug

# Profile performance
cargo flamegraph --bin soul-shell

# Monitor memory
valgrind --leak-check=full ./soul-shell

Next Steps