TerminalSkills/skills

restate

Restate is a durable execution engine that makes distributed application code resilient to crashes, with SDKs for TypeScript, Java, Go and more.

View source

How to use

  1. Copy the installation command and review the source package before installing it in your own agent environment.
  2. Check the source document for required tools, credentials and supported hosts.
  3. Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/TerminalSkills/skills --skill restate

Before you start

  • Installation does not include a model subscription, API allowance or permission to access your files.
  • Repository stars and installation counts describe activity, not an independent quality or security test.
Original skill document

Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.

Restate

Overview

Restate is a durable execution engine — your code runs reliably even when things crash. Write normal async functions, and Restate ensures they complete: if a service crashes mid-execution, it resumes exactly where it left off. No lost state, no duplicate side effects. Like Temporal but with a simpler programming model — just annotate your functions, no state machines or DSLs.

Two processes are involved: the Restate Server (a single binary that stores the execution journal, exposes the ingress on port 8080 and the admin API and UI on port 9070) and your service (an HTTP endpoint on port 9080 by default). Callers always talk to the server, never to the service directly.

When to Use

  • Distributed transactions (payment → inventory → shipping)
  • Long-running workflows that must complete (onboarding, provisioning)
  • Saga pattern with compensating actions (rollback on failure)
  • Exactly-once processing of events
  • Replacing complex retry/queue logic with durable execution

Instructions

Setup

bash
# SDK for your service
npm install @restatedev/restate-sdk

# Server and CLI (also on Homebrew: restatedev/tap/restate-server, restatedev/tap/restate)
npm install --global @restatedev/restate-server@latest @restatedev/restate@latest
restate-server            # ingress :8080, admin API + UI :9070, data in ./restate-data

# Or run the server in Docker
docker run --name restate_dev --rm -p 8080:8080 -p 9070:9070 \
  --add-host=host.docker.internal:host-gateway \
  docker.restate.dev/restatedev/restate:latest

After starting your service, register it with the server. Nothing can be invoked until this is done, and it must be repeated whenever handlers are added or renamed:

bash
restate deployments register http://localhost:9080 --yes   # --yes skips the confirmation prompt
# Server in Docker: restate deployments register http://host.docker.internal:9080 --yes
# Development, same URL with changed handlers: add --force to replace the earlier registration
restate services list

Durable Service

typescript
// services/payment.ts — Durable payment service
import * as restate from "@restatedev/restate-sdk";

type Order = { orderId: string; userId: string; amountCents: number };

export const paymentService = restate.service({
  name: "payments",
  handlers: {
    // If the process crashes between steps, the handler is replayed and
    // completed steps return their recorded result instead of running again
    processPayment: async (ctx: restate.Context, order: Order) => {
      // Stable across retries — unlike crypto.randomUUID()
      const idempotencyKey = ctx.rand.uuidv4();

      const charge = await ctx.run("charge-payment", async () => {
        return await stripeApi.charge(order.userId, order.amountCents, idempotencyKey);
      });

      await ctx.run("confirm-order", async () => {
        await orderDb.confirm(order.orderId, charge.id);
      });

      await ctx.run("notify", async () => {
        await emailApi.send(order.userId, "Order confirmed!");
      });

      return { orderId: order.orderId, chargeId: charge.id, status: "completed" };
    },
  },
});

// Starts an HTTP/2 server; port defaults to 9080. List every service, object and workflow you define
restate.serve({ services: [paymentService], port: 9080 });

restate.endpoint().bind(...).listen() from older tutorials is deprecated; use restate.serve, or restate.createEndpointHandler (imported from @restatedev/restate-sdk/lambda or /fetch) for AWS Lambda, Deno and Cloudflare Workers.

A step that throws an ordinary Error is retried with exponential backoff; after 70 attempts (the server default) the invocation is paused, not failed. Throw restate.TerminalError to stop retrying and return the failure to the caller, or cap a step with a retry policy:

typescript
await ctx.run("charge-payment", () => stripeApi.charge(order.userId, order.amountCents), {
  initialRetryInterval: { milliseconds: 500 },
  maxRetryAttempts: 5, // afterwards a TerminalError is thrown
});

Virtual Objects (Stateful Entities)

typescript
// services/cart.ts — Stateful shopping cart (single-writer per key)
type CartItem = { sku: string; quantity: number; priceCents: number };

