|
| 1 | +# ADR-0013: Action declares handler execution strategy |
| 2 | + |
| 3 | +- **Status:** accepted |
| 4 | +- **Date:** 2026-03-29 |
| 5 | +- **Deciders:** @Aksem |
| 6 | +- **Tags:** actions, architecture |
| 7 | + |
| 8 | +## Context |
| 9 | + |
| 10 | +When an action has multiple handlers, FineCode needs an explicit rule for how |
| 11 | +those handlers relate to one another. |
| 12 | + |
| 13 | +Some actions are **ordered pipelines**: each handler transforms the result of |
| 14 | +the previous one, so handler order is part of the action's semantics. Other |
| 15 | +actions are **independent fan-out**: handlers produce separate results, so the |
| 16 | +action does not depend on a particular order. |
| 17 | + |
| 18 | +If this relationship is not declared explicitly, the framework must infer it |
| 19 | +from naming conventions or other implementation-specific heuristics. That makes |
| 20 | +the action contract harder to review, easier to misapply, and less durable as |
| 21 | +the implementation evolves. |
| 22 | + |
| 23 | +The design also needs to stay clear about scope: |
| 24 | + |
| 25 | +- This ADR concerns the relationship **between handlers within one action**. |
| 26 | +- It does not decide whether a collection action processes different items |
| 27 | + sequentially or in parallel. |
| 28 | +- It must apply consistently whether the action processes one unit of work |
| 29 | + directly or schedules work that is later associated back to individual items. |
| 30 | + |
| 31 | +The architectural question is therefore not "how should the current runtime |
| 32 | +implement concurrency," but "where does the action's handler relationship |
| 33 | +belong as part of the contract?" |
| 34 | + |
| 35 | +### Where to declare the strategy |
| 36 | + |
| 37 | +Three levels were considered: |
| 38 | + |
| 39 | +- **Per-handler**: each handler declares its own execution preference. Rejected |
| 40 | + because the relationship is between handlers, not inside one handler. |
| 41 | + Individual handlers do not have enough context to define the contract for the |
| 42 | + whole action, and conflicting declarations would be ambiguous. |
| 43 | +- **Per-action**: the action definition declares the strategy for how its |
| 44 | + handlers relate. This places the decision with the action designer, who owns |
| 45 | + the action's semantics. |
| 46 | +- **Per-invocation**: the caller or dispatcher chooses the strategy at runtime. |
| 47 | + Rejected because the handler relationship is part of what the action means, |
| 48 | + not a caller-specific tuning option. |
| 49 | + |
| 50 | +## Related ADRs Considered |
| 51 | + |
| 52 | +- [ADR-0012](0012-item-level-and-collection-level-action-granularity.md) — defines item-level and collection-level action granularity. The handler execution strategy applies at both levels: item actions with sequential handlers (format), collection actions with concurrent handlers (lint). The two ADRs are complementary. |
| 53 | +- [ADR-0009](0009-explicit-partial-result-token-propagation.md) — defines partial result flow across action boundaries. This ADR complements it by defining how one action's handlers relate when that action emits work incrementally. |
| 54 | + |
| 55 | +## Decision |
| 56 | + |
| 57 | +The **action definition** declares one handler execution strategy for the |
| 58 | +action. The framework applies that strategy uniformly wherever that action's |
| 59 | +handlers are composed. |
| 60 | + |
| 61 | +- **Sequential** (default): handlers run in definition order. Each handler's output can feed the next handler's input. Appropriate for transform pipelines (formatting: isort → ruff → save). |
| 62 | + |
| 63 | +- **Concurrent**: handlers run in parallel. Handlers produce independent results. Appropriate for independent analyzers (linting: ruff + mypy). |
| 64 | + |
| 65 | +### The action designer owns this decision |
| 66 | + |
| 67 | +The execution strategy describes how handlers within the action interact — whether they form a sequential pipeline (each transforms the previous output) or run independently (each produces separate results). This is a property of the action's semantics: |
| 68 | + |
| 69 | +- A formatting action is a sequential pipeline by design: handler order is part of the semantics. |
| 70 | +- A linting action runs independent analyzers by design: handler order is irrelevant. |
| 71 | + |
| 72 | +Individual handlers cannot correctly determine this. A handler only knows about itself — it cannot know whether other handlers in the chain depend on its output or run independently. Placing this decision on handlers would allow contradictory declarations and shift responsibility to the wrong party. |
| 73 | + |
| 74 | +### No mixed execution within a single action |
| 75 | + |
| 76 | +If one action would need some handlers to form a sequential pipeline and others |
| 77 | +to behave as independent concurrent peers during the same processing step, that |
| 78 | +is a sign that the action is combining distinct concerns. The correct response |
| 79 | +is to split it into multiple focused actions, each with a clear handler |
| 80 | +relationship, and compose them through orchestration. |
| 81 | + |
| 82 | +This rule is about **handler processing semantics**, not about every lifecycle |
| 83 | +phase that may exist around handler execution. Framework-managed setup, |
| 84 | +teardown, or other lifecycle behavior may still evolve independently so long as |
| 85 | +the action exposes one coherent handler relationship during processing. |
| 86 | + |
| 87 | +This is consistent with [ADR-0012](0012-item-level-and-collection-level-action-granularity.md) and the design principle "prefer multiple focused actions over one overloaded action." |
| 88 | + |
| 89 | +### Item-level parallelism is a separate concern |
| 90 | + |
| 91 | +Whether a collection action processes different items sequentially or in |
| 92 | +parallel is not governed by this decision. That is an orchestration concern. An |
| 93 | +action may therefore have sequential handler semantics per item while still |
| 94 | +being orchestrated across multiple items in parallel. |
| 95 | + |
| 96 | +## Consequences |
| 97 | + |
| 98 | +- **Action semantics become explicit.** Reviewers can see whether an action is |
| 99 | + an ordered pipeline or a set of independent handlers without relying on |
| 100 | + implementation-specific behavior. |
| 101 | +- **Handler responsibilities stay narrow.** Individual handlers do not need to |
| 102 | + negotiate concurrency policy with one another. |
| 103 | +- **The same contract applies across action shapes.** The action's handler |
| 104 | + relationship does not change just because the action is used for one item or |
| 105 | + for a collection. |
| 106 | +- **Mixed processing semantics require composition.** Designs that genuinely |
| 107 | + need both pipeline and fan-out semantics must express that through multiple |
| 108 | + actions or explicit orchestration boundaries. |
| 109 | +- **The model can be extended later if needed.** If future action designs show a |
| 110 | + recurring architectural need for more than one handler-processing dimension, a |
| 111 | + later ADR can refine this rule without changing the meaning of the current |
| 112 | + one. |
| 113 | + |
| 114 | +### Alternatives Considered |
| 115 | + |
| 116 | +**Handler-level declaration.** Each handler declares whether it should run |
| 117 | +sequentially or concurrently. Rejected because handler execution strategy is a |
| 118 | +relationship across the action, not a local property of one handler. |
| 119 | + |
| 120 | +**Configuration-level override.** The execution strategy is supplied from |
| 121 | +project configuration rather than from the action definition. Rejected as the |
| 122 | +primary mechanism because handler execution strategy is intrinsic to the action |
| 123 | +contract and should travel with the action definition. |
| 124 | + |
| 125 | +**Separate strategies for direct handler composition and per-item handler |
| 126 | +composition.** Deferred because current use cases treat these as the same |
| 127 | +architectural relationship. Revisit this if FineCode gains stable action |
| 128 | +semantics where handlers must be sequential in one processing context and |
| 129 | +concurrent in another, and that difference cannot be expressed cleanly as a |
| 130 | +lifecycle concern rather than a processing contract. |
| 131 | + |
| 132 | +### Related Decisions |
| 133 | + |
| 134 | +- Complements [ADR-0012](0012-item-level-and-collection-level-action-granularity.md) (item/collection granularity) |
| 135 | +- Clarifies the handler-composition side of incremental per-item work described alongside [ADR-0009](0009-explicit-partial-result-token-propagation.md) |
| 136 | + |
| 137 | +## Implementation Notes |
| 138 | + |
| 139 | +- In the current implementation, the same declared strategy should be applied |
| 140 | + both when handlers run directly and when handler-produced work is later |
| 141 | + grouped per item through `partial_result_scheduler`. |
| 142 | +- Runtime mechanisms such as task groups, scheduler internals, or future |
| 143 | + lifecycle hooks are implementation choices rather than the architectural |
| 144 | + decision itself. |
0 commit comments