Execution model
One binary
A pipeline compiles into one binary. That binary executes in two roles; the code is the same, the launch environment assigns the role:
| Role | Where it runs | What it does |
|---|---|---|
run | the run's process: a container launched by the server, or a process launched by the user | drives the run |
machine | a container on an agent's machine | executes the actions addressed to that machine |
There is no third form: everything the user writes executes in one of these two instances of the same binary.
Who connects to whom
There are exactly three kinds of connections in the system, all leading to the server — outbound. The internal infrastructure — Temporal (the durable core), the image registry, the blob store — is invisible and unaddressable from outside:
The pipeline, the agent, and the browser know one address: the server's port.
The run
A run starts through the server. The identifier names exactly one
logical execution, in every state. The same request under the same id
— the pipeline and the params — answers exists and returns that run,
open or closed, however it closed: a client whose answer was lost asks
again and nothing starts twice. Another request under a taken id is a
conflict (ALREADY_EXISTS), never a second execution; a re-run is a
new id. The horizon of this promise is the namespace's retention of
executions: after it, an id is a new name. Absent is said only when
the service says so — a server that could not ask Temporal answers
UNAVAILABLE, not NOT_FOUND.
Under the pipeline's queue policy a firing that arrives while a run
is live is queued: it has its run id at once and starts when the live
run ends. The queue holds one firing — cron does not pile up behind a
long run — and a newer firing displaces the older one, which is named
in the answer and never runs.
There are two ways to give a run its executor:
- Managed — an image is given at start: the server launches the container itself and removes it after completion.
- Inplace — the user launches the binary themselves, anywhere: local development, their own orchestrator. The server only starts the run's process.
A run is recoverable: the execution history lives in Temporal, not in the process. A crash, a restart, a network drop lose nothing — when the executor comes back, the run continues from where it stopped. Successfully completed actions never execute twice.
Actions on a machine
An action is always addressed to an agent and executes on its machine — not in the run's process. One container per (agent × run) pair, brought up by the first action:
The agent is a host, not an executor: it brings the container up and watches it, but never looks inside the actions.
The recording pass
Before the run starts, the pipeline function executes once in recording mode:
- nothing executes — declarations of actions and resources only get registered;
- reads from resources return optimistic zeros (booleans —
true), so guard conditions do not cut the walk short; - everything found — actions, resource kinds — is known to the executor before work begins.
This is why actions and resources are declared right where they are used, with no separate registration. The price: pipeline code must survive a pass with zero-valued data.
Run completion
Success, failure, and cancellation end down the same road: