Skip to content
Technical preview. This site is published for review. Everything on it, including the API, tokens and module protocol, is subject to change.

API

This page describes the API AutoFlow serves today. A new shape for the Workflow and Execution APIs is proposed in ADR 0011 in the design-doc repository, so expect the RPCs, messages and tokens on this page to change.

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 aud claim must be gitlab-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

FieldTypeConstraintsMeaning
idempotency_keystring1 to 255 bytesDeduplication key, scoped to the namespace. Required: a caller that wants no deduplication picks a value that cannot collide.
workflow_definitionbytes1 byte to 100 KiBThe workflow definition as Starlark source.
argsrepeated Valueshares a 256 KiB budget with the definition and kwargsPositional arguments bound to main(). Order is preserved so replays bind the same way.
kwargsrepeated Kwargsame budgetKeyword arguments bound to main().
namespace_idint64positiveThe namespace the workflow belongs to. Required, opaque to AutoFlow.
schedule_to_complete_timeoutDurationpositive if set, at most 60 daysHow long the workflow may run. Unset means 30 days.
token_bindingbytesrequired, exactly 64 bytesCaller-generated unguessable value that binds the workflow’s tokens to whoever started it.
annotationsrepeated Annotationat most 32, keys uniqueKey and value tags. The key follows the Kubernetes annotation key format, the value is at most 1 KiB. Every child workflow inherits them.

StartWorkflowResponse

FieldTypeConstraintsMeaning
workflow_keystring1 to 255 bytesThe opaque key of the workflow. It embeds the shard, so later calls route without a central lookup.
workflow_tokenstring1 byte to 4 KiBCapability for the workflow, required by every other RPC.
channel_tokensrepeated ChannelTokenOne 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

The workflow token, channel token and token binding described here are an interim design. Expect them to change once GATE is available.
TokenScopeNotes
Workflow tokenOne workflowRequired 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 tokenOne channel of one workflowSend-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_bindingThe workflowExactly 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_DENIED and 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

FieldTypeConstraintsMeaning
workflow_keystring1 to 255 bytesThe key StartWorkflow returned.
workflow_tokenstringrequired, 1 byte to 4 KiBMust verify against the addressed workflow.
namespace_idint64positiveThe namespace the workflow was started in.

GetWorkflowResponse

FieldTypeConstraintsMeaning
workflow_keystring1 to 255 bytesEchoes the request.
namestring1 to 255 bytesThe engine’s workflow name. RunWorkflow for every AutoFlow workflow.
idempotency_keystring1 to 255 bytesThe key the workflow was created under.
stateWorkflowStateSee below.
created_atTimestampWhen the workflow was created.
updated_atTimestampWhen the execution row was last written: at creation, at the terminal transition, and when a channel value is promoted. Not a last-progress timestamp.
resultResultThe 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

FieldTypeConstraintsMeaning
idempotency_keystring1 to 255 bytesPer-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_tokenstring1 byte to 4 KiBEither 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.
valueValuerequired, at most 64 KiB marshaledThe value to put on the channel. Must not contain a channel.
workflow_tokenstringrequired, 1 byte to 4 KiBMust verify against the workflow the channel token addresses. It is the whole of the authorization.
namespace_idint64positiveThe 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.

KindStarlark typeNotes
none_valueNoneType
bool_valuebool
integer_valueintSigned 64-bit.
float_valuefloat
string_valuestringAt most 256 KiB.
bytes_valuebytesAt most 256 KiB.
timestamp_valuetime.time
duration_valuetime.duration
list_valuelist
tuple_valuetupleHashable when all elements are.
set_valuesetElements must be hashable.
dict_valuedictKeys must be hashable.
message_valueproto messageType must come from the module’s own descriptor set.
sensitive_stringsensitive_stringOpaque to the workflow, at most 256 KiB.
sensitive_bytessensitive_bytesOpaque to the workflow, at most 256 KiB.
channel_valuechannelFrom a caller or a workflow into a module only. A module cannot send one.
struct_valuestructField names must be unique.
module_valuemoduleMember 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"'
CommandPurposeFlags
autoflow runSubmits 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

Last updated on