# How Session Capture Works in Sandcastle Using `hostSessionStore`

> Discover how Sandcastle captures agent sessions with hostSessionStore. Learn how JSON-L session data is transferred and paths are rewritten for seamless host integration.

- Repository: [Matt Pocock/sandcastle](https://github.com/mattpocock/sandcastle)
- Tags: how-to-guide
- Published: 2026-05-24

---

**Sandcastle captures agent sessions by creating dual `SessionStore` instances—one backed by the sandbox filesystem and one by the host—then uses `transferSession` to copy JSON-L session data from the sandbox to the host while rewriting absolute paths to match the host's working directory.**

When running Claude Code agents inside Docker or Podman sandboxes, Sandcastle persists conversation history to the host filesystem through a structured capture mechanism. The `hostSessionStore` function serves as the primary interface for host-side storage, managing JSON-L files under `~/.claude/projects/<encoded-cwd>/` according to the implementation in [`src/SessionStore.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/SessionStore.ts).

## Core Components of the Session Capture System

The session capture architecture relies on three main components defined in [`src/SessionStore.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/SessionStore.ts) that coordinate file movement across the sandbox boundary.

### `hostSessionStore` – Host-Backed Persistence

Located at lines 57-76 of [`src/SessionStore.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/SessionStore.ts), `hostSessionStore` returns a `SessionStore` interface that reads and writes session JSON-L files directly to the host filesystem. It organizes sessions under `~/.claude/projects/<encoded-cwd>/`, where `<encoded-cwd>` represents a filesystem-safe encoding of the current working directory path. This store serves as the canonical location for all captured sessions, making them available for later resumption, inspection, or debugging.

### `sandboxSessionStore` – Sandbox-Backed Writing

Defined at lines 91-134 of [`src/SessionStore.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/SessionStore.ts), `sandboxSessionStore` creates a store that operates within the sandbox environment. Unlike the host store that uses direct filesystem calls, this implementation uses a `BindMountSandboxHandle` with `copyFileIn` and `copyFileOut` methods to move session data across the sandbox boundary. It maintains the same directory structure (`~/.claude/projects/`) but inside the sandbox's isolated filesystem.

### `transferSession` – Cross-Boundary Data Movement

The `transferSession` function (lines 42-70 of [`src/SessionStore.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/SessionStore.ts)) orchestrates the actual capture process. It accepts a source store, target store, and session ID, then performs three critical operations: reading the JSON-L from the source, rewriting any `cwd` fields from the source's working directory to the target's working directory, and writing the modified content to the target store.

## How the Orchestrator Captures Sessions

The capture process triggers automatically during agent execution when `provider.captureSessions` is enabled. According to the implementation in [`src/Orchestrator.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/Orchestrator.ts) (lines 71-88), the orchestrator executes the following sequence after each iteration:

1. Creates a `sandboxSessionStore` using the current sandbox context and bind-mount handle
2. Creates a `hostSessionStore` pointing to the host repository directory
3. Calls `await transferSession(sbStore, hStore, sessionId)` to persist the sandbox session to the host

This transfer reads the session file from the sandbox using `copyFileOut`, streams through each JSON-L line to replace sandbox paths with host paths, and writes the result to the host filesystem using standard Node.js file operations.

## Path Rewriting and Session File Locations

When `transferSession` processes a session file, it specifically checks each JSON-L entry for `cwd` fields. If an entry's current working directory matches the source store's path (the sandbox location), the function rewrites it to point to the target store's path (the host location). This ensures that absolute paths in the conversation history remain valid when the session is later loaded on the host or resumed in a new sandbox.

After a successful transfer, the captured session lives at `~/.claude/projects/<encoded-host-cwd>/<sessionId>.jsonl` on the host filesystem. You can locate the exact path by calling `hostStore.sessionFilePath(sessionId)` on any `hostSessionStore` instance.

## Resuming Captured Sessions

Session capture also works in reverse. When initiating a run with the `resumeSession` option, Sandcastle copies an existing host session back into the sandbox before starting the agent. As implemented in [`src/run.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/run.ts) (lines 76-85), this creates both stores and executes `transferSession(hStore, sbStore, resumeSession)` to move the JSON-L file from the host into the sandbox's `~/.claude/projects/` directory, ensuring the agent continues with the previous conversation history intact.

## Complete Implementation Example

The following TypeScript example demonstrates how to manually capture a session using the core functions:

```typescript
import { hostSessionStore, sandboxSessionStore, transferSession } from "./SessionStore.js";
import type { BindMountSandboxHandle } from "./SandboxProvider.js";

async function captureSession(
  hostRepoDir: string,          // e.g. "/home/alice/project"
  sandboxDir: string,           // sandbox cwd, e.g. "/workspace"
  handle: Pick<BindMountSandboxHandle, "copyFileIn" | "copyFileOut" | "exec">,
  sessionId: string,
) {
  // Create the dual stores
  const hostStore = hostSessionStore(hostRepoDir);
  const sandboxStore = sandboxSessionStore(sandboxDir, handle, "/root/.claude/projects");

  // Transfer from sandbox to host (capture)
  await transferSession(sandboxStore, hostStore, sessionId);

  // Verify the captured file location
  console.log("Captured at:", hostStore.sessionFilePath(sessionId));
}

```

Running this after an agent iteration completes will leave the JSON-L file on the host filesystem, ready for inspection or subsequent runs using `sandcastle run --resumeSession <id>`.

## Summary

- **`hostSessionStore`** creates a filesystem-backed store at `~/.claude/projects/<encoded-cwd>/` for persisting JSON-L session files on the host machine (lines 57-76 of [`src/SessionStore.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/SessionStore.ts)).
- **`transferSession`** moves session data between stores while rewriting `cwd` paths to match the target environment (lines 42-70 of [`src/SessionStore.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/SessionStore.ts)).
- The **Orchestrator** automatically triggers capture after each iteration when `captureSessions` is enabled, calling `transferSession` with the sandbox store as source and host store as target (lines 71-88 of [`src/Orchestrator.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/Orchestrator.ts)).
- **Path encoding** ensures safe filesystem operations by encoding the current working directory into the projects directory name.
- Sessions can be **resumed** by reversing the transfer direction, copying from the host store back to the sandbox store before agent initialization (lines 76-85 of [`src/run.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/run.ts)).

## Frequently Asked Questions

### How does `hostSessionStore` differ from `sandboxSessionStore`?

`hostSessionStore` writes directly to the host filesystem using standard Node.js file operations, storing files under `~/.claude/projects/<encoded-cwd>/`. In contrast, `sandboxSessionStore` uses a bind-mount sandbox handle with `copyFileIn` and `copyFileOut` methods to access files inside the sandbox's isolated environment, requiring explicit file copy operations across the container boundary.

### What happens to file paths during session transfer?

`transferSession` rewrites absolute paths in the JSON-L entries. Specifically, it replaces any `cwd` field values that match the source store's working directory with the target store's working directory. This ensures that when a session captured from a sandbox (where the path might be `/workspace`) is loaded on the host (where the path is `/home/user/project`), the working directory references remain valid.

### Where are captured sessions stored on the host?

Captured sessions are stored at `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl`, where `<encoded-cwd>` is a filesystem-safe encoding of the repository's current working directory. You can retrieve the exact path programmatically by calling the `sessionFilePath(sessionId)` method on a `hostSessionStore` instance.

### Can I resume a session that was captured from a previous run?

Yes. When you provide the `resumeSession` option to `sandcastle run`, the system reverses the capture flow before starting the agent. It creates both stores and calls `transferSession` with the host store as the source and the sandbox store as the target, copying the JSON-L file back into the sandbox so the agent can continue the previous conversation.