Skip to content

ADR 007: Task vs TaskRun Internal Representation

Context

A classic architectural decision in workflow orchestrators is whether to introduce a separate TaskRun dataclass representing a specific execution attempt of a Task, or to maintain Task as the state container updated in-place with historical transitions preserved in SQLite.

Decision

Retain Task as the primary lifecycle object during execution, rather than introducing a public TaskRun abstraction: 1. Task encapsulates the current in-flight state, attempt_count, and last_error. 2. Every state transition and attempt is immutably logged to task_events tagged with run_id, timestamp, and attempt_count. 3. An internal _run_id attribute on Task links tasks to their run context without widening public constructor contracts. 4. Engine scheduling reads task.state as the single source of truth, eliminating desynchronization between in-memory structures and dictionaries.

Consequences

  • The public API surface remains minimal, clean, and backwards-compatible.
  • Detailed execution history is fully preserved in SQLite for querying, replay, and resumption.