Help us shape the future of Go! Take the 2026 Go Developer Survey

stack

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 16, 2026 License: Apache-2.0 Imports: 27 Imported by: 0

README

looprig/stack

stack is the supported single-process composition of Looprig: Factory (the public API and browser realtime link), a pooled Host running harness agents through host/harnessruntime, and one storage profile — started with one call. It is what the browser-app starter's ~600 lines of app.go and runtime.go did by hand, with every agreement those lines had to keep derived from one place.

go get github.com/looprig/stack@latest

v0.1.1 is a dependency-only patch: Factory v0.16.0, Host v0.18.0, harness v0.45.1 and SessionStore v0.15.0, with centrifuge-go v0.12.1 and centrifuge v0.39.3 selected by MVS. Factory v0.16.0 fixes the HostLink reconnect race during Stop exposed by the centrifuge upgrade. The exported Stack API is unchanged; upgrade these pins together rather than pairing the centrifuge upgrade with an older Factory.

Use

dev, _ := devauth.New("dev", "")          // DEVELOPMENT-ONLY identity
store, _ := localdisk.Open("./data")      // fsstore roots; refuses a pre-v0.6.0 dir
s, err := stack.Start(ctx, stack.Options{
    Storage:  store,
    Tenants:  []sessionwire.TenantID{"dev"},
    Identity: dev.Identity(),
    Agents: []stack.Agent{{
        ID:            "assistant",
        Compatibility: "my-app/assistant/v1",
        Capabilities:  department.Capabilities{CaptureSafety: department.CaptureSafetyStreaming},
        Define: func(ctx context.Context, b stack.Binding) (*rig.Rig, error) {
            return rig.Define(rig.WithLoops(agent), rig.WithPrimers("assistant"),
                rig.WithSessionStore(b.Journal)) // REQUIRED: journal where Host reads
        },
    }},
    Live:    &host.LiveTextOptions{IncludeReasoning: true, IncludeToolSteps: true},
    UI:      dev.Routes(myUI),
    Origins: []string{"http://localhost:8080"},
    Logger:  logger,                     // devauth needs one enabled at WARN
    AllowDevelopmentIdentity: true,      // the explicit opt-in devauth requires
})
// serve s.Handler(); on shutdown: s.Stop(ctx) before closing your listener.

examples/browser-app is the complete program: its whole composition (app.go) is under 60 lines, and its tests boot it, create a session over Factory's REST API, send input to a scripted inference/inferencetest model, read the journal, and restart over the same data directory to restore.

API

Options{Storage, Tenants, Identity, Agents, Hosts, Live, UI, Origins, Logger, AllowDevelopmentIdentity, Limits} one composition
Validate(Options) error every refusal that needs no I/O, as *OptionError{Field, Reason, Cause}
Start(ctx, Options) (*Stack, error) validate → open storage → factory.New → host.Compose → start Host → start Factory. Owns Storage: closes it on failure, and Stop closes it last
(*Stack).Handler / Factory / Host / Stop Stop runs Factory Quiesce → Host Stop (drain while HostLink is served) → Factory Stop → HostLink listener → stores → Storage.Close, on its own lifecycle: the caller's ctx bounds only the wait, storage closes only after every component has finished, and a retried Stop waits for the same teardown. A runtime awaiting a gate is abandoned crash-equivalently (gate preserved, journal lease released; DrainReport().Abandoned) so the next Start — even in the same process — restores it with the gate answerable; a Parked runtime is a leak and makes Stop return *ParkedSessionsError
Agent{ID, Compatibility, Capabilities, Define, Decode} Define(ctx, Binding) builds the rig for every launch; Binding{Tenant, Session, Journal, WorkspaceRoot, Restore}
InProcess{Listen, HostID, Capacity} / RemoteHosts{} where agents run
ServeHost(ctx, HostOptions) (*host.Service, http.Handler, error) a Host process for RemoteHosts, over the same Storage, Tenants and Agents; an explicit Generation becomes the floor of the automatic counter
TokenVerifier(token) the Host side of Limits.HostLinkCredential
stack/localdisk Open(dir): <dir>/control, <dir>/journal/<tenant>, <dir>/workspaces; bounded blob readers; *LegacyDataDirError wraps fsstore.ErrLegacyLayout ("move or delete", never retried); single-process only
stack/memory New() backend with shareable Storage() handles, Open() for one use; tests only
stack/devauth development-only "<user>:<token>" identity, login form, per-process CSRF key; refused unless Options.AllowDevelopmentIdentity is set and Options.Logger is enabled at WARN; logs a banner on every start

