API
AutoFlow is entered over gRPC. It serves gitlab.agent.autoflow.rpc.AutoFlow on the API listener, the endpoint configured under api.listen and dedicated to GitLab.
Authentication
Every call carries a JWT in the authorization metadata header as bearer <token>.
- Signed with HMAC, as HS256, HS384 or HS512, using the shared secret from
api.listen.authentication_secret_file. - The
audclaim must begitlab-kas. - The issuer is not validated.
The service performs no authorization on starting a workflow. Whoever holds the shared secret can start any script in any namespace, so deciding who may start what is the caller’s job. Reading, canceling and sending to a workflow are different: each requires that workflow’s own token, which stops one caller from reaching another caller’s workflow.
The service
service AutoFlow {
// Start a workflow with a caller-supplied Starlark definition and arguments.
rpc StartWorkflow(StartWorkflowRequest) returns (StartWorkflowResponse) {
option idempotency_level = IDEMPOTENT;
}
// Retrieve a workflow's status and terminal result by its key.
rpc GetWorkflow(GetWorkflowRequest) returns (GetWorkflowResponse) {
option idempotency_level = NO_SIDE_EFFECTS;
}
// Request cancellation of a workflow by its key. Cancellation is an
// asynchronous best-effort signal; the workflow transitions to CANCELED on a
// later replay round.
rpc CancelWorkflow(CancelWorkflowRequest) returns (CancelWorkflowResponse) {
option idempotency_level = IDEMPOTENT;
}
// Send a value into the channel a channel token addresses. The send is
// asynchronous and fire-and-forget; the value reaches the workflow on a later
// replay round.
//
// A workflow that is already terminal is refused with FAILED_PRECONDITION,
// and one that has spent the number of channel values a workflow may receive
// with RESOURCE_EXHAUSTED. Neither condition ever clears, so the send is
// refused again however long the caller waits, under the same or a new
// idempotency key.
rpc SendToWorkflowChannel(SendToWorkflowChannelRequest)
returns (SendToWorkflowChannelResponse) {
option idempotency_level = IDEMPOTENT;
}
}StartWorkflow
Submits a workflow definition and starts a workflow for it. The call returns as soon as the workflow is persisted; it does not wait for the workflow to run, and a workflow that later fails does not make StartWorkflow fail.
StartWorkflowRequest
| Field | Type | Constraints | Meaning |
|---|---|---|---|
idempotency_key | string | 1 to 255 bytes | Deduplication key, scoped to the namespace. Required: a caller that wants no deduplication picks a value that cannot collide. |
workflow_definition | bytes | 1 byte to 100 KiB | The workflow definition as Starlark source. |
args | repeated Value | shares a 256 KiB budget with the definition and kwargs | Positional arguments bound to main(). Order is preserved so replays bind the same way. |
kwargs | repeated Kwarg | same budget | Keyword arguments bound to main(). |
namespace_id | int64 | positive | The namespace the workflow belongs to. Required, opaque to AutoFlow. |
schedule_to_complete_timeout | Duration | positive if set, at most 60 days | How long the workflow may run. Unset means 30 days. |
token_binding | bytes | required, exactly 64 bytes | Caller-generated unguessable value that binds the workflow’s tokens to whoever started it. |
annotations | repeated Annotation | at most 32, keys unique | Key and value tags. The key follows the Kubernetes annotation key format, the value is at most 1 KiB. Every child workflow inherits them. |
StartWorkflowResponse
| Field | Type | Constraints | Meaning |
|---|---|---|---|
workflow_key | string | 1 to 255 bytes | The opaque key of the workflow. It embeds the shard, so later calls route without a central lookup. |
workflow_token | string | 1 byte to 4 KiB | Capability for the workflow, required by every other RPC. |
channel_tokens | repeated ChannelToken | One send-only token per distinct channel name in args and kwargs, in first-encounter order. Empty when the request carried no channel. |
Arguments may carry a channel_value with a caller-chosen name at any depth. The workflow receives it as an ordinary channel, and the caller receives a token for it. Nothing checks a name against the script: a name main() never receives still gets a working token, and values sent to it count against the workflow’s limits and are never read.
Tokens
| Token | Scope | Notes |
|---|---|---|
| Workflow token | One workflow | Required by GetWorkflow, CancelWorkflow and SendToWorkflowChannel. A missing one is INVALID_ARGUMENT, one that does not verify is PERMISSION_DENIED. Verified before the call returns. |
| Channel token | One channel of one workflow | Send-only. It names no principal, so a send presents the workflow token as well. Verified inside AutoFlow after the RPC returned, so a forged one is accepted here and dropped later. |
token_binding | The workflow | Exactly 64 bytes, generated by the caller and stored before the call. AutoFlow keeps only its SHA-256. A re-submission is handed tokens only if it presents the same value. |
Both tokens are JWTs whose expiry is their exp claim: start time plus the schedule-to-complete timeout plus one hour. That instant is recorded when the workflow is created, so tokens minted later for the same workflow carry it too. Store the workflow token. AutoFlow does not read it back out; the only way to get another is to re-submit under the same idempotency key with the registered binding.
Idempotency
Deduplication is keyed on namespace_id plus idempotency_key.
- Re-submitting a used key returns the existing workflow instead of starting a second one, and mints fresh tokens for it.
- The original submission wins. A different
workflow_definition, or different arguments, sent under a used key are ignored. To run new bytes, pick a new key. - A re-submission that presents a different binding, or none, fails with
PERMISSION_DENIEDand returns nothing at all, not even the workflow key. - Channel tokens are minted for the channel names of the retry request, so send the same request on retry.
- Keys starting with
__autocore_internal:are reserved and rejected.
GetWorkflow
Reads a workflow by its key. This is the only way to observe progress: there is no callback, no notification and no streaming variant, so a caller that must react to completion polls.
GetWorkflowRequest
| Field | Type | Constraints | Meaning |
|---|---|---|---|
workflow_key | string | 1 to 255 bytes | The key StartWorkflow returned. |
workflow_token | string | required, 1 byte to 4 KiB | Must verify against the addressed workflow. |
namespace_id | int64 | positive | The namespace the workflow was started in. |
GetWorkflowResponse
| Field | Type | Constraints | Meaning |
|---|---|---|---|
workflow_key | string | 1 to 255 bytes | Echoes the request. |
name | string | 1 to 255 bytes | The engine’s workflow name. RunWorkflow for every AutoFlow workflow. |
idempotency_key | string | 1 to 255 bytes | The key the workflow was created under. |
state | WorkflowState | See below. | |
created_at | Timestamp | When the workflow was created. | |
updated_at | Timestamp | When the execution row was last written: at creation, at the terminal transition, and when a channel value is promoted. Not a last-progress timestamp. | |
result | Result | The terminal outcome: the value main() returned, or the error. Unset while the workflow is running, canceled or timed out. |
States
enum WorkflowState {
WORKFLOW_STATE_UNSPECIFIED = 0;
WORKFLOW_STATE_RUNNING = 1;
WORKFLOW_STATE_COMPLETED = 2;
WORKFLOW_STATE_FAILED = 3;
WORKFLOW_STATE_CANCELED = 4;
WORKFLOW_STATE_TIMED_OUT = 5;
// System failure (abort retry budget exhausted or deterministic internal
// error), as opposed to WORKFLOW_STATE_FAILED which is a user/business
// failure returned by the workflow itself.
WORKFLOW_STATE_SYSTEM_FAILED = 6;
}CancelWorkflow
Requests cancellation. The request carries the same three fields as GetWorkflowRequest, and the response is empty on purpose: cancellation is an asynchronous best-effort signal. A successful response acknowledges the request only. The workflow moves to WORKFLOW_STATE_CANCELED on a later replay round, which GetWorkflow reports.
SendToWorkflowChannel
Sends one value into one channel of a running workflow.
SendToWorkflowChannelRequest
| Field | Type | Constraints | Meaning |
|---|---|---|---|
idempotency_key | string | 1 to 255 bytes | Per-message deduplication key, scoped to the addressed workflow. A retry under the same key is delivered at most once; a fresh key is a second delivery. |
channel_token | string | 1 byte to 4 KiB | Either a token StartWorkflow returned, or one a module handed out during the workflow. A handed-out token also reaches channels the workflow created for itself. |
value | Value | required, at most 64 KiB marshaled | The value to put on the channel. Must not contain a channel. |
workflow_token | string | required, 1 byte to 4 KiB | Must verify against the workflow the channel token addresses. It is the whole of the authorization. |
namespace_id | int64 | positive | The namespace the workflow was started in. |
The response is empty: acceptance is not delivery. The value reaches the workflow when the signal is promoted on a later replay round, and an accepted value is still dropped at promotion if the channel token does not verify. A terminal workflow is refused with FAILED_PRECONDITION, and one that has spent its channel-value budget with RESOURCE_EXHAUSTED; neither condition ever clears.
Rate limit
Each namespace may make 60,000 calls per minute across all four RPCs. A call over the limit is refused with RESOURCE_EXHAUSTED.
Value encoding
args, kwargs, value and result all use Value, a oneof of 18 kinds.
| Kind | Starlark type | Notes |
|---|---|---|
none_value | NoneType | |
bool_value | bool | |
integer_value | int | Signed 64-bit. |
float_value | float | |
string_value | string | At most 256 KiB. |
bytes_value | bytes | At most 256 KiB. |
timestamp_value | time.time | |
duration_value | time.duration | |
list_value | list | |
tuple_value | tuple | Hashable when all elements are. |
set_value | set | Elements must be hashable. |
dict_value | dict | Keys must be hashable. |
message_value | proto message | Type must come from the module’s own descriptor set. |
sensitive_string | sensitive_string | Opaque to the workflow, at most 256 KiB. |
sensitive_bytes | sensitive_bytes | Opaque to the workflow, at most 256 KiB. |
channel_value | channel | From a caller or a workflow into a module only. A module cannot send one. |
struct_value | struct | Field names must be unique. |
module_value | module | Member names must be unique. |
Hashable excludes messages, lists, dicts and sets. A Starlark value of any other type, such as a future or an action, has no encoding here and cannot cross the boundary at any depth.
The autoflow CLI
The autoflow binary in the Relay repository is a thin client over this service. All three subcommands share the connection flags --address (default 127.0.0.1:8153), --secret-file (required, the base64-encoded API secret), --tls and --ca-cert-file. The client signs an HS256 JWT with audience gitlab-kas.
autoflow run -s workflow.star --secret-file /path/to/secret --arg '"hello"'| Command | Purpose | Flags |
|---|---|---|
autoflow run | Submits a workflow definition and, unless --detach is given, polls until the workflow finishes. | --script/-s (required), --arg (repeatable), --kwarg name=<expr> (repeatable), --annotation key=value (repeatable), --namespace-id (default 1), --idempotency-key (default generated), --detach/-d, --timeout (0 means the server default), --poll-interval (default 2s) |
autoflow get <workflow-key> | Prints the state and result of a workflow. | --workflow-token (required), --namespace-id, --wait, --poll-interval |
autoflow cancel <workflow-key> | Requests cancellation. | --workflow-token (required), --namespace-id, --wait, --poll-interval |
--arg and --kwarg values are Starlark expressions parsed with the same dialect as a workflow definition, plus a CLI-only sensitive("...") builtin for marking an argument sensitive. run generates a random token binding, prints it with the workflow key and the workflow token, and exits non-zero unless the workflow completed.
More detail
- The AutoFlow API in the design doc.
internal/module/autoflow/rpc/rpc.protoandpkg/autoflow/value.proto.