rewind

Programmatic tool calling, durable by design

Your model writes the script.
Rewind runs it, and remembers every step.

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.

triage.ts pending Nov 3, 09:12
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}
    Live: every effect is recorded once.
    A real script shape: one tool call per line of business logic, a human approval that may take days, a ten-day sleep. Drag the bar, or rewind: replaying reads results from the journal and sends no tool call twice.

    One journal. Everything else is derived from it.

    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.

    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.

    Replay

    Any run re-executes from its journal, locally, with identical results. Scripts are content-addressed: no workflow versioning problem.

    Testing

    A recorded run becomes a cassette of mocks. Time is virtual, so a ten-day workflow is tested in milliseconds.

    Observability

    OpenTelemetry traces projected from the journal, and a dashboard of every run and all the code executed on the platform.

    Built for agent harness developers

    1. Register a toolset. MCP servers and your own host tools. Rewind pins their schemas and returns TypeScript declarations for the system prompt.
    2. Give the model 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".
    3. Answer host tools from your worker. Ask the end user, call another LLM, start a sub-agent: the SDK worker long-polls tasks, like activity workers.
    4. Resume the conversation on webhooks. 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
    });

    Agent tools, ready to plug in

    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.

    run_script
    Type-checks, starts the run, returns the output or its state.
    check_script
    Type-check only, with hints for what models reach for by habit.
    test_script
    Runs against mocks and cassettes, in virtual time.
    get_run
    Status and LLM trace, or one journal entry in full.
    list_runs
    The runs of the same conversation, scoped by labels.
    signal_run
    Sends a signal: an approval, an answer, an event.
    cancel_run
    Cancels a run, with a grace period for cleanup.
    retry_run
    Forks a failed run with a fixed script; finished calls are reused, not repeated.
    search_tools
    Finds declarations in toolsets too large for the prompt.

    Deterministic by construction

    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 scriptIn the sandbox
    Date.now(), new Date(), Temporal.NowLogical 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.logJournaled as log annotations, next to the line that wrote them
    fetchAbsent, unless the toolset allows the http tool and its hosts
    File system, env, process, WebAssemblyAbsent. No ambient authority: every capability comes from the toolset

    Start from the dashboard

    Every run, all the code executed, every tool call with its latency and errors. The API lives at api.rewind.codes.