Documentation / guides
Runtime Model Selection
Describe model selection through loop.Handle and loop.Controller for a live loop runtime.
There are two separate selection layers:
- A parent composition resolves a catalog row into a bound runtime tuple.
- A live loop controller applies a validated model or effort change at a turn boundary.
The live read surface is:
type Handle interface {
ID() uuid.UUID
Mode() ModeName
Model() model.Model
}
func ChangeModel(model.Model) Change
func ChangeEffort(model.Effort) Change
Handle.Model() is read-only by convention and returns a model value. A
Controller embeds Handle and accepts one or more sealed Change values.
Catalog selection
RuntimeCatalog is parent-scoped and immutable. Resolve uses empty harness,
alias, and effort selectors as deterministic defaults; explicit selectors are
checked only inside the selected/default harness. ResolveWithExplicitEffort
distinguishes omitted effort from an explicit model.EffortNone. A managed
native entry has no concrete model row and returns
RuntimeSelectionHarnessManaged.
resolved, err := catalog.ResolveWithExplicitSource(
identity.AgentName("worker"),
loop.AgentHarnessName("claude"),
loop.RuntimeSourceGateway,
loop.ModelAlias("balanced"),
effort, // model.Effort selected by the caller.
true,
)
if err != nil {
var catalogErr *loop.RuntimeCatalogError
if errors.As(err, &catalogErr) {
log.Println(catalogErr.Kind)
}
return err
}
The resolved tuple carries both the model-facing alias and the concrete target
alias used by a gateway/launcher. The target descriptor is a defensive clone.
RuntimeIdentity.Digest excludes endpoints and credentials, and managed
selection excludes model and effort identity.
Live change
Controller validation is whole-batch and atomic. A model change validates the model, key, transport membership, and supported effort; an effort change checks the currently selected model. If any item is invalid, no item is applied and no durable change event is emitted. A successful change is persisted before the new setting becomes visible.
if err := liveLoop.Change(ctx,
loop.ChangeModel(candidate),
loop.ChangeEffort(model.EffortHigh),
); err != nil {
var changeErr *loop.ChangeError
if errors.As(err, &changeErr) {
log.Printf("change refused: %s", changeErr.Kind)
}
return err
}
fmt.Println(liveLoop.Model().Name)
%%{init: {"theme":"dark"}}%%
sequenceDiagram
participant C as Controller
participant A as loop actor
participant J as durable journal
C->>A: Change(model, effort)
A->>A: validate whole batch and transport membership
alt invalid or shutting down
A-->>C: ChangeError, no state change
else valid
A->>J: append enduring change
J-->>A: acknowledgement
A-->>C: success at turn boundary
end