Skip to content

Native APL and the canonical model

APL documents are not translated into BPMN before execution. AplParser compiles abada.io/v1 YAML directly into the same canonical executable graph that BPMN XML produces, so both schemas share the runtime, persistence, versioning and outbox machinery.

The engine sniffs the deployment source:

  • a document whose first meaningful line is version: abada.io/v1 compiles through the native APL parser;
  • anything starting with < validates and compiles as canonical BPMN 2.0 XML.

Persisted definitions are recompiled through the same dispatch on restart. The stored schema is recorded per definition version in process_definitions.schema_type (BPMN_XML or APL_NATIVE) and surfaced in deployment/list DTOs as schemaType with definitionFormatVersion (canonical-1 / apl-native-1).

flowchart LR
    APL[APL YAML abada.io/v1] --> Parser[AplParser]
    BPMN[BPMN 2.0 XML] --> Detect[Dialect detect + validate]
    Detect --> Canon[Canonical executable graph]
    Parser --> Canon
    Canon --> Persist[(Immutable version + schema type)]
    Persist --> Runtime[Transactional runtime]

The APL node vocabulary maps 1:1 onto the runtime primitives that BPMN imports produce:

APL node Runtime element
webhook Start event
end End event
human-input User task (candidate groups from assignees); with outcomes, a decision that routes like an exclusive choice (approval-gate is a deprecated alias)
engine-task External service task on service
agent External service task on the fixed abada:agent topic
script In-transaction script task (the APL form of an embedded Java delegate)
decision-table Business rule task with native abada:decisionTable semantics
condition Exclusive gateway
inclusive Inclusive gateway
parallel Parallel gateway
message-catch / timer / signal Durable catch-event primitives
event-gateway Event-based gateway with atomic sibling cancellation

The APL twin of the engine’s kitchen-sink process is proven 1:1 against the BPMN original by AplKitchenSinkTest (PostgreSQL) and Studio’s APL↔BPMN round-trip check — the two surfaces really do compile to one model.

APL closes and tightens holes relative to XML:

  • A cycle is allowed only when the step it returns to declares loop.max_iterations; any other cycle — or a cycle back to the start node or a parallel or inclusive gateway — is rejected at deployment. Cycle detection is linear in the size of the graph.
  • Duplicate ids, a second webhook, a missing metadata.name, undeclared routing targets, unrecognized node types and invalid bounds fail deployment with index-friendly stable error codes and roll back — nothing partial persists.
  • event-gateway requires ≥2 competing children; the first to fire advances the instance and every sibling subscription/timer/token is cancelled in the same transaction.
  • Every error in every node is reported at once, each with its node id and a JSON Pointer path. The engine-owned JSON Schema (engine/src/main/resources/apl/apl-v1.schema.json, served at GET /api/v1/apl/schema) additionally reports unknown fields and wrong value types — warnings in the 1.1.0 release candidates, errors from 1.1.0.
  • POST /api/v1/apl/validate runs this whole pipeline without persisting anything; Studio uses it instead of keeping its own rules, and generates its APL types from the same schema.

Besides next, a waiting step can leave through named routes, all compiled into the same canonical graph as boundary routes attached to the step:

APL Taken when
on_error (optionally per error code) A worker reports an error, or the last attempt fails
on_timeout: { after, then } The step is still running after the duration; its work is cancelled
on_low_confidence, on_invalid_output The engine rejects an agent result
outcomes.<name>.next A reviewer decides that outcome
loop.on_exhausted A loop step is reached once more than max_iterations allows

Timers behind on_timeout and sla_hours are durable jobs, so a restart never loses them. A route that a step needs but does not declare becomes an incident (WORK_FAILED, LOOP_EXHAUSTED) instead of a silent stop. BPMN timer and error boundary events compile to the same routes. Details and examples: Loops, routes and review decisions.

Deploying a process key creates a new immutable version that stores the exact source (YAML or XML), its checksum and schema type. New instances select the latest committed definition; running instances retain their stored deployment ID. Studio deploys the exact saved revision of a project document — parsed cache entries use the immutable deployment ID, so redeployment can never change an active instance’s semantics.

Project documents carry a stable metadata.key unique inside their project and autosave with optimistic revisions (If-Match); a stale save is rejected instead of overwriting another editor. Project archive is reversible and blocks document edits, deployment and new starts while running instances drain normally.