What the stack fixes

The journal binding (stack-journal/v1), the per-agent launch templates, Host's settlement evidence router and Factory's journal and object resolvers are derived from one source. Placement is pooled and cross-tenant isolated; Factory's pending-command placement is always on; the Host advertises a bare base; the HostLink credential is random per process; the Host generation is a durable counter in Storage.Control (stack/host-generation/<sha256(HostID)>), not the clock. Every runtime is harnessruntime.Target, so recovery (AttemptCloser, PersistenceFaults), live options, principal and metadata, create re-presentation and payload-reference refusal hold by construction, and Factory stamps the verified principal.

Obligations that remain yours

  • Size the shutdown budget above Drain.Grace + 2 × Drain.IdleBoundary (20 s at the defaults): both the context you pass to Stop and the platform's termination grace (terminationGracePeriodSeconds, TimeoutStopSec). A session waiting at a gate spends the whole grace having its release refused, then is abandoned under up to two idle boundaries. A shorter budget kills the process mid-abandon; a Stop whose context ends early returns and the teardown continues.

  • Sandboxed tools on Linux: call sandbox.Init() as the very first statement of main if any agent runs commands through github.com/looprig/sandbox.

  • Pass Binding.Journal to rig.WithSessionStore.

  • Change Agent.Compatibility when a build cannot replay an old journal.

  • Declare Capabilities.CaptureSafety honestly (streaming or bounded); set RequiresWorkspace if the agent needs Binding.WorkspaceRoot.

  • Identity: devauth is for your own machine only.

Moving Hosts out

Change Hosts: stack.InProcess{} to stack.RemoteHosts{}, set Limits.HostLinkCredential, move Storage to a shared backend (natsstore, or pgstore + s3store; localdisk is refused), and run stack.ServeHost in each Host process with the same Agents and its own bare base. Stop order for the split: Factory Quiesce → each Host's Stop → Factory Stop → storage.

Not in v0.1.0

No object policy (Factory's object route answers 503), no checkpointer (an agent that RequiresCheckpoint is refused), no dedicated placement (Kubernetes placement is controller's). Carbon stays on its own composition until the stack reaches parity.

Where it sits

Tier 6 composition library. Direct Looprig dependencies: core, factory, host (including host/harnessruntime), harness, sessionstore, storage, inference (examples and tests), and fsstore through stack/localdisk only. It imports no tools, llm or sandbox; import_boundary_test.go enforces that and that only localdisk imports fsstore and only memory imports memstore.

Verify

GOWORK=off GOTOOLCHAIN=go1.26.9 make check

Apache-2.0.

Documentation

Overview

Package stack is the supported single-process composition of Looprig: Factory (the public API and browser realtime link), a pooled Host running harness agents through host/harnessruntime, and one storage profile.

s, err := stack.Start(ctx, stack.Options{
    Storage:  store,                       // stack/localdisk, stack/memory, or shared
    Tenants:  []sessionwire.TenantID{"dev"},
    Identity: dev.Identity(),              // stack/devauth is DEVELOPMENT-ONLY
    Agents: []stack.Agent{{
        ID: "assistant", Compatibility: "my-app/assistant/v1",
        Capabilities: department.Capabilities{CaptureSafety: department.CaptureSafetyStreaming},
        Define: defineRig, // rig.WithSessionStore(binding.Journal), ...
    }},
    Origins: []string{"http://localhost:8080"},
    Logger:  logger,                // enabled at WARN: devauth's banner
    AllowDevelopmentIdentity: true, // the explicit devauth opt-in
})
defer s.Stop(shutdownCtx)
http.ListenAndServe(addr, s.Handler())

