Documentation / guides
Create and restore
Create new Sessions and restore persisted Sessions by identifier.
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:
- Check the caller context and validate topology requirements.
- Mint a non-zero session UUID.
- Acquire
Store.AcquireLeasefor that UUID. - Call
Store.OpenJournalWithOpeningAppend; the first record is ajournal.FenceRecordcontaining the lease epoch. - Build checked event, command, and gate appenders over that journal.
- Resolve workspace placement and process resources, materialize an optional seed, and construct the root loop.
- Commit
SessionStartedand rootLoopStartedthrough the durable hub tap. - 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.