Replies: 3 comments 4 replies
-
| 
         I really appreciate the flexibility of the tags solution—it's powerful and enables a lot of creative use cases. However, I have concerns about using this API as the primary mechanism for workflow ID/idempotency management: 1. Atomicity IssuesThe try/catch pattern for implementing common idempotency policies is fundamentally non-atomic: try {
  await setTag("idempotencyKey", "...", {unique: true});
} catch (error) {
    if (error instanceof TagConflictError) {
      // RACE CONDITION WINDOW OPENS HERE
      await error.existingRun.cancel();
      // What if the existing run completes successfully during cancel?
      // What if another deployment with the same ID starts right here?
      // What if the cancel fails but we continue anyway?
    }
}
// We are guaranteed to be alone here...Operations like "terminate existing and start new" need to be atomic at the server level to avoid race conditions. 2. Boilerplate for Common CasesLooking at real-world usage, 80% of idempotency use cases fall into three patterns: 
 Having to write 5-10 lines of try/catch boilerplate for each of these common cases seems tedious when they could be expressed as simple configuration options. 3. Explicit APIs vs Reserved TagsFor core workflow semantics like ID management and conflict resolution, I believe explicit API parameters are clearer than reserved tags. When someone reads code or configuration, seeing  Reserved tags like  4. Future 
 | 
  
Beta Was this translation helpful? Give feedback.
-
| 
         In complete agreement with @hugo082 I would also add: if there is no standard way to call a workflow idempotently (and given workflows body is always idempotent it makes close to no sense ever calling a workflow non idempotently) any higher order abstraction becomes impossible, think for example an API where the user passes in a workflow and it gets executed behind the scenes, such API would have no knowledge of the specific way idempotency is implemented for the specific workflow. Overall I think this RFC is very good, just not for idempotency of workflow invocation.  | 
  
Beta Was this translation helpful? Give feedback.
-
| 
         Should we call it annotations instead of tags, like OpenTelemetry does?  | 
  
Beta Was this translation helpful? Give feedback.

Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Tags
Tags are piece of metadata that are associated with runs, steps, hooks, and other future entities. It's a simple key/value solution that addresses two problems
Let's first see it in use:
This is the simplest use of tags - it upserts two tags on the active run - "bar", and "baz".
Signature/Spec:
Key Points
Usage for idempotency
Most alternatives implement idempotency purely as an "idempotency key" that needs to be set when the run is invoked, or allow you to customize the Run ID. The main challenge here is that you don't always know what the idempotency key is when starting the run itself
Take this example of a code review workflow:
Having idempotency controlled within the workflow is more flexible than leaving it to the caller only. Caller based idempotency actually comes for free if we also allow users to set tags at creation time. For instance, with runs,
startcan be extended to allow passing in tags at invoke time. Example syntax:Doing the "idempotencyKey" is a slippery slope to having to support various kinds of behaviors (for example, Temporal has a whole doc on ID reuse and conflicts to support multiple behaviours. I find the whole thing too complicated).
setTaggives all the power to the user.Let's model various possible behaviours
Fail the current run if an existing run already exists with the same tag
(example already shown above)
Fail the current run only if the conflicting run did not fail
Cancel the existing run, and continue the current one
Do anything really...
Tags are really powerful 🤯
An Implementation Gotcha!
It's tempting to implement
setTag, and especiallygetTag, directly in the runtime without forking out to a step (for instance,getTagcan be completely synchronous since the run data is available already). But calls tosetTagandgetTagmust have the return values cached in the event log to preserve determinism. Otherwiseunqiuewould always throw, and even simple examples like this would fail:This is not a problems if these APIs are simply implemented as steps themselves, although an optimization later is running these as "local steps" - i.e. run in the workflow process itself while recording them in event log
Special Tags / Reserved Namespace
Tags names beginning with
$are reserved for special functionality and metadata. Here are some proposed tags -$name(type:string). If set, the observability CLI and UI will prefer this instead of the ID as a more "human friendly" name.unique? The benefit is we can make commans likenpm wf cancel <run-name>work as expected*
$id(type:string. enforced uniqueness). 💡 insight: this could be particularly useful for solving the steps "stable ID" problem - whereby we would use$idover the implicit generated step IDs during replay$ai.model/$ai.provider/etc. (for pretty UI icons, etc),Other examples of using tags
Steps
Since uniqueness is enforced globally across a project (across all runs/workflows), this tags solution extends naturally to providing a layer of step idempotency. This can be particularly helpful to preserve idempotency for steps that is guaranteed across re-runs/upgrades
Another Implementation Gotcha / Open Question
Using unique setTags inside steps would cause every retry attempt to fail.
Also, what is the intended behaviour for tags if a step fails anyway? should tags accumulate across all retries, or do users only care about the final state of the tags (i.e. on the only successful attempt)
Pranay's 2c. Using
setTagin a run is actually scoped to the current attempt (note: we don't currently have an "attempt" entity). The state of the tags on the step entity is just a (materialized) pointer to the latest "attempt"'s tags.uniquedoesn't follow the pointers, so it won't throw across attempts, but will throws across steps(Also Pranay: Is this getting too complex? There's got to be an occam's razor solution to this that still lets us use tags across runs, steps, and hooks)
Hooks
Tagging hooks is only really meant for o11y, and can only be set when creating the hook. Unique
uniquewhen setting tags on a hook is pretty redundant since the token is already unique - the only exception is if for some reason you do want to use autogenerated hook tokens for aesthetics, but you want to fail the workflow run on a unique tag. Pretty esoteric 🤷🏼For completeness, here's hook usage
Now, on he o11y UI, I would have a more useful/readable view in the hook so I can identify what each hook actually is rather than the currently non-descriptive hook ID
Related APIs
getTag(get value of a specific tag inside a run)listTags(list all the tags and values inside a run)start(...)can be extended to allow setting tags at call timecreateHook/createWebhookneeds to be extended to set tegs on hook creation timenotably, steps don't have a way to set keys at invoke time, but this limitation seems fine to me since steps can always set them as the first thing. We can also solve this later by having a
run(step, args, opts)api as an alternative to calling steps directly if we need itRelated World Spec Changes
<Not filling this in - should be easy to infer from the above and we can sort out as we go in PR reviews>
Open Questions
workflow(root), orworkflow/api?workflowforsetTag,getTag, andlistTags(which are all automatically applied to the run/step scope in which they are called), andworkflow/apiforlistRunsByTags(or similar o11y APIs)workflowName,runId, etc. in the key names?Beta Was this translation helpful? Give feedback.
All reactions