---
title: "Save and reopen a debug session"
url: https://recursiveai.org/onescope/cookbook/save-and-reopen-debug-session/
description: "Save simulation sources, forks, cursor and view layout in a OneScope session. Reopen embedded recordings or reference the original replay files."
version: "1.0.0"
---

# Save and reopen a debug session

**OneScope** is a free, open-source viewer for simulation recordings, with a desktop window or command-line control, from Recursive AI. MIT or Apache-2.0.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneScope.Tool) · [GitHub](https://github.com/recursive-ai-oss/onescope-csharp) · [Docs](https://recursiveai.org/onescope/docs/) · [Download example (.zip)](https://recursiveai.org/onescope/cookbook/save-and-reopen-debug-session/save-and-reopen-debug-session.zip)

An `.oscope` file saves replay sources, forks, cursor and layout. OneScope debugs recorded simulation steps, such as physics; a fork is a branched copy of a recording.

```shell
dotnet tool install --global RecursiveAI.OneScope.Tool
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

The examples use `--headless`, which renders offscreen but still needs a graphics device and driver. On Linux, a Vulkan software renderer such as lavapipe can supply it; `ONEGAME_GPU_ADAPTER=software` selects software rendering. Remove `--headless` to open the window.

```shell
onescope open run.oppr --headless \
  --artifacts output \
  --run 'session_save {
    "path":"s.oscope","embed":true}' \
  --run 'session_open {
    "path":"output/s.oscope"}' 
```

Uses `run.oppr` from the download; writes `output/s.oscope` and reopens it. Save paths are relative to `--artifacts`; open paths are relative to the current directory.

Unzip [the example](https://recursiveai.org/onescope/cookbook/save-and-reopen-debug-session/save-and-reopen-debug-session.zip), then `dotnet run`.

The last command returns this session state (the machine-specific `path` is omitted). `views: 2` means two scene-view panels were restored; it does not count replay sources.

```json
{
  "step": 0,
  "views": 2,
  "sources": [
    {
      "source": 0,
      "label": "run.oppr",
      "module": "onephysics",
      "startStep": 0,
      "steps": 120,
      "step": 0,
      "hash": "5a09a642111ebae2"
    }
  ]
}
```

## The full program

`run.oppr` is included: a generated 2D OnePhysics recording, not a capture from a user’s game. It has a ball of radius 0.5 meters starting at (0, 4), a static ground box at (0, −1), and 120 recorded steps. The ground is 20 × 1 meters. The example does not need a running game.

The C# launcher installs the published OneScope tool into `obj`, seeks to step 20, forks, saves, and restores the session. It also saves a second session without embedding the original file.

```csharp
using System.Diagnostics;
using System.Text.Json;

string toolDir = Path.GetFullPath("obj/onescope-tool");
string tool = Path.Combine(toolDir,
    OperatingSystem.IsWindows() ? "onescope.exe" : "onescope");
if (!File.Exists(tool))
    Run("dotnet", "tool", "install", "RecursiveAI.OneScope.Tool",
        "--version", "1.0.0", "--tool-path", toolDir);

var saved = Open("run.oppr",
    "seek {\"step\":20}", "fork {}",
    "session_save {\"path\":\"saved.oscope\",\"embed\":true}");
Console.WriteLine($"Saved at step: {saved[^1].GetProperty("cursor")}");
foreach (var source in saved[^1].GetProperty("sources").EnumerateArray())
    Console.WriteLine($"Source {source.GetProperty("source")}: {source.GetProperty("kind")}");
var reopened = Open("run.oppr",
    "session_open {\"path\":\"output/saved.oscope\"}");
Console.WriteLine($"Restored step: {reopened[^1].GetProperty("step")}");
Console.WriteLine($"Restored sources: {reopened[^1].GetProperty("sources").GetArrayLength()}");
var referenced = Open("run.oppr", "fork {\"step\":20}",
    "session_save {\"path\":\"referenced.oscope\",\"embed\":false}");
Console.WriteLine("Without embed: " + string.Join(", ",
    referenced[^1].GetProperty("sources").EnumerateArray()
        .Select(s => s.GetProperty("kind").GetString())));

