Skip to content

Adoption-ready long-document reasoning for MLX Swift apps#7

Merged
joelnishanth merged 19 commits into
offlyn-ai:mainfrom
rahulraonatarajan:adoption-readiness-and-diagnostic-chains
Apr 27, 2026
Merged

Adoption-ready long-document reasoning for MLX Swift apps#7
joelnishanth merged 19 commits into
offlyn-ai:mainfrom
rahulraonatarajan:adoption-readiness-and-diagnostic-chains

Conversation

@rahulraonatarajan

@rahulraonatarajan rahulraonatarajan commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR evolves mlx-swift-chain into a more complete Swift-native long-document reasoning layer for private, on-device MLX apps.

It adds production-oriented primitives for long transcripts, Markdown/PDF-extracted docs, Xcode/simulator logs, crash reports, and offline reference material while preserving the package's lightweight Swift-first design.

Why this matters

MLX Swift and MLX Swift LM provide the model/runtime layer. This package now provides the app workflow layer above inference:

  • structure-aware chunking
  • prompt/context budgeting
  • adaptive stuff vs map-reduce routing
  • hierarchical reduce for large inputs
  • progress and SwiftUI integration
  • retry/cancellation options
  • source-grounded prompt templates
  • diagnostics-focused chunking for Swift developer workflows

Highlights

Core reliability

  • Added prompt-overhead-aware chain routing.
  • Added token-aware budgeting hooks (TokenCounter, PromptBudgeter, TokenAwareBackend).
  • Added hierarchical reduce to prevent final-prompt overflow.
  • Added bounded concurrent map execution.
  • Added retry policy and cooperative cancellation.
  • Added memory pressure utility for on-device workflows.

Domain workflows

  • Improved transcript handling for meetings, voice notes, lectures, and memos with adaptive attribution modes.
  • Added DocumentStructureChunker for Markdown/PDF-extracted documents with heading, page, table, and code block preservation.
  • Added AppleCrashReportChunker for .crash/.ips crash report analysis with section detection, metadata extraction, and symbolication heuristic.
  • Enhanced LogChunker with diagnostic classification (18 LogChunkKind cases) and severity metadata.
  • Added DiagnosticSourceLabel for human-readable chunk citations.

Prompt templates

  • Added templates for transcript summaries, action items, voice notes, lectures, personal memos, document briefs, crash triage, simulator root cause, build failures, and test failures.

SwiftUI / app integration

  • Added ChainRunner for reactive progress, result, and error state in SwiftUI apps.

Docs and adoption

  • Repositioned README around "Swift-native long-document reasoning for private, on-device MLX apps."
  • Added workflow table, core concepts, and explicit positioning sections.
  • Added adoption metadata guidance, PR summary, and changelog.

Compared with current repo

Area Current upstream This PR Why it matters
Long input handling Basic stuff/map-reduce Adaptive routing with prompt budgeting and hierarchical reduce Safer for large docs
Token/context budgeting Word-count oriented Token-aware hooks and prompt overhead accounting Reduces context overflow
Transcript support Basic transcript chunking Meeting + single-speaker voice note/memo support with adaptive attribution Better voice note and lecture workflows
Document summarization No structure-aware chunker DocumentStructureChunker with page/heading/table preservation Grounded document summaries
Logs/crash Generic log chunking Diagnostic classification and crash-report support Strong developer adoption wedge
SwiftUI Manual integration Reactive ChainRunner Easier app adoption
Prompts Few generic templates 11 domain-specific templates Faster time-to-value
Tests ~30 tests 96 tests 3x coverage increase
Docs Basic package docs Adoption-focused README and docs Easier for MLX/Swift developers to understand

Validation

  • swift package resolve
  • swift build — 0 errors, 0 warnings
  • swift test — 96 tests, 0 failures

Final local status:

  • Tests passing: 96
  • Warnings: 0
  • Source files: 30
  • Test files: 8

Compatibility

  • Existing public APIs are preserved. No init signatures were changed.
  • Existing chunkers continue to work unchanged.
  • New metadata fields are additive (default nil).
  • All pre-existing tests continue to pass.

Limitations

  • This package does not parse PDFs directly or perform OCR.
  • This package does not acquire logs from Xcode, devices, or Console.app.
  • It analyzes user-provided text.
  • Whether inference stays local depends on the backend supplied by the app.
  • Diagnostic and emergency outputs should be reviewed by humans for high-stakes use cases.

Suggested review order

  1. README and docs positioning
  2. Public metadata/API additions (TextChunkMetadata, LogMetadata, DiagnosticSourceLabel)
  3. Core chain behavior (AdaptiveChain, MapReduceChain, PromptBudgeter)
  4. Domain chunkers (TranscriptChunker, DocumentStructureChunker, LogChunker, AppleCrashReportChunker)
  5. Prompt templates
  6. Tests

Note

Medium Risk
Touches core chain execution (budgeting/routing, map-reduce behavior, concurrency, retry/cancellation) and adds large new chunking/parsing code paths, which could change runtime behavior and performance on large inputs. Most changes are additive but the new routing/budget heuristics may affect when calls are made and how prompts are constructed.

Overview
Expands the library from basic stuff/map-reduce into a production-oriented long-document reasoning layer. Chains now support prompt-overhead-aware budgeting (with optional exact token counting via TokenAwareBackend/TokenCounter), a configurable ChainExecutionOptions surface (reserved output tokens, retries, concurrency, reduce limits), and a stuffPrompt override for single-call execution.

Strengthens MapReduceChain execution. The map phase can run with bounded concurrency, supports cooperative cancellation and retry, and the reduce phase gains hierarchical reduce with [Chunk N]-style source labels that propagate through intermediate reduction levels (with ChainError.reduceDepthExceeded safety).

Adds new domain capabilities and adoption docs. Introduces structure/diagnostic-aware chunkers (TranscriptChunker with attribution modes, MarkdownHeadingChunker, DocumentStructureChunker, LogChunker with LogMetadata, and AppleCrashReportChunker with CrashReportMetadata/symbolication heuristics), plus PromptTemplates, DiagnosticSourceLabel, ChainRunner for SwiftUI, MemoryPressure, updated tests, and streamlined CI/docs including a new CHANGELOG.md.

Reviewed by Cursor Bugbot for commit 2291033. Bugbot is set up for automated code reviews on this repo. Configure here.

- New hero: Swift-native long-document reasoning for private, on-device MLX apps
- Add "How This Fits" section explaining relationship to MLX Swift / MLX Swift LM
- Add "Target Use Cases" section (transcripts, markdown, logs, offline docs)
- Fix Requirements: Swift 6.1+ (was 5.9+, but Package.swift requires 6.1)

Made-with: Cursor
- Add ChainExecutionOptions for controlling map/reduce behavior
- Update DocumentChain protocol with stuffPrompt and options params
- Backward-compatible overloads preserve existing callers
- AdaptiveChain now accounts for prompt + text + reserved output
  in budget check, not just raw text word count
- StuffChain uses stuffPrompt when provided, falls back to reducePrompt
- ContextBudget gains fitsInBudget helper method
- Add tests for prompt overhead, stuffPrompt, and reserved output

Made-with: Cursor
TranscriptChunker:
- Splits at speaker-turn boundaries, not sentence boundaries
- Recognizes HH:MM:SS Speaker: and Speaker: patterns
- overlapTurns for context continuity across chunks
- Populates metadata with speaker labels and timestamps

MarkdownHeadingChunker:
- Splits at # heading boundaries up to configurable level
- Preserves heading text in each chunk
- Falls back to sentence-aware chunking for oversized sections

LogChunker:
- Splits at ISO 8601 and common log timestamp boundaries
- Detects stack traces (indented at/frame lines) and keeps them intact
- Extracts timestamps into chunk metadata

These three chunkers are the adoption wedge for the library,
targeting meeting transcripts, document processing, and log analysis.

Made-with: Cursor
When combined summaries exceed the context budget, MapReduceChain now
groups summaries, reduces each group, and repeats recursively until
the final result fits in a single reduce call.

- Add ChainError.reduceDepthExceeded for safety limit on recursion
- Chunk labels ([Chunk N]) propagate through reduce levels as
  [Chunks X-Y] for source traceability
- MapReduceChain gains optional contextBudget init parameter
- Without a budget, single reduce is used (backward compatible)
- maxReduceGroupSize and maxReduceDepth in ChainExecutionOptions
  control hierarchical reduce behavior

Made-with: Cursor
- ChainPromptTemplate bundles map/reduce/stuff prompts for reuse
- PromptTemplates provides pre-built templates:
  - transcriptSummary: meeting notes with speaker attribution
  - actionItems: extract tasks with owners and deadlines
  - logRootCause: error analysis with debugging steps
  - markdownBrief: section-aware document summarization
