This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
mobilecritical PriorityID: kmp-native-flow-backpressure-fence

KMP 2.1 Native Flow Backpressure & Memory Bounding Across Swift Runtime Boundary

Enforces deterministic backpressure and bounded native buffer allocation when streaming high-throughput Kotlin Coroutines Flow instances across the Kotlin/Native Objective-C bridge to Swift. Prevents unmanaged native memory runaway and thread exhaustion caused by unbounded queueing across foreign function interfaces (FFI). Eliminates silent OOM crashes on iOS by mandating explicit suspension fences, buffer caps, and lifecycle-bound cancellation propagation.

Prohibited Anti-Pattern: Unbounded Direct Flow Bridging Inducing Native Heap Bloat and Swift Task Desynchronization

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: KMP 2.1 Native Flow Backpressure & Memory Bounding Across Swift Runtime Boundary - Enforces deterministic backpressure and bounded native buffer allocation when streaming high-throughput Kotlin Coroutines Flow instances across the Kotlin/Native Objective-C bridge to Swift. Prevents unmanaged native memory runaway and thread exhaustion caused by unbounded queueing across foreign function interfaces (FFI). Eliminates silent OOM crashes on iOS by mandating explicit suspension fences, buffer caps, and lifecycle-bound cancellation propagation.
globs: shared/src/commonMain/**/*.kt, shared/src/iosMain/**/*.kt, iosApp/**/*.swift
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/kmp-native-flow-backpressure-fence"
---

# KMP 2.1 Native Flow Backpressure & Memory Bounding Across Swift Runtime Boundary

> **Severity**: CRITICAL | **Category**: mobile
> **Anti-Pattern Warning**: Unbounded Direct Flow Bridging Inducing Native Heap Bloat and Swift Task Desynchronization

## System Constraints
You are a Principal Mobile Architect enforcing Kotlin Multiplatform (KMP) 2.1 Native Flow backpressure fences and cross-runtime boundary safety. Mandate that any Coroutine Flow exposed from Kotlin to Swift/Objective-C adheres to explicit buffer limits (BufferOverflow.DROP_OLDEST or SUSPEND with finite capacity), converts to a controlled native wrapper with lifecycle awareness, and prevents detached Swift AsyncStream buffer accumulation. Disallow naked Flow exposure across the ObjC-export boundary without a backpressured bridge adapter.

## Strict Guidelines
- Engineers MUST ALWAYS apply explicit `.buffer(capacity = N, onBufferOverflow = BufferOverflow.SUSPEND)` or `.buffer(capacity = N, onBufferOverflow = BufferOverflow.DROP_OLDEST)` on high-frequency Flows before exposing them to the iOS native boundary.
- Engineers MUST NOT expose raw Kotlin `Flow<T>` or `SharedFlow<T>` directly to Swift without an intermediate thread-safe, cancellable NativeFlow/Skie wrapper or explicit `flow.collect` boundary wrapper.
- Swift clients MUST consume native Flows via controlled `AsyncStream(bufferingPolicy: .bufferingNewest(N))` or direct backpressured iterator loops that maintain backpropagation into Kotlin suspension points.
- Services MUST ensure Kotlin Native cancellation tokens (`Job.cancel()`) are synchronously or deterministically triggered when the consumer Swift Task, SwiftUI View, or UIViewController deallocates.
- Components NEVER bridge unbounded flows (such as raw sensor streams, live trade feeds, or location changes) using unconstrained callbacks that allocate Objective-C retained objects faster than the Darwin RunLoop drains them.

## Bad vs Good Implementations
### High-Throughput Native Flow Wrapper with Explicit Backpressure Fence

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Kotlin Native exports this raw Flow via Objective-C dispatch. Swift consumers attaching async closures or standard collectors will suffer from unbounded unmanaged memory allocation when emission rate exceeds Swift UI/MainActor consumption, ultimately triggering an uncatchable iOS JetSam OOM crash.
```kotlin
// BAD: Exposing naked, unbounded Flow directly across the Kotlin/Native boundary
package com.example.telemetry

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

class TelemetryRepository {
    // Emits 1000+ events/sec directly across Darwin FFI without buffer bounds
    fun streamHighFrequencyTelemetry(): Flow<TelemetryData> = flow {
        while (true) {
            emit(pollHardwareSensors()) // Native heap balloons if Swift consumer is delayed
        }
    }
}
```