string Run(string command, params string[] arguments)
{
    var start = new ProcessStartInfo(command)
    {
        RedirectStandardOutput = true, RedirectStandardError = true
    };
    foreach (string argument in arguments) start.ArgumentList.Add(argument);
    using var process = Process.Start(start)!;
    var output = process.StandardOutput.ReadToEndAsync();
    var errors = process.StandardError.ReadToEndAsync();
    process.WaitForExit();
    if (process.ExitCode != 0)
        throw new InvalidOperationException(output.Result + errors.Result);
    return output.Result;
}
JsonElement[] Open(string file, params string[] operations)
{
    var arguments = new List<string>
    {
        "open", file, "--headless", "--size", "640x480",
        "--artifacts", "output"
    };
    foreach (string operation in operations)
        arguments.AddRange(["--run", operation]);
    return Run(tool, arguments.ToArray())
        .Split('\n', StringSplitOptions.RemoveEmptyEntries)
        .Select(line => JsonDocument.Parse(line).RootElement.Clone()).ToArray();
}
```

`dotnet run` prints:

```text
Saved at step: 20
Source 0: embedded
Source 1: embedded
Restored step: 20
Restored sources: 2
Without embed: referenced, embedded
```

`session_open` replaces everything currently open; if it fails with `not_found` or `bad_session`, the current session stays unchanged.

## Variations

Keep the original replay outside the session. Fork replays are still embedded:

```shell
onescope open run.oppr --headless \
  --artifacts output \
  --run 'fork {"step":20}' \
  --run 'session_save {
    "path":"referenced.oscope",
    "embed":false}'
```

The saved sources’ `kind` values are `referenced` and `embedded`, respectively. Keep the original replay at its saved path; reopening checks its SHA-256 and refuses a changed file.

## Related recipes

- [Compare library versions](https://recursiveai.org/onescope/cookbook/compare-library-versions/)
- [Compare shared entities](https://recursiveai.org/onescope/cookbook/compare-shared-entities/)
- [Overlay a reference pose cache](https://recursiveai.org/onescope/cookbook/overlay-reference-pose-cache/)
- [Write a reference pose cache](https://recursiveai.org/onescope/cookbook/write-reference-pose-cache/)

## The short answer

Use OneScope’s `session_save` operation to write an `.oscope` file and `session_open` to restore it. This saves the debugging workspace, not just the world at one instant. Sources, fork ancestry and poke replays, the shared step cursor, and layout and camera data belong to the session. Set `embed:true` when the original recording should travel with it.

The distinction matters when you stop investigating a simulation and return later. A standalone replay gives you the recorded simulation again. A session also remembers which sources you had open and where you were looking. It does not save arbitrary process memory or turn a live program into a suspended executable.

## How do I save a simulation debugging session?

Choose a path inside the shell’s output folder and call `session_save`. In the example, `--artifacts output` makes `saved.oscope` land in `output/`. The `embed` argument controls whether opened files are stored inside the session. The returned JSON lists each source and its storage kind, making it possible to check what the session depends on before moving it.

A fork always carries its own replay, including the pokes that distinguish it from its parent. Its saved parent index and fork step preserve that relationship. You do not need to export the fork separately just to retain it in this workspace. The example’s fork has no pokes.

The cursor is a simulation step, not elapsed wall-clock time. Saving at step 20 and reopening restores that cursor. Sources may have different ranges, so each source’s reported step also matters. The example returns both sources at 20, with the original still containing its full 120-step recording.

## Can I reopen a session without the original replay files?

Yes, for sources saved as embedded. Their payload is the module’s replay data. A referenced source instead stores its path and a SHA-256 digest of the file at save time. The restore operation reads that path and rejects a file whose digest differs. Referencing saves duplication, but moving or replacing the original breaks that dependency.

Use embedding for a self-contained recording bundle. Use references when the files will remain available at the same paths. A session is not a package of module assemblies: the reader still needs the appropriate module to open each saved source. The bundled OnePhysics module handles this example’s `.oppr` files; a custom format needs its own module loaded as well.

Restore constructs each source’s history at the saved cursor. Forks reopen from their own replay and reproduce their saved hashes. Layout data is an opaque part of the file that the shell interprets; the SDK does not assign meaning to its camera or panel bytes. See the [session format](https://recursiveai.org/onescope/docs/session-file/) for the embedded and referenced record layouts.