- All templates instruct the model to cite [Chunk N] labels
- DocumentChain gains run(_:template:) convenience method

Made-with: Cursor
@mainactor ChainRunner provides reactive state for SwiftUI views:
- phase: current ChainProgress.Phase
- isRunning: execution state
- result: final output
- error: any failure

Supports both explicit prompts and ChainPromptTemplate convenience.
Handles cancellation and progress observation automatically.

Made-with: Cursor
- TokenCounter protocol for pluggable token counting
- WordHeuristicTokenCounter as lightweight fallback (1.33 tokens/word)
- TokenAwareBackend protocol extends LLMBackend with context window
  and token counter info (opt-in, keeps LLMBackend clean)
- PromptBudgeter resolves the best counting strategy:
  1. TokenAwareBackend -> real token counting
  2. .tokens budget -> word heuristic with ratio
  3. .words budget -> raw word counting
- AdaptiveChain refactored to use PromptBudgeter
- Add MockTokenAwareBackend and tests verifying precise budgeting

Made-with: Cursor
MLXBackend now accepts GenerateParameters at init, allowing callers to
configure temperature, maxTokens, topP, and other MLXLMCommon sampling
parameters. Parameters are passed to each ChatSession created per call.
Keeps LLMBackend protocol clean (prompt in, text out) while giving
MLX-specific callers full control over generation behavior.

Made-with: Cursor
MemoryPressure enum provides .current() check using mach_task_basic_info
to estimate available memory headroom. Returns .ok, .warning(<300MB),
or .critical(<100MB). Includes os_log integration via logIfConstrained()
for surfacing memory issues before inference.

Important for iOS where jetsam kills memory-hungry processes.

Made-with: Cursor
- Updated Quick Start with MLXBackend + GenerateParameters
- Added prompt template usage example
- Expanded Chunkers table with TranscriptChunker, MarkdownHeadingChunker, LogChunker
- Added domain-specific examples: transcript, log, markdown, action items
- Added SwiftUI integration example with ChainRunner
- Added Production Options section: token budgeting, hierarchical reduce,
  concurrency, retries, memory awareness

Made-with: Cursor
MapReduceChain now supports concurrent map execution via
maxConcurrentMapTasks in ChainExecutionOptions. Default remains 1
(sequential), optimal for on-device MLX where the GPU serializes calls.

When concurrency > 1, uses TaskGroup with bounded in-flight tasks.
Results are always returned in original chunk order regardless of
completion order. Task.checkCancellation() added before each map
and reduce call for cooperative cancellation support.

Made-with: Cursor
RetryPolicy struct with maxAttempts and delayMilliseconds, integrated
into ChainExecutionOptions. Applied to all map and reduce calls in
MapReduceChain via withRetry utility function.

Default is .none (no retry), suited for on-device MLX where failures
are deterministic. Useful for remote backends with transient errors.

Made-with: Cursor
MockLLMBackend and MockTokenAwareBackend now use NSLock to protect
all mutable state (promptsReceived, generateCallCount, cannedResponse,
shouldThrow). This prevents data races when used with concurrent map
phases (maxConcurrentMapTasks > 1) under Swift 6.1 strict concurrency.

TransientFailBackend similarly hardened. MLXBackend's @unchecked
Sendable is documented as safe (ModelContainer is an actor).

Made-with: Cursor
- CI now runs resolve -> build -> test for better failure diagnosis
- Updated architecture.md with all new components: domain chunkers,
  TokenAwareBackend, PromptBudgeter, ChainRunner, PromptTemplates,
  MemoryPressure, hierarchical reduce, and production reliability

Made-with: Cursor
…ribution modes

- Add AppleCrashReportChunker for .crash/.ips crash report analysis
- Add DocumentStructureChunker for heading/page/table-aware document chunking
- Add DiagnosticsMetadata types (LogChunkKind, CrashReportMetadata, LogMetadata)
- Add DiagnosticSourceLabel for human-readable chunk citations
- Add TranscriptAttributionMode (auto/speaker/temporal/topical)
- Enhance LogChunker with diagnostic classification metadata
- Enhance TranscriptChunker with single-speaker and topical attribution
- Add diagnostic prompt templates (appleCrashTriage, simulatorLogRootCause,
  xcodeBuildFailure, testFailureAnalysis)
