This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
mobilecritical PriorityID: kmp-coroutine-native-ffi-cancellation-boundary

KMP Swift-Kotlin Coroutine FFI Boundary Structured Cancellation Guardrail

Enforces strict structured concurrency lifecycle propagation across the Kotlin Multiplatform to Swift/Objective-C interop boundary. Ensures native Swift Tasks propagate task cancellation signals into Kotlin Coroutine `Job` handles and tear down underlying native Dispatchers to prevent thread pool starvation, unmanaged background work, and memory leaks in iOS runtimes.

Prohibited Anti-Pattern: Unbridged Swift Task deinit resulting in orphaned Kotlin Coroutine Jobs and native iOS thread pool starvation

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: KMP Swift-Kotlin Coroutine FFI Boundary Structured Cancellation Guardrail - Enforces strict structured concurrency lifecycle propagation across the Kotlin Multiplatform to Swift/Objective-C interop boundary. Ensures native Swift Tasks propagate task cancellation signals into Kotlin Coroutine `Job` handles and tear down underlying native Dispatchers to prevent thread pool starvation, unmanaged background work, and memory leaks in iOS runtimes.
globs: **/*.kt, **/*.swift, **/iosMain/**/*.kt, **/appleMain/**/*.kt
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/kmp-coroutine-native-ffi-cancellation-boundary"
---

# KMP Swift-Kotlin Coroutine FFI Boundary Structured Cancellation Guardrail

> **Severity**: CRITICAL | **Category**: mobile
> **Anti-Pattern Warning**: Unbridged Swift Task deinit resulting in orphaned Kotlin Coroutine Jobs and native iOS thread pool starvation

## System Constraints
You are a Principal Mobile Architect enforcing bulletproof structured concurrency at the Kotlin Multiplatform (KMP) Apple FFI boundary. You mandate explicit Job lifecycle binding, cooperative cancellation tokens, and synchronous Swift Task cancellation bridging to eliminate orphaned jobs and Mach thread exhaustion.

## Strict Guidelines
- Engineers MUST ALWAYS expose a cancellation-capable handle (such as `Closeable`, `Job`, or a custom disposable token) from Kotlin suspend/flow functions exposed to Swift/Objective-C interop.
- Engineers MUST wrap Swift-side invocations of Kotlin suspending functions inside `withTaskCancellationHandler` or store the Kotlin `Job`/disposable handle in a Swift deinit lifecycle hook.
- Engineers MUST NOT discard the return value of Kotlin coroutine launch bridges in Swift view models or background services.
- Kotlin-side FFI wrappers MUST check `isActive` or invoke `ensureActive()` around long-running native computations and IO loops across the boundary.
- Code MUST NEVER use `GlobalScope` or non-lifecycle-bound scopes in `iosMain` source sets when vending APIs to Apple platforms.
- Swift `AsyncSequence` bridges wrapping Kotlin `Flow` instances MUST explicitly invoke collection cancellation when the consuming Swift Task is cancelled or falls out of scope.

## Bad vs Good Implementations
### Swift FFI Consumer: Bridging Swift Task Cancellation to Kotlin Coroutine Job

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Default Swift async/await interop generated by Kotlin/Native compiler historically detaches cancellation propagation. When the Swift Task running `loadProfile` is cancelled (e.g., view dismissed), the underlying Kotlin suspend worker thread continues execution indefinitely, causing resource leakage and thread contention.
```swift
import SharedKit

@MainActor
final class ProfileViewModel: ObservableObject {
    private let repository = UserRepository()
    
    func loadProfile(userId: String) async {
        // ANTI-PATTERN: Direct invocation using async/await generated bridge.
        // Swift Task cancellation will NOT cancel the underlying Kotlin Job,
        // leaving the network request and background coroutine active.
        do {
            let user = try await repository.fetchUserProfile(id: userId)
            print("Loaded: \(user.name)")
        } catch {
            print("Failed: \(error)")
        }
    }
}
```

#### Production Standard (Correct)
> **Standard**: Wraps the FFI call in `withTaskCancellationHandler` and stores an explicit cancellation handle (`KmpCancellable`), invoking `cancel()` as soon as the enclosing Swift Task terminates or the holding object deinits. This guarantees immediate teardown of native Mach threads in the Kotlin dispatcher pool.
```swift
import SharedKit

@MainActor
final class ProfileViewModel: ObservableObject {
    private let repository = UserRepository()
    private var loadJob: KmpCancellable? = nil
    
    func loadProfile(userId: String) async {
        await withTaskCancellationHandler {
            do {
                let user = try await withCheckedThrowingContinuation { continuation in
                    self.loadJob = repository.fetchUserProfileBridged(id: userId) { result, error in
                        if let error = error {
                            continuation.resume(throwing: error)
                        } else if let result = result {
                            continuation.resume(returning: result)
                        }
                    }
                }
                print("Loaded: \(user.name)")
            } catch {
                print("Failed: \(error)")
            }
        } onCancel: {
            // INVARIANT: Explicitly propagate Swift cancellation into Kotlin runtime
            self.loadJob?.cancel()
        }
    }
    
    deinit {
        loadJob?.cancel()
    }
}
```

