agentleFS
ariaraminariaramin/flutter-skills/skills/flutter-squadron-concurrency-engineering/SKILL.md

flutter-squadron-concurrency-engineering

Skill8 starsChanged 48 days agoMITView on GitHub

Design, implement, review, debug, test, migrate, and optimize production Flutter/Dart concurrency built with Squadron. Use this skill whenever work involves CPU-heavy processing, Dart isolates, Web Workers, Squadron workers or worker pools, squadron_builder code generation, marshaling custom DTOs, cancellation, worker streams, pool sizing, backpressure, worker lifecycle, stale-result races, web worker deployment, or diagnosing whether offloading improves Flutter responsiveness—even when the user only reports UI jank, slow parsing, expensive image processing, worker startup latency, queue saturation, or worker-related memory/thermal issues.

The file

Show its rules and text

We split this skill into the rules it gives an agent

56 rules. About 0 are common, 44 are distinctive, and 12 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈44

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 12

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Distinctive:

    Use this decision loop:

    line 11
  2. Distinctive:

    Use workers mainly for meaningful CPU-bound or long-running isolated work.

    line 28
  3. Distinctive:

    Do not offload ordinary HTTP, socket, storage, or timer waits merely because they are asynchronous.

    line 29
  4. Distinctive:

    Treat every worker method as a serialization boundary API.

    line 31
  5. Distinctive:

    Keep messages compact and task APIs coarse-grained.

    line 32
  6. Distinctive:

    Use a pool only when true parallel throughput is needed.

    line 35
  7. Distinctive:

    Do not set pool size mechanically to processor count.

    line 38
  8. Distinctive:

    Keep worker computation pure and platform-independent where practical.

    line 40
  9. Very specific:

    Never pass BuildContext, Widgets, RenderObjects, controllers, or other UI/runtime objects across workers.

    line 41
  10. Distinctive:

    Verify any Flutter plugin used from workers explicitly supports the required background-isolate/platform behavior.

    line 42
  11. Very specific:

    Use supported wire types or explicit SquadronMarshalers for custom DTOs.

    line 43
  12. Distinctive:

    Do not claim zero-copy unless the exact platform/type path truly provides transfer semantics.

    line 44
  13. Distinctive:

    Use cooperative cancellation when expensive running CPU work must actually stop.

    line 45
  14. Very specific:

    Treat one worker's async methods as potentially interleaving across await points.

    line 47
  15. Very specific:

    Do not assume pool calls hit the same worker.

    line 48
  16. Distinctive:

    Read only what the task needs:

    line 54
  17. Distinctive:

    Before changing concurrency, inspect:

    line 64
  18. Distinctive:

    Do not offload an entire repository method merely because one substage is expensive.

    line 128
  19. Distinctive:

    Use this default ladder:

    line 131
  20. Distinctive:

    Do not benchmark only code inside the worker and call that the architecture's latency.

    line 165
  21. Very specific:

    Keep core algorithms extractable as ordinary Dart logic so most correctness tests do not require workers.

    line 183
  22. Very specific:

    After changing service signatures/annotations/marshalers, regenerate:

    line 184
  23. Distinctive:

    Never hand-edit generated protocol files.

    line 188
  24. Very specific:

    Always establish one owner responsible for stop().

    line 199
  25. Distinctive:

    Do not prewarm many rare workers at app launch.

    line 200
  26. Distinctive:

    Use one worker when:

    line 203
  27. Distinctive:

    Use a pool when independent tasks benefit from genuine parallel throughput.

    line 208
  28. Very specific:

    Do not leave that accidental in bursty production CPU paths.

    line 222
  29. Distinctive:

    If producers can outpace workers, choose explicit policy:

    line 226
  30. Very specific:

    Use task waitTime vs runningTime to distinguish queue pressure from slow computation.

    line 237
  31. Distinctive:

    prefer a dedicated Worker.

    line 254
  32. Distinctive:

    Never assume two ordinary pool calls land on the same worker.

    line 257
  33. Distinctive:

    Keep marshalers compact, stable, and round-trip tested:

    line 267
  34. Very specific:

    Do not send giant arbitrary domain graphs when a small DTO will do.

    line 271
  35. Distinctive:

    Before moving a payload, estimate:

    line 276
  36. Distinctive:

    If the same large dataset is reused repeatedly, consider whether a dedicated worker should own a worker-local copy and receive small …

    line 282
  37. Distinctive:

    Only do this when lifecycle, memory, state affinity, and invalidation are explicit.

    line 283
  38. Distinctive:

    Do not send a 100 MB dataset repeatedly to compute a tiny answer.

    line 284
  39. Distinctive:

    Use worker streams for meaningful progressive output, not per-byte/per-iteration chatter.

    line 307
  40. Distinctive:

    Keep on main isolate:

    line 317
  41. Very specific:

    Do not expose Worker/Pool/Task/WorkerException in UI state unless the product explicitly manages worker infrastructure.

    line 333
  42. Distinctive:

    Verify background-isolate support before using Flutter plugins inside workers.

    line 334
  43. Very specific:

    Do not assume native tests or flutter build web alone prove the worker artifact is available at runtime.

    line 358
  44. Distinctive:

    Treat Squadron errors as infrastructure details.

    line 361
  45. Distinctive:

    Do not log full sensitive payloads.

    line 378
  46. Distinctive:

    Do not assert exact completion order unless the API/business contract guarantees it.

    line 392
  47. Distinctive:

    If a worker fails to start, check:

    line 396
  48. Distinctive:

    If method-not-found occurs, regenerate before debugging protocol IDs.

    line 405
  49. Distinctive:

    If work hangs, inspect:

    line 406
  50. Distinctive:

    If worker is slower than local, inspect startup + transfer + queue + granularity before adding workers.

    line 415
  51. Distinctive:

    If UI still janks, profile main-isolate preprocessing, result materialization, state mapping, widget build/layout, and raster work.

    line 416
  52. Distinctive:

    Use production-sized workloads and profile/release conditions.

    line 427
  53. Distinctive:

    Before upgrading Squadron or moving local code into workers:

    line 440
  54. Distinctive:

    read intervening changelog/migration notes.

    line 442
  55. Distinctive:

    When reviewing a repository, report findings in this order:

    line 452
  56. Distinctive:

    Use Squadron only when the worker boundary produces a measurable improvement in responsiveness, throughput, or isolation that justifies its …

    line 492

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Flutter Squadron Concurrency Engineering

