5.8 KiB
id, type, title, status, created, updated
| id | type | title | status | created | updated |
|---|---|---|---|---|---|
| task-flow-engine-spec | design-spec | Task Flow Engine Specification | draft | 2026-05-01 | 2026-05-01 |
Task Flow Engine Specification
Purpose
The task flow engine is a lightweight, declarative workflow substrate for information objects that move through named workstations. It replaces local status enums and hardcoded transition tables with pure assertions over an object's observable properties.
The engine is intentionally small: it receives a plain dictionary plus a flow definition, evaluates assertions, and returns a machine-readable result. It does not know about SQLAlchemy, FastAPI, State Hub routers, or Custodian domain rules.
Core Terms
Information Object
An information object is any entity with:
- a current workstation label, usually exposed as
workstationorstatus - a bag of observable properties
- optional nested collections of related entities
Examples include workstreams, tasks, contributions, capability requests, and future interface changes. The engine treats all of them as plain dictionaries.
WorkstationDef
A workstation is a named position an information object can occupy.
name: active
description: Work is underway.
entry_assertions: []
exit_assertions:
- id: tasks.all_done
target: tasks.*.status
op: all_eq
value: [done, cancelled]
description: All child tasks are done or cancelled.
Schema:
name: strentry_assertions: list[AssertionDef]exit_assertions: list[AssertionDef]description: str
A workstation with no entry assertions is always reachable. A workstation with no exit assertions is always exitable.
AssertionDef
An assertion is a pure predicate over object data.
Schema:
id: strtarget: strop: strvalue: Anydescription: str
The target is a dot path into the information object. It supports normal dict
and attribute traversal plus * for collection expansion:
tasks.*.statusdependencies.all.workstationmetadata.approved_by
The built-in operations are:
all_eq: every resolved value equals the expected value, or is included in the expected listany_eq: at least one resolved value equals the expected value, or is included in the expected listnone_eq: no resolved values equal the expected value, or are included in the expected listexists: at least one non-empty value resolvescount_gte: the number of resolved values is greater than or equal to the expected integercustom: delegates evaluation to a host-injected callable
Assertions never mutate state.
FlowDef
A flow definition is a named workstation graph for one entity type.
Schema:
id: strentity_type: strworkstations: list[WorkstationDef]
Multiple flows may exist for the same entity type, for example a lightweight workstream flow and a governance-heavy workstream flow.
Transition
Transition is not a first-class model. The engine derives reachable workstations by evaluating every workstation's entry assertions against the current object state. If the assertions for a target workstation are satisfied, that workstation is reachable from the current workstation.
The current workstation's exit assertions determine whether the object is blocked where it is. Unsatisfied exit assertions become blocking reasons.
FlowResult
Evaluation returns:
current_workstation: active
exit_blocked: true
blocking_assertions:
- id: tasks.all_done
passed: false
reason: "Expected all values at tasks.*.status to be in ['done', 'cancelled']; got ['done', 'todo']."
reachable:
- todo
- active
unreachable:
- workstation: completed
blocking:
id: tasks.all_done
passed: false
reason: "Expected all values at tasks.*.status to be in ['done', 'cancelled']; got ['done', 'todo']."
Schema:
current_workstation: strexit_blocked: boolblocking_assertions: list[AssertionResult]reachable: list[str]unreachable: list[UnreachableWorkstation]
Expressiveness Across Existing Entities
Workstreams
Workstreams can express readiness for completion by asserting that child tasks
are done or cancelled. They can express dependency blocking by checking that
all dependency workstreams have reached completed.
Tasks
Tasks can express human intervention with the existing needs_human flag.
Returning from blocked to in_progress is an entry assertion over that same
flag. Lightweight completion remains unconstrained because curator intent is
the deciding signal.
Contributions
Contributions can reproduce the current draft, submitted, acknowledged, accepted, merged, rejected, and withdrawn lifecycle by giving each workstation entry assertions that describe which previous statuses may enter it. This keeps the current lifecycle readable without baking domain transitions into engine code.
Capability Requests
Capability requests can reproduce the existing requested, routing disputed, accepted, in progress, ready for review, completed, rejected, and withdrawn lifecycle the same way. Host-specific effects such as notifications remain in the State Hub router; the flow engine only answers whether the target workstation is reachable.
Host Boundary
The engine owns:
- dataclasses for flow definitions and results
- target path resolution
- built-in predicate evaluation
- host-injected custom predicate dispatch
- reachable and blocked derivation
State Hub owns:
- loading domain-specific YAML flow definitions
- converting ORM entities into plain dictionaries
- migrations from enum-backed status fields to strings
- router side effects such as timestamps and notifications
- MCP tools and user-facing explanations
This boundary keeps the first implementation extractable into a standalone
task-flow-engine package once the API stabilizes.