export const cartObject = restate.object({
  name: "cart",
  handlers: {
    // Exclusive handler: only one runs per cart key at a time — no race conditions
    addItem: async (ctx: restate.ObjectContext, item: CartItem) => {
      const items = (await ctx.get<CartItem[]>("items")) ?? [];
      items.push(item);
      ctx.set("items", items);
      return { items: items.length };
    },

    checkout: async (ctx: restate.ObjectContext, userId: string) => {
      const items = (await ctx.get<CartItem[]>("items")) ?? [];
      if (items.length === 0) {
        throw new restate.TerminalError("Cart is empty", { errorCode: 400 });
      }
      const amountCents = items.reduce((sum, i) => sum + i.priceCents * i.quantity, 0);

      // Durable call to another service; ctx.key is the cart ID from the URL
      const result = await ctx
        .serviceClient(paymentService)
        .processPayment({ orderId: ctx.key, userId, amountCents });

      ctx.clear("items");
      return result;
    },

    // Shared handler: read-only, runs concurrently. Without the wrapper
    // the handler is exclusive and queues behind writers.
    getItems: restate.handlers.object.shared(
      async (ctx: restate.ObjectSharedContext) => (await ctx.get<CartItem[]>("items")) ?? []
    ),
  },
});

Call other handlers with ctx.serviceClient(svc), ctx.objectClient(obj, key) and ctx.workflowClient(wf, id). The ...SendClient variants send a one-way message; add restate.rpc.sendOpts({ delay: { hours: 5 } }) as the last argument to schedule it.

Workflows

A workflow runs its run handler exactly once per workflow ID. Other handlers take a WorkflowSharedContext and can query state or resolve a promise the workflow is waiting on.

typescript
// services/signup.ts — Wait for an email click without holding a process
export const signupWorkflow = restate.workflow({
  name: "signup",
  handlers: {
    run: async (ctx: restate.WorkflowContext, user: { email: string }) => {
      const code = ctx.rand.uuidv4();
      await ctx.run("send-email", () => emailApi.sendVerification(user.email, code));
      ctx.set("status", "waiting-for-click");

      const clicked = await ctx.promise<string>("email-clicked");
      ctx.set("status", "verified");
      return clicked === code;
    },
    click: async (ctx: restate.WorkflowSharedContext, code: string) => {
      await ctx.promise<string>("email-clicked").resolve(code);
    },
    status: async (ctx: restate.WorkflowSharedContext) =>
      (await ctx.get<string>("status")) ?? "unknown",
  },
});

Saga Pattern (Compensating Actions)

typescript
// services/booking.ts — Saga: undo completed steps when a later one fails for good
export const bookingService = restate.service({
  name: "booking",
  handlers: {
    bookTrip: async (ctx: restate.Context, trip: TripRequest) => {
      const compensations: (() => Promise<unknown>)[] = [];
      try {
        // Register the undo before the action, in case the action half-succeeds
        compensations.push(() => ctx.run("cancel-flight", () => flightApi.cancel(trip.tripId)));
        const flightId = await ctx.run("book-flight", () => flightApi.book(trip.flight));

        compensations.push(() => ctx.run("cancel-hotel", () => hotelApi.cancel(trip.tripId)));
        const hotelId = await ctx.run("book-hotel", () => hotelApi.book(trip.hotel));

        const carId = await ctx.run("book-car", () => carApi.book(trip.car));
        return { flightId, hotelId, carId, status: "confirmed" };
      } catch (e) {
        // Only terminal errors mean "give up"; anything else is retried by Restate
        if (e instanceof restate.TerminalError) {
          for (const undo of compensations.reverse()) await undo();
        }
        throw e;
      }
    },
  },
});

Invoking Handlers over HTTP

bash
# Service: /restate/call/{service}/{handler}
curl localhost:8080/restate/call/payments/processPayment \
  -H 'idempotency-key: order-7731' \
  --json '{"orderId":"order-7731","userId":"user-381","amountCents":12300}'

# Virtual object or workflow: the key goes between service and handler
curl localhost:8080/restate/call/cart/order-1042/getItems

# Fire-and-forget, optionally delayed
curl "localhost:8080/restate/send/payments/processPayment?delay=10s" \
  --json '{"orderId":"order-7732","userId":"user-381","amountCents":4900}'

The /restate/call and /restate/send prefixes are for Restate Server 1.7 and later. On 1.6 and earlier use /{service}/{handler} and /{service}/{handler}/send.

Examples

Example 1: Reliable payment processing

User prompt: "Build a payment flow that never loses a charge — even if the server crashes between charging the card and updating the database."

The agent writes paymentService and cartObject as above, starts restate-server and the service, registers the deployment, then drives it through the ingress:

bash
restate deployments register http://localhost:9080 --yes   # --yes skips the confirmation prompt
curl localhost:8080/restate/call/cart/order-1042/addItem \
  --json '{"sku":"KB-87","quantity":2,"priceCents":4900}'
curl localhost:8080/restate/call/cart/order-1042/checkout --json '"user-381"'
json
{"items":1}
{"orderId":"order-1042","chargeId":"ch_02a61802","status":"completed"}

If the service is killed after charge-payment completes, the retry skips the charge and continues with confirm-order. A second checkout on the now empty cart returns HTTP 400 {"code":400,"message":"Cart is empty","source":"invocation"} instead of retrying.