### Kotlin Side: Exposing Explicit Cancellable Wrapper for Apple Targets

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: The Kotlin launch call discards the resulting `Job` reference. The Swift consumer has no deterministic mechanism to signal cancellation, leading to unconstrained background IO execution and retain cycles via the callback closure.
```kotlin
package com.example.shared

import kotlinx.coroutines.*

class UserRepository {
    private val scope = CoroutineScope(Dispatchers.Default)

    // ANTI-PATTERN: Returns Unit, ignores Job lifecycle, and hides cancellation mechanism from Swift
    fun fetchUserProfileBridged(id: String, onResult: (User?, Exception?) -> Unit) {
        scope.launch {
            val user = api.fetch(id) // Continues running even if iOS caller abandons callback
            onResult(user, null)
        }
    }
}
```

#### Production Standard (Correct)
> **Standard**: Returns a strongly typed `KmpCancellable` contract that closes over the Kotlin `Job`. Also handles `CancellationException` without passing false positives to the completion handler and ensures cooperatively checked boundaries using `ensureActive()`.
```kotlin
package com.example.shared

import kotlinx.coroutines.*

interface KmpCancellable {
    fun cancel()
}

class UserRepository(private val supervisorScope: CoroutineScope) {
    fun fetchUserProfileBridged(id: String, onResult: (User?, Throwable?) -> Unit): KmpCancellable {
        val job = supervisorScope.launch {
            try {
                val user = api.fetch(id)
                ensureActive()
                onResult(user, null)
            } catch (t: Throwable) {
                if (t !is CancellationException) {
                    onResult(null, t)
                }
            }
        }
        return object : KmpCancellable {
            override fun cancel() {
                job.cancel(CancellationException("Cancelled by Swift Task FFI"))
            }
        }
    }
}
```

Recommended MCP Servers (mcp-serverlist.com)

Equip agent with system tools to verify code for this stack.

Architectural Specification & Invariant Rationale

Architectural Invariant Design: KMP Swift-Kotlin Coroutine FFI Boundary Structured Cancellation Guardrail

KMP Swift-Kotlin Coroutine FFI Boundary Structured Cancellation Guardrail defines an industrial-grade behavioral standard for mobile engineering. Enforces strict structured concurrency lifecycle propagation across the Kotlin Multiplatform to Swift/Objective-C interop boundary. Ensures native Swift Tasks propagate task cancellation signals into Kotlin Coroutine `Job` handles and tear down underlying native Dispatchers to prevent thread pool starvation, unmanaged background work, and memory leaks in iOS runtimes. 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: Unbridged Swift Task deinit resulting in orphaned Kotlin Coroutine Jobs and native iOS thread pool starvation 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 DriftUnbridged Swift Task deinit resulting in orphaned Kotlin Coroutine Jobs and native iOS thread pool starvationCritical
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 expose a cancellation-capable handle (such as `Closeable`, `Job`, or a custom disposable token) from Kotlin suspend/flow functions exposed to Swift/Objective-C interop.

Engineers MUST ALWAYS expose a cancellation-capable handle (such as `Closeable`, `Job`, or a custom disposable token) from Kotlin suspend/flow functions exposed to Swift/Objective-C interop.

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 wrap Swift-side invocations of Kotlin suspending functions inside `withTaskCancellationHandler` or store the Kotlin `Job`/disposable handle in a Swift deinit lifecycle hook.

Engineers MUST wrap Swift-side invocations of Kotlin suspending functions inside `withTaskCancellationHandler` or store the Kotlin `Job`/disposable handle in a Swift deinit lifecycle hook.

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

Engineers MUST NOT discard the return value of Kotlin coroutine launch bridges in Swift view models or background services.

Engineers MUST NOT discard the return value of Kotlin coroutine launch bridges in Swift view models or background services.

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

Kotlin-side FFI wrappers MUST check `isActive` or invoke `ensureActive()` around long-running native computations and IO loops across the boundary.

Kotlin-side FFI wrappers MUST check `isActive` or invoke `ensureActive()` around long-running native computations and IO loops across the 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.
5

Code MUST NEVER use `GlobalScope` or non-lifecycle-bound scopes in `iosMain` source sets when vending APIs to Apple platforms.

Code MUST NEVER use `GlobalScope` or non-lifecycle-bound scopes in `iosMain` source sets when vending APIs to Apple platforms.

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.
6

Swift `AsyncSequence` bridges wrapping Kotlin `Flow` instances MUST explicitly invoke collection cancellation when the consuming Swift Task is cancelled or falls out of scope.

Swift `AsyncSequence` bridges wrapping Kotlin `Flow` instances MUST explicitly invoke collection cancellation when the consuming Swift Task is cancelled or falls out of scope.

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 Swift-Kotlin Coroutine FFI Boundary Structured Cancellation Guardrail critical for AI coding assistants?

Enforces strict structured concurrency lifecycle propagation across the Kotlin Multiplatform to Swift/Objective-C interop boundary. Ensures native Swift Tasks propagate task cancellation signals into Kotlin Coroutine `Job` handles and tear down underlying native Dispatchers to prevent thread pool starvation, unmanaged background work, and memory leaks in iOS runtimes. 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 (Unbridged Swift Task deinit resulting in orphaned Kotlin Coroutine Jobs and native iOS thread pool starvation) 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