Skip to main content

Overview

A workflow that clones a repository, opens a pull request or posts a refund acts through a connection - a GitHub, Stripe or Linear account connected to the agent or to its organisation. Rather than freezing a connection id into the source, declare each connection once by key and reference the key. At run time the engine resolves the key against the connections of the agent the run belongs to, so one definition runs unchanged on a hand-built agent, a duplicated agent and a template install.
Declaring connections on createWorkflow, and the compile-time check described below, ship with the next lua-cli release (after 3.31.0). Run-time resolution, the connection.resolved event and the push-time refusal are live on the platform, and agent templates already declare keys the same way.

Declaring connections

connections on createWorkflow is a list of declarations: The declarations ride with every pushed version of the workflow and are not part of the graph hash, so adding one does not change the workflow’s topology.

Using a key

A declared key can stand anywhere a connection id is accepted: The step code itself calls the integration the same way it always has - Integrations.passthrough('stripe', ...) names the integration, not the key.

Example

A refund workflow that declares its Stripe connection once and posts the refund through it after an approval:
Push it to any agent that has a Stripe connection and it runs. Push it to one that does not, and the postRefund step parks with a notice naming the key and the integration to connect (below).

How a key is resolved

When a run first needs a key, the engine picks one connection:
  1. The candidates are the connections of the declared integrationType that belong to the agent the run is on, plus the organisation’s. A person’s own connection is never a candidate - a run acts as the agent, not as a user.
  2. Agent-scoped candidates win. Org-scoped ones are consulted only when the agent has none of that type.
  3. One candidate is chosen. With several, the one whose account label equals the key is chosen; otherwise the step fails as ambiguous (below).
A key is resolved once per run, the first time any step needs it, and every later step reads the same id - a connection added mid-run does not change a run already under way. The choice is appended to the run’s events:
It is also stamped on the run: lua workflows status <runId> --steps --json returns the resolutions under data.connections (key, connectionId, integrationType, scope, at), and from the next CLI release lua workflows status <runId> prints them as a Connections: line (stripe → 66f1c2... (stripe, agent)).

When resolution fails

credentials_unresolved is not retried automatically and no model turn is billed for it. The step’s onError decides what the run does: with onError: 'park' the run waits on an exception gate, so the connect-then-lua workflows retry-step loop resumes it without losing completed work. The run’s events also carry a run.log notice naming the key and the integration to connect.
Resolution applies only to values that are declared keys. A value that is a connection id never enters the resolver: it is mounted or minted exactly as before, and a workflow with no connections block behaves exactly as it did.

Push-time validation

lua push workflow refuses a credentialsRef or requiredConnections entry that looks like a key but is not declared - with or without a connections block - and lua compile refuses it the same way from the next CLI release:
The message names the path and the exact declaration to add. A malformed declaration (a key outside the grammar, such as GitHub) is connection-declaration-invalid. Literal ids keep working. A connection id - a 24-character hex id, or an id with a provider prefix - is not a key: it passes the compile as before and the server verifies it at push. Only a lowercase slug that is not declared fails, which is the shape that was already broken at run time. Templates are unchanged. An agent template declares connections[].key the same way, and an install binds each key to the installer’s connection; that binding still wins. Run-time resolution is the fallback for a definition that reaches the engine with an unbound key.
  • Job tier - how a credentialsRef becomes a short-lived token for the checkout
  • Authoring - the connections field and the requiredConnections step option
  • Runs and events - where connection.resolved appears
  • Workflows Command - the Connections: line on status