NoETL Architecture (Canonical v10)
NoETL is an event-sourced orchestration system with a strict control-plane / data-plane split:
- Server (control plane): validates playbooks, resolves inputs, admits steps, routes tokens via Petri-net arcs, persists the event log, and schedules work.
- Worker pools (data plane): execute step pipelines (tools), apply task outcome policies, and emit events back to the server.
- CLI: manages server/worker lifecycle and provides operational commands.
Canonical v10 DSL details live under:
documentation/docs/reference/dsl/step_spec.mddocumentation/docs/reference/dsl/spec.mddocumentation/docs/reference/dsl/execution_model.md
System components and responsibilities
- API server: hosts execution APIs; ingests worker events; maintains projections; schedules step-runs and (optionally distributed) loop iterations.
- Catalog: stores playbooks (content + metadata) and serves them to the server.
- Event log: append-only store of execution events (authoritative source of truth).
- Queue / broker (implementation-defined): distributes step-run commands to workers; enforces leases and retries at the execution layer.
- Workers: claim leases, execute pipelines deterministically, apply task policies, and report events.
Canonical DSL execution model (high level)
Step structure (Petri-net transition)
A canonical step is:
- Admission gate (server):
step.spec.policy.admit.rules - Ordered pipeline (worker):
step.tool(labeled task list) - Router (server):
step.next(next.spec+next.arcs[])
There is no step.when field and no step-level case.
Task control flow (worker-side)
Retry/pagination/polling/early-exit are expressed as task outcome policy rules:
task.spec.policy.rules[]usingwhenconditions andthen.doactions (retry|jump|continue|break|fail)
There is no legacy eval/expr.
Routing (server-side)
Routing is expressed as Petri-net arcs:
next.spec.mode: exclusive|inclusivenext.arcs[]with optionalwhenandargs
Only the server’s routing creates new tokens/step-runs.
Runtime scopes (templates and policies)
Canonical namespaces:
workload(immutable merged inputs)keychain(resolved credentials; read-only)ctx(execution-scoped mutable context; event-sourced patches)iter(iteration-scoped mutable context inside loops; isolated per iteration)args(token payload / arc inscription)- pipeline locals:
_prev,_task,_attempt,outcome - routing input:
event(boundary event fornext.arcs[].when)
Canonical guidance:
- Use
iterfor pagination cursors and per-item progress. - Use
ctxfor cross-step state and references (ResultRefs/ManifestRefs). - Avoid conflicting
ctxwrites from parallel iterations until reducers/atomics exist.
Server–worker lifecycle (canonical)
- Request: client requests execution; server persists request event(s).
- Resolve: server validates playbook, resolves
keychain, merges request payload intoworkload, initializesctx. - Admit + schedule: server evaluates admission for the entry step(s); schedules step-run commands.
- Execute: worker claims a step-run lease and executes the pipeline tasks:
- each task produces one final
outcome - policy rules decide whether to
continue,retry,jump,break, orfail
- each task produces one final
- Emit: worker emits task/step/loop events; server persists them to the event log.
- Route: server evaluates
next.arcs[]on boundary events (step.done,step.failed,loop.done) and schedules next step-run(s). - Finish: server ends execution when quiescence is reached (no runnable tokens, no in-flight runs).
Reference-first results (canonical)
Canonical rule: large outputs MUST be externalized and represented by references.
- storage tasks are normal tool tasks (no special
sinkkind) - pass ResultRef objects + extracted fields, not giant inline payloads
See:
documentation/docs/reference/result_storage_canonical_v10.mddocumentation/docs/reference/dsl/runtime_results.md