Mission

Move only the right computation off Flutter's main isolate, using the smallest justified worker boundary, message volume, concurrency, and lifecycle cost. Use this decision loop:

measure the slow stage
→ classify CPU vs I/O
→ estimate transfer/startup cost
→ choose main/Future/compute/Worker/Pool/server
→ define coarse worker API + wire types
→ bound concurrency/backpressure
→ add cancellation + stale-result protection
→ own worker lifecycle
→ test native and web separately
→ profile end-to-end + UI frames + memory/thermals

Squadron is structured worker/RPC infrastructure, not a generic wrapper around every Future.

Non-negotiable rules

  1. Measure before offloading.
  2. Use workers mainly for meaningful CPU-bound or long-running isolated work.
  3. Do not offload ordinary HTTP, socket, storage, or timer waits merely because they are asynchronous.
  4. Measure total worker cost, including startup, marshaling, transfer, queue wait, compute, and result handling.
  5. Treat every worker method as a serialization boundary API.
  6. Keep messages compact and task APIs coarse-grained.
  7. Batch tiny operations rather than issuing thousands of RPC calls.
  8. Reuse persistent workers when recurring work justifies startup amortization.
  9. Use a pool only when true parallel throughput is needed.
  10. Bound maxWorkers; do not unintentionally leave CPU burst workloads unbounded.
  11. For CPU-heavy pool tasks, default to maxParallel: 1 unless measurement and semantics prove otherwise.
  12. Do not set pool size mechanically to processor count.
  13. Preserve CPU headroom for Flutter UI/raster work and the operating system.
  14. Keep worker computation pure and platform-independent where practical.
  15. Never pass BuildContext, Widgets, RenderObjects, controllers, or other UI/runtime objects across workers.
  16. Verify any Flutter plugin used from workers explicitly supports the required background-isolate/platform behavior.
  17. Use supported wire types or explicit SquadronMarshalers for custom DTOs.
  18. Do not claim zero-copy unless the exact platform/type path truly provides transfer semantics.
  19. Use cooperative cancellation when expensive running CPU work must actually stop.
  20. Reject stale results even when cancellation exists.
  21. Treat one worker's async methods as potentially interleaving across await points.
  22. Do not assume pool calls hit the same worker; use a dedicated worker or pool.execute() for state affinity.
  23. Own worker/pool lifecycle explicitly and call stop().
  24. Build and deploy web worker artifacts explicitly; native success does not prove web correctness.
  25. Optimize user responsiveness, memory, battery, and thermals—not just raw throughput.

Reference routing

Read only what the task needs:

NeedRead
Decide whether Squadron is appropriate; compare Future/compute/isolates; setup generation; worker lifecycle/startupreferences/01-foundations-workers-lifecycle.md
Pools, ConcurrencySettings, backpressure, stateful workers, marshaling, typed data, streams, cancellation, stale results, errors, observabilityreferences/02-pools-state-serialization-cancellation.md
Flutter/Bloc/DI integration, plugins, Web Workers, JS/Wasm build/deploy, workload patterns, profiling, memory, battery/thermalsreferences/03-flutter-web-workloads-performance.md
Unit/integration/pool/web tests, debugging, migrations, behavior-preserving refactorsreferences/04-testing-debugging-migration.md
Anti-patterns, new-worker workflow, existing-code review, checklists, production gatereferences/05-antipatterns-workflows-checklists.md

Step 1 — Inspect the repository and measure the actual bottleneck

Before changing concurrency, inspect:

Flutter + Dart versions
squadron + squadron_builder versions
cancelation_token version
generated *.worker.g.dart / *.activator.g.dart files
worker ownership and stop() paths
pool ConcurrencySettings
service annotations + baseUrl + targetPlatform
custom marshalers/exceptions
payload sizes and task frequency
web worker build/deployment scripts
state-management integration
existing benchmarks/profile captures

Search for:

@SquadronService
@SquadronMethod
WorkerPool
ConcurrencySettings
maxWorkers
maxParallel
pool.execute
CancelationToken
TimeoutToken
SquadronMarshaler
TransferableTypedData
channelLogger
stop()

Then identify the precise slow stage. Prefer evidence such as:

"parsing 18 MB JSON blocks the main isolate for ~95 ms"

over vague claims such as:

"loading is slow"

Step 2 — Classify CPU-bound vs I/O-bound

Good worker candidates:

large parse/transform
audio/image processing
compression/decompression
CPU-heavy hashing/crypto
large sorting/aggregation
local indexing/search
binary parsing/decoding
large model transforms

Usually keep ordinary waiting on async I/O:

HTTP/socket waits
database/file waits
secure-storage calls
timers

For mixed pipelines, isolate the CPU stage:

HTTP wait
→ large response decode/transform  ← worker candidate

Do not offload an entire repository method merely because one substage is expensive.

Step 3 — Choose the smallest execution model that fits

Use this default ladder:

SituationPrefer
Tiny synchronous workMain isolate
Ordinary nonblocking I/OFuture/async I/O
Occasional native CPU-heavy taskConsider compute() / Isolate.run()
Recurring CPU-heavy operationPersistent Squadron Worker
Multiple independent recurring CPU jobsWorkerPool, after profiling
Cross-platform VM + Web Worker abstractionSquadron
Full custom isolate protocol requiredRaw Isolate
Better done remotelyBackend/server processing
A worker can be worthwhile even when elapsed task time is similar if it removes a visible main-isolate stall.

Step 4 — Model total cost, not only worker compute time

Use:

Tworker =
  startup/warmup
+ marshal input
+ transfer/copy input
+ queue wait
+ compute
+ marshal output
+ transfer/copy output
+ main-isolate postprocessing

Measure at least:

  • cold first call;
  • warm call;
  • request/result size;
  • queue wait;
  • worker runtime;
  • end-to-end latency;
  • Flutter frame impact. Do not benchmark only code inside the worker and call that the architecture's latency.

Step 5 — Design a coarse generated service API

Expose meaningful expensive operations, not tiny implementation steps. Prefer:

resizeImage(bytes, width, height)
transformRows(batch)
search(indexQuery)
parseDocument(bytes)

Avoid:

readByte()
transformOneTinyValue()
writeByte()

for huge loops. Keep core algorithms extractable as ordinary Dart logic so most correctness tests do not require workers. After changing service signatures/annotations/marshalers, regenerate:

dart run build_runner build --delete-conflicting-outputs

Never hand-edit generated protocol files.

Step 6 — Choose worker lifecycle deliberately

Use:

one-shot      → rare + very expensive + startup justified
feature scope → expensive rarely used feature
lazy app      → recurring but not every session
app scope     → common recurring workload; startup amortized

Workers auto-start on first use; call start() when you intentionally want earlier initialization or clearer startup-error handling. Always establish one owner responsible for stop(). Do not prewarm many rare workers at app launch.

Step 7 — Decide whether a pool is actually necessary

Use one worker when:

  • jobs are sequential;
  • state affinity matters;
  • throughput is already sufficient;
  • more workers would duplicate large state. Use a pool when independent tasks benefit from genuine parallel throughput. For CPU-heavy pools start conservatively, for example:
const ConcurrencySettings(
  minWorkers: 1,
  maxWorkers: 2,
  maxParallel: 1,
)

then benchmark alternatives. Important:

maxWorkers == 0 → unbounded maximum

Do not leave that accidental in bursty production CPU paths. maxParallel controls how many tasks may be posted to one worker; it does not create extra CPU threads inside that isolate.

Step 8 — Add backpressure before increasing concurrency

If producers can outpace workers, choose explicit policy:

debounce
throttle
batch
coalesce
latest-wins
bounded upstream queue
cancel obsolete pending work
reject/drop low-value work

Use task waitTime vs runningTime to distinguish queue pressure from slow computation. A 900 ms wait and 30 ms run time is a scheduling/load problem, not an algorithm problem.

Step 9 — Treat stateful workers as an exception

Default to:

input → compute → output

State can be justified for expensive reusable indexes/tables/codecs, but introduces affinity, memory, and interleaving risks. One worker thread does not prevent async logical interleaving:

request A mutates field
→ await
request B mutates same field
→ A resumes with B's state

For stateful multi-call operations:

  • prefer a dedicated Worker; or
  • reserve one pool worker with pool.execute();
  • generally keep maxParallel: 1 for exclusive stateful workflows. Never assume two ordinary pool calls land on the same worker.

Step 10 — Design wire data intentionally

Prefer:

null/bool/int/double/String
small Lists/Maps/Sets
Uint8List / typed numeric data
explicit DTO wire forms via SquadronMarshaler

Keep marshalers compact, stable, and round-trip tested:

unmarshal(marshal(value)) ≈ value

Do not send giant arbitrary domain graphs when a small DTO will do. For binary workloads prefer typed data over object-heavy List<int> when appropriate. Native and web transfer semantics differ. Treat TransferableTypedData as a deliberate VM-specific optimization, including its creation/materialization/ownership costs; do not leak native-only assumptions into cross-platform APIs.

Step 11 — Minimize repeated large transfers

Before moving a payload, estimate:

input MB × calls
+
output MB × calls

If the same large dataset is reused repeatedly, consider whether a dedicated worker should own a worker-local copy and receive small queries. Only do this when lifecycle, memory, state affinity, and invalidation are explicit. Do not send a 100 MB dataset repeatedly to compute a tiny answer.

Step 12 — Design cancellation and stale-result safety separately

Cancellation may mean either:

caller stops caring

or:

worker CPU work stops

For expensive running CPU loops, use cooperative CancelationToken checks. A caller-side timeout or task cancel does not automatically prove CPU work has stopped. For interactive latest-wins flows use both:

debounce
+ cancel previous token
+ monotonically increasing revision/request ID
+ discard result if revision is stale

Cancellation saves resources; revision checks guarantee correctness.

Step 13 — Handle streams carefully

Use worker streams for meaningful progressive output, not per-byte/per-iteration chatter. Throttle/coalesce high-frequency progress events so message overhead does not dominate. Test:

  • event order within one stream;
  • completion;
  • worker-side errors;
  • subscription cancellation;
  • cleanup.

Step 14 — Keep Flutter UI concerns on the main isolate

Keep on main isolate:

Widgets / BuildContext
navigation
rendering
state emission
framework object manipulation

Preferred architecture:

Widget
→ Cubit/ViewModel
→ Repository/Application service
→ CPU processing abstraction
→ Squadron Worker/Pool

Do not expose Worker/Pool/Task/WorkerException in UI state unless the product explicitly manages worker infrastructure. Verify background-isolate support before using Flutter plugins inside workers. When uncertain, keep plugin I/O on its supported isolate and offload pure CPU transformation only.

Step 15 — Treat web as a separate deployment target

Squadron web uses browser Web Workers, not native isolates. Verify:

targetPlatform
baseUrl
generated *.web.g.dart
compiled JS or Wasm worker artifact
deployed URL
CSP
custom marshaling
browser compatibility

Automate the pipeline:

build_runner
→ compile generated web worker entry
→ flutter build web
→ verify/copy artifacts
→ deploy
→ browser worker-start smoke test

Do not assume native tests or flutter build web alone prove the worker artifact is available at runtime.

Step 16 — Map errors at the processing boundary