- Add logMetadata field to TextChunkMetadata
- Add 19 diagnostic tests and 14 document structure tests

Made-with: Cursor
Reorganize README with adoption-focused sections:
- Add "Choose Your Workflow" table mapping use cases to chunkers and templates
- Add "Core Concepts" reference table
- Add "Why Not Just Truncate?" and "Why Not a Generic LangChain?" sections
- Move privacy note to standalone section
- Add links section for CHANGELOG, CONTRIBUTING, architecture, and tests
- Update CONTRIBUTING.md with resolve/build steps and domain contribution areas

Made-with: Cursor
- Add CHANGELOG.md with categorized Unreleased section
- Add docs/pr-summary.md with detailed reviewer-friendly PR summary
- Add docs/pr-body.md with GitHub-ready PR body
- Add docs/repo-adoption-metadata.md with GitHub sidebar, topics, and
  social preview recommendations

Made-with: Cursor

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2291033. Configure here.


let mapReduce = MapReduceChain(backend: backend, chunker: chunker)
return try await mapReduce.run(text, mapPrompt: mapPrompt, reducePrompt: reducePrompt, systemPrompt: systemPrompt, progress: progress)
return try await mapReduce.run(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AdaptiveChain doesn't pass contextBudget to MapReduceChain

High Severity

AdaptiveChain creates a MapReduceChain without forwarding its contextBudget, using the init that sets contextBudget to nil. In MapReduceChain.fitsInSingleReduce, a nil budget causes it to always return true, which completely disables hierarchical reduce. This means large documents processed via AdaptiveChain can overflow the context window during the reduce phase — the exact scenario hierarchical reduce was designed to prevent.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2291033. Configure here.

}
}
}
throw lastError!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retry logic doesn't respect cooperative task cancellation

Medium Severity

withRetry catches all errors including CancellationError, causing cancelled operations to be retried instead of propagating cancellation immediately. When delayMilliseconds is 0 (or the error occurs on the last attempt), there's no Task.sleep to re-check cancellation, so the function unnecessarily retries a cancelled task up to maxAttempts times before eventually re-throwing.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2291033. Configure here.

public init(container: ModelContainer) {
/// Parameters controlling token generation (temperature, maxTokens, topP, etc.).
/// These are passed to each `ChatSession` created per generation call.
public var generateParameters: GenerateParameters

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mutable generateParameters on @unchecked Sendable class causes data race

Medium Severity

generateParameters is a public var on MLXBackend, which is @unchecked Sendable. The doc comment claims safety because ModelContainer is an actor and GenerateParameters is Sendable, but Sendable doesn't protect against concurrent read/write of the property itself. One thread mutating generateParameters while generate() reads it on another creates a data race.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2291033. Configure here.

- Forward contextBudget from AdaptiveChain to MapReduceChain so
  hierarchical reduce is enabled for long documents routed through
  the adaptive path.
- Propagate CancellationError immediately in withRetry instead of
  retrying cancelled operations. Add Task.checkCancellation() at the
  start of each retry loop iteration.
- Protect MLXBackend.generateParameters with NSLock to prevent data
  races on the @unchecked Sendable class. Read a snapshot of the
  parameters before creating each ChatSession.
- Adjust AdaptiveChain routing tests to use realistic budgets that
  are compatible with hierarchical reduce.

Made-with: Cursor
@cursor

cursor Bot commented Apr 27, 2026

Copy link
Copy Markdown

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

- Map chunks are now budget-aware via PromptBudgeter.availableTextBudget
  and automatic rechunking when specialized chunkers emit oversized chunks.
- Hierarchical reduce now uses PromptBudgeter for fit checks, including
  TokenAwareBackend when available.
- preserveOrder now controls concurrent map result ordering while
  preserving correct source chunk labels via MapResult.
- Reduce grouping is now budget-derived and capped by maxReduceGroupSize.
- Default reservedOutputTokens changed from 0 to 512.
- Added ChunkPromptFormatter for richer diagnostic chunk labels.
- MLXBackend @unchecked Sendable comment now precisely describes lock
  protection and resource constraints.
- README and docs now accurately frame PDF-extracted text and .ips support.
- 105 tests passing, 0 build warnings.

Made-with: Cursor
@cursor

cursor Bot commented Apr 27, 2026

Copy link
Copy Markdown

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@joelnishanth
joelnishanth merged commit 0958567 into offlyn-ai:main Apr 27, 2026
1 check passed
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