Factory still reaches the in-process Host over a loopback HostLink WebSocket, so the two talk only through Core records and SessionStore, as they would across machines. Moving the Host out is Hosts: RemoteHosts{} in the Factory process plus ServeHost in each Host process, over shared storage; Agents, Identity, UI and the rig code do not change.

What the stack fixes, so you cannot get it wrong

Every one of these was a runtime failure a hand composition could make:

  • The journal binding (JournalBindingID/JournalBindingVersion), the launch template per agent, the Host's settlement evidence router and Factory's journal resolver are derived from one source, so they cannot disagree ("places nothing", 404, runtime_unavailable).
  • Factory's pending-command placement is always on; the Host is pooled, cross-tenant isolated, and advertises a BARE base.
  • The HostLink credential is random per process under InProcess; the Host generation is a durable counter in Storage.Control, not the clock.
  • Every runtime is harnessruntime.Target: the six required capabilities, AttemptCloser and PersistenceFaults (host's Durable profile), the live-options seam (so Live.IncludeReasoning/IncludeToolSteps work), principal and metadata copied onto every admitted command, a create's first message re-presented, and an unresolved payload reference refused (Host resolves referenced bodies before the attempt). Factory stamps the verified principal (factory.WithPrincipalStamping).
  • Stop runs the one safe order, on its own lifecycle (the caller's context bounds only the wait): Factory Quiesce → Host Stop (drain while HostLink is still served; a runtime awaiting a gate is abandoned with its gate preserved, and a Parked one is a *ParkedSessionsError) → Factory Stop → HostLink listener → stores → Storage.Close.

Obligations that remain yours

  • SANDBOXED TOOLS ON LINUX: if any agent runs commands through github.com/looprig/sandbox, call sandbox.Init() as the very first statement of main. On Linux the sandbox re-executes the binary and refuses to build an executor without it; the stack cannot do this for you because it must run before anything else in the process.
  • Pass Binding.Journal to rig.WithSessionStore in Agent.Define. A rig journaling elsewhere is unreadable to Host and to Factory.
  • Change Agent.Compatibility whenever a new build cannot replay an old journal.
  • Identity is yours. stack/devauth is for a developer's own machine only.

Stack v0.1.0 deliberately has no object policy (Factory's object route answers 503), no checkpointer (an agent that RequiresCheckpoint is refused) and no dedicated placement; Kubernetes placement is the controller's.

Index

Constants

View Source
const (
	JournalBindingID      = "stack-journal"
	JournalBindingVersion = "v1"
)

The journal binding every session the stack creates is pinned to. Factory records it in the session's immutable catalog entry; Host's evidence router and Factory's journal resolver are keyed on it. It is fixed, not configurable: every stack names the same one, so any stack process over the same storage can read any stack session.

View Source
const MinHostLinkCredentialBytes = 32

MinHostLinkCredentialBytes is the shortest HostLinkCredential accepted.

Variables

This section is empty.

Functions

func ServeHost

func ServeHost(ctx context.Context, o HostOptions) (*host.Service, http.Handler, error)

ServeHost composes and starts one Host for a RemoteHosts deployment, over the same Storage, Tenants and Agents the Factory process uses. It returns the running Service and the handler to mount on the listener o.Base names (HostLink, /readyz, /healthz, /metrics).

It does not own o.Storage. Shut down with the Service's Stop (which drains while HostLink is still served), then close the listener, then o.Storage.Close. On error nothing is left running, and o.Storage is still the caller's to close.

func TokenVerifier

func TokenVerifier(token string) host.AuthVerifier

TokenVerifier is the host.AuthVerifier matching a static HostLink service token: the Host side of Options.Limits.HostLinkCredential. It compares in constant time and accepts the token for any tenant the Host serves (ServeHost refuses other tenants before asking it).

func Validate

func Validate(o Options) error

Validate reports the first reason o may not run, without any I/O. Start calls it first; calling it yourself surfaces configuration errors before a process opens storage.

Types

type Agent

type Agent struct {
	// ID is what a create request names. REQUIRED, distinct.
	ID sessionwire.AgentID

	// Compatibility names this agent BUILD. A session is restored only onto
	// the same id, so change it when a new build cannot safely replay an old
	// journal. REQUIRED.
	Compatibility department.CompatibilityID

	// Capabilities is what the agent declares about placement. The stack
	// places every session pooled, so SupportsPooled is set for you;
	// AdmissionWeight zero means 1; Recovery is forced on (the harness
	// runtime supplies both capabilities by construction). CaptureSafety is
	// REQUIRED and must permit pooling: department.CaptureSafetyStreaming
	// when every tool's output is small or streamed, or
	// department.CaptureSafetyBoundedMaterialized under a finite limit.
	// RequiresCheckpoint is refused: stack v0.1.0 has no checkpointer.
	Capabilities department.Capabilities

	// Define builds the rig one launch runs on. REQUIRED. It is called for
	// every create and every restore (rig.Define fixes the journal store and
	// workspace, so a rig is per launch); memoise per tenant if your rig does
	// not depend on the workspace. Pass Binding.Journal to
	// rig.WithSessionStore, or the session journals nowhere Host can read.
	Define func(context.Context, Binding) (*rig.Rig, error)

	// Decode reads a create/input body. Optional; nil means Core's
	// InputRequest (harnessruntime.DecodeInputBlocks), which is what Factory
	// admits.
	Decode harnessruntime.BlockDecoder
}

Agent is one agent a create may name, and how to build its rig.

type Binding

type Binding struct {
	Tenant  sessionwire.TenantID
	Session sessionwire.SessionID
	// Journal is the tenant's harness session store; pass it to
	// rig.WithSessionStore. Its ToolResultObjects() feeds
	// rig.WithToolResultObjects.
	Journal *harnessstore.Store
	// WorkspaceRoot is this session's materialized workspace directory,
	// <Storage.Workspaces>/<tenant>/<session>. It is "" unless the agent
	// declares Capabilities.RequiresWorkspace: Host materializes a workspace
	// only for a target that asks for one.
	WorkspaceRoot string
	// Restore is true when the launch restores an existing conversation.
	Restore bool
}

Binding is what a rig needs that only the stack knows.

type HostLimits

type HostLimits struct {
	WarmTTL           time.Duration // 10m: idle resident sessions are released after it
	RegistryHeartbeat time.Duration // 2s
	RegistryExpiry    time.Duration // 10s; at least host.MinHeartbeatsBeforeExpiry heartbeats
	ClaimTTL          time.Duration // 5s
	ApplyDeadline     time.Duration // 60s; at least host.MinClaimAttemptsBeforeDeadline claims
	CommandQueueSize  int           // 16; at most host.MaxCommandQueueSize
	ReconcileInterval time.Duration // 1s
	ReconcileBatch    int           // 32; at most host.MaxReconcileBatch

	Link host.LinkOptions // zero fields: 64 bindings per link, 256 bindings, 4 links per tenant

	// Drain bounds Stop's drain. Zero fields: grace 10s, idle boundary 5s,
	// publish bound 2s; IdleBoundary and PublishBound must not exceed Grace.
	//
	// SIZE YOUR SHUTDOWN BUDGET ABOVE Grace + 2×IdleBoundary (20s at the
	// defaults) — the Stop context you pass and the platform's termination
	// grace (Kubernetes terminationGracePeriodSeconds, systemd
	// TimeoutStopSec). A session awaiting a gate is never idle, so its
	// release is refused for the whole Grace; Host then abandons the runtime
	// crash-equivalently under a fresh IdleBoundary, waited one more if the
	// abandon ignores cancellation. The gate is preserved and the journal
	// lease released, so the next Start restores the session with the gate
	// still answerable (Stack.DrainReport lists it as Abandoned). A shorter
	// budget kills the process mid-abandon.
	Drain host.DrainOptions

	CompatibilityTimeout time.Duration // 20s
	WorkPoll             time.Duration // 1s

	// MaxCommandBodyBytes bounds a referenced command body. Zero is Host's
	// 8 MiB default.
	MaxCommandBodyBytes int64
}

HostLimits tunes one Host. Zero fields take the single-process defaults named on each.

type HostMode

type HostMode interface {
	// contains filtered or unexported methods
}

HostMode selects where agents run: InProcess or RemoteHosts.

type HostOptions

type HostOptions struct {
	Storage Storage
	Tenants []sessionwire.TenantID
	Agents  []Agent

	// HostID names this Host. REQUIRED.
	HostID sessionwire.HostID
	// Generation is this run's incarnation. Zero means the next value of the
	// counter the stack keeps in Storage.Control for HostID; an explicit value
	// raises that counter to at least itself, so a later automatic start of
	// the same HostID continues above it.
	Generation uint64
	// Base is the BARE HostLink base Factory dials (scheme and authority,
	// no path); Factory derives each tenant's address from it. REQUIRED.
	Base sessionwire.InternalEndpoint
	// Credential verifies the token Factory presents. REQUIRED;
	// TokenVerifier(Limits.HostLinkCredential) is the matching choice.
	Credential host.AuthVerifier
	// Capacity bounds resident sessions. Zero means 16.
	Capacity uint64
	Live     *host.LiveTextOptions
	Limits   HostLimits
	Logger   *slog.Logger
}

HostOptions runs one Host process for a RemoteHosts deployment. Storage, Tenants and Agents are the same values the Factory process uses.

type Identity

type Identity struct {
	// Verifier verifies a presented credential. REQUIRED.
	Verifier identity.Verifier

	// Authorizer decides every public operation. REQUIRED.
	// factory.TenantAuthorizer{} is the stock tenant-boundary choice.
	Authorizer factory.Authorizer

	// CSRF signs browser CSRF tokens. SharedKey is REQUIRED, at least
	// identity.MinCSRFSharedKeyBytes, and must be the same bytes in every
	// replica. TokenTTL zero means 12 hours. TrustedOrigins must be empty:
	// use Options.Origins.
	CSRF identity.CSRFConfig

	// CookieName replaces the browser session cookie name. Optional.
	CookieName string

	// DevelopmentOnly marks a development identity (stack/devauth sets it).
	// Validate then requires Options.AllowDevelopmentIdentity and an
	// Options.Logger enabled at WARN, and Start logs a WARN banner on every
	// start saying this process accepts development credentials.
	DevelopmentOnly bool
}

Identity is how Factory authenticates and authorizes a request.

type InProcess

type InProcess struct {
	// Listen is the HostLink listen address. It must be loopback; "" means
	// "127.0.0.1:0".
	Listen string
	// HostID names the Host. "" means "local".
	HostID sessionwire.HostID
	// Capacity bounds resident sessions. Zero means 16.
	Capacity uint64
}

InProcess runs one pooled Host inside this process. Factory still reaches it over a loopback HostLink WebSocket, exactly as it would across machines, so moving to RemoteHosts changes nothing else.

type Limits

type Limits struct {
	// Host tunes the in-process Host (and ServeHost's).
	Host HostLimits

	// Reconcile, ClientLink, HostLink, HTTP and Routes replace Factory's
	// defaults when non-nil. MaxCommandBodyBytes is Routes'.
	Reconcile  *factory.ReconcileLimits
	ClientLink *factory.ClientLinkLimits
	HostLink   *factory.HostLinkLimits
	HTTP       *factory.HTTPLimits
	Routes     *factory.RouteLimits

	// HostLinkCredential is the service token Factory presents to remote
	// Hosts. REQUIRED under RemoteHosts (at least MinHostLinkCredentialBytes)
	// and refused under InProcess, where the stack mints a random one per
	// process.
	HostLinkCredential string
}

Limits tunes the composition. Every zero field takes its default.

type NoRigError

type NoRigError struct{ Agent sessionwire.AgentID }

NoRigError is an Agent.Define that returned neither a rig nor an error.

func (*NoRigError) Error

func (e *NoRigError) Error() string

type OptionError

type OptionError struct {
	Field  string
	Reason string
	Cause  error
}

OptionError is every refusal Validate (and so Start and ServeHost) makes before any I/O. Field is the Options path, for example "Agents[1].Define".

func (*OptionError) Error

func (e *OptionError) Error() string

func (*OptionError) Unwrap

func (e *OptionError) Unwrap() error

Unwrap returns the lower layer's refusal, if any.

type Options

type Options struct {
	// Storage is where every durable record lives. REQUIRED. Use
	// stack/localdisk for one machine, stack/memory for tests, or assemble one
	// over a shared backend (natsstore, pgstore+s3store) for RemoteHosts.
	Storage Storage

	// Tenants are the tenants this process serves, at least one, distinct.
	// Each gets its own harness journal backend (Storage.Journal). With
	// exactly one tenant it is also Factory's default tenant, so a credential
	// that names no tenant is scoped to it.
	Tenants []sessionwire.TenantID

	// Identity authenticates and authorizes every public request. REQUIRED.
	Identity Identity

	// Agents are the agents a create may name, at least one, distinct IDs.
	Agents []Agent

	// Hosts selects where agents run. Nil means InProcess{}.
	Hosts HostMode

	// Live enables live previews (text, and optionally reasoning and tool
	// steps) for every viewer. Nil keeps committed-only publication. Every
	// runtime the stack launches implements the live-options seam, so
	// IncludeReasoning and IncludeToolSteps need nothing else.
	Live *host.LiveTextOptions

	// UI is optional; Factory mounts it as the fallback after /v1, so an
	// unknown API path is still a JSON 404.
	UI http.Handler

	// Origins are the browser origins trusted by CSRF and ClientLink. They
	// are the ONLY source of trusted origins: Identity.CSRF.TrustedOrigins
	// must be empty. At least one is REQUIRED (Factory's CSRF refuses none).
	Origins []string

	// Logger receives operator diagnostics. Optional, except that a
	// development identity (Identity.DevelopmentOnly) refuses to start
	// without one that is enabled at WARN, so its banner cannot be discarded.
	Logger *slog.Logger

	// AllowDevelopmentIdentity is the explicit opt-in a development identity
	// (stack/devauth) requires. Without it Validate refuses such an identity,
	// so development credentials are never enabled by a dependency alone.
	AllowDevelopmentIdentity bool

	// Limits tunes timings and bounds. Its zero value is the single-process
	// default.
	Limits Limits
}

Options is one single-process composition, described. Validate reports every refusal it can make without I/O; Start runs Validate first.

type ParkedSessionsError

type ParkedSessionsError struct {
	Sessions []host.DrainSession
}

ParkedSessionsError is a Stop that left runtimes running: their release was refused and they could not be abandoned, so they keep their journal leases until the PROCESS exits. A process that keeps running after Stop cannot restore these sessions and should treat this as a leak (exit, or alert). Every harnessruntime runtime can be abandoned, so under the stack this means an abandon failed or overran its bound (see the drain report's Failures and Drain.IdleBoundary).

func (*ParkedSessionsError) Error

func (e *ParkedSessionsError) Error() string

type RemoteHosts

type RemoteHosts struct{}

RemoteHosts runs Factory only; Hosts run in other processes via ServeHost over the same (shared) Storage, Tenants and Agents. Options.Limits.HostLinkCredential is the credential Factory presents to them.

type Stack

type Stack struct {
	// contains filtered or unexported fields
}

Stack is one running composition: Factory, and under InProcess one pooled Host, over one Storage.

func Start

func Start(ctx context.Context, o Options) (_ *Stack, err error)

Start validates o, opens storage, composes Factory and (under InProcess) the Host, then starts them: Host first, so Factory's first placement finds it. Nothing is served until both compositions have succeeded; the only socket opened before host.Compose is the in-process Host's loopback listener, bound (not served) because the Host must advertise its address.

START OWNS o.Storage. When Start returns an error, it has already stopped whatever it started and called o.Storage.Close; otherwise Stop closes it, last.

func (*Stack) DrainReport

func (s *Stack) DrainReport() (host.DrainReport, bool)

DrainReport is the in-process Host's final drain report, and whether there is one: false before Stop has finished, under RemoteHosts, or when the Host never started. Abandoned lists sessions given up crash-equivalently with their gates preserved; Parked is a leak (see ParkedSessionsError).

func (*Stack) Factory

func (s *Stack) Factory() *factory.Server

Factory is the composed Factory.

func (*Stack) Handler

func (s *Stack) Handler() http.Handler

Handler serves /v1, ClientLink and the UI.

func (*Stack) Host

func (s *Stack) Host() *host.Service

Host is the in-process Host, or nil under RemoteHosts.

func (*Stack) Stop

func (s *Stack) Stop(ctx context.Context) error

Stop shuts the composition down in the one safe order and reports every failure: Factory Quiesce (fence new commands, close browser links) → Host Stop (drain while HostLink is still served) → Factory Stop → the HostLink listener → the stores the stack opened → Storage.Close, last. A drain that completed with failures is an error.

THE TEARDOWN RUNS ON ITS OWN LIFECYCLE, NOT ON ctx. The first Stop starts it; ctx bounds only how long this call waits for it. A Stop whose ctx ends first returns an error wrapping ctx.Err() while the teardown continues — storage is never closed under a component still running, such as a command admission Factory's Quiesce is waiting for — and any later Stop waits for the same teardown and returns its one result.

type Storage

type Storage struct {
	// Control holds the SessionStore both halves use: catalog, command inbox,
	// host registry, gates. REQUIRED. Its Blobs must implement
	// storage.BlobReaderLifecycle (SessionStore refuses one that does not);
	// stack/localdisk adapts fsstore with storage.WithBoundedBlobReaders.
	Control *storage.Composite

	// Journal opens (or returns) the harness journal backend of one tenant.
	// REQUIRED. The stack calls it once per tenant at Start. Harness's
	// journal layout holds one tenant per backend, so it must never return
	// one backend for two tenants.
	Journal func(sessionwire.TenantID) (*storage.Composite, error)

	// Workspaces is the local directory a session's workspace is created
	// under, as <Workspaces>/<tenant>/<session>. REQUIRED and absolute.
	Workspaces string

	// SingleProcess marks a backend only one OS process may open
	// (stack/localdisk). Such storage is refused with RemoteHosts, because a
	// Host in another process could not read the journals it writes.
	SingleProcess bool

	// Close releases the profile. Optional. The stack calls it LAST, after
	// Factory and Host have stopped, and only once.
	Close func() error
}

Storage is one storage profile: the control backend Factory and Host share, one harness journal backend per tenant, and the local directory session workspaces are materialized under.

type UnknownJournalBindingError

type UnknownJournalBindingError struct {
	Tenant    sessionwire.TenantID
	BindingID string
	Version   string
}

UnknownJournalBindingError is a journal or object read for a session pinned to a binding this stack does not serve.

func (*UnknownJournalBindingError) Error

type UnknownTenantError

type UnknownTenantError struct{ Tenant sessionwire.TenantID }

UnknownTenantError is a launch or journal read for a tenant this stack does not serve.

func (*UnknownTenantError) Error

func (e *UnknownTenantError) Error() string

Directories

Path Synopsis
Package devauth is DEVELOPMENT-ONLY identity for a stack running on a developer's own machine.
Package devauth is DEVELOPMENT-ONLY identity for a stack running on a developer's own machine.
examples
browser-app command
Command browser-app is a single-process, multi-user Looprig web app built on github.com/looprig/stack: Factory, an in-process Host running a harness agent, local durable storage and dev-only sign-in.
Command browser-app is a single-process, multi-user Looprig web app built on github.com/looprig/stack: Factory, an in-process Host running a harness agent, local durable storage and dev-only sign-in.
Package localdisk is the stack's single-machine storage profile: fsstore roots under one data directory.
Package localdisk is the stack's single-machine storage profile: fsstore roots under one data directory.
Package memory is the stack's in-memory storage profile, for tests and experiments.
Package memory is the stack's in-memory storage profile, for tests and experiments.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL