Tide

Workflow registry and remote runs

Publish immutable workflow packages to S3 and execute exact versions through tide serve.

The Tide workflow registry is a separate application boundary from the plugin lock in tide.json. The registry stores complete, executable workflow packages; the plugin lock inside a package records which statically available Tide plugin hosts that workflow expects. Publishing a workflow does not install native plugins into an already-built Tide binary.

Local development

The repository includes a loopback-only MinIO setup with deterministic development credentials and a private bucket initializer:

docker compose -f compose.registry-dev.yaml up -d minio
docker compose -f compose.registry-dev.yaml run --rm minio-init

Then start the registry against it:

export AWS_ACCESS_KEY_ID=tide-dev-access
export AWS_SECRET_ACCESS_KEY=tide-dev-secret
export TIDE_REGISTRY_BUCKET=tide-workflows
export TIDE_REGISTRY_REGION=us-east-1
export TIDE_REGISTRY_S3_ENDPOINT=http://127.0.0.1:39100
export TIDE_REGISTRY_S3_PATH_STYLE=true
export TIDE_REGISTRY_TOKEN=tide-local-development-token
cargo run -p tide-registry --features s3

Run ./scripts/registry-e2e-smoke.sh for the bounded local MinIO → registry → tide serve vertical-slice validation. It uses isolated temporary state and removes its processes, containers, and volume on exit. Set TIDE_SMOKE_KEEP=1 only when retaining diagnostics is intentional.

Publish a directory. The entrypoint defaults to main.ts; tide.json, tide.lock, generated plugin metadata, and imported local modules travel in the same deterministic package.

export TIDE_REGISTRY_TOKEN=tide-local-development-token
tide publish \
  --server http://127.0.0.1:4200 \
  --name employment-onboarding \
  --version 1.2.0 \
  --entry workflows/onboard.ts \
  examples/employment-onboarding

The command prints the exact name@version, SHA-256 digest, and size. --json prints a machine-readable object. Re-publishing byte-identical content is idempotent. Reusing the same name and semantic version for different bytes returns 409 version_conflict.

Start Tide with both development-local and registry sources:

export TIDE_SERVER_TOKEN=tide-local-server-token
tide serve \
  --workflows ./workflows \
  --registry http://127.0.0.1:4200 \
  --store fs \
  --workers 1

TIDE_REGISTRY_URL is the environment equivalent of --registry. Resolution is unambiguous: an unversioned name uses --workflows; name@version uses the registry and never falls back to disk.

Run and follow remotely:

export TIDE_SERVER_TOKEN=tide-local-server-token
tide run --server http://127.0.0.1:4100 \
  employment-onboarding@1.2.0 \
  --input '{"company":"Acme","name":"Ada"}'

tide follow --server http://127.0.0.1:4100 <invocation-id>

tide resume --server http://127.0.0.1:4100 <invocation-id> \
  --action approve --comment "checked"

Remote run submits the exact version, follows lifecycle changes, emits console output once using log cursors, and uses the same terminal decision prompt as a local HITL wait. --json is non-interactive and emits one result object. Ctrl-C stops following only: the server invocation keeps running and the CLI prints its ID and a reconnect command.

Local filesystem paths remain local. With --server, the positional argument must be an exact registry reference; Tide never sends a local path and asks the server to read the client's filesystem.

Package and cache integrity

The v1 media type is application/vnd.tide.workflow.package.v1.tar. Archives are uncompressed, sorted, and have normalized mode, owner, group, and timestamp fields, so identical inputs produce identical bytes. The first-class tide-workflow.json manifest declares the package version, media type, workflow name, exact semantic version, entrypoint, and optional tide.lock digest.

Publish and pull reject:

  • absolute paths, .., links, special files, duplicate entries, and a missing entrypoint;
  • unknown package versions/media types and invalid names or semantic versions;
  • packages or expanded content over 128 MiB;
  • size or SHA-256 mismatches.

tide serve scopes caches by registry origin under .tide/workflow-cache/origins/<origin-hash>/. It retains verified package bytes and stages/atomically renames extraction by digest. Every reuse compares the extracted files with those package bytes, so local corruption is repaired without trusting a completion marker. Concurrent pulls and crashes cannot expose a partial cache. The invocation queue persists the resolved name, exact version, and digest before execution; a queued or resumed invocation can use that pinned verified package during a registry outage and refuses to run if its identity changes.

Registry API and S3 layout

The stable API is:

GET  /health
GET  /v1/health
GET  /v1/workflows?cursor=&limit=
GET  /v1/workflows/{name}/versions?cursor=&limit=
PUT  /v1/workflows/{name}/versions/{version}
GET  /v1/workflows/{name}/versions/{version}
GET  /v1/workflows/{name}/versions/{version}/artifact

The publish body is the package bytes with the v1 package media type. Lists are stable and keyset-paginated: pass the returned exact cursor unchanged; a cursor that is absent from the current result is rejected as invalid/stale. Cursors are not signed or time-expiring tokens. The registry currently accepts exact versions only; there is no mutable latest alias, so an invocation cannot drift after submit. Errors use { "error": { "code", "message", "details"? }, "requestId": "…" }.

Objects use this explicit key layout:

v1/namespaces/default/workflows/{name}/versions/{version}/metadata.json
v1/namespaces/default/workflows/{name}/versions/{version}/artifacts/sha256-{hex}.tar

The registry, not S3, validates names, versions, manifests, immutability, digest, and size. S3 is private backing storage rather than the public protocol. Conditional object writes prevent a version from being overwritten. Artifacts are written before metadata, so an interrupted or losing divergent publish can leave a harmless content-addressed artifact that is not reachable from metadata. Retrying is safe; automatic orphan collection is not included.

Execution and scaling limits

tide serve starts its internal worker pool by default. One process with --workers 1 needs no separately operated worker; increasing the value adds V8 isolate threads inside that same server process.

There is no remote worker claim/lease/heartbeat protocol yet. Do not run multiple tide serve processes against one filesystem queue and describe that as distributed scaling. The WorkflowSource and WorkflowQueue seams are the extension points for a future leased external-worker protocol.

Trust boundary and production gaps

The standalone registry requires TIDE_REGISTRY_TOKEN; tide publish and registry-backed execution send that value as a bearer token. tide serve protects every /v1 control route when TIDE_SERVER_TOKEN is set, and remote run, follow, and resume read the same variable. Health endpoints remain unauthenticated. Token comparison is constant-time and access logs do not include authorization headers.

Bearer authentication does not provide transport security. Public deployments must terminate TLS and restrict origin ingress. Tide remains single-tenant: there are no tenant identities, per-workflow authorization rules, quotas, or malware scanning. S3 credentials belong only to the registry process; clients use the authenticated HTTP API.

On this page