Design example — not executable yet. The
idempotencyblock shown below is proposed syntax for the next feature; current Markov releases do not recognize or enforce it.
This example publishes a release through an HTTP API. It demonstrates the
unsafe retry case: the API may create the release successfully, but Markov may
crash or lose connectivity before it saves the step as completed. A normal
resume would issue the POST again.
entrypoint: release
vars:
repository: acme/payments
git_sha: "abc123def"
release_name: "payments-2026.09.01"
step_types:
releases_api:
base: http_request
params:
base_url: "https://releases.example.test/api"
headers:
Authorization: "Bearer "
workflows:
- name: release
steps:
- name: verify-artifact
type: assert
that:
- "git_sha != ''"
# Proposed behavior:
# 1. Persist this stable key before attempting the effect.
# 2. On retry, GET the external release first.
# 3. If it exists with this key, adopt it as this step's completed output.
# 4. Otherwise POST once, forwarding the key in Idempotency-Key.
- name: publish-release
type: releases_api
register: published_release
idempotency:
key: "release::"
request_header: Idempotency-Key
reconcile:
type: releases_api
params:
method: GET
path: "/releases//"
found_when: >-
status_code == 200 and
body.metadata.markov_idempotency_key ==
'release::'
params:
method: POST
path: "/releases"
body:
repository: ""
name: ""
git_sha: ""
metadata:
markov_idempotency_key: "release::"
- name: announce-release
type: shell_exec
params:
command: >-
echo "published "
Situation when publish-release resumes |
Reconciliation result | Action |
|---|---|---|
| The original POST never reached the API | No release exists | Send the POST with the same Idempotency-Key. |
| The API created the release but Markov did not checkpoint it | Release exists and carries the same key | Adopt the external release as completed; do not POST again. |
| A release exists with a different key | Conflicting external state | Fail safely and require an operator decision. |
| The API cannot be queried | Reconciliation is inconclusive | Fail safely; do not blindly POST. |
The proposed feature would persist an effect intent before performing the external mutation, then save an effect receipt after it succeeds or is reconciled. The key must be stable across retries and should be derived from the logical effect (repository plus revision above), not from a random run ID.
http_request is the first useful executor because many APIs understand an
idempotency header. Other executors need explicit reconciliation adapters: a
Jira comment might query by a marker, and a script_exec step might run a
read-only check script. Markov must not claim generic retry safety for an
executor until it can establish one of the outcomes above.