Documentation
¶
Overview ¶
Package loop implements persistent goroutine-based delegate loops — lightweight autonomous observers that run continuously alongside the main agent, with direct completion delivery or ordinary tool use for any durable artifacts they need to maintain.
Loops are the universal primitive replacing previously-separate systems (metacognitive loop, observers) with a single Registry.SpawnLoop abstraction. Each loop is a background goroutine that iterates on a randomized bounded sleep schedule, running LLM iterations via the agent runner and optionally delivering results back through a completion path.
A Registry tracks all active loops and provides visibility into what is running, their health, and resource usage. It enforces concurrency limits and coordinates graceful shutdown.
See issue #509 for the full design.
Capability tag lifecycle ¶
Capability tags scope the tool surface, KB articles, talents, and other context a loop iteration sees. The same conceptual value flows through several fields at different stages of a loop's life. They are not redundant — each represents a distinct point on the lifecycle — but in isolation any one of them looks like it does the same job as the others. This map exists so per-field Godoc can stay short and reference here rather than re-explaining the chain.
Spec.Tags declarative, persisted with the spec
│
│ Spec.profileRequest()
▼
requestBase.InitialTags base iteration tags
│
│ + Launch.InitialTags per-invocation runtime override
│ + activatedTags carried from prior iterations'
│ Response.ActiveTags
▼
loop.Request.InitialTags merged set per iteration
│
│ loopAdapter translates to agent.Request
▼
agent.Request.InitialTags seed for the capability scope
│
│ scope.Request(tag) per tag
▼
scope.Snapshot() active tags during the run
│
│ end-of-run capture
▼
agent.Response.ActiveTags fed back into activatedTags
Each layer in plain English:
[Spec.Tags] is the spec-level declaration. Operators editing a loop definition set this field; it persists with the spec. Activated at iteration 0 of any run from this spec, and remains active through the loop's lifetime unless the model deactivates a tag mid-run.
[Launch.InitialTags] is a per-invocation runtime override. Launchers that scope a particular run differently from the spec — the delegate executor, MQTT wake dispatch, programmatic spawners — set it here. Does not persist with the spec.
[Request.InitialTags] (this package's Request) is the merged set the loop hands to its runner each iteration. The merge happens in the loop's request-build path and combines requestBase.InitialTags, requestOverride.InitialTags, and the loop's running activatedTags state.
agent.Request.InitialTags receives the merged set across the loop/agent boundary and becomes the capability scope's seed. Lives in the [agent] package, not here.
agent.Request.RuntimeTags is distinct from InitialTags despite the similar shape. RuntimeTags are trusted runtime-asserted tags the model cannot deactivate within the run; they're pinned via scope.PinChannelTags rather than scope.Request. Set by trusted callers (the Signal bridge pins "message_channel"). Use when a tag must remain active regardless of model behavior.
agent.Response.ActiveTags is the end-of-run snapshot. The loop captures it into activatedTags for the next iteration's merge, which is how a tag activated mid-run by the model survives into iteration N+1.
[Request.SkipTagFilter] (and the matching agent.Request field) bypass the scope filter entirely. Used by self-scoping contexts like the metacognitive loop. Empty Spec.Tags + a SkipTagFilter request together signal "this loop does not participate in tag filtering."
When adding or modifying tag-related fields, anchor your work against this map rather than re-explaining the chain in each field's Godoc. If you find yourself writing "but if FieldB is also set, this is ignored" or "see FieldC for the related case" in a field comment, that's a sign the field belongs in this lifecycle document instead.
Index ¶
- Constants
- Variables
- func FallbackContent(ctx context.Context) string
- func Float64Ptr(v float64) *float64
- func IterationSummary(ctx context.Context) map[string]any
- func LoopIDFromContext(ctx context.Context) string
- func NotifyEnvelopesFromContext(ctx context.Context) []messages.Envelope
- func ProgressFunc(ctx context.Context) func(string, map[string]any)
- func ReportAgentRun(ctx context.Context, s AgentRunSummary) map[string]any
- func ReportConversationID(ctx context.Context, conversationID string) map[string]any
- func SpawnDemoLoops(ctx context.Context, registry *Registry, eventBus *events.Bus, ...) error
- type AgentRunSummary
- type Completion
- type CompletionChannelTarget
- type CompletionDelivery
- type CompletionSink
- type Conditions
- type Config
- type DefinitionEligibilityStatus
- type DefinitionPolicy
- type DefinitionPolicySource
- type DefinitionPolicyState
- type DefinitionRecord
- type DefinitionRegistry
- func (r *DefinitionRegistry) ApplyPolicy(name string, policy DefinitionPolicy, updatedAt time.Time) error
- func (r *DefinitionRegistry) ClearPolicy(name string, updatedAt time.Time) error
- func (r *DefinitionRegistry) Delete(name string, updatedAt time.Time) error
- func (r *DefinitionRegistry) Get(name string) (Spec, bool)
- func (r *DefinitionRegistry) ReplaceOverlay(records map[string]DefinitionRecord) error
- func (r *DefinitionRegistry) ReplacePolicies(policies map[string]DefinitionPolicy, updatedAt time.Time) error
- func (r *DefinitionRegistry) Snapshot() *DefinitionRegistrySnapshot
- func (r *DefinitionRegistry) Upsert(spec Spec, updatedAt time.Time) error
- type DefinitionRegistrySnapshot
- type DefinitionRegistryView
- type DefinitionRuntimeStatus
- type DefinitionSnapshot
- type DefinitionSource
- type DefinitionView
- type DefinitionWarning
- type Deps
- type ImmutableDefinitionError
- type InactiveDefinitionError
- type IneligibleDefinitionError
- type IterationResult
- type IterationSnapshot
- type Launch
- type LaunchResult
- type Loop
- func (l *Loop) CurrentConvID() string
- func (l *Loop) Done() <-chan struct{}
- func (l *Loop) ID() string
- func (l *Loop) Name() string
- func (l *Loop) SetActiveTagsFunc(fn func() []string)
- func (l *Loop) SetNextSleep(d time.Duration)
- func (l *Loop) Start(ctx context.Context) error
- func (l *Loop) Status() Status
- func (l *Loop) Stop()
- type Message
- type NotifyReceipt
- type Operation
- type OutputContextBuilder
- type OutputMode
- type OutputSpec
- type OutputType
- type PausedDefinitionError
- type RandSource
- type Registry
- func (r *Registry) ActiveCount() int
- func (r *Registry) Deregister(id string)
- func (r *Registry) FindByName(name string) []*Loop
- func (r *Registry) Get(id string) *Loop
- func (r *Registry) GetByName(name string) *Loop
- func (r *Registry) Launch(ctx context.Context, launch Launch, deps Deps) (LaunchResult, error)
- func (r *Registry) List() []*Loop
- func (r *Registry) MaxLoops() int
- func (r *Registry) NotifyLoop(ctx context.Context, id string, env messages.Envelope) (NotifyReceipt, error)
- func (r *Registry) NotifyLoopByName(ctx context.Context, name string, env messages.Envelope) (NotifyReceipt, error)
- func (r *Registry) Register(l *Loop) error
- func (r *Registry) ShutdownAll(ctx context.Context) int
- func (r *Registry) SpawnLoop(ctx context.Context, cfg Config, deps Deps) (string, error)
- func (r *Registry) SpawnSpec(ctx context.Context, spec Spec, deps Deps) (string, error)
- func (r *Registry) Statuses() []Status
- func (r *Registry) StopLoop(id string) error
- func (r *Registry) StopLoopByName(name string) error
- type RegistryOption
- type Request
- type Response
- type RetriggerMode
- type RunMessage
- type RunRequest
- type RunResponse
- type Runner
- type RuntimeTool
- type ScheduleCondition
- type ScheduleWindow
- type Spec
- type State
- type Status
- type StreamCallback
- type ToolingState
- type UnknownDefinitionError
Constants ¶
const ( DefaultSleepMin = 30 * time.Second DefaultSleepMax = 5 * time.Minute DefaultSleepDefault = 1 * time.Minute DefaultJitter = 0.2 DefaultSupervisorProb = 0.1 )
Default configuration values. Exported so callers can reference them when building Config values without memorizing magic numbers.
Variables ¶
var ErrLoopStopped = errors.New("loop: cannot start a stopped loop")
ErrLoopStopped is returned by Loop.Start when the loop has already been stopped. A stopped loop cannot be restarted.
var ErrNilRunner = errors.New("loop: Runner is required")
ErrNilRunner is returned by New when Deps.Runner is nil.
var ErrNoOp = errors.New("loop: no-op iteration")
ErrNoOp is returned by a [Config.Handler] to signal that the iteration ran but produced no meaningful work (e.g., all received events were filtered out). When returned, the loop never transitions to StateProcessing, never publishes events.KindLoopIterationStart, and skips all post-iteration accounting (snapshot, attempt count, recentConvIDs) — so the dashboard activity indicator stays quiet. ErrNoOp is not counted as an error and does not increment [Status.ConsecutiveErrors].
Functions ¶
func FallbackContent ¶
FallbackContent returns the loop-configured response fallback from a handler context. Handler-backed interactive loops can pass this through to nested agent.Run calls and use it as a last-resort post-run reply.
func Float64Ptr ¶
Float64Ptr returns a pointer to v. Use it to set optional *float64 config fields like [Config.Jitter]:
Config{Jitter: loop.Float64Ptr(0.3)} // custom jitter
Config{Jitter: loop.Float64Ptr(0)} // explicitly no jitter
Config{} // nil → DefaultJitter
func IterationSummary ¶
IterationSummary returns the summary map for the current iteration from the handler context, or nil if not available. Handler implementations call this to report per-iteration metrics to the dashboard timeline. Values should be small scalars (int, string, bool) to keep SSE payloads compact.
func LoopIDFromContext ¶
LoopIDFromContext extracts the originating loop ID from a handler context. Returns an empty string if the context was not created by a loop handler dispatch.
func NotifyEnvelopesFromContext ¶
NotifyEnvelopesFromContext returns one-shot message envelopes delivered to the current loop iteration, if any.
func ProgressFunc ¶
ProgressFunc returns the loop's progress callback from the handler context, or nil if not available. Handler implementations that dispatch LLM calls can use this to forward in-flight events (tool calls, LLM responses) to the event bus for dashboard visibility.
The returned function has signature func(kind string, data map[string]any) where kind is an events.KindLoop* constant and data carries the event payload.
func ReportAgentRun ¶
func ReportAgentRun(ctx context.Context, s AgentRunSummary) map[string]any
ReportAgentRun writes standard agent-run metrics into the current iteration's summary map. It is the canonical way for handler-only loops that call runner.Run() to surface request_id, model, and token counts on the dashboard.
Callers may add additional custom fields to the summary map after this call (e.g., sender, message_len).
Returns the summary map for chaining, or nil if the context has no iteration summary (e.g., called outside a loop handler).
func ReportConversationID ¶
ReportConversationID overrides the loop-visible conversation ID for the current handler iteration. Handler-only loops normally generate an internal conversation ID before dispatch; handlers that proxy a nested agent.Run can call this so the dashboard timeline and log lookups follow the real child conversation instead.
func SpawnDemoLoops ¶
func SpawnDemoLoops(ctx context.Context, registry *Registry, eventBus *events.Bus, logger *slog.Logger) error
SpawnDemoLoops creates simulated loops covering all visual categories, parent/child hierarchies, error states, and node churn. Intended for dashboard layout iteration without real service dependencies.
Types ¶
type AgentRunSummary ¶
type AgentRunSummary struct {
RequestID string
Model string
InputTokens int
OutputTokens int
ActiveTags []string
EffectiveTools []string
LoadedCapabilities []toolcatalog.LoadedCapabilityEntry
}
AgentRunSummary holds the subset of agent response fields that handler-only loops report to the dashboard timeline. It exists in the loop package (rather than accepting an agent.Response directly) to avoid an import cycle — agent imports loop, not the other way around.
type Completion ¶
type Completion string
Completion describes how a loop's result should be delivered. The zero value is accepted while loops-ng adoption is incremental.
const ( // CompletionReturn delivers the result directly to the caller. CompletionReturn Completion = "return" // CompletionConversation injects the result into a conversation. CompletionConversation Completion = "conversation" // CompletionChannel delivers the result to a channel integration. CompletionChannel Completion = "channel" // CompletionNone means the loop has no outward completion delivery. CompletionNone Completion = "none" )
type CompletionChannelTarget ¶
type CompletionChannelTarget struct {
Channel string `yaml:"channel,omitempty" json:"channel,omitempty"`
Recipient string `yaml:"recipient,omitempty" json:"recipient,omitempty"`
ConversationID string `yaml:"conversation_id,omitempty" json:"conversation_id,omitempty"`
}
CompletionChannelTarget identifies a concrete interactive channel target for a detached loop completion. This keeps transport selection in the app layer while giving launches and deliveries an honest, structured contract for channel-shaped results.
func CloneCompletionChannelTarget ¶
func CloneCompletionChannelTarget(t *CompletionChannelTarget) *CompletionChannelTarget
CloneCompletionChannelTarget returns a shallow copy of the target so callers can safely retain it without sharing mutable state.
func (*CompletionChannelTarget) Validate ¶
func (t *CompletionChannelTarget) Validate() error
Validate checks that the channel target is well formed.
type CompletionDelivery ¶
type CompletionDelivery struct {
Mode Completion `yaml:"mode,omitempty" json:"mode"`
ConversationID string `yaml:"conversation_id,omitempty" json:"conversation_id,omitempty"`
Channel *CompletionChannelTarget `yaml:"channel,omitempty" json:"channel,omitempty"`
Content string `yaml:"content,omitempty" json:"content"`
LoopID string `yaml:"loop_id,omitempty" json:"loop_id"`
LoopName string `yaml:"loop_name,omitempty" json:"loop_name"`
Response *Response `yaml:"response,omitempty" json:"response,omitempty"`
Status *Status `yaml:"status,omitempty" json:"status,omitempty"`
}
CompletionDelivery is the normalized completion payload emitted by detached loops when they deliver a result through a non-return path.
type CompletionSink ¶
type CompletionSink func(ctx context.Context, delivery CompletionDelivery) error
CompletionSink receives detached loop completion deliveries. The app layer wires concrete sinks (e.g. conversation injection) so the loop package can stay free of channel and memory dependencies.
type Conditions ¶
type Conditions struct {
// Schedule constrains when the definition is currently eligible for
// runtime use. When unset, the definition is always eligible unless
// blocked by policy.
Schedule *ScheduleCondition `yaml:"schedule,omitempty" json:"schedule,omitempty"`
}
Conditions describes optional runtime eligibility gates for a loop definition. The first supported condition family is schedule-based, and additional condition families can be added here over time without changing the surrounding loops-ng contract shape.
func (Conditions) Evaluate ¶
func (c Conditions) Evaluate(now time.Time) DefinitionEligibilityStatus
Evaluate returns the effective eligibility of the conditions at the provided time.
func (Conditions) Validate ¶
func (c Conditions) Validate() error
Validate checks that all configured runtime conditions are structurally valid.
type Config ¶
type Config struct {
// Name is the unique identifier for this loop. Required.
Name string
// Task is the LLM prompt for each iteration. It describes what
// the loop should observe, check, or do on each wake.
Task string
// Operation describes the runtime pattern expected for the loop.
Operation Operation
// Completion describes how results should be delivered back to a
// caller, conversation, or channel.
Completion Completion
// Outputs declare durable documents this loop is allowed to
// maintain through scoped runtime tools.
Outputs []OutputSpec
// Tags are capability tags for tool scoping. When non-empty,
// the loop's tool registry is filtered to tools matching these
// tags (plus always-active tags).
Tags []string
// ExcludeTools lists tool names to exclude from the loop's
// available tools.
ExcludeTools []string
// SleepMin is the minimum sleep duration between iterations.
// Default: 30s.
SleepMin time.Duration
// SleepMax is the maximum sleep duration between iterations.
// Default: 5m.
SleepMax time.Duration
// SleepDefault is the initial sleep duration before the loop
// self-adjusts. Default: 1m.
SleepDefault time.Duration
// Jitter is the randomization factor applied to sleep durations
// to break periodicity. Range [0.0, 1.0]. Nil is defaulted to
// DefaultJitter (0.2) by applyDefaults. Use Float64Ptr(0) to
// explicitly disable jitter.
Jitter *float64
// MaxDuration is the maximum wall-clock time the loop may run.
// Zero means unlimited.
MaxDuration time.Duration
// MaxIter is the maximum number of iteration attempts the loop
// may make (including failures). Zero means unlimited.
MaxIter int
// Supervisor enables frontier model dice rolls. When true, a
// fraction of iterations (controlled by SupervisorProb) use a
// more capable model for oversight.
Supervisor bool
// SupervisorProb is the probability [0.0, 1.0] that a given
// iteration uses the supervisor model. Only meaningful when
// Supervisor is true. Zero means never (use DefaultSupervisorProb
// for the recommended default).
SupervisorProb float64
// QualityFloor is the minimum model quality rating for normal
// iterations. Zero uses the router default.
QualityFloor int
// SupervisorContext is an optional prompt prepended to the Task
// during supervisor iterations. Use it to give the frontier model
// review instructions, recent iteration summaries, or oversight
// criteria. Empty means supervisor runs the same Task as normal.
SupervisorContext string
// SupervisorQualityFloor is the minimum model quality rating
// for supervisor iterations. Zero uses the router default.
SupervisorQualityFloor int
// OnRetrigger determines behavior when the loop's start
// condition fires again while running. Default: RetriggerSingle.
OnRetrigger RetriggerMode
// TaskBuilder is called per-iteration to generate a dynamic prompt.
// When set, the static Task field is ignored. The isSupervisor
// argument indicates whether this is a supervisor iteration.
TaskBuilder func(ctx context.Context, isSupervisor bool) (string, error) `json:"-"`
// PostIterate is called after each successful iteration. Use it
// for side effects like appending iteration logs. Errors are
// logged but do not count as iteration failures.
PostIterate func(ctx context.Context, result IterationResult) error `json:"-"`
// WaitFunc blocks until an external event arrives. When set, the
// loop enters [StateWaiting] and calls WaitFunc instead of
// sleeping between iterations. The returned value is passed to
// Handler (if set) or discarded for LLM-based loops. If WaitFunc
// returns a non-context error, the loop treats it as an iteration
// error (backoff + retry).
//
// A (nil, nil) return is treated as a no-op wake: the loop skips
// the processing phase entirely and re-enters the wait state
// without counting an iteration. This means nil is a reserved
// sentinel payload. Implementations that need to deliver a
// meaningful event with no associated data should return a non-nil
// sentinel (e.g. a zero-value struct) instead of nil.
WaitFunc func(ctx context.Context) (any, error) `json:"-"`
// Handler processes each iteration directly without an LLM call.
// When set, [Deps].Runner is not required. Receives the event
// from WaitFunc (nil for timer-triggered loops). Handler-only
// loops still track iterations, errors, and health.
//
// Return [ErrNoOp] to signal that the iteration produced no
// meaningful work (e.g., all events were filtered). The loop
// skips iteration accounting and continues to the next cycle.
Handler func(ctx context.Context, event any) error `json:"-"`
// Hints are merged into Request hints for each iteration.
// Config hints override loop-generated defaults (e.g., setting
// "source" to "metacognitive" instead of "loop").
Hints map[string]string
// FallbackContent is static text used when the loop's nested agent run
// or direct request/reply execution finishes without any user-visible
// content. Interactive loops can set this to guarantee a reply.
FallbackContent string
// Setup is called by [Registry.SpawnLoop] after [New] but before
// [Loop.Start]. Use it to register tools or perform other setup
// that requires a *Loop reference before the goroutine launches.
Setup func(l *Loop) `json:"-"`
// RuntimeTools are request-scoped tools attached during hydration.
RuntimeTools []RuntimeTool `json:"-"`
// OutputContextBuilder renders model-facing context for [Outputs].
OutputContextBuilder OutputContextBuilder `json:"-"`
// Metadata holds arbitrary key/value pairs for the loop.
Metadata map[string]string
// ParentID is the loop ID of the parent that spawned this loop,
// if any. Empty for top-level loops.
ParentID string
}
Config holds the configuration for a loop. All fields with zero values use sensible defaults.
type DefinitionEligibilityStatus ¶
type DefinitionEligibilityStatus struct {
// Eligible reports whether the definition is currently eligible to
// run or launch, before policy and operation-specific logic are
// applied.
Eligible bool `yaml:"eligible" json:"eligible"`
// Reason describes why the definition is currently ineligible, when
// known. Empty means either eligible or no explanatory detail.
Reason string `yaml:"reason,omitempty" json:"reason,omitempty"`
// NextTransitionAt is the next time the eligibility result is
// expected to change, such as the next schedule boundary.
NextTransitionAt time.Time `yaml:"next_transition_at,omitempty" json:"next_transition_at,omitempty"`
}
DefinitionEligibilityStatus is the effective runtime eligibility for one stored loop definition at a point in time.
type DefinitionPolicy ¶
type DefinitionPolicy struct {
State DefinitionPolicyState `yaml:"state,omitempty" json:"state,omitempty"`
Reason string `yaml:"reason,omitempty" json:"reason,omitempty"`
UpdatedAt time.Time `yaml:"updated_at,omitempty" json:"updated_at,omitempty"`
}
DefinitionPolicy is the mutable runtime policy overlay for one loop definition.
type DefinitionPolicySource ¶
type DefinitionPolicySource string
DefinitionPolicySource reports whether the effective runtime state comes from the definition's default baseline or an explicit overlay.
const ( // DefinitionPolicySourceDefault means the effective state is derived // from the definition's own baseline fields. DefinitionPolicySourceDefault DefinitionPolicySource = "default" // DefinitionPolicySourceOverlay means the effective state is driven // by a persisted runtime override. DefinitionPolicySourceOverlay DefinitionPolicySource = "overlay" )
type DefinitionPolicyState ¶
type DefinitionPolicyState string
DefinitionPolicyState describes the effective runtime state of a stored loop definition.
const ( // DefinitionPolicyStateActive means the definition is eligible for // runtime use. Service definitions may auto-start or be launched. DefinitionPolicyStateActive DefinitionPolicyState = "active" // DefinitionPolicyStatePaused means the definition is temporarily // held out of runtime execution. Existing service loops should be // stopped, but the definition remains retained for future resume. DefinitionPolicyStatePaused DefinitionPolicyState = "paused" // DefinitionPolicyStateInactive means the definition is disabled for // runtime use. Existing service loops should be stopped. DefinitionPolicyStateInactive DefinitionPolicyState = "inactive" )
func ParseDefinitionPolicyState ¶
func ParseDefinitionPolicyState(raw string) (DefinitionPolicyState, error)
ParseDefinitionPolicyState validates a caller-provided definition policy state.
type DefinitionRecord ¶
type DefinitionRecord struct {
Spec Spec `yaml:"spec,omitempty" json:"spec,omitempty"`
UpdatedAt time.Time `yaml:"updated_at,omitempty" json:"updated_at,omitempty"`
}
DefinitionRecord is one persistable loop definition plus its update timestamp. It is the durable unit stored in the dynamic overlay.
type DefinitionRegistry ¶
type DefinitionRegistry struct {
// contains filtered or unexported fields
}
DefinitionRegistry holds the immutable config-defined loop definitions plus a mutable persistent overlay for dynamically created definitions. It does not track active loop runs; that remains Registry's job.
func NewDefinitionRegistry ¶
func NewDefinitionRegistry(base []Spec) (*DefinitionRegistry, error)
NewDefinitionRegistry constructs a loop-definition registry from the immutable config-defined base definitions.
func (*DefinitionRegistry) ApplyPolicy ¶
func (r *DefinitionRegistry) ApplyPolicy(name string, policy DefinitionPolicy, updatedAt time.Time) error
ApplyPolicy upserts a runtime policy override for one stored loop definition.
func (*DefinitionRegistry) ClearPolicy ¶
func (r *DefinitionRegistry) ClearPolicy(name string, updatedAt time.Time) error
ClearPolicy removes an explicit runtime policy override for one loop definition and returns it to its default state.
func (*DefinitionRegistry) Delete ¶
func (r *DefinitionRegistry) Delete(name string, updatedAt time.Time) error
Delete removes one dynamic loop definition from the overlay.
func (*DefinitionRegistry) Get ¶
func (r *DefinitionRegistry) Get(name string) (Spec, bool)
Get returns the effective definition with the given name.
func (*DefinitionRegistry) ReplaceOverlay ¶
func (r *DefinitionRegistry) ReplaceOverlay(records map[string]DefinitionRecord) error
ReplaceOverlay replaces the entire dynamic overlay. It is intended for startup-time hydration from persistent state.
func (*DefinitionRegistry) ReplacePolicies ¶
func (r *DefinitionRegistry) ReplacePolicies(policies map[string]DefinitionPolicy, updatedAt time.Time) error
ReplacePolicies swaps the explicit runtime policy overlay with the provided policy map during startup-time hydration.
func (*DefinitionRegistry) Snapshot ¶
func (r *DefinitionRegistry) Snapshot() *DefinitionRegistrySnapshot
Snapshot returns a read-only snapshot of the effective loop definitions, sorted by name.
type DefinitionRegistrySnapshot ¶
type DefinitionRegistrySnapshot struct {
Generation int64 `yaml:"generation,omitempty" json:"generation"`
UpdatedAt time.Time `yaml:"updated_at,omitempty" json:"updated_at,omitempty"`
ConfigDefinitions int `yaml:"config_definitions,omitempty" json:"config_definitions"`
OverlayDefinitions int `yaml:"overlay_definitions,omitempty" json:"overlay_definitions"`
Definitions []DefinitionSnapshot `yaml:"definitions,omitempty" json:"definitions,omitempty"`
}
DefinitionRegistrySnapshot is a read-only snapshot of the effective loop definition registry.
type DefinitionRegistryView ¶
type DefinitionRegistryView struct {
Generation int64 `yaml:"generation,omitempty" json:"generation"`
UpdatedAt time.Time `yaml:"updated_at,omitempty" json:"updated_at,omitempty"`
ConfigDefinitions int `yaml:"config_definitions,omitempty" json:"config_definitions"`
OverlayDefinitions int `yaml:"overlay_definitions,omitempty" json:"overlay_definitions"`
RunningDefinitions int `yaml:"running_definitions,omitempty" json:"running_definitions"`
DefinitionsWithWarnings int `yaml:"definitions_with_warnings,omitempty" json:"definitions_with_warnings,omitempty"`
WarningCount int `yaml:"warning_count,omitempty" json:"warning_count,omitempty"`
ByPolicyState map[string]int `yaml:"by_policy_state,omitempty" json:"by_policy_state,omitempty"`
ByEligibilityState map[string]int `yaml:"by_eligibility_state,omitempty" json:"by_eligibility_state,omitempty"`
ByRuntimeState map[string]int `yaml:"by_runtime_state,omitempty" json:"by_runtime_state,omitempty"`
Definitions []DefinitionView `yaml:"definitions,omitempty" json:"definitions,omitempty"`
}
DefinitionRegistryView is the effective combined view of stored loop definitions plus their current live runtime state.
func BuildDefinitionRegistryView ¶
func BuildDefinitionRegistryView(snapshot *DefinitionRegistrySnapshot, runtime map[string]DefinitionRuntimeStatus) *DefinitionRegistryView
BuildDefinitionRegistryView combines the durable definition snapshot with an optional runtime-state map to produce the effective loops-ng registry view used by API and tool read surfaces.
type DefinitionRuntimeStatus ¶
type DefinitionRuntimeStatus struct {
// Running reports whether a live loop instance currently exists for
// this definition.
Running bool `yaml:"running" json:"running"`
// LoopID is the backing live loop instance, when one is present.
LoopID string `yaml:"loop_id,omitempty" json:"loop_id,omitempty"`
// State is the current runtime lifecycle state of the backing loop.
State State `yaml:"state,omitempty" json:"state,omitempty"`
// StartedAt is when the current backing loop instance started.
StartedAt time.Time `yaml:"started_at,omitempty" json:"started_at,omitempty"`
// LastWakeAt is when the current backing loop most recently began an
// iteration.
LastWakeAt time.Time `yaml:"last_wake_at,omitempty" json:"last_wake_at,omitempty"`
// Iterations is the number of successful iterations completed by the
// current backing loop instance.
Iterations int `yaml:"iterations,omitempty" json:"iterations,omitempty"`
// Attempts is the number of total iteration attempts completed by the
// current backing loop instance.
Attempts int `yaml:"attempts,omitempty" json:"attempts,omitempty"`
// LastError is the most recent runtime error from the current backing
// loop instance.
LastError string `yaml:"last_error,omitempty" json:"last_error,omitempty"`
}
DefinitionRuntimeStatus summarizes the live runtime state currently associated with one stored loop definition.
type DefinitionSnapshot ¶
type DefinitionSnapshot struct {
Name string `yaml:"name,omitempty" json:"name"`
Source DefinitionSource `yaml:"source,omitempty" json:"source"`
UpdatedAt time.Time `yaml:"updated_at,omitempty" json:"updated_at,omitempty"`
PolicyState DefinitionPolicyState `yaml:"policy_state,omitempty" json:"policy_state,omitempty"`
PolicySource DefinitionPolicySource `yaml:"policy_source,omitempty" json:"policy_source,omitempty"`
PolicyReason string `yaml:"policy_reason,omitempty" json:"policy_reason,omitempty"`
PolicyUpdatedAt time.Time `yaml:"policy_updated_at,omitempty" json:"policy_updated_at,omitempty"`
Spec Spec `yaml:"spec,omitempty" json:"spec,omitempty"`
}
DefinitionSnapshot is the API-facing state for one effective loop definition.
type DefinitionSource ¶
type DefinitionSource string
DefinitionSource identifies where a loop definition came from.
const ( // DefinitionSourceConfig means the definition came from immutable // config-file input. DefinitionSourceConfig DefinitionSource = "config" // DefinitionSourceOverlay means the definition came from the mutable // persistent overlay. DefinitionSourceOverlay DefinitionSource = "overlay" )
type DefinitionView ¶
type DefinitionView struct {
DefinitionSnapshot `yaml:",inline"`
Eligibility DefinitionEligibilityStatus `yaml:"eligibility,omitempty" json:"eligibility"`
Runtime DefinitionRuntimeStatus `yaml:"runtime,omitempty" json:"runtime"`
Warnings []DefinitionWarning `yaml:"warnings,omitempty" json:"warnings,omitempty"`
}
DefinitionView is the combined stored-definition and live-runtime view exposed by loops-ng read surfaces.
type DefinitionWarning ¶
type DefinitionWarning struct {
Code string `yaml:"code" json:"code"`
Message string `yaml:"message" json:"message"`
}
DefinitionWarning is a non-fatal authoring concern discovered while inspecting a persistable loops-ng definition. Warnings are surfaced through definition views and lint tooling so the model can correct likely mistakes before they become noisy runtime behavior.
func BuildDefinitionWarnings ¶
func BuildDefinitionWarnings(spec Spec) []DefinitionWarning
BuildDefinitionWarnings returns authoring warnings for one loop spec. These warnings are advisory: the spec may still validate and persist.
type Deps ¶
type Deps struct {
// Runner executes LLM iterations. Required unless [Config].Handler
// is set (Handler-only loops do not call the Runner).
Runner Runner
// CompletionSink receives detached completion deliveries such as
// background-task results injected into conversations.
CompletionSink CompletionSink
// Logger for loop operations. Defaults to slog.Default().
Logger *slog.Logger
// EventBus publishes loop lifecycle events. Nil disables events.
EventBus *events.Bus
// Rand provides randomness for sleep jitter and supervisor dice.
// Nil uses math/rand/v2 default.
Rand RandSource
}
Deps holds injected dependencies for a loop. Using a struct avoids a growing parameter list as loops evolve.
type ImmutableDefinitionError ¶
type ImmutableDefinitionError struct {
Name string
}
ImmutableDefinitionError reports an attempted mutation of an immutable config-defined loop definition.
func (*ImmutableDefinitionError) Error ¶
func (e *ImmutableDefinitionError) Error() string
type InactiveDefinitionError ¶
type InactiveDefinitionError struct {
Name string
}
InactiveDefinitionError reports that a loop definition exists but is currently disabled by effective runtime policy.
func (*InactiveDefinitionError) Error ¶
func (e *InactiveDefinitionError) Error() string
type IneligibleDefinitionError ¶
IneligibleDefinitionError reports that a loop definition exists and is active by policy, but its runtime conditions do not currently permit launch.
func (*IneligibleDefinitionError) Error ¶
func (e *IneligibleDefinitionError) Error() string
type IterationResult ¶
type IterationResult struct {
// ConvID is the conversation ID for this iteration.
ConvID string
// RequestID is the agent-generated request ID for this iteration.
RequestID string
// Model is the LLM model used for this iteration.
Model string
// InputTokens is the number of input tokens consumed.
InputTokens int
// OutputTokens is the number of output tokens produced.
OutputTokens int
// ContextWindow is the maximum context size (in tokens) of the model used.
ContextWindow int
// ToolsUsed maps tool names to invocation counts.
ToolsUsed map[string]int
// EffectiveTools lists the tools that were visible to the model for
// this iteration after allowlists, excludes, capability tags, and
// delegation gating were applied.
EffectiveTools []string
// ActiveTags holds the capability tags active at the end of this
// iteration.
ActiveTags []string
// LoadedCapabilities captures the structured capability entries
// corresponding to the tags loaded for this iteration.
LoadedCapabilities []toolcatalog.LoadedCapabilityEntry
// Elapsed is the wall-clock duration of the iteration.
Elapsed time.Duration
// Supervisor indicates whether this was a supervisor iteration.
Supervisor bool
// Sleep is the computed sleep duration before the next iteration.
Sleep time.Duration
}
IterationResult holds data from a completed loop iteration, passed to [Config.PostIterate] callbacks.
type IterationSnapshot ¶
type IterationSnapshot struct {
// Number is the 1-based iteration number (matches the loop's
// cumulative iteration counter at the time of completion).
Number int `json:"number"`
// ConvID is the conversation ID used for this iteration.
ConvID string `json:"conv_id,omitempty"`
// RequestID is the agent-generated request ID, linking to
// log_request_content for prompt/response inspection.
RequestID string `json:"request_id,omitempty"`
// Model is the LLM model used.
Model string `json:"model,omitempty"`
// InputTokens consumed by this iteration.
InputTokens int `json:"input_tokens,omitempty"`
// OutputTokens produced by this iteration.
OutputTokens int `json:"output_tokens,omitempty"`
// ContextWindow is the model's maximum context size in tokens.
ContextWindow int `json:"context_window,omitempty"`
// ToolsUsed maps tool names to invocation counts.
ToolsUsed map[string]int `json:"tools_used,omitempty"`
// EffectiveTools lists the tools visible to the model for this turn.
EffectiveTools []string `json:"effective_tools,omitempty"`
// ActiveTags holds the capability tags active for this turn.
ActiveTags []string `json:"active_tags,omitempty"`
// Tooling captures the authoritative tool/capability view for this turn.
Tooling ToolingState `json:"tooling,omitempty"`
// ElapsedMs is the wall-clock duration of the iteration in
// milliseconds. Stored as int64 (not time.Duration) so the JSON
// value is directly usable by the client without nanosecond
// conversion.
ElapsedMs int64 `json:"elapsed_ms"`
// Supervisor indicates whether this was a supervisor iteration.
Supervisor bool `json:"supervisor,omitempty"`
// Error holds the error message if the iteration failed.
Error string `json:"error,omitempty"`
// StartedAt is when the iteration began.
StartedAt time.Time `json:"started_at"`
// CompletedAt is when the iteration finished.
CompletedAt time.Time `json:"completed_at"`
// SleepAfterMs is the computed sleep duration (in milliseconds)
// following this iteration. Zero for WaitFunc-based loops.
SleepAfterMs int64 `json:"sleep_after_ms,omitempty"`
// WaitAfter is true when the loop entered WaitFunc after this
// iteration instead of sleeping.
WaitAfter bool `json:"wait_after,omitempty"`
// Summary holds handler-reported metrics for this iteration,
// written by handlers via [IterationSummary] during execution.
// Values should be small scalars (int, string, bool).
Summary map[string]any `json:"summary,omitempty"`
}
IterationSnapshot is a serializable summary of a completed loop iteration, retained in a ring buffer for the dashboard timeline.
type Launch ¶
type Launch struct {
Spec Spec `yaml:"spec,omitempty" json:"spec,omitempty"`
Task string `yaml:"task,omitempty" json:"task,omitempty"`
ParentID string `yaml:"parent_id,omitempty" json:"parent_id,omitempty"`
Metadata map[string]string `yaml:"metadata,omitempty" json:"metadata,omitempty"`
ConversationID string `yaml:"conversation_id,omitempty" json:"conversation_id,omitempty"`
ChannelBinding *memory.ChannelBinding `yaml:"channel_binding,omitempty" json:"channel_binding,omitempty"`
Model string `yaml:"model,omitempty" json:"model,omitempty"`
Hints map[string]string `yaml:"hints,omitempty" json:"hints,omitempty"`
AllowedTools []string `yaml:"allowed_tools,omitempty" json:"allowed_tools,omitempty"`
ExcludeTools []string `yaml:"exclude_tools,omitempty" json:"exclude_tools,omitempty"`
InitialTags []string `yaml:"initial_tags,omitempty" json:"initial_tags,omitempty"`
OnProgress func(kind string, data map[string]any) `yaml:"-" json:"-"`
RunTimeout time.Duration `yaml:"run_timeout,omitempty" json:"run_timeout,omitempty"`
// CompletionConversationID names the live conversation that should
// receive detached completion delivery when Spec.Completion is
// CompletionConversation.
CompletionConversationID string `yaml:"completion_conversation_id,omitempty" json:"completion_conversation_id,omitempty"`
// CompletionChannel identifies the interactive channel target that
// should receive detached completion delivery when Spec.Completion
// is CompletionChannel.
CompletionChannel *CompletionChannelTarget `yaml:"completion_channel,omitempty" json:"completion_channel,omitempty"`
SkipContext bool `yaml:"skip_context,omitempty" json:"skip_context,omitempty"`
SkipTagFilter bool `yaml:"skip_tag_filter,omitempty" json:"skip_tag_filter,omitempty"`
SystemPrompt string `yaml:"system_prompt,omitempty" json:"system_prompt,omitempty"`
FallbackContent string `yaml:"fallback_content,omitempty" json:"fallback_content,omitempty"`
PromptMode agentctx.PromptMode `yaml:"prompt_mode,omitempty" json:"prompt_mode,omitempty"`
MaxIterations int `yaml:"max_iterations,omitempty" json:"max_iterations,omitempty"`
MaxOutputTokens int `yaml:"max_output_tokens,omitempty" json:"max_output_tokens,omitempty"`
ToolTimeout time.Duration `yaml:"tool_timeout,omitempty" json:"tool_timeout,omitempty"`
UsageRole string `yaml:"usage_role,omitempty" json:"usage_role,omitempty"`
UsageTaskName string `yaml:"usage_task_name,omitempty" json:"usage_task_name,omitempty"`
// SuppressAlwaysContext drops the always-on context bucket from
// the system prompt assembler for this run. Default false matches
// main-loop behavior (include presence, episodic memory, working
// memory, notification history, etc.). Delegates set true so the
// child agent sees only tag-scoped context appropriate to the
// bounded task.
SuppressAlwaysContext bool `yaml:"suppress_always_context,omitempty" json:"suppress_always_context,omitempty"`
}
Launch describes a single loops-ng launch request. It is separate from Spec so per-launch overrides and delivery hooks can grow here over time without turning Spec itself into an ephemeral run object.
func (Launch) MarshalJSON ¶
func (*Launch) UnmarshalJSON ¶
type LaunchResult ¶
type LaunchResult struct {
LoopID string `yaml:"loop_id,omitempty" json:"loop_id"`
Operation Operation `yaml:"operation,omitempty" json:"operation"`
Detached bool `yaml:"detached,omitempty" json:"detached"`
Response *Response `yaml:"response,omitempty" json:"response,omitempty"`
FinalStatus *Status `yaml:"final_status,omitempty" json:"final_status,omitempty"`
}
LaunchResult is the outcome of starting a loop via Registry.Launch. Request/reply launches wait for completion and return a final status; detached launches return immediately with the new loop ID.
type Loop ¶
type Loop struct {
// contains filtered or unexported fields
}
Loop is a persistent background goroutine that iterates on a timer or in response to external events. Each iteration runs an LLM call via the agent runner, or a direct [Config.Handler] function for infrastructure loops that don't need an LLM. Create with New, start with [Start], stop with [Stop].
func New ¶
New creates a loop with the given configuration and dependencies. Returns an error if required fields are missing or invalid. Call Loop.Start to launch the background goroutine.
func NewFromLaunch ¶
NewFromLaunch creates a loop from a Launch, validating the launch, compiling the underlying Spec, and applying per-run request and metadata overrides. This is the additive bridge used by Registry.Launch.
func NewFromSpec ¶
NewFromSpec creates a loop from a Spec, validating the loops-ng fields before compiling the engine-facing Config. This is an additive bridge for gradually moving call sites onto Spec.
func (*Loop) CurrentConvID ¶
CurrentConvID returns the conversation ID of the in-flight iteration, or empty string if no iteration is running. Tool handlers use this to tag their outputs with the current conversation.
func (*Loop) Done ¶
func (l *Loop) Done() <-chan struct{}
Done returns a channel that is closed when the loop's goroutine exits. Returns nil if the loop has not been started.
func (*Loop) SetActiveTagsFunc ¶
SetActiveTagsFunc configures an optional callback that returns the currently active capability tags. When set, Status includes the result so the dashboard can display dynamically activated capabilities.
func (*Loop) SetNextSleep ¶
SetNextSleep sets the sleep duration for the next cycle. This is intended for tool handlers (e.g., set_next_sleep) to communicate the LLM's chosen sleep duration back to the loop.
func (*Loop) Start ¶
Start launches the background goroutine. Calling Start on an already running loop is a no-op (returns nil). Returns ErrLoopStopped if Loop.Stop was called before Start. The goroutine runs until ctx is cancelled or Loop.Stop is called.
func (*Loop) Status ¶
Status returns a snapshot of the loop's current state and metrics. The returned Config is a deep copy; callers cannot mutate loop state via the snapshot.
func (*Loop) Stop ¶
func (l *Loop) Stop()
Stop cancels the loop and waits for the goroutine to exit. Safe to call multiple times or before Start. After Stop, Loop.Start will return ErrLoopStopped. Blocks until the goroutine exits or 10 seconds elapse.
type Message ¶
type Message struct {
Role string `yaml:"role" json:"role"`
Content string `yaml:"content" json:"content"`
}
Message is a chat message for the runner.
type NotifyReceipt ¶
type NotifyReceipt struct {
LoopID string `json:"loop_id"`
LoopName string `json:"loop_name"`
State State `json:"state"`
WokeImmediately bool `json:"woke_immediately,omitempty"`
QueuedForNextWake bool `json:"queued_for_next_wake,omitempty"`
ForceSupervisor bool `json:"force_supervisor,omitempty"`
PendingNotifications int `json:"pending_notifications,omitempty"`
}
NotifyReceipt summarizes the effect of notifying a live loop.
type Operation ¶
type Operation string
Operation describes the runtime pattern a loop is expected to follow. The zero value is accepted while loops-ng adoption is incremental.
const ( // OperationRequestReply is a one-shot run that is expected to // conclude with a direct result for the caller. OperationRequestReply Operation = "request_reply" // OperationBackgroundTask is a detached task whose result is // delivered later through a non-blocking completion path. OperationBackgroundTask Operation = "background_task" // OperationService is a persistent loop such as metacognition, // ego, or a long-running watcher. OperationService Operation = "service" )
type OutputContextBuilder ¶
type OutputContextBuilder func(ctx context.Context, outputs []OutputSpec) (string, error)
OutputContextBuilder renders model-facing context for a loop's declared durable outputs.
type OutputMode ¶
type OutputMode string
OutputMode describes the allowed write mode for a loop output.
const ( // OutputModeReplace requires complete replacement content. OutputModeReplace OutputMode = "replace" // OutputModeAppend requires append-only journal entries. OutputModeAppend OutputMode = "append" )
type OutputSpec ¶
type OutputSpec struct {
// Name is the stable semantic name for this output within the loop.
Name string `yaml:"name" json:"name"`
// Type identifies the output behavior, such as maintained_document.
Type OutputType `yaml:"type" json:"type"`
// Ref is the managed document ref, such as core:metacognitive.md.
Ref string `yaml:"ref" json:"ref"`
// Mode is the write mode. It defaults from Type when omitted.
Mode OutputMode `yaml:"mode,omitempty" json:"mode,omitempty"`
// Purpose is optional model-facing guidance for this output.
Purpose string `yaml:"purpose,omitempty" json:"purpose,omitempty"`
// JournalWindow is the default rolling window for journal outputs:
// day, week, or month. Empty uses the document layer default.
JournalWindow string `yaml:"journal_window,omitempty" json:"journal_window,omitempty"`
// MaxWindows caps retained journal windows. Zero uses the document
// layer default for the selected window.
MaxWindows int `yaml:"max_windows,omitempty" json:"max_windows,omitempty"`
}
OutputSpec declares one durable document surface a loop is allowed to maintain. The declaration is persistable; runtime hydration turns it into scoped tools and context.
func (OutputSpec) EffectiveMode ¶
func (o OutputSpec) EffectiveMode() OutputMode
EffectiveMode returns the explicit mode or the default mode implied by the output type.
func (OutputSpec) ToolName ¶
func (o OutputSpec) ToolName() string
ToolName returns the scoped mutation tool name generated for this output declaration.
func (OutputSpec) Validate ¶
func (o OutputSpec) Validate() error
Validate checks that one output declaration is internally consistent.
type OutputType ¶
type OutputType string
OutputType names a durable output contract declared by a loop.
const ( // OutputTypeMaintainedDocument describes a document the loop owns // as a current complete state. OutputTypeMaintainedDocument OutputType = "maintained_document" // OutputTypeJournalDocument describes an append-only journal // document maintained by the loop. OutputTypeJournalDocument OutputType = "journal_document" )
type PausedDefinitionError ¶
type PausedDefinitionError struct {
Name string
}
PausedDefinitionError reports that a loop definition exists but is currently paused by effective runtime policy.
func (*PausedDefinitionError) Error ¶
func (e *PausedDefinitionError) Error() string
type RandSource ¶
type RandSource interface {
Float64() float64
}
RandSource abstracts randomness for deterministic testing.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry tracks all active loops and provides visibility into what is running. It enforces concurrency limits and coordinates graceful shutdown.
func NewRegistry ¶
func NewRegistry(opts ...RegistryOption) *Registry
NewRegistry creates a new loop registry.
func (*Registry) ActiveCount ¶
ActiveCount returns the number of registered loops.
func (*Registry) Deregister ¶
Deregister removes a loop from the registry. Safe to call for a loop that is not registered (no-op).
func (*Registry) FindByName ¶
FindByName returns all live loops with the exact given name.
func (*Registry) GetByName ¶
GetByName returns the first loop with the given name, or nil if not found. If multiple loops share a name, the result is undefined.
func (*Registry) Launch ¶
Launch starts a loops-ng Launch. Request/reply launches wait for completion and return a final status snapshot; background and service launches detach immediately and leave the loop running in the registry.
func (*Registry) MaxLoops ¶
MaxLoops returns the configured concurrency limit. Zero means unlimited.
func (*Registry) NotifyLoop ¶
func (r *Registry) NotifyLoop(ctx context.Context, id string, env messages.Envelope) (NotifyReceipt, error)
NotifyLoop delivers one inter-loop process notification to a live loop by ID. This is loop-runtime control messaging, not Signal-channel transport.
func (*Registry) NotifyLoopByName ¶
func (r *Registry) NotifyLoopByName(ctx context.Context, name string, env messages.Envelope) (NotifyReceipt, error)
NotifyLoopByName delivers one inter-loop process notification to a live loop by exact name. This is loop-runtime control messaging, not Signal-channel transport.
func (*Registry) Register ¶
Register adds a loop to the registry. Returns an error if the loop's ID is already registered or the concurrency limit would be exceeded. The loop is not started — call Loop.Start after registering.
func (*Registry) ShutdownAll ¶
ShutdownAll cancels all registered loops and waits for them to drain. The provided context controls the maximum time to wait; if it expires, remaining loops are abandoned. Returns the number of loops that were stopped.
func (*Registry) SpawnLoop ¶
SpawnLoop creates a new loop with the given config, registers it, and starts it. This is the primary entry point for creating loops. Returns the loop ID on success.
func (*Registry) SpawnSpec ¶
SpawnSpec creates, registers, and starts a loop from a Spec. It is the additive loops-ng entrypoint while existing call sites continue to use Registry.SpawnLoop with Config.
func (*Registry) Statuses ¶
Statuses returns a snapshot of all registered loop statuses sorted by name.
func (*Registry) StopLoop ¶
StopLoop stops a loop by ID and deregisters it once the goroutine has exited. Returns an error if the loop is not found. If the goroutine does not exit within 10 seconds (the Stop timeout), the loop remains registered to avoid orphaning a running goroutine.
func (*Registry) StopLoopByName ¶
StopLoopByName cancels one registered loop by exact name. It returns an error when no loop matches or when the name is ambiguous.
type RegistryOption ¶
type RegistryOption func(*Registry)
RegistryOption configures a Registry.
func WithMaxLoops ¶
func WithMaxLoops(n int) RegistryOption
WithMaxLoops sets the maximum number of concurrent loops the registry will allow. Zero means unlimited.
func WithRegistryLogger ¶
func WithRegistryLogger(l *slog.Logger) RegistryOption
WithRegistryLogger sets the logger for registry operations. Nil is ignored (keeps slog.Default()).
type Request ¶
type Request struct {
Model string `yaml:"model,omitempty" json:"model,omitempty"`
ConversationID string `yaml:"conversation_id,omitempty" json:"conversation_id,omitempty"`
ChannelBinding *memory.ChannelBinding `yaml:"channel_binding,omitempty" json:"channel_binding,omitempty"`
Messages []Message `yaml:"messages,omitempty" json:"messages,omitempty"`
SkipContext bool `yaml:"skip_context,omitempty" json:"skip_context,omitempty"`
AllowedTools []string `yaml:"allowed_tools,omitempty" json:"allowed_tools,omitempty"`
ExcludeTools []string `yaml:"exclude_tools,omitempty" json:"exclude_tools,omitempty"`
SkipTagFilter bool `yaml:"skip_tag_filter,omitempty" json:"skip_tag_filter,omitempty"`
Hints map[string]string `yaml:"hints,omitempty" json:"hints,omitempty"`
// InitialTags are capability tags to activate at the start of the Run,
// in addition to always-active and channel-pinned tags. Used by loops
// to carry forward tags activated in previous iterations.
InitialTags []string `yaml:"initial_tags,omitempty" json:"initial_tags,omitempty"`
// RuntimeTools are request-scoped tools visible only to this run.
RuntimeTools []RuntimeTool `yaml:"-" json:"-"`
// OnProgress is called by the Runner during execution to report
// in-flight activity (tool calls, LLM responses). The kind
// parameter maps to an [events.Kind] constant; data holds
// event-specific fields. The loop automatically injects loop_id
// and loop_name into data before publishing. Nil means no
// progress reporting.
OnProgress func(kind string, data map[string]any) `yaml:"-" json:"-"`
MaxIterations int `yaml:"max_iterations,omitempty" json:"max_iterations,omitempty"`
MaxOutputTokens int `yaml:"max_output_tokens,omitempty" json:"max_output_tokens,omitempty"`
ToolTimeout time.Duration `yaml:"tool_timeout,omitempty" json:"tool_timeout,omitempty"`
UsageRole string `yaml:"usage_role,omitempty" json:"usage_role,omitempty"`
UsageTaskName string `yaml:"usage_task_name,omitempty" json:"usage_task_name,omitempty"`
SystemPrompt string `yaml:"system_prompt,omitempty" json:"system_prompt,omitempty"`
FallbackContent string `yaml:"fallback_content,omitempty" json:"fallback_content,omitempty"`
PromptMode agentctx.PromptMode `yaml:"prompt_mode,omitempty" json:"prompt_mode,omitempty"`
// SuppressAlwaysContext drops the always-on bucket from the
// system-prompt assembler's context output for this run. Default
// false (main-loop behavior: include presence, episodic memory,
// working memory, notification history, etc.). Delegates set true
// so child agents see only tag-scoped context appropriate to the
// bounded task.
SuppressAlwaysContext bool `yaml:"suppress_always_context,omitempty" json:"suppress_always_context,omitempty"`
}
Request mirrors the loop-facing fields of agent.Request. The loop package defines its own type to avoid importing agent.
type Response ¶
type Response struct {
Content string `yaml:"content,omitempty" json:"content,omitempty"`
Model string `yaml:"model,omitempty" json:"model,omitempty"`
FinishReason string `yaml:"finish_reason,omitempty" json:"finish_reason,omitempty"`
InputTokens int `yaml:"input_tokens,omitempty" json:"input_tokens,omitempty"`
OutputTokens int `yaml:"output_tokens,omitempty" json:"output_tokens,omitempty"`
CacheCreationInputTokens int `yaml:"cache_creation_input_tokens,omitempty" json:"cache_creation_input_tokens,omitempty"`
CacheReadInputTokens int `yaml:"cache_read_input_tokens,omitempty" json:"cache_read_input_tokens,omitempty"`
ContextWindow int `yaml:"context_window,omitempty" json:"context_window,omitempty"`
ToolsUsed map[string]int `yaml:"tools_used,omitempty" json:"tools_used,omitempty"`
EffectiveTools []string `yaml:"effective_tools,omitempty" json:"effective_tools,omitempty"`
LoadedCapabilities []toolcatalog.LoadedCapabilityEntry `yaml:"loaded_capabilities,omitempty" json:"loaded_capabilities,omitempty"`
RequestID string `yaml:"request_id,omitempty" json:"request_id,omitempty"`
Iterations int `yaml:"iterations,omitempty" json:"iterations,omitempty"`
Exhausted bool `yaml:"exhausted,omitempty" json:"exhausted,omitempty"`
// ActiveTags is the set of capability tags that were active at the
// end of the Run. Loops use this to carry forward activations to
// subsequent iterations.
ActiveTags []string `yaml:"active_tags,omitempty" json:"active_tags,omitempty"`
}
Response mirrors agent.Response fields that loops consume. It holds the result of an LLM call executed by a Runner.
type RetriggerMode ¶
type RetriggerMode int
RetriggerMode determines what happens when a loop's start condition fires again while the loop is already running.
const ( // RetriggerSingle ignores re-triggers while the loop is running. RetriggerSingle RetriggerMode = iota // RetriggerRestart cancels the current loop and starts fresh. RetriggerRestart // RetriggerQueue queues the trigger and runs after current completes. RetriggerQueue // RetriggerSpawn spawns another instance of the loop. RetriggerSpawn )
func ParseRetriggerMode ¶
func ParseRetriggerMode(raw string) (RetriggerMode, error)
ParseRetriggerMode parses the stable textual form of a retrigger mode.
func (RetriggerMode) MarshalText ¶
func (m RetriggerMode) MarshalText() ([]byte, error)
MarshalText implements encoding.TextMarshaler.
func (RetriggerMode) String ¶
func (m RetriggerMode) String() string
String returns the stable textual form of the retrigger mode.
func (*RetriggerMode) UnmarshalText ¶
func (m *RetriggerMode) UnmarshalText(text []byte) error
UnmarshalText implements encoding.TextUnmarshaler.
type RunMessage ¶
type RunMessage = Message
RunMessage is kept as a compatibility alias while loops-ng migrates onto Message as the primary loop-facing message type.
type RunRequest ¶
type RunRequest = Request
RunRequest is kept as a compatibility alias while loops-ng migrates onto Request as the primary loop-facing run descriptor.
type RunResponse ¶
type RunResponse = Response
RunResponse is kept as a compatibility alias while loops-ng migrates onto Response as the primary loop-facing response type.
type Runner ¶
type Runner interface {
Run(ctx context.Context, req Request, stream StreamCallback) (*Response, error)
}
Runner abstracts the agent loop for LLM calls. Satisfied by *agent.Loop. Defined here to avoid a circular import.
type RuntimeTool ¶
type RuntimeTool struct {
Name string `yaml:"-" json:"-"`
Description string `yaml:"-" json:"-"`
Parameters map[string]any `yaml:"-" json:"-"`
Handler func(ctx context.Context, args map[string]any) (string, error) `yaml:"-" json:"-"`
SkipContentResolve bool `yaml:"-" json:"-"`
ContentResolveExempt []string `yaml:"-" json:"-"`
}
RuntimeTool is a request-scoped tool hydrated from runtime state. It exists so loops can expose narrow interfaces, such as declared output mutation tools, without registering those tools globally.
type ScheduleCondition ¶
type ScheduleCondition struct {
// Timezone is the IANA timezone used to interpret window start and
// end times. Empty means the local system timezone.
Timezone string `yaml:"timezone,omitempty" json:"timezone,omitempty"`
// Windows are the recurring day/time windows during which the
// definition is eligible to run or launch.
Windows []ScheduleWindow `yaml:"windows,omitempty" json:"windows,omitempty"`
}
ScheduleCondition constrains definition eligibility to one or more recurring local-time windows.
func (*ScheduleCondition) Evaluate ¶
func (s *ScheduleCondition) Evaluate(now time.Time) DefinitionEligibilityStatus
Evaluate returns the effective eligibility of the schedule at the provided time.
func (*ScheduleCondition) Validate ¶
func (s *ScheduleCondition) Validate() error
Validate checks that the schedule condition is well-formed.
type ScheduleWindow ¶
type ScheduleWindow struct {
// Days limits the window to specific weekdays using stable short
// names such as mon, tue, wed, thu, fri, sat, and sun. Empty means
// every day.
Days []string `yaml:"days,omitempty" json:"days,omitempty"`
// Start is the local wall-clock start time in HH:MM 24-hour form.
Start string `yaml:"start,omitempty" json:"start,omitempty"`
// End is the local wall-clock end time in HH:MM 24-hour form. When
// End is earlier than Start, the window crosses midnight into the
// next day.
End string `yaml:"end,omitempty" json:"end,omitempty"`
}
ScheduleWindow is one recurring local-time eligibility window.
func (ScheduleWindow) Validate ¶
func (w ScheduleWindow) Validate() error
Validate checks that the schedule window is structurally valid.
type Spec ¶
type Spec struct {
// Name is the unique identifier for the loop. Required.
Name string `yaml:"name,omitempty" json:"name,omitempty"`
// Enabled marks the definition as eligible for runtime lifecycle
// management. Service definitions only auto-start when enabled.
Enabled bool `yaml:"enabled" json:"enabled"`
// Task is the static prompt for each iteration. Ignored when
// TaskBuilder is set.
Task string `yaml:"task,omitempty" json:"task,omitempty"`
// Profile shapes loop execution: routing hints, context-injection
// tags, tool exclusions, and related request-shaping guidance.
Profile router.LoopProfile `yaml:"profile,omitempty" json:"profile,omitempty"`
// Operation describes the runtime pattern expected for the loop.
Operation Operation `yaml:"operation,omitempty" json:"operation,omitempty"`
// Completion describes how results should be delivered back to a
// caller, conversation, or channel.
Completion Completion `yaml:"completion,omitempty" json:"completion,omitempty"`
// Outputs declare durable documents this loop is allowed to
// maintain through scoped runtime tools.
Outputs []OutputSpec `yaml:"outputs,omitempty" json:"outputs,omitempty"`
// Conditions constrain when the definition is currently eligible to
// run or launch. When empty, the definition is always eligible
// unless blocked by policy.
Conditions Conditions `yaml:"conditions,omitempty" json:"conditions,omitempty"`
// Tags are the capability tags scoping this loop. They are
// activated at iteration 0 — seeding the active-tag set used for
// tool-registry scope filtering, KB article exposure, and any
// other tag-driven context surface — and remain active across
// iterations unless the model deactivates them. Per-invocation
// runtime overrides layer on top via [Launch.InitialTags].
Tags []string `yaml:"tags,omitempty" json:"tags,omitempty"`
// ExcludeTools lists tool names to exclude from the loop's
// available tools.
ExcludeTools []string `yaml:"exclude_tools,omitempty" json:"exclude_tools,omitempty"`
// SleepMin is the minimum sleep duration between iterations.
SleepMin time.Duration `yaml:"sleep_min,omitempty" json:"sleep_min,omitempty"`
// SleepMax is the maximum sleep duration between iterations.
SleepMax time.Duration `yaml:"sleep_max,omitempty" json:"sleep_max,omitempty"`
// SleepDefault is the initial sleep duration before the loop
// self-adjusts.
SleepDefault time.Duration `yaml:"sleep_default,omitempty" json:"sleep_default,omitempty"`
// Jitter randomizes sleep durations to break periodicity.
Jitter *float64 `yaml:"jitter,omitempty" json:"jitter,omitempty"`
// MaxDuration is the maximum wall-clock time the loop may run.
MaxDuration time.Duration `yaml:"max_duration,omitempty" json:"max_duration,omitempty"`
// MaxIter is the maximum number of iteration attempts the loop
// may make.
MaxIter int `yaml:"max_iter,omitempty" json:"max_iter,omitempty"`
// Supervisor enables frontier model dice rolls.
Supervisor bool `yaml:"supervisor,omitempty" json:"supervisor,omitempty"`
// SupervisorProb is the probability of using the supervisor model.
SupervisorProb float64 `yaml:"supervisor_prob,omitempty" json:"supervisor_prob,omitempty"`
// QualityFloor is the minimum model quality rating for normal
// iterations.
QualityFloor int `yaml:"quality_floor,omitempty" json:"quality_floor,omitempty"`
// SupervisorContext is prepended during supervisor iterations.
SupervisorContext string `yaml:"supervisor_context,omitempty" json:"supervisor_context,omitempty"`
// SupervisorQualityFloor is the quality floor for supervisor
// iterations.
SupervisorQualityFloor int `yaml:"supervisor_quality_floor,omitempty" json:"supervisor_quality_floor,omitempty"`
// OnRetrigger determines behavior when the loop is triggered again
// while already running.
OnRetrigger RetriggerMode `yaml:"on_retrigger,omitempty" json:"on_retrigger,omitempty"`
// TaskBuilder generates a prompt per-iteration.
TaskBuilder func(ctx context.Context, isSupervisor bool) (string, error) `yaml:"-" json:"-"`
// PostIterate runs after each successful iteration.
PostIterate func(ctx context.Context, result IterationResult) error `yaml:"-" json:"-"`
// WaitFunc blocks until an external event arrives.
WaitFunc func(ctx context.Context) (any, error) `yaml:"-" json:"-"`
// Handler processes an iteration directly without an LLM call.
Handler func(ctx context.Context, event any) error `yaml:"-" json:"-"`
// Hints are merged into Request hints for each iteration.
Hints map[string]string `yaml:"hints,omitempty" json:"hints,omitempty"`
// FallbackContent is static text used when the loop completes a
// request/reply run without any user-visible content. Interactive
// loops can set this to guarantee a reply.
FallbackContent string `yaml:"fallback_content,omitempty" json:"fallback_content,omitempty"`
// Setup is called by the registry spawn helpers after [New] or
// [NewFromSpec] but before [Loop.Start].
Setup func(l *Loop) `yaml:"-" json:"-"`
// RuntimeTools are request-scoped tools attached during hydration.
RuntimeTools []RuntimeTool `yaml:"-" json:"-"`
// OutputContextBuilder renders model-facing context for [Outputs].
OutputContextBuilder OutputContextBuilder `yaml:"-" json:"-"`
// Metadata holds arbitrary key/value pairs for the loop.
Metadata map[string]string `yaml:"metadata,omitempty" json:"metadata,omitempty"`
// ParentID is the parent loop ID, if any.
ParentID string `yaml:"parent_id,omitempty" json:"parent_id,omitempty"`
}
Spec is the loops-ng contract for describing a loop. It carries both the current engine-facing config fields and the forward-looking loops-ng semantics. Today it compiles to Config, while [Profile] already shapes requests for loops created via NewFromSpec. Operation and Completion are retained for the upcoming RunV2 work.
func (*Spec) EffectiveConfig ¶
EffectiveConfig returns the engine-facing configuration for this spec with loop runtime defaults applied. This is useful for inspection, linting, and warning surfaces that need to explain what a partially specified definition will actually do at runtime.
func (Spec) MarshalJSON ¶
MarshalJSON renders a loops-ng spec in a human-facing contract shape suitable for APIs and tools: durations are strings and retrigger mode is named instead of using the engine's integer form.
func (*Spec) ToConfig ¶
ToConfig compiles the current engine-facing portion of a Spec into today's Config shape. [Spec.Operation] and [Spec.Completion] already flow through into the runtime config, while [Spec.Profile] remains a request-shaping layer applied by NewFromSpec rather than a field on Config.
func (*Spec) UnmarshalJSON ¶
UnmarshalJSON accepts the same human-facing contract shape emitted by Spec.MarshalJSON.
func (*Spec) Validate ¶
Validate checks that the loops-ng-facing fields and the current engine-facing configuration are internally consistent.
func (*Spec) ValidatePersistable ¶
ValidatePersistable checks that the spec is valid and safe to store in config or a persistent overlay. Persisted loop definitions are data, not code, so runtime-only hooks must remain nil.
type State ¶
type State string
State represents the lifecycle state of a running loop.
const ( // StatePending means the loop is registered but not yet started // (e.g., waiting for a StartWhen condition). StatePending State = "pending" // StateSleeping means the loop is between iterations, waiting for // the next wake. StateSleeping State = "sleeping" // StateWaiting means the loop is blocked on a WaitFunc, waiting // for an external event to trigger the next iteration. StateWaiting State = "waiting" // StateProcessing means the loop is actively running an iteration // (LLM call or Handler execution). StateProcessing State = "processing" // StateError means the loop's last iteration failed. It will // retry on the next sleep cycle. StateError State = "error" // StateStopped means the loop has been cancelled and is no // longer running. StateStopped State = "stopped" )
Loop states.
type Status ¶
type Status struct {
// ID is the unique loop identifier.
ID string `json:"id"`
// Name is the human-readable loop name.
Name string `json:"name"`
// State is the current lifecycle state.
State State `json:"state"`
// ParentID is the ID of the parent loop, if any.
ParentID string `json:"parent_id,omitempty"`
// StartedAt is when the loop was started.
StartedAt time.Time `json:"started_at"`
// LastWakeAt is when the loop last began an iteration.
LastWakeAt time.Time `json:"last_wake_at,omitempty"`
// Iterations is the total number of completed (successful) iterations.
Iterations int `json:"iterations"`
// Attempts is the total number of iteration attempts (including failures).
Attempts int `json:"attempts"`
// TotalInputTokens is the cumulative input tokens across all iterations.
TotalInputTokens int `json:"total_input_tokens"`
// TotalOutputTokens is the cumulative output tokens across all iterations.
TotalOutputTokens int `json:"total_output_tokens"`
// LastInputTokens is the input token count from the most recent iteration.
LastInputTokens int `json:"last_input_tokens,omitempty"`
// LastOutputTokens is the output token count from the most recent iteration.
LastOutputTokens int `json:"last_output_tokens,omitempty"`
// ContextWindow is the maximum context size (in tokens) of the model used.
ContextWindow int `json:"context_window,omitempty"`
// LastError is the error message from the most recent failed iteration.
LastError string `json:"last_error,omitempty"`
// ConsecutiveErrors is the number of consecutive failed iterations.
ConsecutiveErrors int `json:"consecutive_errors"`
// RecentConvIDs holds conversation IDs from the most recent iterations
// (up to 10), newest first. Used by the visualizer to query log entries
// scoped to this loop.
RecentConvIDs []string `json:"recent_conv_ids,omitempty"`
// HandlerOnly is true when the loop uses a Handler instead of LLM
// iterations. Handler-only loops have no token metrics.
HandlerOnly bool `json:"handler_only,omitempty"`
// EventDriven is true when the loop uses a WaitFunc instead of
// timer-based sleeping.
EventDriven bool `json:"event_driven,omitempty"`
// RecentIterations holds up to 10 completed iteration snapshots
// (newest first), used by the dashboard timeline.
RecentIterations []IterationSnapshot `json:"recent_iterations,omitempty"`
// LastSupervisorIter is the iteration number of the most recent
// successful supervisor iteration. Zero means no supervisor
// iteration has completed yet.
LastSupervisorIter int `json:"last_supervisor_iter,omitempty"`
// LLMContext holds enrichment data from the most recent
// loop_llm_start event (model, est_tokens, messages, tools,
// complexity, intent, reasoning). Only populated while the loop
// is in processing state, so late-connecting dashboard clients
// can display it immediately.
LLMContext map[string]any `json:"llm_context,omitempty"`
// ActiveTags holds the currently active capability tags at the time
// of the snapshot. Nil when capability tagging is not configured.
ActiveTags []string `json:"active_tags,omitempty"`
// Tooling captures the resolved loop-level tool/capability state.
Tooling ToolingState `json:"tooling,omitempty"`
// Config is a copy of the loop's configuration.
Config Config `json:"config"`
}
Status is a snapshot of a loop's current state and metrics, suitable for external inspection via the registry.
type StreamCallback ¶
type StreamCallback func(event any)
StreamCallback receives streaming events. Nil disables streaming.
type ToolingState ¶
type ToolingState struct {
ConfiguredTags []string `json:"configured_tags,omitempty"`
LoadedTags []string `json:"loaded_tags,omitempty"`
LoadedCapabilities []toolcatalog.LoadedCapabilityEntry `json:"loaded_capabilities,omitempty"`
EffectiveTools []string `json:"effective_tools,omitempty"`
ExcludedTools []string `json:"excluded_tools,omitempty"`
ToolsUsed map[string]int `json:"tools_used,omitempty"`
}
ToolingState captures the resolved tool/capability surface for a loop or a single historical iteration. It gives the dashboard one authoritative payload to render instead of re-deriving state from a mix of config, live context, and snapshot arrays.
func BuildToolingState ¶
func BuildToolingState(configuredTags, loadedTags, effectiveTools, excludedTools []string, loadedCapabilities []toolcatalog.LoadedCapabilityEntry, toolsUsed map[string]int) ToolingState
BuildToolingState normalizes a loop or iteration tool/capability snapshot into a stable, sorted payload for the API and dashboard.
type UnknownDefinitionError ¶
type UnknownDefinitionError struct {
Name string
}
UnknownDefinitionError reports a missing dynamic loop definition.
func (*UnknownDefinitionError) Error ¶
func (e *UnknownDefinitionError) Error() string