# Session bundles

> A session bundle is the portable JSON envelope for moving a persisted Harn run between local debugging, support handoff, sharing, and replay workflows. It is built from the...

Website: https://harnlang.com/session-bundles.html

This page documents Harn, which is pre-1.0. Language, standard library, and CLI APIs may change. If the intended version is unclear, clarify before using this page.

---

A **session bundle** is the portable JSON envelope for moving a
persisted Harn run between local debugging, support handoff, sharing,
and replay workflows. It is built from the existing run record instead
of introducing a second persistence model, so transcripts, tool call
recordings, HITL questions, event-log pointers, replay fixtures, trace
spans, and workflow checkpoints keep one canonical shape.

The current bundle type is `harn_session_bundle` with
`schema_version: 1`. The checked schema lives at
`spec/schemas/session-bundle.v1.schema.json`; regenerate it with:

```bash
make gen-session-bundle-schema
make check-session-bundle-schema
```

## Export modes

`harn session export` reads a persisted run-record JSON file and writes a
bundle:

```bash
harn session export .harn-runs/<run>/run.json --out run.bundle.json
harn session export .harn-runs/<run>/run.json --local --out run.local.bundle.json
harn session export .harn-runs/<run>/run.json --replay-only --out run.replay.bundle.json
```

The default mode is **sanitized**. It walks the whole bundle with the
runtime redaction policy and records every replacement in
`redaction.entries`. Bundle export also treats local path fields such
as `persisted_path`, `primary` (the workspace anchor path), `run_path`,
and `snapshot_path` as share-sensitive. This is the right mode for
support tickets, PR attachments, issue reproductions, and any hosted
share link.

`--local` preserves the raw run record content. It is useful for
workstation-to-workstation debugging inside the same trust boundary.
Import and validate still reject high-confidence secret patterns unless
`--allow-unsafe-secret-markers` is passed.

`--replay-only` applies redaction and then withholds prompt/tool payload
fields such as `content`, `prompt`, `system_prompt`, `arguments`,
`result`, and `private_reasoning`. It keeps deterministic replay
metadata, tool-call hashes, stage transitions, checkpoints, trace spans,
and replay fixtures, so another host can inspect or rerun structure
without receiving prompt text.

Artifacts are omitted unless `--include-attachments` is passed. The
bundle always includes attachment metadata that already exists inside the
embedded run record; the top-level `attachments` array is reserved for
payloads that callers intentionally include.

## Import and validation

Use `harn session validate` to check the envelope without writing
anything:

```bash
harn session validate run.bundle.json
harn session validate run.bundle.json --json
```

Validation rejects:

- missing required schema fields,
- `_type` values other than `harn_session_bundle`,
- unsupported future `schema_version` values,
- unredacted high-confidence secret markers unless explicitly allowed.

Use `harn session import` to materialize a local run record:

```bash
harn session import run.bundle.json --out imported-run.json
```

On success, import appends `session.imported` to the `session.imports` topic in
the configured event log (`.harn/events.sqlite` by default). The event records
the bundle ID and SHA-256 digest, source path, resulting run-record ID and path,
and materialized worker-snapshot count and directory. If Harn cannot open the
event log, it exits before writing the run record.

Import prefers the embedded `replay.run_record` because that preserves
the complete run record. If a bundle has no embedded run record but does
carry a replay fixture and transcript sections, import reconstructs a
minimal replayable run record from those canonical sections.

### Suspended workers

Checkpoint a suspended worker when another machine or process must resume the
same agent loop:

```bash
harn session checkpoint /path/to/worker.snapshot.json --out worker.bundle.json
harn session import worker.bundle.json \
  --out imported-run.json \
  --worker-snapshot-dir worker-snapshots \
  --json
harn run --resume /path/reported/in/worker_snapshots/0/path
```

Import materializes a destination-local snapshot and rewrites only local
snapshot references, including the snapshot's self-reference, suspension
reference, and imported run metadata. The transcript itself is unchanged. Its
messages and events therefore retain provider reasoning blocks, `harness.llm.call`
usage, and provider outcome facts. A self-suspending turn records those facts
before its snapshot is written.

The JSON import report contains each new path and a `resume_command`; callers
should use that report instead of retaining the source-machine path. Once the
bundle and destination snapshot exist, the source workspace can be removed.
Resume does not depend on it.

## Fixtures

Contract fixtures live under `spec/session-bundles/fixtures/`:

- `sample-run-record.json` is the source run record.
- `sample-local.bundle.json` preserves local-only fields.
- `sample-sanitized.bundle.json` demonstrates the redaction manifest.
- `sample-replay-only.bundle.json` demonstrates prompt/tool payload
  withholding.

The runtime unit tests validate and import these checked fixtures, while
the negative tests cover missing required fields, future schema versions,
and unsafe secret markers.

## Workspace anchor

When the session that produced the run was opened with a typed
`WorkspaceAnchor` (via `harness.agent.open(id?, opts: {workspace_anchor:
{primary, additional_roots?, anchored_at?}})` or
`agent_session_set_workspace_anchor`), the anchor rides through transcript
metadata into `bundle.workspace`:

```json
"workspace": {
  "primary": "/workspace/example",
  "additional_roots": [
    {"path": "/workspace/lib", "mount_mode": "read_only",
     "mounted_at": "2026-05-23T00:00:00Z"}
  ],
  "anchored_at": "2026-05-23T00:00:00Z",
  "policy": "safe_identity_only"
}
```

`additional_roots[*].mount_mode` is one of `read_only` (default),
`extend`, or `sandboxed`. In sanitized and replay-only modes the
`primary` and `additional_roots[*].path` values are share-sensitive and
redacted to `[redacted]`. Sessions that never set an anchor omit the
`workspace` section entirely.

## Migration

Before session bundles, the closest export story was "attach the raw
run record plus whatever sidecars seemed useful." That leaked trust
boundary decisions into every caller and made replay handoffs brittle.
New callers should export a session bundle instead of sharing raw
`.harn-runs/*` directories. Existing run records remain the source of
truth; migration is a direct cutover at the boundary where a run leaves
the local machine.

The workspace anchor moved from soft `RunRecord.metadata.{workspace_id,
project_root, workspace_root}` keys to a typed `SessionState`
field in v0.8.35 (#2215). Hosts populating the old keys must move to
`harness.agent.open(..., {workspace_anchor: ...})` or
`harness.agent.set_workspace_anchor(...)` — the legacy read path is
gone.

---

## Read next

- [Workspace anchor cache contract](https://harnlang.com/agents/cache_contract.md)
- [Agent state](https://harnlang.com/agent-state.md)
