Tcl Apple Intelligence Interface

index
Login

tclai-apple

A Tcl extension that bridges Apple's Foundation Models framework (the on-device LLM powering Apple Intelligence, macOS 26+) to Tcl. Built with practcl using its Swift language support.

Requirements

Building

cd deps/sobyk-src/tclai-apple
tclsh make.tcl library

This produces libapplefm1.0.dylib and pkgIndex.tcl in the current directory. The build uses practcl to compile the Swift source via swiftc -emit-library, linking against the system FoundationModels and Foundation frameworks.

Installation

tclsh make.tcl install /usr/local/lib/tcl

Or manually copy libapplefm1.0.dylib and pkgIndex.tcl to a directory on your auto_path.

Usage

Loading

load ./libapplefm1.0.dylib Applefm

Or via package require (if installed on auto_path):

package require applefm

Checking availability

applefm::availability
# → "available"

applefm::available
# → 1 (or 0 if unavailable)

availability returns one of: - available — the model is ready - unavailable apple_intelligence_not_enabled — Apple Intelligence is off - unavailable model_not_ready — model not yet downloaded - unavailable unknown — other

Synchronous completion

set reply [applefm::respond "Say hello in one word"]
puts $reply

applefm::respond blocks the Tcl thread until the model responds. Optional -instructions sets a system prompt:

set reply [applefm::respond "Describe this scene" -instructions "You are a dungeon master"]

Asynchronous completion (promise/future)

For non-blocking use — especially from coroutines — use the promise pattern:

# Start the request, get a token immediately
set token [applefm::ask "Say hello in one word"]

# ... do other work ...

# Wait for the result (yields to event loop, coroutine-safe)
set result [applefm::wait $token]
puts $result

The low-level commands:

Multi-turn conversations (sessions)

The one-shot commands (respond, ask) create a fresh LanguageModelSession each call, so the model sees no prior context. For multi-turn conversations, create a session — the session holds a LanguageModelSession that maintains a transcript across calls.

# Create a session with optional system instructions
set sid [applefm::session create -instructions "You are a concise assistant"]

# Synchronous multi-turn
puts [applefm::session respond $sid "My name is Bob."]
puts [applefm::session respond $sid "What is my name?"]
# → "Bob."

# Asynchronous multi-turn
set tok [applefm::session ask $sid "What did I tell you?"]
puts [applefm::session wait $tok]
# → "You told me your name is Bob."

# Clean up when done
applefm::session destroy $sid

Session commands: - applefm::session create ?-instructions $sys? — creates a session, returns a session id (e.g. session0) - applefm::session respond $sessionId $prompt — synchronous completion with conversation history - applefm::session ask $sessionId $prompt — async: returns a token, starts a Task - applefm::session wait $token — polls until ready (coroutine-safe), returns result - applefm::session set-transcript $sessionId $entries — inject arbitrary conversation history - applefm::session get-transcript $sessionId — read back the full transcript - applefm::session destroy $sessionId — destroys the session and frees its resources

Transcript injection

For harnesses that manage context externally (compaction, summarization, background material), set-transcript replaces the session's entire conversation history. The format is a flat list of {role text} pairs:

applefm::session set-transcript $sid {
    system    "You are a concise assistant."
    user      "My name is Alice and I like turtles."
    assistant "Nice to meet you, Alice!"
}

# The model now sees the injected history
puts [applefm::session respond $sid "What do I like?"]
# → "You like turtles."

# Read back the full transcript (including new turns)
foreach {role text} [applefm::session get-transcript $sid] {
    puts "$role: $text"
}

Roles: system, user, assistant, reasoning, toolcall, tooloutput. UUIDs are auto-generated.

Coroutine example

coroutine chat apply {{} {
    set sid [applefm::session create -instructions "You are a helpful assistant"]
    set tok [applefm::session ask $sid "What is the capital of France?"]
    set answer [applefm::session wait $tok]
    puts "Answer: $answer"
    applefm::session destroy $sid
}}
vwait forever

Architecture

The promise pattern

The async path avoids all cross-thread Tcl API calls. The flow:

  1. applefm::ask creates a token (integer) via a thread-safe ResultStore, starts a Swift Task, and returns the token immediately.
  2. The Swift Task runs LanguageModelSession.respond(to:) on Swift's cooperative thread pool. When done, it writes the result to the ResultStore (protected by NSLock).
  3. applefm::wait polls applefm::ready in a loop using after 10 + vwait, yielding back to the Tcl event loop. This makes it safe to call from coroutines.
  4. applefm::get retrieves and deletes the result from the store.

No Tcl_Eval, Tcl_AsyncMark, or event queue manipulation happens from the Swift thread. The Swift side is pure computation; the Tcl side handles all event loop integration.

Session store

For multi-turn conversations, LanguageModelSession instances are held in a SessionStore — a thread-safe dictionary keyed by integer id, mirroring the ResultStore pattern. The Tcl side references sessions by string tokens (session0, session1, …). Reusing the same session across calls gives the model the full conversation transcript, enabling context-aware follow-up questions.

Practcl integration

The extension is built entirely with practcl's build system:

Swift toolset for practcl

This project drove the addition of Swift language support to practcl:

Commands reference

Command Args Description
applefm::availability Returns availability status string
applefm::available Returns 1 if available, 0 otherwise
applefm::respond prompt ?-instructions sys? Synchronous completion (blocks)
applefm::ask prompt ?-instructions sys? Async: returns token, starts Task
applefm::ready token Returns 1 if result is available
applefm::get token Retrieves result, deletes token
applefm::wait token High-level: polls until ready, returns result
applefm::session create ?-instructions sys? Creates a session, returns session id
applefm::session respond sessionId prompt Sync completion with conversation history
applefm::session ask sessionId prompt Async: returns token, starts Task
applefm::session wait token Polls until ready (coroutine-safe)
applefm::session set-transcript sessionId entries Inject arbitrary conversation history
applefm::session get-transcript sessionId Read back full transcript as {role text} pairs
applefm::session destroy sessionId Destroys the session
applefm::version Returns package version (1.0)

License

The Tcl Community License. (BSD basically.)