Documentation
¶
Overview ¶
Package host is the Looprig Department runtime host.
A Host owns resident sessions for one tenant: it consumes commands, keeps runtime targets resident, serves the HostLink realtime surface, and drains on release. It is consumed BY Factory and imports nothing from it; the dependency boundary that states this is enforced by import_boundary_test.go rather than by convention.
A Host is constructed from validated Options and holds a resolved, immutable configuration: identity and tenant, the HostLink address it advertises, its Department, its collaborators, and the placement, capacity and timing bounds it runs under. The Department it serves — the immutable set of launch targets, and the segregated runtime capabilities Host consumes — lives in the department subpackage. Residency, command consumption, HostLink and drain follow.
Index ¶
- Constants
- Variables
- func Run(ctx context.Context, blueprint Composition, listen ListenOptions) error
- type AttachError
- type AttachRequest
- type AuthVerifier
- type Checkpointer
- type Clock
- type Collaborators
- type Composition
- type CompositionClock
- type DrainFailure
- type DrainOptions
- type DrainReport
- type DrainSession
- type EvidenceKey
- type Host
- func (h *Host) ApplyDeadline() time.Duration
- func (h *Host) Auth() AuthVerifier
- func (h *Host) Capacity() uint64
- func (h *Host) ClaimTTL() time.Duration
- func (h *Host) Clock() Clock
- func (h *Host) CommandQueueSize() int
- func (h *Host) Department() *department.Department
- func (h *Host) FixedSessionID() sessionwire.SessionID
- func (h *Host) ID() sessionwire.HostID
- func (h *Host) InternalEndpoint() sessionwire.InternalEndpoint
- func (h *Host) IsolationClass() sessionwire.HostIsolationClass
- func (h *Host) Placement() sessionwire.HostPlacement
- func (h *Host) ReconcileBatch() int
- func (h *Host) ReconcileInterval() time.Duration
- func (h *Host) RegistryExpiry() time.Duration
- func (h *Host) RegistryHeartbeat() time.Duration
- func (h *Host) SessionStore() SessionStore
- func (h *Host) WarmTTL() time.Duration
- func (h *Host) Workspaces() WorkspaceProvider
- type InvalidCompositionError
- type InvalidOptionsError
- type LinkOptions
- type ListenOptions
- type LiveTextOptions
- type NamespaceLayout
- type OptionErrorCode
- type Options
- type PublicJournal
- type PublicJournals
- type Registrar
- type RegistrarFunc
- type Residency
- type RigSessionIDsdeprecated
- type RuntimeJournalReader
- type RuntimeProfile
- type Service
- func (s *Service) Attach(ctx context.Context, request AttachRequest) (Residency, error)
- func (s *Service) CloseUnstarted(ctx context.Context) error
- func (s *Service) Department() *department.Department
- func (s *Service) Handler() http.Handler
- func (s *Service) Host() *Host
- func (s *Service) Live() bool
- func (s *Service) MetricsHandler() http.Handler
- func (s *Service) Ready() bool
- func (s *Service) Routes() http.Handler
- func (s *Service) Start(ctx context.Context) error
- func (s *Service) Stop(ctx context.Context) (DrainReport, error)
- type ServiceLifecycleError
- type SessionStore
- type WarmTimer
- type WorkspaceProvider
- type Workspaces
Constants ¶
const DefaultPublicJournalCapacity = 1024
DefaultPublicJournalCapacity is the session count NewPublicJournals keeps when given no positive capacity.
const HostLinkPathPrefix = compose.HostLinkPathPrefix
HostLinkPathPrefix is the path every HostLink connection arrives under on the Handler; the segment after it is the tenant the connection claims and the server then authenticates. It is Core's sessionwire.HostLinkPathPrefix, the prefix sessionwire.HostLinkEndpoint derives every tenant's address with.
const MaxCommandQueueSize = hostconfig.MaxCommandQueueSize
MaxCommandQueueSize bounds the command queue: an unbounded one converts backpressure into memory growth.
const MaxReconcileBatch = hostconfig.MaxReconcileBatch
MaxReconcileBatch bounds one reconciliation pass, so a Host that has drifted far does not attempt to converge in a single unbounded sweep.
const MinClaimAttemptsBeforeDeadline = hostconfig.MinClaimAttemptsBeforeDeadline
MinClaimAttemptsBeforeDeadline is how many claim lifetimes must fit inside the apply deadline, so a claim that lapses mid-apply still leaves a whole attempt inside the deadline. It is stricter than the runbook's "below"; see internal/hostconfig for the failure model it is chosen against.
const MinHeartbeatsBeforeExpiry = hostconfig.MinHeartbeatsBeforeExpiry
MinHeartbeatsBeforeExpiry is how many heartbeat intervals must fit inside the registry expiry. "Safely below" is a MARGIN, not `<`: three means two consecutive heartbeats may be late or lost before Factory stops seeing this Host. The rule is internal/hostconfig's; this is its published value.
const StepCloseStore = "close_store"
StepCloseStore names the Stop step that closes the session store Compose opened; a failure there is reported on the DrainReport under this step.
Variables ¶
var ErrPublicJournalScope = errors.New("host: the journal read names another tenant or runtime session than the public journal was bound to")
ErrPublicJournalScope reports a read naming a journal other than the one a PublicJournal was bound to. A JournalResolver reads under the binding's RUNTIME session id, so a request naming anything else is a composition defect, and projecting it with this session's identities would be wrong.
var ErrServiceActive = compose.ErrServiceActive
ErrServiceActive means the Service is running or has admitted work.
var ErrServiceDisposed = compose.ErrServiceDisposed
ErrServiceDisposed means disposal has begun and the Service cannot restart.
var ErrServiceNotStarted = compose.ErrServiceNotStarted
ErrServiceNotStarted means Start has not completed successfully.
var ErrUndiallableEndpoint = hostconfig.ErrUndiallableEndpoint
ErrUndiallableEndpoint is the cause of an InternalEndpoint refusal whose authority no client could dial — an empty port, a port outside 1..65535, or more than one port — although Core's InternalEndpoint.Validate and sessionwire.HostLinkEndpoint accept it. Reach it with errors.Is. A base Core itself refuses carries a *sessionwire.HostLinkEndpointError instead, whose Code names the reason.
Functions ¶
func Run ¶ added in v0.2.0
func Run(ctx context.Context, blueprint Composition, listen ListenOptions) error
Run composes the Host, starts it, serves Routes until ctx is done or the listener fails, then drains within Drain.Grace and shuts the listener down.
THE ORDER IS COMPOSE, START, SERVE, DRAIN, CLOSE, and each refusal happens as early as it can be made: a Composition that cannot be validated opens no store; one whose first advertisement the directory refuses fails at Start rather than running invisibly. The drain runs BEFORE the listener closes, because a Factory reads the bounded drain-status observation over HostLink and a listener closed at the signal would leave it inferring completion from a disconnect.
A drain failure is returned as an error, one DrainFailure per failed step joined with the shutdown's own, because a process that drained with failures has left something for an operator to reconcile and must not exit zero. Run's caller decides whether that is fatal.
Types ¶
type AttachError ¶ added in v0.2.0
type AttachError struct {
Step string
Code sessionwire.HostLinkErrorCode
CurrentLeaseEpoch uint64
Reason string
Cause error
// Unreleased names every compensation that itself failed, so "the attach
// failed and took nothing" can be told from "something is still held".
Unreleased []string
}
AttachError reports a refused or failed attach.
Step names WHERE it failed. Code is the HostLink class a Factory branches on and is EMPTY when the failure is not a placement outcome — a store that failed, a launch that failed outright — which is Core's own rule for such failures. CurrentLeaseEpoch is set for epoch_mismatch when the OTHER holder's epoch was readable; it is that holder's, never this Host's, and a caller must not bind with it.
func (*AttachError) Error ¶ added in v0.2.0
func (e *AttachError) Error() string
func (*AttachError) HostLinkCode ¶ added in v0.2.0
func (e *AttachError) HostLinkCode() (sessionwire.HostLinkErrorCode, bool)
HostLinkCode returns the refusal class Factory branches on, and whether this failure has one at all.
func (*AttachError) Unwrap ¶ added in v0.2.0
func (e *AttachError) Unwrap() error
Unwrap returns the typed error a lower layer produced, if any.
type AttachRequest ¶ added in v0.2.0
type AttachRequest struct {
TenantID sessionwire.TenantID
SessionID sessionwire.SessionID
AgentID sessionwire.AgentID
// Mode is Core's closed attach mode: create or restore.
Mode sessionwire.HostLinkAttachMode
// RuntimeCompatibilityID is the build the session was placed on. Optional;
// when set it must equal the target's, and a resident session's.
RuntimeCompatibilityID string
// ActorID is who the attach acts for — a service identity, not an end
// user — and is REQUIRED so the attach can be attributed. TraceID is
// optional correlation.
ActorID string
TraceID string
}
AttachRequest asks this Host to make one session resident, in process.
type AuthVerifier ¶
type AuthVerifier interface {
// VerifyTenant reports whether a presented credential acts for the tenant.
VerifyTenant(context.Context, sessionwire.TenantID, string) error
}
AuthVerifier authenticates an inbound HostLink or command caller.
type Checkpointer ¶
type Checkpointer interface {
Checkpoint(ctx context.Context, tenant sessionwire.TenantID, session sessionwire.SessionID) error
}
Checkpointer commits the checkpoints a nonterminal release requires.
IT HAS NO IMPLEMENTATION IN THIS MODULE AND THAT IS A STATEMENT ABOUT WHERE THE KNOWLEDGE LIVES, not an omission to be filled in with a no-op. A release checkpoint is the runtime's conversation state and the workspace's contents, and Host holds neither. A composition that silently treated the step as done would report a drained Host whose sessions had lost whatever was not already durable, which is the one drain failure that destroys work. So the seam is REQUIRED rather than optional: a Host composed without one does not start.
type Collaborators ¶ added in v0.2.0
type Collaborators struct {
// Backend is the storage composite the session store is opened over. It
// is REQUIRED. Compose opens the store itself — see Compose for why a
// caller may not hand one in.
Backend *storage.Composite
// StoreOptions are passed to sessionstore.Open ahead of the settlement
// evidence option Compose adds. WithDispositionEvidence must NOT be among
// them: the store refuses two, so a caller's would fail the composition
// rather than silently replace the router Compose built.
StoreOptions []sessionstore.Option
// JournalStores are this deployment's runtime journal stores, one per
// (tenant, storage binding): the settlement evidence readers, and — as of
// v0.3.0 — what an attach reads to decide whether a session's conversation
// already exists. REQUIRED and non-empty: a Host with no reader cannot
// settle anything and is refused at composition rather than at the first
// command.
//
// A CREATE IS DECIDED BY THE JOURNAL, NOT BY THE ATTACH MODE. Factory sends
// every attach as create, and harness does not verify that the id it is
// asked to create under is fresh: a create over an existing conversation
// silently re-opens that stream and restores nothing. So Host looks first,
// under the runtime session id the session's binding names. The reader
// registered for the binding must therefore be the released harness
// session store (*harness sessionstore.Store, or a value embedding one) —
// the same journal the runtime writes. Compose REFUSES a reader that is
// not one. A session whose binding has no reader at all is refused a
// create with runtime_unavailable; a journal READ that fails refuses the
// attach with the empty, unclassified code, like every other durable-read
// failure. Neither ever starts a conversation over.
JournalStores map[EvidenceKey]sessionstore.DispositionEvidenceReader
// Registrar produces the Department this Host serves. REQUIRED.
Registrar Registrar
// Checkpointer commits the checkpoints a nonterminal release requires.
// REQUIRED; see Checkpointer.
Checkpointer Checkpointer
// Auth verifies the service credential on every HostLink connection.
// REQUIRED.
Auth AuthVerifier
// Workspaces materializes and releases session workspaces. REQUIRED.
Workspaces Workspaces
// NamespaceLayout is REQUIRED; see NamespaceLayout.
NamespaceLayout NamespaceLayout
// RigSessionIDs is OPTIONAL and consulted only for a record with no
// binding.
//
// Deprecated: see RigSessionIDs.
RigSessionIDs RigSessionIDs
// Logger receives this Host's operator diagnostics. It is OPTIONAL: nil
// discards, and nothing about running depends on it. An attach refused
// while hydrating a session — no journal store serves its binding, a
// restore needs a checkpoint it has not got, its binding names no runnable
// runtime identity, its journal could not be read — is logged at WARN,
// because a Factory counts those refusals without logging them. A failed
// gate publication and a failed advertisement heartbeat are WARN too;
// ordinary placement refusals (epoch_mismatch, no_capacity,
// not_admitting) are DEBUG.
Logger *slog.Logger
}
Collaborators is everything a deployment supplies that this module cannot know: which store, which journals, which agents, how to checkpoint, who may connect, where workspaces live.
type Composition ¶ added in v0.2.0
type Composition struct {
// MaxCommandBodyBytes bounds a referenced command body. Zero defaults to
// 8 MiB; explicit values must exceed SessionStore's 64 KiB inline limit
// and stay within Harness's 16 MiB object-backed runtime body limit.
MaxCommandBodyBytes int64
// Options is the Host's configuration. ITS COLLABORATOR FIELDS MUST BE
// NIL — Department, SessionStore, Workspaces, Clock and Auth — because
// Compose supplies every one of them from Collaborators: the Department
// from the Registrar, the store it opened, the Workspaces, the clock, the
// verifier. A caller-supplied one would be a second store or a second
// clock beside the one the composition runs on, and is refused.
Options Options
// Generation is this process's incarnation, non-zero. It belongs to the
// RUN and not the configuration: a Host restarted on identical Options is
// a new generation, which is why Options does not carry it.
Generation uint64
Link LinkOptions
Drain DrainOptions
// LiveText is opt-in. Nil keeps the committed-only relay. Reasoning and tool
// steps require IncludeReasoning or IncludeToolSteps as well as a non-nil
// LiveText option.
LiveText *LiveTextOptions
// CompatibilityTimeout bounds the synchronous compatibility wait; WorkPoll
// is how often resident sessions are asked what they are doing. Both are
// required and positive.
CompatibilityTimeout time.Duration
WorkPoll time.Duration
// Clock is optional; nil means the system clock.
Clock CompositionClock
// RuntimeProfile is RuntimeProfileDurable unless set: every registered
// target must declare department.Recovery in full or Compose refuses the
// composition. See RuntimeProfile.
RuntimeProfile RuntimeProfile
Collaborators Collaborators
}
Composition is one runnable Host, described.
type CompositionClock ¶ added in v0.2.0
type CompositionClock interface {
Now() time.Time
After(time.Duration) <-chan time.Time
NewTimer(time.Duration) *time.Timer
NewWarmTimer(time.Duration) WarmTimer
}
CompositionClock is the composition's single time source: the Host's Clock, the drain's bound and the warm releaser's countdown, all one timeline. Its zero value in Composition means the system clock.
type DrainFailure ¶ added in v0.2.0
type DrainFailure struct {
TenantID sessionwire.TenantID
SessionID sessionwire.SessionID
Step string
Err error
}
DrainFailure is one drain step that did not succeed.
func (DrainFailure) Error ¶ added in v0.2.0
func (f DrainFailure) Error() string
func (DrainFailure) Unwrap ¶ added in v0.2.0
func (f DrainFailure) Unwrap() error
Unwrap returns the step's own error.
type DrainOptions ¶ added in v0.2.0
type DrainOptions struct {
// Grace is the platform's termination grace; Run also bounds Stop by it.
//
// SIZE THE PLATFORM'S TERMINATION GRACE ABOVE Grace + 2×IdleBoundary.
// Since v0.17.0 a runtime whose release the drain saw refused (a session
// awaiting a gate spends the whole Grace being refused) is then abandoned
// under a fresh budget of one IdleBoundary, waited up to one more if the
// abandon ignores its cancellation, so Stop can run that much past Grace.
Grace time.Duration
// IdleBoundary bounds the wait for a session to reach a safe boundary, and
// the crash-equivalent abandon of a runtime whose release was refused.
IdleBoundary time.Duration
// PublishBound bounds the nonaccepting publication.
PublishBound time.Duration
}
DrainOptions bound the drain.
type DrainReport ¶ added in v0.2.0
type DrainReport struct {
Generation uint64
State sessionwire.HostLinkDrainState
Failures []DrainFailure
// Abandoned are the sessions whose runtime refused a graceful release —
// a session awaiting a gate is never idle, so harness refuses it — and
// was then given up crash-equivalently: torn down, its journal lease
// released, nothing durable written. An open gate is preserved, and a
// successor (in this process or another) restores the session and can
// answer it. Since v0.17.0.
Abandoned []DrainSession
// Parked are the sessions whose runtime refused its release and could not
// be abandoned (it offers no department.PersistenceFaults, or the abandon
// failed or exceeded its bound). Such a runtime keeps running and holds its
// journal lease until the process exits, so a process that stays alive
// after Stop cannot restore these sessions and should treat a non-empty
// Parked as a leak. The abandon failure, if any, is also in Failures.
//
// PARKED IS A SNAPSHOT AT REPORT TIME. A teardown that was still in flight
// when the drain gave up waiting keeps running; if it later succeeds, the
// session context is cancelled then and the success is logged, but this
// report is not revised.
Parked []DrainSession
}
DrainReport is what Stop reports: the drain's generation and terminal state and every step that did not succeed. A drained Host with failures IS drained; the failures are what an operator must reconcile, and Core's two-valued drain state cannot carry them over HostLink.
type DrainSession ¶ added in v0.17.0
type DrainSession struct {
TenantID sessionwire.TenantID
SessionID sessionwire.SessionID
}
DrainSession names one session a DrainReport lists.
type EvidenceKey ¶ added in v0.2.0
type EvidenceKey struct {
TenantID sessionwire.TenantID
StorageBindingID string
}
EvidenceKey names the journal store that serves one tenant's sessions under one storage binding. The tenant is half of the key on purpose: a harness Store files ONE tenant's sessions and refuses a request naming another, so a table keyed on the binding alone could route tenant-b's settlement into tenant-a's journal — refused correctly, and only after the dispatch.
type Host ¶
type Host struct {
// contains filtered or unexported fields
}
Host is the tenant-scoped Department runtime host.
It holds a RESOLVED configuration: every option was validated once at construction and nothing here changes afterwards. Residency, command consumption, HostLink and drain are separate concerns; what this type fixes is that a Host which exists is a Host whose configuration was coherent.
The fields are unexported and the accessors return values. Nothing mutable is handed out, and nothing the caller still holds can reach in: Options is taken BY VALUE, so a caller mutating its own copy afterwards changes nothing here. Department and the collaborator interfaces are shared by design — Department is immutable by construction, and a copied SessionStore would be a different SessionStore.
SINCE v0.2.0 THE RESOLVED CONFIGURATION LIVES IN internal/hostconfig AND THIS TYPE CARRIES IT. Every package under internal/ consumes the configuration, and this package now also exports the composition that runs it (Compose), which imports those packages; a leaf both sides import cannot live in either, so it moved down and this type became the exported carrier. Nothing a v0.1.0 caller could see has changed: the method set, the Options fields, the error codes and the constants are the same names with the same values, held to the internal ones by test rather than by aliasing — an alias would have changed the exported type's identity, which is a break.
func New ¶
New validates options and returns a Host.
It returns *InvalidOptionsError, carrying a stable OptionErrorCode, for every refusal. A Host with an incoherent configuration is never returned: there is no partially valid Host and no package-global default to fall back on, so a caller that ignores the error has nothing to use. The rules themselves are internal/hostconfig's, and every one of them reaches this constructor; TestNewDelegatesEveryRefusal holds that.
func (*Host) ApplyDeadline ¶
ApplyDeadline returns the outer bound for applying an admitted command.
func (*Host) CommandQueueSize ¶
CommandQueueSize returns the inbound command buffer size.
func (*Host) Department ¶
func (h *Host) Department() *department.Department
Department returns the immutable set of launch targets this Host serves.
func (*Host) FixedSessionID ¶
func (h *Host) FixedSessionID() sessionwire.SessionID
FixedSessionID returns the session a dedicated Host is bound to, or empty for a pooled Host.
func (*Host) InternalEndpoint ¶
func (h *Host) InternalEndpoint() sessionwire.InternalEndpoint
InternalEndpoint returns the HostLink base address this Host advertises. Factory dials sessionwire.HostLinkEndpoint(base, tenant), never the base.
func (*Host) IsolationClass ¶
func (h *Host) IsolationClass() sessionwire.HostIsolationClass
IsolationClass returns the advertised cross-tenant boundary, which is also this Host's pooled admission rule. There is no TenantID accessor beside it: see Options.IsolationClass for H8.
func (*Host) Placement ¶
func (h *Host) Placement() sessionwire.HostPlacement
Placement returns the admission model this Host runs under.
func (*Host) ReconcileBatch ¶
ReconcileBatch returns the bound on one reconciliation pass.
func (*Host) ReconcileInterval ¶
ReconcileInterval returns how often reconciliation runs.
func (*Host) RegistryExpiry ¶
RegistryExpiry returns how long an advertisement survives without a refresh.
func (*Host) RegistryHeartbeat ¶
RegistryHeartbeat returns the advertisement refresh interval.
func (*Host) SessionStore ¶
func (h *Host) SessionStore() SessionStore
SessionStore returns the durable session state collaborator.
func (*Host) Workspaces ¶
func (h *Host) Workspaces() WorkspaceProvider
Workspaces returns the workspace collaborator. It does not checkpoint; see WorkspaceProvider.
type InvalidCompositionError ¶ added in v0.2.0
InvalidCompositionError reports a Composition that may not run.
func (*InvalidCompositionError) Error ¶ added in v0.2.0
func (e *InvalidCompositionError) Error() string
func (*InvalidCompositionError) Unwrap ¶ added in v0.2.0
func (e *InvalidCompositionError) Unwrap() error
Unwrap returns the lower layer's error, if any.
type InvalidOptionsError ¶
type InvalidOptionsError struct {
Code OptionErrorCode
Field string
Reason string
Cause error
}
InvalidOptionsError reports an option Host may not run with.
Code is what a caller branches on, Field names the option, Reason is for a person, and Cause carries the typed error a lower layer produced — Core's *RequestValidationError for an endpoint, for instance — so errors.As reaches it instead of a caller substring-matching a sentence.
func (*InvalidOptionsError) Error ¶
func (e *InvalidOptionsError) Error() string
func (*InvalidOptionsError) Unwrap ¶
func (e *InvalidOptionsError) Unwrap() error
Unwrap returns the typed error this one was built from, if any.
type LinkOptions ¶ added in v0.2.0
type LinkOptions struct {
// PingInterval and PongTimeout are the HostLink heartbeat. Optional and
// paired: one without the other is refused.
PingInterval time.Duration
PongTimeout time.Duration
// MaxBindingsPerLink and MaxBindings bound what one link and one tenant's
// routing table may hold. MaxTenantLinks bounds how many per-tenant
// transports this Host allocates at once; it is a memory bound and not a
// tenant allowlist.
MaxBindingsPerLink int
MaxBindings int
MaxTenantLinks int
}
LinkOptions bound the HostLink transport.
type ListenOptions ¶ added in v0.2.0
type ListenOptions struct {
// Address is the TCP address to listen on. Ignored when Listener is set.
Address string
// Listener is an already-bound listener, for a platform that hands one in
// or a test that needs to know the port. Run closes it on return.
Listener net.Listener
// ReadHeaderTimeout bounds a client's request headers; zero means ten
// seconds. It is never unbounded: an unbounded header read is a slowloris.
ReadHeaderTimeout time.Duration
}
ListenOptions is where Run serves.
type LiveTextOptions ¶ added in v0.12.0
type LiveTextOptions struct {
RateBytesPerSecond int
BurstBytes int
FlushInterval time.Duration
IncludeReasoning bool
IncludeToolSteps bool
}
LiveTextOptions enables transient text previews. Zero fields use safe defaults. IncludeReasoning also streams visible model reasoning when true. It defaults to false; opaque/redacted provider state and signature-only chunks are never published. Text and reasoning share the same transport budget and limits. Enable only after Factory v0.12.1 or later is deployed; earlier Factory mailboxes charge every frame, and WUI through v0.4.0 discards previews.
IncludeToolSteps also streams each tool call's start and completion. It is opt-in and defaults to false. The previews are the tool's own redacted audit summary (never its raw arguments; a tool without one shows only its name) and a result preview of at most 2 KiB, and every viewer of the session sees them — the same content the committed step shows moments later. They share the text budget, are never merged or retried, and a refused one is simply dropped: the committed StepDone supersedes it. They need a runtime implementing department.LiveOptionsSubscriber; a runtime on harness v0.42.0 or later also carries tool_use_id, the key a viewer joins the preview to the committed row on, while an older runtime's frames still render by name.
type NamespaceLayout ¶ added in v0.2.0
type NamespaceLayout func(sessionwire.TenantID, sessionwire.SessionID) string
NamespaceLayout answers the durable object prefix a session's runtime writes under. It is a REQUIRED collaborator: the released store publishes no layout accessor and the residency manager refuses a hydration with no namespace in both modes, so a Host composed without one could never attach a session.
type OptionErrorCode ¶
type OptionErrorCode string
OptionErrorCode is a stable, machine-readable reason an option was refused. Reason is free text a caller must not branch on; this is what it branches on.
const ( OptionErrorCodeMissing OptionErrorCode = OptionErrorCode(hostconfig.OptionErrorCodeMissing) OptionErrorCodeInvalid OptionErrorCode = OptionErrorCode(hostconfig.OptionErrorCodeInvalid) OptionErrorCodeUnknownEnum OptionErrorCode = OptionErrorCode(hostconfig.OptionErrorCodeUnknownEnum) OptionErrorCodeNotPositive OptionErrorCode = OptionErrorCode(hostconfig.OptionErrorCodeNotPositive) OptionErrorCodeAboveBound OptionErrorCode = OptionErrorCode(hostconfig.OptionErrorCodeAboveBound) OptionErrorCodeHeartbeatMargin OptionErrorCode = OptionErrorCode(hostconfig.OptionErrorCodeHeartbeatMargin) OptionErrorCodeClaimMargin OptionErrorCode = OptionErrorCode(hostconfig.OptionErrorCodeClaimMargin) OptionErrorCodeDedicatedFixed OptionErrorCode = OptionErrorCode(hostconfig.OptionErrorCodeDedicatedFixed) OptionErrorCodeDedicatedCap OptionErrorCode = OptionErrorCode(hostconfig.OptionErrorCodeDedicatedCap) OptionErrorCodePooledFixed OptionErrorCode = OptionErrorCode(hostconfig.OptionErrorCodePooledFixed) )
type Options ¶
type Options struct {
// HostID identifies this Host to Factory and in HostLink bindings.
HostID sessionwire.HostID
// InternalEndpoint is this Host's credential-free HostLink BASE address:
// a ws or wss scheme and an authority, with no path. Factory derives each
// tenant's address from it with sessionwire.HostLinkEndpoint(base,
// tenant), which is base + HostLinkPathPrefix + the escaped tenant. As of
// v0.3.0 a value HostLinkEndpoint would refuse (including the v0.2.1
// per-tenant spelling ".../hostlink/<tenant>") or one whose authority
// cannot be dialled is refused by New; see ErrUndiallableEndpoint.
InternalEndpoint sessionwire.InternalEndpoint
// IsolationClass is advertised with capacity so Factory can place
// cross-tenant work correctly, AND IT IS THIS HOST'S POOLED ADMISSION RULE.
//
// THERE IS NO TenantID BESIDE IT, and that is human gate H8, answered
// 2026-09-04: a Host advertising cross_tenant_isolated may hold several
// tenants resident; one advertising tenant_exclusive is exclusive by
// PLACEMENT, not by construction. The admission ledger holds the local
// backstop.
IsolationClass sessionwire.HostIsolationClass
// Department is the immutable set of launch targets this Host serves.
Department *department.Department
// SessionStore, Workspaces, Clock and Auth are Host's collaborators.
SessionStore SessionStore
Workspaces WorkspaceProvider
Clock Clock
Auth AuthVerifier
// Placement is the admission model this Host runs under.
Placement sessionwire.HostPlacement
// Capacity is how many sessions this Host may hold resident. Dedicated
// placement requires exactly one.
Capacity uint64
// FixedSessionID binds a dedicated Host to one session for its lifetime. It
// is REQUIRED for dedicated placement and REJECTED for pooled.
FixedSessionID sessionwire.SessionID
// WarmTTL is how long a POOLED session stays resident after it is observed
// whole-session idle before this Host warm-releases it: nonterminally, so
// the next command re-places and restores it. A session with a gate open,
// a turn in flight or durable command work outstanding is never released.
// A dedicated Host does not warm-release; its controller's drain ends the
// session.
WarmTTL time.Duration
// RegistryHeartbeat and RegistryExpiry are the advertisement lifetime.
RegistryHeartbeat time.Duration
RegistryExpiry time.Duration
// ClaimTTL and ApplyDeadline bound command application.
ClaimTTL time.Duration
ApplyDeadline time.Duration
// CommandQueueSize is the inbound command buffer.
CommandQueueSize int
// ReconcileInterval and ReconcileBatch bound reconciliation.
ReconcileInterval time.Duration
ReconcileBatch int
}
Options configures a Host. Every field is required unless its documentation says otherwise; nothing here is discovered, and there is no package-global default Host to fall back on.
type PublicJournal ¶ added in v0.10.0
type PublicJournal struct {
// contains filtered or unexported fields
}
PublicJournal is a Host-owned session's runtime journal as a client may see it (finding W1).
A harness public body names the RUNTIME session id on every event and the RUNTIME command id on every event a command caused. Both are private. Host rewrites them on the live tail it relays; Factory serves /journal (and every live-tail repair) by reading the runtime journal DIRECTLY, never through Host, so the same rewrite has to be applied there or /journal hands a browser exactly what the live tail withholds. This is that rewrite, as the reader a Factory JournalResolver returns.
IT IS THE LIVE TAIL'S FUNCTION, NOT A SECOND ONE. Both paths run internal/publicbody.Project over the same stored body with the same identities, so an event reads byte-for-byte the same live and durably:
- every `session_id` equal to the runtime session id becomes the public one;
- `cause.command_id` becomes the public command id the client admitted, read here from the journal's own application prefix for that command, or is removed (and an emptied `cause` with it) when the command has no public identity — a machine-originated cause.
Nothing else changes: sequences, event ids, coverage, cursors, member order, and every other byte of every body are the runtime journal's.
It is safe for concurrent use.
func NewPublicJournal ¶ added in v0.10.0
func NewPublicJournal(reader RuntimeJournalReader, tenant sessionwire.TenantID, session sessionwire.SessionID, binding sessionstore.SessionBinding) (*PublicJournal, error)
NewPublicJournal binds reader to one Host-owned session: its tenant, its PUBLIC session id, and the durable binding naming its runtime session.
It keeps a command mapping for the life of the value, read incrementally from the journal; a composition that builds one per read pays a scan of the journal's records up to the page each time. PublicJournals keeps them.
func (*PublicJournal) ReadPublicJournal ¶ added in v0.10.0
func (j *PublicJournal) ReadPublicJournal(ctx context.Context, req sessionstore.ReadPublicJournalRequest) (sessionwire.JournalPage, error)
ReadPublicJournal reads one page of the runtime journal and projects every body in it. The request must name the bound tenant and the RUNTIME session, as a Factory JournalResolver's reads do.
type PublicJournals ¶ added in v0.10.0
type PublicJournals struct {
// contains filtered or unexported fields
}
PublicJournals keeps each session's command mapping across reads, so a JournalResolver can answer a PublicJournal per call without re-reading a journal's records from the start every time. It holds at most its capacity of sessions, forgetting the least recently used; a forgotten session costs one re-read, never a wrong answer — a mapping is immutable.
It is safe for concurrent use.
func NewPublicJournals ¶ added in v0.10.0
func NewPublicJournals(capacity int) *PublicJournals
NewPublicJournals returns an empty cache holding at most capacity sessions.
func (*PublicJournals) Reader ¶ added in v0.10.0
func (p *PublicJournals) Reader(reader RuntimeJournalReader, tenant sessionwire.TenantID, session sessionwire.SessionID, binding sessionstore.SessionBinding) (*PublicJournal, error)
Reader is NewPublicJournal sharing this cache's mapping for the session.
type Registrar ¶
type Registrar interface {
Register(context.Context) ([]department.Registration, error)
}
Registrar produces the Department registrations one deployment serves.
IT IS CALLED ONCE, AT STARTUP, AND ITS RESULT IS IMMUTABLE. Department.New validates the registrations and fixes them for the life of the process, so a Registrar that answered differently later would be answering a question nobody asks again. It takes a context because building a launch target may require I/O — reading a model catalogue, resolving a runtime build — and a startup that can block must be cancellable.
type RegistrarFunc ¶
type RegistrarFunc func(context.Context) ([]department.Registration, error)
RegistrarFunc adapts a function to Registrar.
func (RegistrarFunc) Register ¶
func (f RegistrarFunc) Register(ctx context.Context) ([]department.Registration, error)
Register calls f.
type Residency ¶ added in v0.2.0
type Residency struct {
TenantID sessionwire.TenantID
SessionID sessionwire.SessionID
AgentID sessionwire.AgentID
RuntimeCompatibilityID string
// LeaseEpoch is this Host's residency grant: the value a bind presents.
LeaseEpoch uint64
// JournalEpoch is the RUNTIME's grant, read from the launched runtime and
// never derived from LeaseEpoch; JournalEpochHeld is the half to branch
// on, since a headless runtime legitimately holds none.
JournalEpoch uint64
JournalEpochHeld bool
// Attached reports whether THIS call established the residency. False
// means the session was already resident and this call took nothing.
Attached bool
}
Residency is what an attach reports.
type RigSessionIDs
deprecated
added in
v0.2.0
type RigSessionIDs func(context.Context, sessionwire.TenantID, sessionwire.SessionID) (uuid.UUID, error)
RigSessionIDs answers Harness's own identity for a session whose durable record carries NO BINDING (a legacy record), and is consulted for nothing else.
Deprecated: As of v0.3.0 the Harness identity of every bound session is read from its immutable durable binding (SessionBinding.RuntimeSessionID), which is where Factory records the identity it derived at create. That value is the authority — settlement reads the runtime's journal by it — and a second answer from a composition could only agree with it or name a journal nothing else reads. The collaborator is optional and is kept only so a v0.2.x composition still compiles; no session this workspace ships can be hosted without a binding. A legacy record's journal is never probed, so a create over one is now refused (v0.2.1 launched it).
type RuntimeJournalReader ¶ added in v0.10.0
type RuntimeJournalReader interface {
ReadPublicJournal(context.Context, sessionstore.ReadPublicJournalRequest) (sessionwire.JournalPage, error)
ReadRuntimeJournal(context.Context, sessionstore.ReadRuntimeJournalRequest) (sessionstore.RuntimePage, error)
}
RuntimeJournalReader is what PublicJournal reads a Host-owned session's runtime journal through: the public page it projects, and the privileged record read it takes the command mapping from. A *sessionstore.Store opened over the runtime's backend — the reader a Factory JournalResolver already returns — satisfies it.
type RuntimeProfile ¶ added in v0.16.0
type RuntimeProfile uint8
RuntimeProfile is how strictly a composition holds its launch targets to the recovery a durable deployment needs.
THE ZERO VALUE IS DURABLE, AND THAT IS A BEHAVIOUR CHANGE IN host v0.16.0. Under RuntimeProfileDurable, Compose refuses any registration whose target does not declare both department.Recovery capabilities, because a runtime without them fails silently and late: a failover with an attempt in flight wedges the session's command stream for good (no AttemptCloser), and a latched journal fault leaves the session resident and refusing every command (no PersistenceFaults). A composition that never declared recovery — every one built before v0.16.0 — is therefore refused until it declares (harnessruntime.Target does so by construction) or opts out.
The best-effort profile is that opt-out, for fakes, experiments and runtimes that genuinely cannot recover. It admits undeclared targets and is logged at WARN once, on Start. It changes nothing a target DOES declare: a NewRigTarget still refuses, at launch, a session missing a capability its target declared.
const ( // RuntimeProfileDurable refuses targets that do not declare full recovery. // It is the zero value and the default. RuntimeProfileDurable RuntimeProfile = iota // RuntimeProfileBestEffort admits targets without declared recovery. RuntimeProfileBestEffort )
func (RuntimeProfile) String ¶ added in v0.16.0
func (p RuntimeProfile) String() string
String names the profile.
type Service ¶ added in v0.2.0
type Service struct {
// contains filtered or unexported fields
}
Service is one composed Host: built, then started, then stopped.
func Compose ¶ added in v0.2.0
func Compose(ctx context.Context, blueprint Composition) (*Service, error)
Compose validates the composition, opens the session store, builds the Department and every collaborator, and returns a Service with nothing running.
COMPOSE OPENS THE STORE ITSELF, and that is the whole reason Collaborators carries a Backend and not a *sessionstore.Store. Settlement refuses without a configured evidence reader, and the released store publishes no way to ask an open one whether it has one; a deployment that opened the store and forgot the reader would attach, list, claim, dispatch, and fail at every settlement, leaving each command applying with a real effect behind it. Opening here means the router built from JournalStores is installed on every store this composition can run on, so that failure is unrepresentable rather than documented. Compose closes the store on any later refusal.
func (*Service) Attach ¶ added in v0.2.0
Attach makes a session resident on this Host, in process.
It is the SAME entry point the hostlink.attach RPC reaches, so its semantics are the RPC's: serialized per session, idempotent on a resident session (Attached false, the existing epoch), refused with a HostLink class where placement should be re-run and with none where it should not. Attach requires a successful Start; it returns ErrServiceNotStarted before that and ErrServiceDisposed after disposal begins.
func (*Service) CloseUnstarted ¶ added in v0.6.0
CloseUnstarted disposes a Service that never started and holds no residency. It closes the SessionStore opened by Compose, but leaves the caller's Storage backend open. If initial publication committed before Start returned an error, its advertisement remains until expiry; this is not a graceful drain. Caller cancellation only ends that call's wait. Later calls wait for the same cleanup and return its final result. Start and Stop refuse after disposal.
func (*Service) Department ¶ added in v0.2.0
func (s *Service) Department() *department.Department
Department is the immutable set of launch targets this Service serves.
func (*Service) Handler ¶ added in v0.2.0
Handler serves HostLink at HostLinkPathPrefix + the tenant.
func (*Service) Live ¶ added in v0.2.0
Live reports whether this process should keep running: the liveness probe's answer. It stays true throughout and after a drain and turns false only when Stop has run, so a platform never kills a Host mid-checkpoint or before it has answered `drained` over HostLink.
func (*Service) MetricsHandler ¶ added in v0.2.0
MetricsHandler serves this Host's Prometheus series, and only this Host's: the registry behind it holds one collector rather than the default registry's process, Go and dependency series. It is a handler and not a prometheus.Collector so that client_golang's types stay out of this API.
func (*Service) Ready ¶ added in v0.2.0
Ready reports whether this Host is ACCEPTING: the readiness probe's answer. A draining Host is not ready from the instant its ledger flips.
func (*Service) Routes ¶ added in v0.2.0
Routes serves HostLink, the two probes and the metrics on one handler: HostLinkPathPrefix, /readyz (Ready), /healthz (Live) and /metrics. It is what Run mounts, exported so a binary that owns its listener can mount the same four routes beside its own.
func (*Service) Start ¶ added in v0.2.0
Start publishes this Host's advertisements and begins its heartbeat. The first publication is synchronous, so a Host whose directory refuses it fails here rather than running invisibly. A Service starts once and returns ErrServiceDisposed after CloseUnstarted begins.
A Service composed under RuntimeProfileBestEffort logs one WARN, on the first Start, naming the targets that declare no recovery.
func (*Service) Stop ¶ added in v0.2.0
func (s *Service) Stop(ctx context.Context) (DrainReport, error)
Stop drains this Host, waits for the drain to finish, closes every tenant transport, and then closes the session store Compose opened. The store is last because everything before it may still write. Before a successful Start it returns ErrServiceNotStarted; after disposal it returns ErrServiceDisposed.
type ServiceLifecycleError ¶ added in v0.6.0
type ServiceLifecycleError = compose.ServiceLifecycleError
ServiceLifecycleError reports an operation refused by the Host lifecycle.
type SessionStore ¶
type SessionStore interface {
// LoadSession returns the durable record for a session, or an error the
// caller can classify.
LoadSession(context.Context, sessionwire.TenantID, sessionwire.SessionID) ([]byte, error)
}
SessionStore is the durable session state Host reads and writes.
type WarmTimer ¶ added in v0.2.0
WarmTimer is one session's warm countdown, in time.Timer's own three methods.
type WorkspaceProvider ¶
type WorkspaceProvider interface {
// EnsureWorkspace makes a workspace available and returns its root.
EnsureWorkspace(context.Context, sessionwire.TenantID, sessionwire.SessionID) (string, error)
}
WorkspaceProvider materializes session workspaces.
It does NOT checkpoint them; see Checkpointer for that half. Nor does it release them: the composition needs the release half too, which is Workspaces, and it is Workspaces a composition takes.
type Workspaces ¶ added in v0.2.0
type Workspaces interface {
EnsureWorkspace(context.Context, sessionwire.TenantID, sessionwire.SessionID) (string, error)
// ReleaseWorkspace drops the local materialization. It never deletes
// durable state.
ReleaseWorkspace(context.Context, sessionwire.TenantID, sessionwire.SessionID) error
}
Workspaces materializes and releases session workspaces. It is WorkspaceProvider plus the release half the composition's warm release and drain need; a value satisfying it satisfies WorkspaceProvider too.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
host
command
Command host runs one headless Department runtime host.
|
Command host runs one headless Department runtime host. |
|
Package department defines the immutable set of launch targets a Host serves.
|
Package department defines the immutable set of launch targets a Host serves. |
|
departmenttest
Package departmenttest is test support for products that register a runtime with Host: an exported conformance run that launches one real session through a department.LaunchTarget and checks the contract Host relies on.
|
Package departmenttest is test support for products that register a runtime with Host: an exported conformance run that launches one real session through a department.LaunchTarget and checks the contract Host relies on. |
|
Package harnessruntime is the Host runtime backed by harness: it binds Host's department seams to the released github.com/looprig/harness rig and session API, so a product registers a harness rig with Host instead of hand-writing the adapter.
|
Package harnessruntime is the Host runtime backed by harness: it binds Host's department seams to the released github.com/looprig/harness rig and session API, so a product registers a harness rig with Host instead of hand-writing the adapter. |
|
internal
|
|
|
bodyclass
Package bodyclass holds the shared pre-attempt decision about an unreadable stored command body.
|
Package bodyclass holds the shared pre-attempt decision about an unreadable stored command body. |
|
commands
Applying an admitted command is §10.4's durable protocol around ONE call into a runtime, and this file is that protocol.
|
Applying an admitted command is §10.4's durable protocol around ONE call into a runtime, and this file is that protocol. |
|
compose
Package compose is Host's composition root: it builds the Department runtime, the residency manager, the command consumers, the live event relay, the per-tenant HostLink servers, the metrics and the drain into one running service, with an explicit start and stop order.
|
Package compose is Host's composition root: it builds the Department runtime, the residency manager, the command consumers, the live event relay, the per-tenant HostLink servers, the metrics and the drain into one running service, with an explicit start and stop order. |
|
controlbody
Package controlbody strictly reads interrupt and restore request bodies before a dispatch attempt.
|
Package controlbody strictly reads interrupt and restore request bodies before a dispatch attempt. |
|
createbody
Package createbody reads a create command's private body, which is Core's CreateRequest, and re-presents the session's first message in the shape a Host's block decoder reads.
|
Package createbody reads a create command's private body, which is Core's CreateRequest, and re-presents the session's first message in the shape a Host's block decoder reads. |
|
gateresponse
Package gateresponse reads a gate_response command's private body, the one rule both of its readers share.
|
Package gateresponse reads a gate_response command's private body, the one rule both of its readers share. |
|
gates
Package gates publishes a resident session's open gates to the durable gate projection, so a Factory can show every AskUser or permission question to a user and admit the answer.
|
Package gates publishes a resident session's open gates to the durable gate projection, so a Factory can show every AskUser or permission question to a user and admit the answer. |
|
harnesstest
Package harnesstest is TEST SUPPORT over the released harness runtime: a real harness session store, a real rig with one loop, and a recording inference client that answers every turn.
|
Package harnesstest is TEST SUPPORT over the released harness runtime: a real harness session store, a real rig with one loop, and a recording inference client that answers every turn. |
|
inputbody
Package inputbody strictly reads a stored Core input request before the dispatch attempt, without rewriting the bytes handed to a product decoder.
|
Package inputbody strictly reads a stored Core input request before the dispatch attempt, without rewriting the bytes handed to a product decoder. |
|
lifecycle
Package lifecycle owns this Host's drain: the single-owner state machine that stops admission, releases every resident session in order, and reports when the Host is drained.
|
Package lifecycle owns this Host's drain: the single-owner state machine that stops admission, releases every resident session in order, and reports when the Host is drained. |
|
livetext
Package livetext reads and joins bounded transient text and reasoning bodies.
|
Package livetext reads and joins bounded transient text and reasoning bodies. |
|
livetool
Package livetool reads and bounds transient tool-step bodies: harness's public projection of ToolCallStarted and ToolCallCompleted.
|
Package livetool reads and bounds transient tool-step bodies: harness's public projection of ToolCallStarted and ToolCallCompleted. |
|
modfiles
Package modfiles enumerates Go source files owned by this module while excluding structural directories and nested repository or module boundaries.
|
Package modfiles enumerates Go source files owned by this module while excluding structural directories and nested repository or module boundaries. |
|
modfiles/cmd/modfiles
command
|
|
|
publicbody
Package publicbody projects a runtime's committed public event body onto the identities a client may hold (finding W1).
|
Package publicbody projects a runtime's committed public event body onto the identities a client may hold (finding W1). |
|
realtime/hostlink
Package hostlink owns the authenticated realtime server used by Factory to reach one Host.
|
Package hostlink owns the authenticated realtime server used by Factory to reach one Host. |
|
registry
Package registry indexes the sessions a Host currently holds resident.
|
Package registry indexes the sessions a Host currently holds resident. |
|
residency
Package residency creates and restores sessions under the durable session lease, in one fixed order, and releases everything it took when any step of that order fails.
|
Package residency creates and restores sessions under the durable session lease, in one fixed order, and releases everything it took when any step of that order fails. |
|
service
Package service derives what a Host publishes about itself, and relays what its resident sessions have already committed.
|
Package service derives what a Host publishes about itself, and relays what its resident sessions have already committed. |
|
sessionstoreadapter
Package sessionstoreadapter binds Host's narrow local store seams to the released github.com/looprig/sessionstore v0.7.0 Store.
|
Package sessionstoreadapter binds Host's narrow local store seams to the released github.com/looprig/sessionstore v0.7.0 Store. |
|
settlement
Package settlement holds the end-to-end proof that one command APPLIES and SETTLES across the three modules that had to release for it to be possible.
|
Package settlement holds the end-to-end proof that one command APPLIES and SETTLES across the three modules that had to release for it to be possible. |
|
testkit
Package testkit holds test doubles shared by this module's tests.
|
Package testkit holds test doubles shared by this module's tests. |