#### Production Standard (Correct)
> **Standard**: Applies a deterministic buffer fence with `BufferOverflow.DROP_OLDEST` and bounded capacity of 64, ensuring Kotlin emissions cannot flood the Darwin heap. Wraps the collection into a NativeCancellableFlow providing a discrete cancellation callback for Swift deallocation handling.
```kotlin
// GOOD: Bounded native flow wrapper with explicit suspension fences and lifecycle cancellation
package com.example.telemetry

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*

class TelemetryRepository {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)

    fun streamHighFrequencyTelemetry(): NativeCancellableFlow<TelemetryData> {
        val boundedFlow = flow {
            while (currentCoroutineContext().isActive) {
                emit(pollHardwareSensors())
            }
        }
        .buffer(capacity = 64, onBufferOverflow = BufferOverflow.DROP_OLDEST)
        .flowOn(Dispatchers.Default)

        return NativeCancellableFlow(scope, boundedFlow)
    }
}

// Safe bridge boundary adapter for Kotlin/Native to Swift
class NativeCancellableFlow<T : Any>(
    private val scope: CoroutineScope,
    private val upstream: Flow<T>
) {
    fun subscribe(
        onItem: (T) -> Unit,
        onComplete: () -> Unit,
        onError: (Throwable) -> Unit
    ): () -> Unit {
        val job = scope.launch {
            upstream
                .catch { onError(it) }
                .onCompletion { onComplete() }
                .collect { onItem(it) }
        }
        return { job.cancel() }
    }
}
```

### Swift AsyncStream Consumer with Backpressure Buffering Policy and Deinit Cancellation

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: The Kotlin subscription runs indefinitely because the returned cancellation token is discarded. Rapid async dispatches flood the MainActor queue, retaining native objects and causing high memory watermark crashes.
```swift
// BAD: Accumulating unmanaged callbacks in unbounded Swift buffer
import Shared

final class TelemetryViewModel: ObservableObject {
    private let repo = TelemetryRepository()

    func startStream() {
        // Unbounded accumulation on Swift side; ignores backpressure and cancellation
        _ = repo.streamHighFrequencyTelemetry().subscribe(
            onItem: { data in
                DispatchQueue.main.async {
                    self.processData(data)
                }
            },
            onComplete: {},
            onError: { _ in }
        )
    }
}
```

#### Production Standard (Correct)
> **Standard**: Wraps the KMP subscription in an `AsyncStream` using `.bufferingNewest(64)` matching the Kotlin boundary fence. The `onTermination` hook triggers the Kotlin cancellation function, ensuring zero native heap leaks or orphaned coroutine dispatchers.
```swift
// GOOD: Bounded Swift AsyncStream tied to Task lifecycle and explicit KMP subscription disposal
import Shared
import Foundation

@MainActor
final class TelemetryViewModel: ObservableObject {
    private let repo = TelemetryRepository()
    private var observationTask: Task<Void, Never>?

    func startStream() {
        observationTask?.cancel()
        
        observationTask = Task { [weak self, repo] in
            let stream = AsyncStream<TelemetryData>(bufferingPolicy: .bufferingNewest(64)) { continuation in
                let canceller = repo.streamHighFrequencyTelemetry().subscribe(
                    onItem: { continuation.yield($0) },
                    onComplete: { continuation.finish() },
                    onError: { _ in continuation.finish() }
                )
                continuation.onTermination = { @Sendable _ in
                    canceller()
                }
            }
            
            for await data in stream {
                guard !Task.isCancelled, let self = self else { break }
                self.processData(data)
            }
        }
    }

    deinit {
        observationTask?.cancel()
    }
}
```

Architectural Specification & Invariant Rationale

Architectural Invariant Design: KMP 2.1 Native Flow Backpressure & Memory Bounding Across Swift Runtime Boundary

KMP 2.1 Native Flow Backpressure & Memory Bounding Across Swift Runtime Boundary defines an industrial-grade behavioral standard for mobile engineering. Enforces deterministic backpressure and bounded native buffer allocation when streaming high-throughput Kotlin Coroutines Flow instances across the Kotlin/Native Objective-C bridge to Swift. Prevents unmanaged native memory runaway and thread exhaustion caused by unbounded queueing across foreign function interfaces (FFI). Eliminates silent OOM crashes on iOS by mandating explicit suspension fences, buffer caps, and lifecycle-bound cancellation propagation. Modern software architectures require strict adherence to deterministic patterns to guarantee high availability, maintainability, and zero security vulnerabilities.

Framework Paradigm Shift & Drift MechanicsLLMs are trained on millions of open-source repositories containing outdated code, legacy idioms, and unpatched antipatterns. When coding assistants generate implementations for this stack, they default to common statistical tokens: Unbounded Direct Flow Bridging Inducing Native Heap Bloat and Swift Task Desynchronization Without an explicit system instruction file, developers spend hours debugging silent failures.
Failure Modes & Production Blast Radius

Hazard Analysis: What Breaks Without This Rule

The following failure modes have been cataloged from hundreds of hours of production code reviews where AI assistants produced non-compliant implementations:

