Documentation
¶
Overview ¶
Package department defines the immutable set of launch targets a Host serves.
A Department is a REGISTRY, fixed at construction: it maps an agent identity to the target that can launch a runtime for it, and it never changes afterwards. Residency, admission, command consumption and HostLink are other tasks; this package answers one question, "what may this Host launch, and under what constraints", and answers it the same way for the life of the process.
Nothing here is a Factory type. A Runtime is a composition of narrow Harness session capabilities that Host requires, declared on Host's side.
Command bodies reach a runtime as bytes. Every Host built by host.Compose wires the control store's object reader into its disposition applier and advertises Core's hostlink.payload.reference capability, so a body Factory stored by reference is resolved and verified by Host before the attempt; see RuntimeCommand.
A harness-backed product does not implement these interfaces itself: github.com/looprig/host/harnessruntime adapts a harness rig into a LaunchTarget that declares and supplies every capability, recovery included. departmenttest.RunRuntimeConformance checks any target against the same contract.
Index ¶
- Constants
- Variables
- func RigSessionID(runtime Runtime) (uuid.UUID, bool)
- type AttemptCloser
- type Capabilities
- type CaptureSafety
- type CommandApplier
- type CompatibilityID
- type CompatibilityMismatchError
- type CreateRequest
- type DefinitionErrorCode
- type Department
- type Identity
- type IdleWaiter
- type IncapableRuntimeError
- type InvalidCapabilitiesError
- type InvalidCompatibilityIDError
- type InvalidDepartmentError
- type LaunchTarget
- type LeaseEpochReporter
- type LiveOptions
- type LiveOptionsSubscriber
- type LivePublication
- type LivePublicationSubscriber
- type Liveness
- type PersistenceFaults
- type PublicationSubscriber
- type ReasoningPublicationSubscriber
- type Recovery
- type Registration
- type Releaser
- type RestoreRequest
- type Rig
- type RigCreateRequest
- type RigLaunchError
- type RigRestoreRequest
- type RigSession
- type Runtime
- type RuntimeCommand
- type StorageContext
- type UnknownAgentError
Constants ¶
const MaxCompatibilityIDBytes = sessionwire.MaxIDBytes
MaxCompatibilityIDBytes mirrors sessionwire.MaxIDBytes, which is what Core enforces on the wire field this value is written to. Naming it separately would let the two drift; it is derived.
Variables ¶
var ErrDispositionUnsupported = errors.New("department: this runtime's durable log cannot record a command disposition, so nothing was dispatched and nothing durable was written")
ErrDispositionUnsupported reports a dispatch refused because the runtime's durable log cannot record a command disposition.
IT IS DECLARED HERE, IN THE SEAM, BECAUSE BOTH SIDES MUST NAME IT. The adapter raises it and the applier acts on it, and neither may import the other; a refusal recognisable only by its message text is a refusal a caller cannot distinguish from a transport failure, which is precisely the mistake harness exported its own error type to stop.
NOTHING DURABLE WAS WRITTEN, so the command MAY be re-offered — which a transport failure does not license, because a transport failure says the opposite: something may have happened.
var ErrEnduringEffect = errors.New("department: the runtime's journal holds an enduring effect for this command, so its attempt may not be closed")
ErrEnduringEffect reports a recovery closure refused because the runtime's journal holds an enduring event caused by the command being closed.
IT IS TERMINAL FOR THE CLOSURE AND IS NEVER RETRYABLE. The predecessor's effect committed and only its evidence is missing, so a tombstone over it is the one error this protocol cannot recover from. A caller that retried this into a closure would destroy a real effect.
var ErrNoAttemptCloser = errors.New("department: this runtime offers no recovery closure, so a predecessor's stranded attempt cannot be closed")
ErrNoAttemptCloser reports a runtime that offers no recovery closure.
IT EXISTS BECAUSE A WRAPPER CANNOT BE ABSENT. rigRuntime forwards an optional capability, and a Go wrapper that declares the method satisfies the interface for every runtime it wraps — so a caller's type assertion stops being able to say "this one has none". The distinction moves from the assertion to this sentinel, and a caller that needs it must read the error rather than the assertion.
A BLOCKED PASS IS THE CORRECT RESPONSE, never a conclusion: a composition that cannot close a predecessor's stranded attempt must leave the command where it is, not decide its fate without the evidence a closure would have produced.
var ErrNoPersistenceFaults = errors.New("department: this runtime reports no persistence faults and cannot be abandoned")
ErrNoPersistenceFaults is what a department runtime wrapper answers from AbandonResidency when the runtime it wraps offers no PersistenceFaults.
var ErrNoRigSession = errors.New("the rig reported success and returned no session")
ErrNoRigSession is the cause of a RigLaunchError raised when a rig reports success and hands back nothing. It is EXPORTED so a test — and a caller — can tell that failure apart from a rig that refused, which is the difference between a broken rig and a busy one.
var ErrPrefixCommitted = errors.New("department: the runtime durably recorded the command's application prefix before the dispatch failed")
ErrPrefixCommitted marks a dispatch that FAILED AFTER the runtime durably wrote the command's application prefix. It is JOINED to the runtime's own error, never substituted for it.
IT SAYS THE RUNTIME TOOK THE COMMAND, not that the effect committed. What it licenses is narrow: for a gate_response it means the answer's GateResolved may already be durable with only its disposition lost (the enduring-effect gap), and the runtime holding the answer in memory must then be KEPT — abandoning it would throw the answer away without settling anything. A dispatch failed with nothing durable (a zero prefix) never carries it.
var ErrResidencyStillHeld = errors.New("department: the runtime tore down but its lease was not released, so its residency is still held")
ErrResidencyStillHeld marks a runtime release or abandon that tore the runtime down but could NOT give its leases back — harness v0.45.0's *session.LeaseReleaseError (journal lease or exclusive workspace-root lease). It is JOINED to the runtime's own error, never substituted.
THE RESIDENCY IS STILL HELD, and Host must not report the hand-off complete: no pinned storage backend expires a lease, so a successor — in this process or another — stays locked out until the lease is released. The drain reports such a session Parked and withholds `drained`.
var ErrRigSessionIdentity = errors.New("the rig launched the session under a different Harness identity from the one requested")
ErrRigSessionIdentity is the cause of a RigLaunchError raised when a Rig launched or restored a session under a different Harness identity from the one the request named. Reach it with errors.Is.
Functions ¶
func RigSessionID ¶
RigSessionID reports Harness's UUID for a runtime this package adapted, for callers that must correlate with Harness's own records. It is deliberately not part of Runtime: Host's contracts speak sessionwire identities.
PROVISIONAL. It is a *rigRuntime type assertion on a public API, which is an escape hatch rather than a contract, and it exists today because it is the only way to assert create-side identity translation from outside this package. O2.2 is the task that will actually need to correlate with Harness's records; when it does, decide then whether this stays an assertion-shaped helper or becomes a field on something the registry holds. Do not build on it before that decision.
Types ¶
type AttemptCloser ¶
type AttemptCloser interface {
// CloseAttempt durably records that the named attempt was not applied.
//
// AttemptJournalEpoch is the grant the ATTEMPT was authorized under, read
// from the durable record. The author grant is NOT a parameter: the closer
// stamps that from the live lease it holds, because a caller-supplied author
// epoch would be a caller-authored proof, and a tombstone is the one thing
// that must never be one.
CloseAttempt(ctx context.Context, command sessionwire.CommandID, runtimeCommand uuid.UUID, kind string, attempt string, attemptJournalEpoch uint64) error
}
AttemptCloser writes the recovery closure for an attempt a PREVIOUS runtime never finished, under this runtime's own strictly later journal grant.
IT IS DISCOVERED BY ASSERTION AND IS DELIBERATELY NOT PART OF Runtime, which is the shape harness's runtimecommand.AttemptCloser has for the same reason: folding a recovery-only capability into the ordinary control path would make every implementer of that path — the composed adapter, every test double — carry a method only a successor ever calls. A composition whose runtime does not satisfy it blocks on a stranded attempt rather than concluding anything.
A product session backed by harness can forward to the real journal closer. Here s.closer is a runtimecommand.AttemptCloser from that session's live journal lease; it supplies the author grant itself. The example uses context, github.com/looprig/core/sessionwire/v1 (as sessionwire), github.com/looprig/core/uuid, and github.com/looprig/harness/pkg/runtimecommand:
func (s *Session) CloseAttempt(ctx context.Context, command sessionwire.CommandID, runtimeID uuid.UUID, kind, attempt string, epoch uint64) error {
closure := runtimecommand.Closure{CommandID: runtimecommand.CommandID(command), RuntimeCommandID: runtimeID, Kind: runtimecommand.Kind(kind), AttemptID: runtimecommand.AttemptID(attempt), AttemptJournalEpoch: epoch}
if err := closure.Validate(); err != nil { return err }
_, err := s.closer.CloseAttempt(ctx, closure)
return err
}
Forward the closer's error; a refusal to close must leave the command applying. The attempt epoch comes from Host's durable command record.
type Capabilities ¶
type Capabilities struct {
// SupportsPooled and SupportsDedicated declare the admission models the
// target can run under. At least one must be true.
SupportsPooled bool
SupportsDedicated bool
// RequiresWorkspace declares that a launch needs a materialized workspace.
RequiresWorkspace bool
// RequiresCheckpoint declares that a restore needs a checkpoint to exist.
RequiresCheckpoint bool
// AdmissionWeight is this target's cost against a Host's capacity, in the
// same units the capacity report uses. It must be at least one: a target
// that costs nothing admits without bound.
AdmissionWeight uint64
// CaptureSafety describes the target's highest-output tool.
CaptureSafety CaptureSafety
// Recovery declares the recovery capabilities every runtime this target
// launches offers. It is additive and Validate does not read it: the zero
// value declares nothing, which is what every target built before host
// v0.16.0 declared.
//
// IT IS A DECLARATION THAT IS CHECKED TWICE. host.Compose reads it at
// composition time — under the default RuntimeProfileDurable a target that
// does not declare both is refused before anything runs — and a target
// built with NewRigTarget verifies it at every launch, refusing (and
// releasing) a session that lacks a capability its target declared. So a
// declaration the runtime cannot honour is caught at the first launch
// rather than at the first crash, which is when a missing recovery
// capability would otherwise surface.
Recovery Recovery
}
Capabilities is what a target declares about how it may be placed and what it needs. It is a value: a target returns a copy and no caller can reach into one target's declaration through another.
func (Capabilities) PermittedPlacements ¶
func (c Capabilities) PermittedPlacements() []sessionwire.HostPlacement
PermittedPlacements returns the admission models this target may be placed under, in a deterministic sorted order.
func (Capabilities) PoolingPermitted ¶
func (c Capabilities) PoolingPermitted() bool
PoolingPermitted reports whether the target may share a Host.
An unknown or unbounded materialized high-output tool makes the target DEDICATED-ONLY until it gains streaming capture, regardless of what SupportsPooled declares: the declaration is about what the target can do, and this is about what its capture behaviour makes safe.
func (Capabilities) Validate ¶
func (c Capabilities) Validate() error
Validate holds every rule about a Capabilities value, in one place, so the registry and the rig-target constructor cannot disagree about what a legal declaration is.
It was extracted in O1.2, which is a REFACTOR OF O1.1's code from a later task. The trigger was real — NewRigTarget needed the same rules and restating them would have been the two-site shape this file has spent several rounds removing — but the consequence crossed a task boundary and is recorded here rather than left to be discovered: the rules gained a typed cause, *InvalidCapabilitiesError, and O1.1's assertion that a capability rule unwraps to nil moved to duplicate registration.
type CaptureSafety ¶
type CaptureSafety string
CaptureSafety describes how a target's highest-output tool returns its output, which is what decides whether the target may share a Host.
const ( // CaptureSafetyUnknown NAMES the unknown case for a target that wants to // say so explicitly. It is NOT the zero value of CaptureSafety — that is // the empty string — and do not branch on it to find an undescribed // target. A Capabilities nobody filled in carries "", not this, and // `if c.CaptureSafety == CaptureSafetyUnknown` walks straight past exactly // the case it looks like it covers. // // Ask PoolingPermitted instead. It lists the SAFE values and defaults // everything else to unsafe, so the empty string, this constant and any // value a future Core adds are all refused without anyone editing a branch. CaptureSafetyUnknown CaptureSafety = "unknown" // CaptureSafetyStreaming reports that high-output tools stream their // output, so peak memory does not scale with output size. CaptureSafetyStreaming CaptureSafety = "streaming" // CaptureSafetyBoundedMaterialized reports that output is materialized in // memory but under a limit the target enforces. CaptureSafetyBoundedMaterialized CaptureSafety = "bounded_materialized" // CaptureSafetyUnboundedMaterialized reports that output is materialized in // memory with no enforced limit. CaptureSafetyUnboundedMaterialized CaptureSafety = "unbounded_materialized" )
type CommandApplier ¶
type CommandApplier interface {
ApplyCommand(context.Context, RuntimeCommand) error
}
CommandApplier applies one admitted runtime command. This is Host's control path into a runtime.
type CompatibilityID ¶
type CompatibilityID string
CompatibilityID identifies the runtime build a target launches, so a restore onto an incompatible runtime is refused before it starts.
It is declared HERE and not taken from Core because Core v0.7.0 spells this as a bare string field on the HostLink records (HostLinkCapacityReport.RuntimeCompatibilityID and its siblings) and exports no named type for it. Host needs the name to state its own contract; when Core promotes the field to a named type this becomes a one-line alias and the conversion at the wire edge disappears. The validation rule below is Core's, restated rather than imported because the function that holds it, validateHostLinkOpaque, is unexported.
func (CompatibilityID) Validate ¶
func (id CompatibilityID) Validate() error
Validate reports whether the identifier is a permitted opaque runtime identity: non-empty, at most MaxCompatibilityIDBytes bytes, valid UTF-8.
type CompatibilityMismatchError ¶
type CompatibilityMismatchError struct {
AgentID sessionwire.AgentID
SessionID sessionwire.SessionID
Durable CompatibilityID
Target CompatibilityID
}
CompatibilityMismatchError reports a restore onto a runtime build that did not write the durable state.
func (*CompatibilityMismatchError) Error ¶
func (e *CompatibilityMismatchError) Error() string
func (*CompatibilityMismatchError) ErrorCode ¶
func (e *CompatibilityMismatchError) ErrorCode() sessionwire.ErrorCode
ErrorCode returns the stable public code a client branches on. A restore onto an incompatible runtime is not an invalid request: the request is well formed and this Host has no runtime that can serve it.
type CreateRequest ¶
type CreateRequest struct {
TenantID sessionwire.TenantID
SessionID sessionwire.SessionID
AgentID sessionwire.AgentID
Placement sessionwire.HostPlacement
WorkspaceRoot string
Storage StorageContext
// RigSessionID is HARNESS'S identity to launch the new session under: the
// runtime session id the session's immutable durable binding names, which
// Factory derived at create and which is NOT the sessionwire SessionID. A
// target must launch under exactly this id (harness: rig.WithSessionID),
// because the binding is the only record of where the session's journal
// lives; a session launched under a minted id writes a journal nothing
// can find again. Host sends a create only after reading no journal under
// it, under the residency lease; a lease lost mid-launch can still race a
// successor (closing that needs harness create-if-absent). Zero arises only
// for a session with no catalog record, and the target mints one.
RigSessionID uuid.UUID
}
CreateRequest is a Host-side launch request.
It is NOT sessionwire.CreateRequest. That record is a client's durable command envelope — a wire type with a CommandID, a version and canonical JSON blocks — and a launch is neither a command nor a wire record: it is Host asking a target for a process, and the fields it needs (a workspace root, a resolved placement) have no place on the wire.
type DefinitionErrorCode ¶
type DefinitionErrorCode string
DefinitionErrorCode is a stable, machine-readable reason a definition could not become a Department.
It exists because Reason is free text and two rules had already collided in it: the empty Department said "registers no launch target" and a nil target said "no launch target", so a test matching the substring passed for both inputs — it was green for the wrong reason and would have stayed green if the nil-target rule were deleted. A caller branching on a rule needs something that cannot be made ambiguous by rewording a sentence.
const ( // DefinitionErrorCodeNoRegistrations reports a Department with no // registrations at all. DefinitionErrorCodeNoRegistrations DefinitionErrorCode = "no_registrations" // DefinitionErrorCodeDuplicateAgent reports an agent registered more than // once. DefinitionErrorCodeDuplicateAgent DefinitionErrorCode = "duplicate_agent" // DefinitionErrorCodeInvalidAgentID reports an agent identity sessionwire // would refuse. The cause is the *sessionwire.IDValidationError. DefinitionErrorCodeInvalidAgentID DefinitionErrorCode = "invalid_agent_id" // DefinitionErrorCodeNoTarget reports a registration with a nil target. DefinitionErrorCodeNoTarget DefinitionErrorCode = "no_target" // DefinitionErrorCodeInvalidCompatibilityID reports a runtime identity Host // may not write to the wire. The cause is the // *InvalidCompatibilityIDError. DefinitionErrorCodeInvalidCompatibilityID DefinitionErrorCode = "invalid_compatibility_id" // DefinitionErrorCodeZeroAdmissionWeight reports a target that would admit // without bound. DefinitionErrorCodeZeroAdmissionWeight DefinitionErrorCode = "zero_admission_weight" // DefinitionErrorCodeNoPlacement reports a target that declares neither // pooled nor dedicated support. DefinitionErrorCodeNoPlacement DefinitionErrorCode = "no_placement" // DefinitionErrorCodePooledUnsafeCapture reports a target declaring pooled // support whose capture behaviour makes it dedicated-only. DefinitionErrorCodePooledUnsafeCapture DefinitionErrorCode = "pooled_unsafe_capture" )
type Department ¶
type Department struct {
// contains filtered or unexported fields
}
Department is the immutable set of launch targets a Host serves.
The map is UNEXPORTED and the type is a struct rather than a named map type, which is the difference between an immutable registry and a mutable one with a comment saying not to write to it. A named map type has no way to refuse d[agent] = other, and no way to stop a caller ranging it and getting a different order every time.
The immutability is of the REGISTRY, and that is the whole of the claim. New copies the registrations out of the caller's slice, so nothing the caller does to that slice afterwards reaches inside; it does not and cannot copy the TARGETS, which are interface values a caller may still hold and whose implementations may carry state. A LaunchTarget is a factory and has to be shared to be useful. What is fixed here is which agent maps to which target, how many there are, and the order they are listed in.
func New ¶
func New(registrations []Registration) (*Department, error)
New validates every registration and returns an immutable Department.
func (*Department) AgentIDs ¶
func (d *Department) AgentIDs() []sessionwire.AgentID
AgentIDs returns every registered agent identity in ascending order.
func (*Department) Len ¶
func (d *Department) Len() int
Len reports how many agents this Department registers.
func (*Department) Target ¶
func (d *Department) Target(agent sessionwire.AgentID) (LaunchTarget, error)
Target returns the launch target registered for agent, or *UnknownAgentError carrying sessionwire.ErrorCodeRuntimeUnavailable if this Department does not register one.
type Identity ¶
type Identity interface {
SessionID() sessionwire.SessionID
AgentID() sessionwire.AgentID
}
Identity reports the identities a runtime is bound to.
It is NOT called Controller. It carries two getters and controls nothing; Host's control path is CommandApplier, which applies an admitted command. Naming a pure accessor after the thing it is adjacent to is how a type ends up with a promise its methods do not keep.
type IdleWaiter ¶
IdleWaiter blocks until the runtime has no work in flight. H4.1's shape.
A RUNTIME THAT IS ALREADY IDLE MUST ANSWER nil WITHOUT WAITING, EVEN FOR A CONTEXT THAT IS ALREADY DONE, and a busy one must answer an error at once for such a context. That is harness's behaviour (its hub checks quiescence before it consults the context), and a composed Host relies on it: it probes each resident session with a done context to decide whether it is idle for warm release. A runtime that consults its context first is fail-safe — it always reads as busy and is never warm-released — but it is never released either.
type IncapableRuntimeError ¶
type IncapableRuntimeError struct {
AgentID sessionwire.AgentID
SessionID sessionwire.SessionID
Missing []string
}
IncapableRuntimeError reports a launched session missing capabilities Host requires. Missing names every one of them rather than the first, because a session short of three capabilities should cost one round trip to find out.
func (*IncapableRuntimeError) Error ¶
func (e *IncapableRuntimeError) Error() string
type InvalidCapabilitiesError ¶
type InvalidCapabilitiesError struct {
Code DefinitionErrorCode
Reason string
}
InvalidCapabilitiesError reports a Capabilities value no target may declare.
It exists so the rules have ONE site. department.New validates a registered target's capabilities and NewRigTarget validates a target it is about to build; a rule applied in one and not the other is not a rule, and this file has spent several rounds removing exactly that shape. Code is the same DefinitionErrorCode the registry reports, so the two paths are not merely consistent but observably identical.
func (*InvalidCapabilitiesError) Error ¶
func (e *InvalidCapabilitiesError) Error() string
type InvalidCompatibilityIDError ¶
type InvalidCompatibilityIDError struct {
Reason string
}
InvalidCompatibilityIDError reports a runtime identity Host may not write to the wire field it is destined for.
func (*InvalidCompatibilityIDError) Error ¶
func (e *InvalidCompatibilityIDError) Error() string
type InvalidDepartmentError ¶
type InvalidDepartmentError struct {
Code DefinitionErrorCode
AgentID sessionwire.AgentID
Reason string
Cause error
}
InvalidDepartmentError reports a definition that could not become a Department. AgentID names the registration at fault and is empty when the fault is the Department as a whole.
Code is what a caller branches on. Reason is for a human and may be reworded freely. Cause carries the typed error a lower layer produced — Core's *sessionwire.IDValidationError, or this package's *InvalidCompatibilityIDError — so errors.As reaches it instead of a caller having to substring-match a sentence this type has already changed twice.
func (*InvalidDepartmentError) Error ¶
func (e *InvalidDepartmentError) Error() string
func (*InvalidDepartmentError) Unwrap ¶
func (e *InvalidDepartmentError) Unwrap() error
Unwrap returns the typed error this one was built from, if any.
type LaunchTarget ¶
type LaunchTarget interface {
// CompatibilityID identifies the runtime build this target launches.
CompatibilityID() CompatibilityID
// Capabilities returns how this target may be placed and what it needs.
Capabilities() Capabilities
// Create launches a new runtime.
Create(context.Context, CreateRequest) (Runtime, error)
// Restore relaunches a runtime over existing durable state.
Restore(context.Context, RestoreRequest) (Runtime, error)
}
LaunchTarget creates and restores runtimes for one agent.
func NewRigTarget ¶
func NewRigTarget(rig Rig, compatibility CompatibilityID, capabilities Capabilities) (LaunchTarget, error)
NewRigTarget adapts a Rig into a LaunchTarget for one agent.
It validates BEFORE returning a target, so an unusable target cannot reach a Registration and therefore cannot reach a Department: the rejection happens with nothing registered rather than with a registry holding something that will fail on first use.
type LeaseEpochReporter ¶
LeaseEpochReporter reports the JOURNAL single-writer lease epoch the RUNTIME holds. It is harness v0.33.0's session.LeaseEpochReporter shape, verbatim, for the reason every other shape in this block is verbatim: a capability declared here with a different signature turns a mechanical adapter into a semantic one.
IT IS NOT HOST'S GRANT AND MUST NEVER BE SOURCED FROM ONE. A deployment has two monotonic per-session counters — Host's residency grant and the runtime's journal grant — issued by different holders over different namespaces. sessionstore.ResidencyEpoch says in terms that it "must never be compared with, or used as, a journal epoch", and harness checks an admitted command's LeaseEpoch for EQUALITY against the lease the runtime itself holds. A Host that answered this from its own lease would produce work that check rejects, or — worse, in disposition mode — an attempt no evidence can ever match. That the two numbers often agree early in a session's life is an accident of two fresh counters both starting at 1, not a relationship.
THE TWO RESULTS ARE THE CONTRACT. held reports whether the runtime holds a lease that reports an epoch AT ALL; the epoch is meaningful only when held is true and is zero otherwise. A caller must branch on held: no pinned provider zeroes a released lease's epoch, so reading the number alone hands back a live-looking dead value once the lease is gone.
type LiveOptions ¶ added in v0.15.0
LiveOptions selects which transient classes a runtime's live stream carries. The zero value is the text-only stream SubscribeLivePublic delivers.
IncludeReasoning adds visible reasoning deltas, as SubscribeLivePublicWithReasoning does. IncludeToolSteps adds the public ToolCallStarted and ToolCallCompleted bodies: the tool's redacted audit summary and a capped result preview, never raw tool arguments.
type LiveOptionsSubscriber ¶ added in v0.15.0
type LiveOptionsSubscriber interface {
SubscribeLivePublicWith(context.Context, LiveOptions) (<-chan LivePublication, error)
}
LiveOptionsSubscriber supersedes the LivePublicationSubscriber and ReasoningPublicationSubscriber pair. Host prefers it when a runtime implements it and falls back to the pair otherwise. Tool-step previews require it; a runtime with only the pair streams text and reasoning alone.
type LivePublication ¶ added in v0.12.0
type LivePublication struct {
Enduring *sessionwire.EnduringPublication
Ephemeral *sessionwire.EphemeralPublication
Terminal error
}
LivePublication is one member of a runtime's ordered public event stream. Exactly one publication or terminal cause is set.
type LivePublicationSubscriber ¶ added in v0.12.0
type LivePublicationSubscriber interface {
SubscribeLivePublic(context.Context) (<-chan LivePublication, error)
}
LivePublicationSubscriber is optional. Older runtimes retain the committed subscription above; a runtime implementing this capability supplies both classes from one producer subscription.
type Liveness ¶
type Liveness interface {
Done() <-chan struct{}
}
Liveness reports when the runtime has stopped answering. H4.1's shape: a channel closed once, which any number of drain supervisors can select on, rather than a poll each of them has to run.
type PersistenceFaults ¶ added in v0.8.0
type PersistenceFaults interface {
PersistenceFaulted() <-chan struct{}
PersistenceFault() error
AbandonResidency(context.Context) error
}
PersistenceFaults is a runtime's durable-health capability, and the one way to give up a runtime whose durable log can no longer be trusted. It is harness v0.38.0's session.PersistenceFaultReporter and session.ResidencyAbandoner, together, verbatim.
A RUNTIME THAT HAS LATCHED A PERSISTENCE FAULT IS NOT RESIDENT IN ANY USEFUL SENSE. One failed journal append — a brief database or object-store outage is enough — faults it for good, by design, and it refuses every command after: the next attempt sits `applying` with nothing to close it and the stream behind it never moves (D3). The runtime cannot recover in place, because the failed append may or may not have landed; a successor RESTORING from the journal can. So Host treats PersistenceFaulted closing as loss of residency: it stops the session's work, calls AbandonResidency — crash-equivalent, it writes nothing — and releases the session so a successor restores it and closes the in-flight attempt through the ordinary recovery path.
IT IS OPTIONAL. A runtime without it is never supervised for faults, which is the pre-v0.8.0 behaviour; its composition owns noticing a wedged session. PersistenceFaulted must return the same channel on every call.
type PublicationSubscriber ¶
type PublicationSubscriber interface {
SubscribeCommitted(context.Context, sessionwire.EventID) (<-chan sessionwire.EnduringPublication, error)
}
PublicationSubscriber delivers committed public journal events after the given event, so a subscriber can join the live path with a bounded journal read. Only COMMITTED public events cross this seam.
type ReasoningPublicationSubscriber ¶ added in v0.13.0
type ReasoningPublicationSubscriber interface {
SubscribeLivePublicWithReasoning(context.Context) (<-chan LivePublication, error)
}
ReasoningPublicationSubscriber optionally supplies the same ordered stream with visible reasoning deltas included. The ordinary live subscription keeps its text-only behavior. A runtime must implement both this interface and LivePublicationSubscriber to stream reasoning from Host.
type Recovery ¶ added in v0.16.0
Recovery is the pair of optional runtime capabilities a durable deployment cannot run without. Both are discovered by assertion on the Runtime and neither is part of Runtime, for the reasons AttemptCloser and PersistenceFaults give; this is where a target PROMISES them.
- AttemptCloser: the runtime implements AttemptCloser, so a successor can close a predecessor's stranded attempt. Without it an ordinary failover with an attempt in flight wedges the session's whole command stream.
- PersistenceFaults: the runtime implements PersistenceFaults, so Host notices a latched journal fault and gives the runtime up for a successor to restore. Without it a faulted session stays resident and refuses every command.
type Registration ¶
type Registration struct {
AgentID sessionwire.AgentID
Target LaunchTarget
}
Registration binds one agent identity to the target that launches it.
New takes a SLICE of these rather than a map, and the reason is that a map cannot carry the fault the constructor must reject: duplicate constant keys in a map literal are a compile error, and duplicate computed keys silently last-wins. A map-taking constructor would receive an already-deduplicated input and report a clean Department built from an ambiguous definition — it could never see the duplicate, so it could never reject it. The slice is the smallest input shape that can express the mistake.
type Releaser ¶
Releaser releases RESIDENCY, which is nonterminal: the session remains resumable and no SessionStopped is appended. H4.1's shape, including the name, because the name is the part that carries the distinction.
THE RESULT MUST BE THE TEARDOWN'S OWN OUTCOME, and this applies equally to PersistenceFaults.AbandonResidency. Host calls each at most once per attempt and never "joins" a teardown by calling again, so a runtime that finishes a begun teardown after its caller's context ended must still report how the teardown went — not the caller's deadline. Host reads an error as a failed release; if the runtime's liveness (Done) has closed by then, the teardown is taken to have happened and failed, and is not retried. harnessruntime meets this for harness by joining a begun teardown itself.
type RestoreRequest ¶
type RestoreRequest struct {
TenantID sessionwire.TenantID
SessionID sessionwire.SessionID
AgentID sessionwire.AgentID
Placement sessionwire.HostPlacement
WorkspaceRoot string
Storage StorageContext
// CompatibilityID is the runtime build the durable state was written by. A
// target whose own CompatibilityID differs must refuse the restore.
CompatibilityID CompatibilityID
// RigSessionID is HARNESS'S identity for the session being restored, which
// Host recorded when it created the session and cannot derive: the two
// identity spaces are independent, so a restore that does not carry this
// has nothing to restore FROM. It is a field rather than something the
// adapter reconstructs, because reconstructing it would mean deriving a
// UUID from an opaque sessionwire string, which is the conflation this
// package exists to prevent.
RigSessionID uuid.UUID
}
RestoreRequest is a Host-side relaunch request over existing durable state.
type Rig ¶
type Rig interface {
// NewSession launches a session for a create request.
NewSession(context.Context, RigCreateRequest) (RigSession, error)
// RestoreSession relaunches a session over existing durable state. The UUID
// is Harness's identity for it, which is not Host's; see rigRuntime.
RestoreSession(context.Context, uuid.UUID, RigRestoreRequest) (RigSession, error)
}
Rig is what Host requires of a Harness Rig: something that creates a session and restores one. It is a LOCAL interface, and that is a decision worth its paragraph rather than a shrug.
THIS PACKAGE does not import github.com/looprig/harness, and the reason is a layering one. An earlier version of this paragraph gave a different reason — that "the published harness (v0.30.2) does not have H4.1's capabilities at all — no WaitIdle, no Done, no ReleaseResidency" — and that was already false when internal/harnessadapter (now harnessruntime) bound to harness v0.31.0: session.IdleWaiter, session.Liveness and session.Releaser are exported and are asserted on by name there. It is recorded rather than quietly deleted because a stale justification for a live decision is the failure the paragraph above this one exists to stop.
The standing reason is that the concrete edge is harnessruntime's, not department's: department declares WHAT Host requires of a runtime in Host's own identities, and the adapter is where a released type is made to satisfy it. CLAUDE.md's paragraph on this is the authority — an adapter is mandatory whatever H4.1 names, because Harness identifies a session with a core/uuid.UUID and these identify a runtime with the opaque sessionwire strings Host and Factory exchange. What is narrow here is deliberate — rig.Rig's real NewSession takes variadic SessionOption and returns session.SessionController, and Host wants neither. Host wants a session it can identify and interrogate.
type RigCreateRequest ¶
type RigCreateRequest struct {
TenantID sessionwire.TenantID
SessionID sessionwire.SessionID
AgentID sessionwire.AgentID
Placement sessionwire.HostPlacement
WorkspaceRoot string
Storage StorageContext
// RigSessionID is the Harness identity to launch under; see
// CreateRequest.RigSessionID. A Rig over harness passes it as
// rig.WithSessionID when it is non-zero, and the launched session's ID()
// must equal it or the launch is refused.
RigSessionID uuid.UUID
}
RigCreateRequest is what Host hands a Rig to launch a session. It carries Host's identities and the storage and workspace context the session needs.
type RigLaunchError ¶
type RigLaunchError struct {
AgentID sessionwire.AgentID
SessionID sessionwire.SessionID
Operation string
Cause error
}
RigLaunchError wraps a failure the Rig itself reported, preserving the cause.
It carries no ErrorCode, and neither does IncapableRuntimeError, while CompatibilityMismatchError and UnknownAgentError do. That is deliberate and worth saying, because every other code choice in this file is justified: the two that carry one describe a well-formed request this Host cannot serve, which is a client's business and a thing to branch on. A rig that refused to launch, or one that produced a session missing capabilities, is a HOST DEFECT — a misconfigured target or a broken build — and mapping it to a public code would tell a client to retry something only an operator can fix. Whoever maps these to the wire should choose a code at that seam.
func (*RigLaunchError) Error ¶
func (e *RigLaunchError) Error() string
func (*RigLaunchError) Unwrap ¶
func (e *RigLaunchError) Unwrap() error
type RigRestoreRequest ¶
type RigRestoreRequest struct {
TenantID sessionwire.TenantID
SessionID sessionwire.SessionID
AgentID sessionwire.AgentID
Placement sessionwire.HostPlacement
WorkspaceRoot string
Storage StorageContext
}
RigRestoreRequest is RigCreateRequest for a relaunch over durable state.
type RigSession ¶
RigSession is the minimum a launched session must offer. Everything else Host needs is discovered by type assertion, because Harness's session type is wide and H4.1's capabilities are added to implementations rather than required of them: a session either has them or this package refuses to wrap it.
type Runtime ¶
type Runtime interface {
Identity
IdleWaiter
Liveness
Releaser
PublicationSubscriber
CommandApplier
LeaseEpochReporter
}
Runtime is a live agent runtime, expressed as the composition of the narrow capabilities Host consumes. It is deliberately a composition and not one wide interface: nothing here should have to fake six methods to test one.
LeaseEpochReporter IS REQUIRED RATHER THAN DISCOVERED, and that is a deviation from the sentence on harness's own capability — "a caller MUST treat a false ok as 'this session does not report a lease epoch', not as an error" — so it is argued rather than assumed. That sentence is about the ASSERTION; the conditional half of the capability is the `held` result, which Host does branch on and never treats as an error. harness pins `var _ session.LeaseEpochReporter = (*Session)(nil)` and states there is no configuration under which its Session lacks the METHOD, so requiring it refuses nothing harness produces. What it does refuse is a WRAPPER that fails to forward it — the hazard harness's own doc names, where a wrapped live session is silently opted out and every later epoch read answers a plausible zero. Host already requires Releaser on the same argument, so this is the established rule here and not one invented for it.
type RuntimeCommand ¶
type RuntimeCommand struct {
// CommandID is the public, retry-stable identity.
CommandID sessionwire.CommandID
// RuntimeCommandID is the once-allocated mapping from the winning
// acceptance record. It is what correlates the runtime's own durable effect
// with the command that asked for it, which is why §16 forwards it rather
// than letting each side derive one.
RuntimeCommandID uuid.UUID
// Kind is the command kind the acceptance record carries, opaque here.
Kind string
// Payload and PayloadRef are the private body. At most one is set, and
// neither is set for a command that has none. Under host.Compose only
// Payload is ever set; see the type's doc for why a runtime must refuse a
// PayloadRef rather than resolve it.
Payload []byte
PayloadRef sessionwire.ObjectReference
// AttemptID is the immutable identity of the ONE dispatch the durable
// record authorized, and it is what makes this dispatch settleable.
//
// IT IS THE STORE'S AND IS NEVER MINTED ON THIS SIDE OF THE SEAM.
// BeginDispositionAttempt writes it immutably before any dispatch happens,
// so a runtime's durable disposition can name the attempt it is about and a
// settler can verify the two agree. A value invented here would name an
// attempt no evidence could ever be about.
//
// IT IS A STRING RATHER THAN A TYPED IDENTITY for the reason Kind is: this
// seam is Host's control path into ANY runtime, and typing it would put
// either sessionstore's or harness's vocabulary in department's own. It
// carries harness's grammar — bounded opaque UTF-8 — because both modules
// bound it the same way, and the adapter validates rather than assumes.
//
// EMPTY IS A LEGITIMATE VALUE AND MEANS NO ATTEMPT WAS AUTHORIZED. harness
// writes no disposition frame for one, by design, leaving a legacy
// journal's bytes unchanged. A Host driving the disposition family always
// sets it; a caller that does not is asking for the legacy behaviour and
// gets it.
AttemptID string
// Principal is the Factory-stamped sender read strictly before the attempt.
// It is a recorded assertion, never an authorization decision by Host.
Principal *sessionwire.Principal
// Metadata is the client's message field bag on create and input only.
Metadata sessionwire.MessageMetadata
}
RuntimeCommand is one admitted command as Host hands it to a runtime.
IT IS NOT sessionwire.CommandEnvelope, AND THAT WAS A DEFECT RATHER THAN A CHOICE. This seam took the envelope, which released Core defines as exactly a wire version and a public CommandID — so the two things a runtime needs in order to DO anything never crossed it. §16 requires Host to forward the RuntimeCommandID into the Harness API and H3.1 states the same contract from the other side, and the command's substance could not cross either: the inbox payload is private to Factory and Host, and SessionStore imports only Core and storage, so a runtime handed a bare CommandID has no way to obtain the blocks it is being asked to apply. An input command applied across that seam was a durable no-op.
Payload is the verified private body. The disposition applier resolves a body stored by reference through the session's control store (size, SHA-256, kind and scope checked) before the attempt, so a runtime launched by host.Compose always receives bytes and never dereferences an object. PayloadRef is set only by a composition that gave the applier no object reader (legacy commands.Applier); a runtime MUST refuse such a command rather than resolve it, as harnessruntime does.
The optional principal and metadata are the only decoded values crossing this seam. The private body still travels unchanged.
type StorageContext ¶
type StorageContext struct {
// Namespace is the durable prefix the session's objects live under. It is
// opaque to this package and is not parsed.
Namespace string
}
StorageContext scopes every durable object a launched session writes.
It is a struct with one field rather than a bare string because the thing being passed is a CONTEXT, and the next thing it needs — a retention class, a provider hint — is a field here rather than a second parameter threaded through every signature between Host and the Rig.
type UnknownAgentError ¶
type UnknownAgentError struct {
AgentID sessionwire.AgentID
}
UnknownAgentError reports a lookup for an agent this Department does not register.
It carries sessionwire.ErrorCodeRuntimeUnavailable rather than an invalid-request code, and the distinction is the point: the request was well formed, and this Host simply has no runtime for that agent. A client branches on the code, and telling it its request was malformed sends it to fix something that is not broken.
func (*UnknownAgentError) Error ¶
func (e *UnknownAgentError) Error() string
func (*UnknownAgentError) ErrorCode ¶
func (e *UnknownAgentError) ErrorCode() sessionwire.ErrorCode
ErrorCode returns the stable public code a client branches on.
Directories
¶
| Path | Synopsis |
|---|---|
|
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. |