Skip to content

feat(manifold-tools): add llama and mlx local backend support#1763

Closed
roryford wants to merge 6 commits into
mainfrom
fix/voice-mainactor-isolation-crash
Closed

feat(manifold-tools): add llama and mlx local backend support#1763
roryford wants to merge 6 commits into
mainfrom
fix/voice-mainactor-isolation-crash

Conversation

@roryford

@roryford roryford commented Jun 12, 2026

Copy link
Copy Markdown
Owner

What does this change?

Adds local backend support to manifold-tools for llama (GGUF) and mlx (safetensors snapshots), plus --model-path for explicit model selection. It also incorporates latest main by resolving the merge conflict in Package.swift without changing the intended feature behavior.

Motivation

manifold-tools needed parity with local backend workflows so scenarios can be exercised against GGUF and MLX models, not only Ollama/mock paths. This also keeps the PR mergeable after upstream trait/config changes landed on main.

Release Note

  • manifold-tools: add local llama and mlx backend support with model auto-discovery and --model-path override; preserve trait-gated behavior for optional backend builds.

Testing

  • Attempted: swift build --target manifold-tools --traits Llama,MLX,Ollama
  • Attempted: swift build --target manifold-tools --disable-default-traits
  • Verified merge conflict resolution completed cleanly (no unresolved conflicts after merge)
  • Note: this runner hit environment/platform build limits (MachO / Security / os modules unavailable), so full local backend compile validation could not complete here.

Sabotage evidence (regression-test PRs only)

  • N/A (not a regression-test PR)
  • Reverted the fix / broke the path under test
  • Confirmed the new test went RED (log pasted or linked below)
  • Re-applied the fix and confirmed GREEN

Checklist

  • Tests added or updated for new behaviour
  • Public API changes have /// doc comments
  • No hardcoded secrets, API keys, or personal data
  • Breaking change? (if yes, describe migration path below)

DX checklist

  • Did this change a public API a user might write in their README? If yes, updated the README snippet.
  • Did this add or change a trait, backend, or capability? If yes, updated Sources/ManifoldKit/FeatureMatrix.swift (when present).
  • Is this a breaking change for an existing consumer? If yes, added a migration note to CHANGELOG.md or docs/.
  • Did this change the quickStart() path or MinimalExample? If yes, the example still compiles and runs end-to-end.
  • Did this introduce a new public error type or surface? If yes, it conforms to LocalizedError with a user-facing errorDescription.

roryford and others added 5 commits June 10, 2026 18:09
Adds BackendDescriptor.swift with CloudProviderDescriptor,
LocalModelDescriptor (+ sortOrder), and BackendDescriptorRegistry —
a read-mostly OSAllocatedUnfairLock-guarded registry pre-seeded with
all built-in provider and model-type metadata.

Migrates six switch sites that were pure metadata lookups:

- ModelLifecycleCoordinator.backendDisplayName(for:ModelType)
- ModelLifecycleCoordinator.modelTypeLogLabel  (duplicate of the above)
- ModelLifecycleCoordinator.backendDisplayName(for:APIProvider)
- CompiledBackends.orderedCloudProviders (now sortOrder-driven)
- ModelInfo.backendLabel
- ModelSelectionTabView.typeSortRank

Dispatch-logic and protocol-behaviour switches (encoding strategy,
auth policy, SSE parsing, backend construction) are left as exhaustive
enum switches — the compiler must check those.

Allowlists BackendDescriptor.swift in TrafficBoundaryAuditTest
(same justification as APIProvider.swift: static default URL data).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… vars)

- Replace deprecated String(cString:)/String(validatingUTF8:) with
  withUnsafeBytes + String(decoding:as:)/String(validating:as:) in
  LlamaModelLoader and LlamaTokenization
- Replace deprecated MLX.GPU.set(cacheLimit:) with Memory.cacheLimit =
  in FluxDiffusionBackend and MLXDiffusionBackend
- Add @sendable to completionHandler params in CompositeURLSessionDelegate
  and NetworkActivityTrackingDelegate
- Mark StructuredOutputStrategy/Target @unchecked Sendable (metatypes
  carry no mutable state)
- Restructure DefaultBackends switch to exhaustive per-case #if blocks,
  eliminating unreachable default
- Add @discardableResult to GGUFSignedManifest.requireAcceptedSignature
- Silence discarded withLock result in MemoryPressureBroadcaster
- Drop unused handle binding in ConversationRuntimeScenario
- Remove spurious await in InternalMCPTransport.readOutputLoop
- var → let for uuid tuple in UUID+v5

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Transcriber

AVAudioNodeTap and SFSpeechRecognitionTask callbacks both fire on non-main
threads. Closures defined inside @mainactor functions inherit @mainactor
isolation (SE-0420), so both callbacks were crashing at runtime via
dispatch_assert_queue_fail when the Swift 6 executor check ran on the
realtime audio thread.

Fix: extract both callbacks into nonisolated static factory methods
(makeTapBlock / makeRecognitionHandler) so the returned closures are not
@MainActor-bound. The latestTranscript write and onUpdate call inside
makeRecognitionHandler still hop to @mainactor via Task { @mainactor in }.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add --backend llama and --backend mlx to the manifold-tools CLI. Both
backends auto-discover models from ~/Documents/Models/, respect
LLAMA_TEST_MODEL/MLX_TEST_MODEL env vars, and accept an explicit
--model-path override. Trait-gated behind Llama/MLX compile defines;
clear error message when built without the required trait. LlamaBackend
teardown uses unloadAndWait() between scenarios.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nd setup

- `hint` for both llama and mlx cases incorrectly used `cli.modelPath ?? ...`,
  which caused a filesystem path string to be passed as `nameHint` to the
  discovery functions. Since `pathOverride` already receives `cli.modelPath` and
  short-circuits before `nameHint` is consulted, this was redundant but
  semantically wrong — a path is not a name hint. Fixed to compute hint as
  `model == scenario.backend.model ? nil : model` (a model name only).

- Removed the unused `isDirectory: Bool = false` parameter from the private
  `searchModelsDirectory` helper. The parameter was never read; directory vs.
  file discrimination is handled entirely by the `isValid` closure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@roryford

Copy link
Copy Markdown
Owner Author

@copilot resolve the merge conflicts in this pull request

Copilot AI commented Jun 12, 2026

Copy link
Copy Markdown

@copilot resolve the merge conflicts in this pull request

Resolved. I merged origin/main into this branch and fixed the conflict in Package.swift in commit 60fd1617.

@roryford

Copy link
Copy Markdown
Owner Author

Closing — overtaken by the v0.48 packaging split (#1771).

Core can no longer host family-backend (llama.cpp / MLX) dependencies without reintroducing the exact dependency cycle the split was built to remove, so manifold-tools in core can't carry local GGUF/MLX backend support. This PR's own premise (--traits Llama,MLX,Ollama) also no longer exists — those traits were retired in #1768/#1769/#1771.

The intent (exercising tool scenarios against real local models) re-homes to the companion packages (roryford/manifold-llama, roryford/manifold-mlx), where the family backends now live and can be depended on without a cycle.

Note: the ManifoldVoice @mainactor isolation fix that shared this branch already landed on main by content, so nothing else here is lost.

@roryford roryford closed this Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants