restate-workflows

$npx mdskill add joelhooks/joelclaw/restate-workflows

Provides a compatibility alias for substrate-specific ADR-0217 runtime bridge tasks, focusing on external-repo bridging and low-level mechanics.

  • Helps with external repository submission, queue-family contracts, and low-level runtime submission mechanics.
  • Integrates with Redis for queue management, Dkron for scheduling, and Restate for durable execution.
  • Recommends using workflow-rig for new work and restricts usage to verified runtime bridge problems.
  • Delivers results through the joelclaw CLI without direct Redis access or internal package imports.
SKILL.md
.github/skills/restate-workflowsView on GitHub ↗
---
name: restate-workflows
displayName: Restate Workflows
description: "Compatibility alias for substrate-specific ADR-0217 runtime bridge work. For new work, load `workflow-rig` first; use this skill only when the task is explicitly about external-repo bridging, queue-family contracts, or low-level runtime submission mechanics."
version: 0.2.0
author: Joel Hooks
tags: [restate, workflows, queue, cli, sandbox, adr-0217, integration]
---

# Restate Workflows

This skill is now a **compatibility alias for substrate work**.

For new work, load **`workflow-rig`** first. Use this skill only when the real problem is the runtime bridge itself: external repo submission, queue-family contracts, or low-level ADR-0217 substrate mechanics.

The boundary is the `joelclaw` CLI.

Do not reach into Redis directly. Do not import private joelclaw packages. Do not pretend the other repo knows internal queue schemas unless you have explicitly verified them.

## The runtime shape

ADR-0217 currently reads best as:

- **Redis is the pressure layer** — queue admission, bursts, pause/resume, family control
- **Dkron is the clock** — scheduled starts
- **Restate is the durable executor** — workflow identity, retries, DAG progress
- **Sandboxed runners are the side-effect boundary** — isolated code-changing work

An external repo should integrate at the edge, not by coupling itself to the middle.

That means:

- submit saved workload artifacts through `joelclaw workload run` when the work has already been shaped at the workload layer
- use `joelclaw queue emit` only as the low-level escape hatch for verified raw queue families
- carry enough metadata for idempotency and tracing
- let joelclaw decide how that request reaches Restate or a sandbox runner

## When to use

Use this skill when a user asks for any of these:

- "send work to the queue"
- "have this repo request background work"
- "bridge this repo into joelclaw"
- "submit sandboxed work"
- "emit a workflow request"
- "wrap joelclaw queue emit"
- "run this in Restate from another repo"

## Non-negotiable rules

- **CLI boundary only.** Prefer `joelclaw workload run` for approved workload artifacts; use `joelclaw queue emit` only when you are intentionally working at the raw queue-family layer.
- **Never talk to Redis directly** from the external repo.
- **Never depend on private joelclaw internals** (`@joelclaw/*`, Redis keys, internal TypeScript types, worker-only contracts) unless the repo itself is the joelclaw monorepo.
- **Keep changes inside the calling repo.** Build a wrapper in that repo; do not assume you can edit joelclaw at the same time.
- **Inspect live help first.** The CLI contract wins over memory.
- **Prefer explicit workflow families.** If there is no verified family for the job, stop and document the proposed family/contract instead of guessing silently.
- **Machine-readable output by default.** The wrapper should return stable JSON that callers can parse.
- **Support dry-run.** The repo should be able to show the exact request without enqueuing it.

## First steps

Run these before designing the bridge:

```bash
joelclaw workload run --help
joelclaw queue --help
joelclaw queue emit --help
```

If the target repo is supposed to monitor results too, also inspect:

```bash
joelclaw jobs status --hours 1 --count 10
joelclaw queue observe --hours 1
```

If you are unsure whether a workflow family already exists, search docs or ask instead of inventing one.

## What to build in the external repo

The default deliverable is a thin wrapper command/module/script around `joelclaw workload run` when the repo can produce a saved workload artifact. Fall back to `joelclaw queue emit` only when you have a verified low-level family and there is no workload artifact to bridge.

Good shapes:

- `scripts/request-work.ts`
- `bin/request-background-work`
- `src/lib/joelclaw/requestWorkflow.ts`
- `src/cli/restate-workflow.ts`

Bad shape:

- raw shell one-liners scattered across the repo with no contract doc or tests

## Minimum request payload

Every request payload should include these fields unless the target family has a stricter contract:

```json
{
  "idempotencyKey": "repo-name:task-type:base-sha:input-hash",
  "requestId": "uuid-or-stable-derived-id",
  "repo": {
    "path": "/abs/path/or/null",
    "url": "git@github.com:owner/repo.git",
    "name": "repo-name",
    "baseSha": "abc123",
    "branch": "main"
  },
  "task": {
    "kind": "sandboxed-change",
    "prompt": "what the runtime should do",
    "artifacts": ["patch", "test-report", "summary.json"]
  },
  "caller": {
    "source": "external-repo-name",
    "requestedBy": "human-or-agent",
    "sessionId": "optional-session-id",
    "callback": {
      "kind": "poll",
      "target": "joelclaw jobs status"
    }
  },
  "meta": {
    "dryRun": false,
    "createdAt": "2026-03-08T05:00:00.000Z"
  }
}
```

### Required semantics

- `idempotencyKey` must be stable for the same intended work request
- `baseSha` must reflect the code the sandbox should start from
- `prompt` must say what success looks like
- `artifacts` must list what the caller expects back
- `caller.source` must identify who emitted the request

## Emission pattern

The wrapper should construct JSON, then call the CLI.

Example pattern:

```bash
joelclaw queue emit <event-family> --data '<json>'
```

Example Node/Bun sketch:

```ts
import { spawn } from "node:child_process";

export async function requestWorkflow(
  event: string,
  payload: unknown,
  dryRun = false,
) {
  const data = JSON.stringify(payload);

  if (dryRun) {
    return {
      ok: true,
      mode: "dry-run",
      event,
      payload,
    };
  }

  return await new Promise((resolve, reject) => {
    const child = spawn("joelclaw", ["queue", "emit", event, "--data", data], {
      stdio: ["ignore", "pipe", "pipe"],
    });

    let stdout = "";
    let stderr = "";

    child.stdout.on("data", (chunk) => {
      stdout += chunk.toString();
    });

    child.stderr.on("data", (chunk) => {
      stderr += chunk.toString();
    });

    child.on("close", (code) => {
      if (code === 0) {
        resolve({ ok: true, mode: "live", event, payload, stdout });
        return;
      }
      reject(
        new Error(`joelclaw queue emit failed (${code}): ${stderr || stdout}`),
      );
    });
  });
}
```

## Family selection

Do not hijack an unrelated proof family just because it exists.

Rules:

- `content/updated` is a dogfood family, not a generic bucket for outside repos
- if a verified workflow request family already exists, use it
- if no family exists, document the proposed name and payload contract in the repo instead of guessing

A good stop-and-document line is:

> No canonical workflow-request family was verified in the target runtime. This repo now emits a validated payload object locally and documents the proposed `sandbox/work.requested` contract, but does not enqueue live requests until the runtime family is confirmed.

That is honest. Honesty beats fake integration.

## Required wrapper features

### 1. Dry-run mode

The command must be able to print the exact event + payload without sending it.

Dry-run output should be parseable JSON, for example:

```json
{
  "ok": true,
  "mode": "dry-run",
  "event": "sandbox/work.requested",
  "payload": { "...": "..." }
}
```

### 2. Clear errors

Handle these explicitly:

- `joelclaw` binary missing
- invalid JSON/payload serialization
- command non-zero exit
- caller omitted `baseSha`, `prompt`, or `idempotencyKey`

### 3. Machine-readable success output

Return stable JSON on success, not prose.

At minimum:

```json
{
  "ok": true,
  "mode": "live",
  "event": "sandbox/work.requested",
  "idempotencyKey": "...",
  "stdout": "raw joelclaw output"
}
```

### 4. Local tests

Tests should verify:

- payload construction
- idempotency key stability
- dry-run behavior
- CLI invocation shape
- failure handling

Mock the CLI process if needed. Do not require the real runtime for unit tests.

## README contract

Document these four things in the calling repo:

1. what command submits work
2. what event family it emits
3. what payload fields are required
4. how to verify the request from joelclaw surfaces

Minimum verification section:

```bash
joelclaw jobs status --hours 1 --count 10
joelclaw queue observe --hours 1
```

If there is a known workflow ID or downstream surface, include it.

## Anti-patterns

Do not do this:

- writing directly to Redis
- importing joelclaw monorepo code into the external repo
- hiding event-family uncertainty behind vague names like `task/run`
- omitting `baseSha` for code-changing work
- printing unstructured human-only output from the wrapper
- building a wrapper with no dry-run or no tests
- pretending Restate-native execution is already wired if the current path is still transitional

## Recommended deliverables

When asked to implement this integration in another repo, deliver:

1. the wrapper command/module
2. payload contract documentation
3. example invocation
4. verification steps
5. tests

## Short prompt for another agent

Paste this:

> Use `/skill:restate-workflows`. Add a thin wrapper in this repo around `joelclaw queue emit` so it can submit work into joelclaw without talking to Redis or private joelclaw internals. Check `joelclaw queue emit --help` first. Emit machine-readable JSON with `requestId`, `idempotencyKey`, repo `url`/`branch`/`baseSha`, task prompt, expected artifacts, and caller metadata. Support `--dry-run`, add a README example and tests, and if the canonical event family is not verified, stop and document the proposed family instead of guessing.
More from joelhooks/joelclaw