Skip to documentation
Documentation navigation

Documentation navigation

Documentation / guides

Create and restore

Create new Sessions and restore persisted Sessions by identifier.

developer

Rig owns construction. A caller supplies a validated topology and a sessionstore.Store; the lifecycle mints or receives the session identity, acquires the per-session lease, opens the journal, and only then exposes a live controller.

Public entry points

The exact public methods are:

func (r *Rig) NewSession(ctx context.Context, opts ...SessionOption) (session.SessionController, error)
func (r *Rig) RestoreSession(ctx context.Context, id uuid.UUID) (session.SessionController, error)

NewSession accepts WithSeedSnapshot through SessionOption; the seed is materialized before the first loop starts. RestoreSession takes the existing ID and has no per-call options. Restore-only policy such as allowing config mismatch is captured when the rig is defined.

New-session ownership

The live path is ordered so no reachable session can write without ownership:

  1. Check the caller context and validate topology requirements.
  2. Mint a non-zero session UUID.
  3. Acquire Store.AcquireLease for that UUID.
  4. Call Store.OpenJournalWithOpeningAppend; the first record is a journal.FenceRecord containing the lease epoch.
  5. Build checked event, command, and gate appenders over that journal.
  6. Resolve workspace placement and process resources, materialize an optional seed, and construct the root loop.
  7. Commit SessionStarted and root LoopStarted through the durable hub tap.
  8. Return the controller with the lease-release hook owned by the session.

If a stage after lease acquisition fails, the lifecycle releases the lease. If the Session has accepted cleanup ownership, its construction abort path seals hub admission, drains already-admitted publication, cancels loops/resources, releases the workspace root, then releases the session lease.

Restore transaction

Restore is a replay and rebuild transaction, not a call to NewSession with a pre-filled ID:

%%{init: {"theme":"dark"}}%%
sequenceDiagram
    participant R as Rig
    participant L as Lease
    participant J as Journal
    participant P as Full replay
    participant S as Session

    R->>L: AcquireLease(sessionID)
    R->>J: OpenJournal (append opening fence)
    R->>P: OpenInternalRecordReplayer(from 0)
    P-->>R: records in ledger sequence order
    R->>R: discover SessionStarted and root LoopStarted
    R->>R: compare fingerprint or assess manifest drift
    R->>J: append RestoreStarted
    R->>J: append crash TurnInterrupted / gate closures / adoption
    R->>S: build and attach every durable loop
    R->>J: append RestoreDone
    R-->>R: transfer lease and context ownership to Session

The first restore mutation after the fence is RestoreStarted. Open turns are closed with durable TurnInterrupted records. The workspace pointer is parsed and materialized before RestoreDone. RestoreDone is the commit point: a failure before it records RestoreErrored, releases ownership, and returns no live session.

Ownership and failure

Restore fails closed by default on config drift. Legacy sessions return *session.ConfigMismatchError; manifest sessions return *session.RestoreRejectedError when the configured decider rejects the typed assessment. Discovery, lease, journal, replay, append, loop, runtime, and materialization stages are wrapped in *session.RestoreError with a RestoreErrorKind. Use errors.As to preserve the stage while inspecting the cause.

live, err := rig.RestoreSession(ctx, id)
if err != nil {
	var mismatch *session.ConfigMismatchError
	var rejected *session.RestoreRejectedError
	var restore *session.RestoreError
	switch {
	case errors.As(err, &mismatch):
		// Rebuild the Rig with the intended configuration or explicit policy.
	case errors.As(err, &rejected):
		// Inspect rejected.Assessment and rejected.Source.
	case errors.As(err, &restore):
		// Inspect restore.Kind; Cause remains available through Unwrap.
	}
	return err
}
defer live.Shutdown(context.Background())

Lifecycle excerpt

The following excerpt comes from a lifecycle example that exercises real rig.Define, an in-memory sessionstore, submit, subscription, shutdown, and restore:

id := live.SessionID()
if err := live.Shutdown(ctx); err != nil {
	return err
}
restored, err := harness.RestoreSession(ctx, id)
if err != nil {
	return err
}
defer restored.Shutdown(context.Background())
fmt.Println(restored.SessionID() == id)

The restore keeps the same session ID and loop identity. It does not replay Ephemeral delivery or recreate abandoned in-memory cancellation handles.

Source and proof

← back to documentation