Treat Squadron errors as infrastructure details. Preferred:

WorkerException / worker-channel failure
→ ProcessingFailure / SearchFailure / ImageFailure
→ state-management failure state
→ user-facing recovery

Preserve sanitized diagnostics:

task type
payload size
queue wait
worker runtime
worker/pool initialization
error category

Do not log full sensitive payloads. Retry only operations proven safe to replay; do not assume worker crashes make external side effects idempotent.

Step 17 — Test correctness without depending on concurrency first

Extract pure computation and unit-test it directly. Then add boundary tests for:

  • worker start/call/stop;
  • result + exception propagation;
  • marshaler round trips;
  • cancellation;
  • streams;
  • pool queueing/bounds;
  • state affinity if used;
  • web worker artifact startup and RPC. Do not assert exact completion order unless the API/business contract guarantees it. Native tests do not replace real browser worker tests.

Step 18 — Debug from the boundary inward

If a worker fails to start, check:

versions
annotations
generated code
constructor marshaling
target platform
web artifact/baseUrl

If method-not-found occurs, regenerate before debugging protocol IDs. If work hangs, inspect:

pending vs running
queue saturation
await that never resolves
stateful interleaving/affinity
channel/worker crash
cancellation

If worker is slower than local, inspect startup + transfer + queue + granularity before adding workers. If UI still janks, profile main-isolate preprocessing, result materialization, state mapping, widget build/layout, and raster work. Squadron cannot fix a raster bottleneck.

Step 19 — Profile performance as a system

Compare appropriate variants:

A main isolate
B compute / Isolate.run
C persistent Worker
D pool size 2
E other bounded pool sizes

Use production-sized workloads and profile/release conditions. Measure:

  • median and tail latency;
  • cold/warm latency;
  • queue wait and compute time;
  • Flutter frame timings;
  • CPU;
  • memory peaks;
  • mobile battery/thermals;
  • browser-specific behavior. A faster CPU benchmark that introduces scrolling jank, excessive memory, or thermal throttling is not a win.

Step 20 — Review migrations behavior-first

Before upgrading Squadron or moving local code into workers:

  1. record squadron and squadron_builder versions;
  2. read intervening changelog/migration notes;
  3. regenerate code;
  4. characterize results/errors/ordering/cancellation first;
  5. rerun native integration tests;
  6. rebuild web worker artifacts;
  7. run browser tests;
  8. benchmark representative workloads. Worker-boundary changes can alter timing, copying, object identity, exception shape, cancellation, state affinity, and plugin availability even when public business signatures look unchanged.

Existing-code review output

When reviewing a repository, report findings in this order:

  1. Workload suitability — which stages are actually CPU-bound and measured.
  2. Execution strategy — whether main/compute/Worker/Pool is justified.
  3. Lifecycle — startup, ownership, and missing stop() paths.
  4. Pool policy — worker bounds, maxParallel, queue/backpressure.
  5. Message boundary — payload sizes, granularity, marshalers, native/web differences.
  6. Concurrency correctness — async interleaving, affinity, stale results, cancellation.
  7. Flutter integration — UI/plugin boundary violations.
  8. Web deployment — generated artifacts, base URL, CSP, browser verification.
  9. Performance evidence — cold/warm, queue, frames, memory, thermals.
  10. Tests/migration risk — missing boundary, browser, or regression coverage. For each problem give:
finding
→ why it matters
→ evidence
→ smallest safe change
→ verification

Production definition of done

A Squadron change is production-ready only when:

  • workload suitability is demonstrated;
  • main-isolate baseline and end-to-end worker behavior are measured;
  • worker/pool lifecycle and stop() ownership are explicit;
  • concurrency is bounded;
  • queue/backpressure policy exists for bursty producers;
  • task granularity is sensible;
  • payload sizes and marshaling are reviewed;
  • custom DTOs round-trip correctly;
  • stateful worker interleaving/affinity is deliberate;
  • long-running obsolete CPU jobs can stop when required;
  • stale results cannot overwrite newer state;
  • worker errors map to application failures;
  • plugin/background-isolate assumptions are verified;
  • web artifacts are generated, deployed, and browser-tested when web is supported;
  • profile/release measurements include frames and memory;
  • low-end/mobile thermal implications are acceptable;
  • generated code and package versions are compatible;
  • pure logic and worker-boundary tests pass. Use Squadron only when the worker boundary produces a measurable improvement in responsiveness, throughput, or isolation that justifies its communication and lifecycle cost.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.