Durable execution
await sleep("10d") or wait for a signal. The run survives crashes and
deploys, and resumes by re-executing the script against its journal.
Programmatic tool calling, durable by design
Instead of calling tools one at a time through the conversation, the model writes one TypeScript script that calls your MCP servers and harness tools. Rewind runs it in a deterministic V8 sandbox and journals every interaction with the outside world. A run can wait days for a human, survive a crash or a deploy, and replay exactly.
1import { github, slack } from "ptc:tools";
2import { sleep, signal } from "ptc";
3
4const DAY = 86_400_000;
5
6export default async function main({ repo }: { repo: string }) {
7 const issues = await github.list_issues({ repo, state: "open" });
8 const stale = issues.filter((i) => Date.now() - Date.parse(i.updated_at) > 30 * DAY);
9
10 await slack.post_message({ channel: "#triage", text: `${stale.length} stale issues` });
11
12 const ok = await signal<{ close: boolean }>("approval", { timeout: "3d" });
13 if (!ok?.close) return { closed: 0 };
14
15 await Promise.all(stale.map((i) => github.close_issue({ repo, number: i.number })));
16
17 await sleep("10d");
18 const reopened = await github.list_issues({ repo, state: "open", labels: "reopened" });
19 return { closed: stale.length, reopened: reopened.length };
20}
Every tool call, timer, signal and log line is an event in the run's journal, linked to the line of TypeScript that caused it. No feature gets a side channel.
await sleep("10d") or wait for a signal. The run survives crashes and
deploys, and resumes by re-executing the script against its journal.
Any run re-executes from its journal, locally, with identical results. Scripts are content-addressed: no workflow versioning problem.
A recorded run becomes a cassette of mocks. Time is virtual, so a ten-day workflow is tested in milliseconds.
OpenTelemetry traces projected from the journal, and a dashboard of every run and all the code executed on the platform.
run_script. If the run ends within
wait, the result comes back inline. Otherwise the model gets the run's
state: "suspended, waiting for signal approval".
run.completed,
run.failed and run.suspended arrive signed.
const toolset = await ptc.toolsets.create({
mcp: [{ name: "github", url: "https://mcp.example.com/github" }],
host: [{ name: "ask_user", inputSchema, policy: { onInterruption: "at_most_once" } }],
});
// toolset.declarations → the system prompt
const run = await ptc.runs.start({
toolset: toolset.id,
script: code, // written by the model
wait: "30s", // inline result if it ends in time
idempotencyKey: toolCall.id, // a retry returns the same run
labels: { conversation: conversation.id },
traceparent, // joins the agent's trace
});
The model needs more than run_script: it checks, tests, follows and steers
its runs. The SDK ships the definitions and handlers, with adapters for the Anthropic and
OpenAI tool formats.
Replay only works if the script cannot observe anything the journal does not hold. Rewind owns the runtime, so determinism is guaranteed by the sandbox, not by developer discipline.
| In the script | In the sandbox |
|---|---|
Date.now(), new Date(), Temporal.Now | Logical time, recorded with each event |
Math.random(), crypto.randomUUID() | Seeded generators, keyed by the run's 256-bit seed |
setTimeout, sleep("10d") | Durable timers: the run sleeps in the database, not in memory |
console.log | Journaled as log annotations, next to the line that wrote them |
fetch | Absent, unless the toolset allows the http tool and its hosts |
File system, env, process, WebAssembly | Absent. No ambient authority: every capability comes from the toolset |
Every run, all the code executed, every tool call with its latency and errors. The API
lives at api.rewind.codes.