Example 2: Distributed saga for e-commerce

User prompt: "Implement an order workflow: reserve inventory → charge payment → ship. If any step fails, roll back everything."

The agent builds a service shaped like bookingService, one ctx.run per step, with each compensation registered before its action. When the last step throws new restate.TerminalError("No cars left", { errorCode: 409 }), the compensations run in reverse order and the caller receives the error:

bash
curl -i localhost:8080/restate/call/booking/bookTrip \
  --json '{"tripId":"LIS-2292","flight":"TP1351","hotel":"Bairro Alto","car":"compact"}'
text
HTTP/1.1 409 Conflict
{"code":409,"message":"No cars left","source":"invocation"}

The service log shows cancel-hotel then cancel-flight. Inspect any invocation, its journal and its retries in the UI at http://localhost:9070 or with restate invocations list.

Guidelines

  • `ctx.run()` for side effects — wrap every non-deterministic operation (HTTP calls, database writes, Date.now(), random IDs). Name each step; the name shows up in the UI and CLI.
  • No context calls inside `ctx.run` — ctx.get, ctx.sleep and nested ctx.run are not allowed in the closure.
  • A step is done only once its result is journaled — if the process dies or the closure throws midway, the whole closure runs again on retry. Pass an idempotency key (ctx.rand.uuidv4()) to external APIs.
  • `TerminalError` to stop retries — by default the server gives an invocation 70 attempts and then pauses it instead of failing it. Validation and business failures must be terminal or the caller never gets an answer.
  • Sagas compensate on `TerminalError` only — a bare catch that compensates on every error also undoes work on transient failures that Restate was about to retry.
  • Virtual objects for stateful entities — single-writer guarantee per key. A long ctx.sleep or a waiting call in an exclusive handler blocks every other call to that key; request-response calls between exclusive handlers (A → B → A) can deadlock.
  • `ctx.sleep({ seconds: 10 })` for delays — durable timers that survive crashes. For long delays prefer a delayed message so the handler can finish.
  • Workflows run once per ID — submitting the same ID again returns HTTP 409. State and results are kept for 24 hours after completion by default.
  • Changing handler code under in-flight invocations causes journal mismatches. Deploy the new version to a new URL and register it; running invocations finish on the old deployment.
  • Secure the service endpoint — the SDK warns that it accepts unsigned requests. Keep port 9080 private, or pass identityKeys to restate.serve so only your Restate instance can call it. Do not expose port 9070 (admin API) publicly.
  • Self-hosted — single binary with an embedded store, no external database. Deleting restate-data wipes all invocations, state and registrations.
  • When not to use — plain stateless request handlers with no multi-step side effects gain nothing from the extra hop through the server.
from this repository

More skills

All skills
TerminalSkills
Community

3dsmax-rendering

Scripts and configures production rendering in Autodesk 3ds Max with the V-Ray and Corona renderers: output size and files, render elements, denoising, light mix, batch and command-line rendering, and network rendering. Use when a user asks to set up a production render, render several cameras in one batch, render from the command line or on a render farm, add render passes for compositing, or cut render time for archviz and product shots.

installs
0
GitHub stars
180
Updated
Oct 25
TerminalSkills
Community

3dsmax-scripting

Covers scripting Autodesk 3ds Max, the 3D modeling and rendering application, with MAXScript and Python (pymxs): scene manipulation, object creation, material assignment, camera and light setup, batch operations, and file I/O. Use when tasks involve automating repetitive 3ds Max workflows, batch processing scenes, running scripts headless with 3dsmaxbatch, creating custom tools, or scripting scene setup for archviz, product visualization, or VFX.

installs
0
GitHub stars
180
Updated
Oct 25
TerminalSkills
Community

3proxy

3proxy is a small open-source proxy server that runs HTTP/HTTPS, SOCKS4/5, SNI and TCP/UDP port-mapping proxies from one config file. Use when a user asks to set up an HTTP or SOCKS5 proxy, add proxy users and passwords, write 3proxy access rules, chain or rotate upstream (parent) proxies, limit bandwidth, connections or monthly traffic per user, run 3proxy in Docker, or fix a 3proxy.cfg that will not start.

installs
0
GitHub stars
180
Updated
Oct 25
TerminalSkills
Community

a2a-protocol

Builds Agent2Agent (A2A) servers and clients, the open protocol (originally from Google, now under the Linux Foundation) that lets AI agents from different frameworks call each other. Use when the user wants to create an A2A-compliant agent, build an Agent Card, implement task management, connect agents across frameworks, set up agent discovery, handle streaming responses, implement push notifications, or orchestrate multi-agent workflows. Trigger words: a2a, agent to agent, agent2agent, a2a pro

installs
0
GitHub stars
180
Updated
Oct 25