Failure ModeAI Symptom (The Anti-Pattern)SeverityProduction Impact & Remedy
AI Hallucination & Deprecation DriftUnbounded Direct Flow Bridging Inducing Native Heap Bloat and Swift Task DesynchronizationCritical
Violating this industrial standard introduces regression bugs, security risks, and unexpected runtime failures in production.
Enforce explicit behavioral system directives across all AI coding assistants in your repository.
Silent Architecture InconsistencyAI assistants produce mixed paradigms across files, creating fragmented codebase conventions.Medium
Increases cognitive load during code reviews and leads to maintenance debt across engineering teams.
Pin project conventions in .cursor/rules, CLAUDE.md, and AGENTS.md so all team agents follow identical guardrails.
Architectural Directives in Detail

Step-by-Step Production Directives

Each rule constraint below is injected into the AI agent's system prompt to establish deterministic boundaries:

1

Engineers MUST ALWAYS apply explicit `.buffer(capacity = N, onBufferOverflow = BufferOverflow.SUSPEND)` or `.buffer(capacity = N, onBufferOverflow = BufferOverflow.DROP_OLDEST)` on high-frequency Flows before exposing them to the iOS native boundary.

Engineers MUST ALWAYS apply explicit `.buffer(capacity = N, onBufferOverflow = BufferOverflow.SUSPEND)` or `.buffer(capacity = N, onBufferOverflow = BufferOverflow.DROP_OLDEST)` on high-frequency Flows before exposing them to the iOS native boundary.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
2

Engineers MUST NOT expose raw Kotlin `Flow<T>` or `SharedFlow<T>` directly to Swift without an intermediate thread-safe, cancellable NativeFlow/Skie wrapper or explicit `flow.collect` boundary wrapper.

Engineers MUST NOT expose raw Kotlin `Flow<T>` or `SharedFlow<T>` directly to Swift without an intermediate thread-safe, cancellable NativeFlow/Skie wrapper or explicit `flow.collect` boundary wrapper.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
3

Swift clients MUST consume native Flows via controlled `AsyncStream(bufferingPolicy

Swift clients MUST consume native Flows via controlled `AsyncStream(bufferingPolicy: .bufferingNewest(N))` or direct backpressured iterator loops that maintain backpropagation into Kotlin suspension points.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
4

Services MUST ensure Kotlin Native cancellation tokens (`Job.cancel()`) are synchronously or deterministically triggered when the consumer Swift Task, SwiftUI View, or UIViewController deallocates.

Services MUST ensure Kotlin Native cancellation tokens (`Job.cancel()`) are synchronously or deterministically triggered when the consumer Swift Task, SwiftUI View, or UIViewController deallocates.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
5

Components NEVER bridge unbounded flows (such as raw sensor streams, live trade feeds, or location changes) using unconstrained callbacks that allocate Objective-C retained objects faster than the Darwin RunLoop drains them.

Components NEVER bridge unbounded flows (such as raw sensor streams, live trade feeds, or location changes) using unconstrained callbacks that allocate Objective-C retained objects faster than the Darwin RunLoop drains them.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
Technical Questions & Answers

Frequently Asked Technical Questions

Common questions developers face when configuring AI coding assistants for this stack:

Q:Why is KMP 2.1 Native Flow Backpressure & Memory Bounding Across Swift Runtime Boundary critical for AI coding assistants?

Enforces deterministic backpressure and bounded native buffer allocation when streaming high-throughput Kotlin Coroutines Flow instances across the Kotlin/Native Objective-C bridge to Swift. Prevents unmanaged native memory runaway and thread exhaustion caused by unbounded queueing across foreign function interfaces (FFI). Eliminates silent OOM crashes on iOS by mandating explicit suspension fences, buffer caps, and lifecycle-bound cancellation propagation. Without explicit behavioral boundaries, AI coding assistants (Cursor, Claude Code, Windsurf, Codex) rely on training data averages, which frequently produce deprecated, inefficient, or vulnerable code patterns.

Q:How does adopting this rule prevent AI hallucinations?

This rule explicitly declares what the AI must avoid (Unbounded Direct Flow Bridging Inducing Native Heap Bloat and Swift Task Desynchronization) and prescribes verified positive examples. Grounding the LLM with negative and positive pairs eliminates guesswork.

Q:Which AI coding environments are supported?

This specification is multi-platform compatible: Cursor (.cursor/rules/*.mdc & .cursorrules), Anthropic Claude Code (CLAUDE.md), Windsurf (.windsurfrules), Codex / Cline (AGENTS.md), and GitHub Copilot (.github/copilot-instructions.md).

Q:Can I combine this rule with other technology stacks?

Yes. Use the Interactive Stack Builder or download an Agent Pack to merge this rule with other frontend, backend, database, security, and DevOps rules without syntax collision.

Open Specification · CC BY 4.0·Free for Personal, Team & Commercial In-Repo Use

Citation & Open Source Attribution Guidelines

This specification is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0). You are completely free to adapt and share this rule in your workspace. If citing, including in public AI benchmark datasets, or redistributing, attribution to AgentRules.org is required.

Related MOBILE Rules

View all rules →
1 Rule in Stack

KMP