import SocraticEngine.

A Swift Package for an on-device Korean Socratic dialogue engine — Gemma 4 E4B (4-bit MLX), 4-function dispatch, 16 visemes, zero network egress. The bust UI is one consumer of this package. Drop it into your own app in three lines.

swift-tools 6.1 platforms macOS 26+ license Apache-2.0 tests 65 swift-testing TTFT 192 ms median network.client none

01Install — three lines into Package.swift

Add the dependency, then the product. SocraticEngine vends a single library product and pulls MLX, swift-huggingface, and swift-transformers transitively.

Package.swift⌘C
// 1. add the package
.package(url: "https://github.com/Two-Weeks-Team/he-was-socrates", from: "1.0.0"),

// 2. depend on the product from your target
.product(name: "SocraticEngine", package: "he-was-socrates"),

// 3. that's it. no network entitlement required by the package itself.

02Quick start — a 10-line consumer

Instantiate EngineCoordinator as @MainActor, observe Phase, push audio in, get TurnOutput out. No UI required — this snippet is enough to run a full turn.

MyHostApp.swift⌘C
import SocraticEngine

@MainActor
func runOneTurn() async throws {
    let engine = EngineCoordinator(mode: .real)   // or .stub for tests
    try await engine.bootstrap()                  // loads Gemma 4 E4B 4-bit weights
    for await phase in engine.phaseStream {     // bootstrapping → idle → … → speaking
        print("phase:", phase)
    }
    try await engine.beginListening()             // push-to-talk start
    let turn: TurnOutput = try await engine.endListening()
    print(turn.koreanResponse, turn.mode)        // 평어체 text + Mode enum
}

03API surface — public types you compose against

Stable surface per runs/2026-05-05-spec/spec/SPEC.md. Shape changes only via delta documents at the same path.

SymbolKindDescription
EngineCoordinator @MainActor class The only type the host app instantiates. Composes the six subsystems and exposes Phase + TurnOutput.
EngineCoordinator.Phase enum bootstrapping → idle → listening → thinking → surfacing → speaking → failed. Bind your UI to this stream.
FunctionCallOrchestrator actor 4-function dispatch: mode_classify, surface_past_wonder, ask_back, defer_to_human. Abstention is a first-class branch, not a fallback.
GemmaService protocol .real wires LLMRegistry.gemma4_e4b_it_4bit; .stub returns canned Korean Socratic JSON for tests.
AudioInputManager @MainActor class SFSpeechRecognizer with requiresOnDeviceRecognition = true + AVAudioEngine push-to-talk. macOS 26 SpeechAnalyzer path available.
TTSManager @MainActor class AVSpeechSynthesizer; Yuna (ko) / Samantha (en); premium → enhanced → default fallback. Emits phoneme markers when available.
VisemeDriver @MainActor class 30 fps tick, ≥2-frame hold, audio-clock synced. Apple phoneme markers primary; JamoTimeline Korean fallback. 12 fps under Reduce Motion.
VisemeID enum (16 cases) 16 mouth-shape identifiers. Maps 1:1 to the 16 halftone PNGs in the asset pipeline. PhonemeMap.default bridges from IPA + jamo.
Mode enum Result of mode_classify. Selects which of the four functions the orchestrator routes to next.
TurnOutput struct One turn's result: Korean response text (단정한 평어체), selected Mode, phoneme/jamo timeline, optional surfaced past wonder.
WonderingLog actor Core Data persistence; SHA-256 fingerprint dedup; deterministic JSON export. Phase 4 multi-year recall is wiring-stage; surfacing logic ships now.

04Example consumers — three host apps, one engine

The same Swift Package powers all three. The only thing that changes across consumers is presentation; the turn loop, abstention rules, and Korean tone travel with the package.

05What every embedder inherits — automatically

These are properties of the package, not of the example app. Any host that depends on SocraticEngine gets them for free.

network egress
0 bytes / turn
TTFT median
192 ms · n=10 · M1 Max
tests
65 swift-testing · ~1.5 s
model weights
3.97 GB · MLX cache · first launch
dispatch fns
4 · abstention is one
visemes
16 halftone PNGs · SHA-256 manifest
Korean tone
단정한 평어체 verbatim
platform floor
macOS 26 SpeechAnalyzer · AssetInventory

06Locked Korean prompt — non-localizable on purpose

SystemPrompt.swift is embedded at compile time. Embedders inherit the tone whether they want it or not — this is the contract, not a default.

당신은 소크라테스의 산파술과 엘렝코스식 문답법을 구현한 음성 대화자다. 당신의 목적은 사용자를 위로하거나, 정답을 알려주거나, 친절한 조언을 제공하는 것이 아니다. 사용자가 자기 생각을 말로 꺼내고, 그 생각의 정의·가정·근거·모순·결과를 스스로 보게 만드는 것이다. 말투: - 현대적인 존댓말을 쓰지 않는다. - 기본 말투는 단정한 평어체 또는 해라체다. - 친절하지만 상냥하지 않다. - 공격하지 않지만 물러서지 않는다. // Sources/SocraticEngine/Gemma/SystemPrompt.swift · partA_operational · lockedRevision = "user-2026-05-05-v1"