Skip to content

Commit d8a81a5

Browse files
committed
ADRs 0012-0015
1 parent 356222e commit d8a81a5

6 files changed

Lines changed: 406 additions & 0 deletions
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# ADR-0012: Actions that process multiple items distinguish item and collection granularity
2+
3+
- **Status:** accepted
4+
- **Date:** 2026-03-29
5+
- **Deciders:** @Aksem
6+
- **Tags:** actions, architecture
7+
8+
## Context
9+
10+
Some FineCode actions process more than one item of the same kind in one request, such as files, tests, or artifacts. This ADR addresses those actions.
11+
12+
That creates a tension between two handler needs:
13+
14+
1. **Per-item handlers** — formatters, simple linters — process each item independently. They gain nothing from seeing the full item list and become simpler when they receive exactly one item.
15+
16+
2. **Batch-aware handlers** — type checkers, cross-file analyzers — benefit from receiving all items at once for whole-program analysis, shared caches, or batch-optimized tool invocations.
17+
18+
If every such action is modeled only as a collection-level contract, single-item usage and naturally per-item handlers become awkward. A caller with one item must still use a batch-shaped payload and result model, and a handler that only transforms one item must still adapt itself to a collection contract.
19+
20+
If every such action is modeled only as an item-level contract, batch-aware handlers become harder to express. Tools that need the full item set for correctness, shared context, or efficiency would be forced through per-item invocation patterns that do not match their natural unit of work.
21+
22+
The design needs to support both per-item and batch-aware handlers without forcing one model onto every action that processes multiple items.
23+
24+
## Related ADRs Considered
25+
26+
- [ADR-0008](0008-explicit-specialization-metadata-for-language-actions.md) — defines language-specific specialization metadata that applies to both item-level and collection-level actions. This ADR is orthogonal: it addresses payload cardinality, not language dispatch.
27+
- [ADR-0009](0009-explicit-partial-result-token-propagation.md) — defines how partial results flow across action boundaries. Collection-level actions use partial results to stream per-item results; item-level actions typically do not need partial results since they produce a single result.
28+
- [ADR-0010](0010-progress-reporting-for-actions.md) — defines progress reporting for actions. Collection-level actions own per-item progress; item-level actions do not report progress for their single item (the parent orchestrator owns the narrative).
29+
30+
## Decision
31+
32+
FineCode will support **two complementary action granularity levels** for actions that process multiple items:
33+
34+
1. **Item action** — the payload carries a single item, the result describes that one item. Handlers receive and return exactly one unit of work.
35+
36+
2. **Collection action** — the payload carries a list of items, and the result describes that batch, typically with entries keyed per item. Handlers receive the full list and may iterate over items, batch-optimize, or cross-reference items.
37+
38+
Both levels are ordinary actions in the FineCode action model. The distinction is architectural rather than hierarchical: the difference is in payload cardinality and handler intent, not in a separate framework category.
39+
40+
### Main handler action
41+
42+
When both item and collection actions exist for the same kind of operation, one usually serves as the **main handler action**. That is the action where the main handler logic naturally lives. The other action mainly adapts to it.
43+
44+
- **Item-primary**: the main handlers attach to the item action. Appropriate when each tool processes one item independently (e.g. formatting).
45+
46+
- **Collection-primary**: the main handlers attach to the collection action. An item action may exist as a convenience entry point for single-item callers. Appropriate when tools benefit from seeing all items at once (e.g. linting with type checkers).
47+
48+
### Coexistence of both levels
49+
50+
Both granularities may exist for the same kind of operation. In most cases, one of the corresponding actions serves as the main handler action: either an item-primary design or a collection-primary design. A collection-primary design may offer an item action as a convenience entry point for single-item callers, but it is not required.
51+
52+
The architectural rule is that item and collection granularity remain distinct contracts. How a non-primary level adapts to the primary level is a design-guide concern rather than part of this decision record.
53+
54+
### Language subactions at both levels
55+
56+
Language-specific specialization ([ADR-0008](0008-explicit-specialization-metadata-for-language-actions.md)) applies independently at each level. An item-primary design may have:
57+
58+
- `format_file` / `format_python_file` (item-level, language-specific — handlers register here)
59+
- `format_files` / `format_python_files` (collection-level, language-specific — orchestrates via delegation)
60+
61+
A collection-primary design may have:
62+
63+
- `lint_files` / `lint_python_files` (collection-level, language-specific — handlers register here)
64+
- `lint_file` / `lint_python_file` (item-level, language-specific — delegates, optional)
65+
66+
## Consequences
67+
68+
- **Cleaner single-item contract.** Callers and handlers can work with a contract shaped around one item rather than adapting a batch-oriented payload and result model.
69+
- **Batch optimization remains available.** Handlers that benefit from the full item list can still be exposed through collection-level actions or specialized subactions without forcing every invocation through a batch-oriented contract.
70+
- **More actions to define.** Each granularity level is a separate action with its own payload, context, and result types. This is additional surface area, but each type is simpler and more focused.
71+
- **Action designers must choose where the main handler logic lives.** The designing-actions guide documents criteria for this choice. Choosing incorrectly leads to awkward delegation patterns but is correctable without breaking handler contracts.
72+
73+
### Alternatives Considered
74+
75+
**Single collection-level action with framework-provided single-item adapter.** The framework would automatically unwrap single-element lists and wrap single results. Rejected because it hides the semantic difference between "one item" and "a batch of one" — the caller's intent and the handler's contract are genuinely different, and conflating them makes the API harder to reason about.
76+
77+
**Force all multi-item work to item-level, with framework-provided batching.** Rejected because batch-aware handlers (type checkers, cross-file analyzers) need the full item list for correctness or performance. Forcing per-item invocation would either degrade their results or require them to maintain external state across calls.
78+
79+
**Single collection-level design as an implementation approach.** These operations could be modeled only as collection-level actions, with single-item work represented as a batch of one. Rejected as the architectural rule because it makes the public contract batch-shaped even when the natural unit of work is one item, and because it pushes per-item handlers through an interface that does not match their semantics.
80+
81+
### Related Decisions
82+
83+
- Builds on [ADR-0008](0008-explicit-specialization-metadata-for-language-actions.md) (language subactions apply at both levels)
84+
- Builds on [ADR-0009](0009-explicit-partial-result-token-propagation.md) (collection actions use partial results for per-item streaming)
85+
- Builds on [ADR-0010](0010-progress-reporting-for-actions.md) (collection actions own per-item progress)
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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

Comments
 (0)