Documentation
¶
Index ¶
- Constants
- Variables
- func EphemeralFrom(ctx context.Context) bool
- func WithEphemeral(ctx context.Context) context.Context
- func WithExcludedTools(ctx context.Context, names []string) context.Context
- type ApprovalGranter
- type AttachmentResolver
- type AuthorityFunc
- type AuthzDecision
- type AuthzInput
- type AuthzOutcome
- type BootstrapLoader
- type Config
- type ContextManager
- type ContextPreparer
- type EventPublisher
- type ExecutionLedger
- type Grader
- type LLMCallRecord
- type MemoryProviderNamer
- type MemoryService
- type NoopAttachmentResolver
- type Provider
- type ProviderAuthResolver
- type ProviderResolver
- type ProviderResolverOption
- type ProviderRetryPayload
- type RefinementAuditCommandProvider
- type RubricRequest
- type RubricVerdict
- type RubricVerifier
- type Runner
- func (r *Runner) AcknowledgeTaskTerminal(ctx context.Context, workspaceID, taskID string) error
- func (r *Runner) ActiveModelName(threadID string) string
- func (r *Runner) ActiveReasoningEffort(threadID string) string
- func (r *Runner) ActiveTurnExecutions(ctx context.Context) ([]turnjournal.Record, error)
- func (r *Runner) AvailableCommands() []commands.Command
- func (r *Runner) ExecuteAssignedSubagent(ctx context.Context, taskID string, envelope subagent.ExecutionEnvelope, ...) (subagent.Result, error)
- func (r *Runner) InterruptTurn(ctx context.Context, workspaceID, taskID, reason string) error
- func (r *Runner) ResumeTurn(ctx context.Context, workspaceID, taskID string) (protocol.ChatMessage, error)
- func (r *Runner) Run(ctx context.Context, addr protocol.MessageAddress, user protocol.ChatMessage) (_ protocol.ChatMessage, err error)
- func (r *Runner) RunSubagent(ctx context.Context, req subagent.Request) (_ subagent.Result, err error)
- func (r *Runner) StartBackground(ctx context.Context, req subagent.Request) (string, error)
- func (r *Runner) SteeringInbox() *steering.Inbox
- type ScheduledOperationsCommandProvider
- type StreamingProvider
- type ThreadRegistrar
- type ThreadSpec
- type ToolAuthorizer
- type ToolCatalogBindingFunc
- type ToolProvider
- type ToolSuggestionProvider
- type TurnRecorder
Constants ¶
const DynamicProviderRetry = "provider_retry"
DynamicProviderRetry is the dynamic-part kind carrying a provider retry to the user's channel. A retry the user cannot see is a session that appears hung: the turn is alive and waiting, but nothing on screen says so or says for how long. Renderers should show the wait as a countdown.
Variables ¶
var ( ErrMissingStore = errors.New("turn: history store required") ErrMissingLoader = errors.New("turn: bootstrap loader required") ErrMissingProvider = errors.New("turn: provider required") ErrToolLoopLimit = errors.New("turn: tool loop limit reached") ErrRecoveryUnsafe = errors.New("turn: execution recovery is unsafe") )
Functions ¶
func EphemeralFrom ¶
EphemeralFrom reports whether the turn carried on ctx is ephemeral.
func WithEphemeral ¶
WithEphemeral returns ctx marked as an ephemeral one-shot turn. The transport/ distribution sets it when an inbound turn must not read or write durable state.
func WithExcludedTools ¶
WithExcludedTools scopes a turn's tool set: the named tools are neither advertised to the model (dropped from the assembled specs) NOR executable (invokeTool rejects them). A general per-turn seam — the distribution uses it to restrict a one-shot/ephemeral turn (e.g. excluding spawn_subagent so an ephemeral turn can't spawn durable child threads). Empty names → no-op.
Types ¶
type ApprovalGranter ¶
type ApprovalGranter interface {
GrantSession(workspaceID, tool string)
GrantAlways(ctx context.Context, workspaceID, tool string) error
}
ApprovalGranter records tool authorizations the user grants via the approval flow. Implemented by the distribution's dynamic policy (tools.DynamicPolicy).
type AttachmentResolver ¶
type AttachmentResolver interface {
Resolve(ctx context.Context, addr protocol.MessageAddress, messages []protocol.ChatMessage) ([]protocol.ChatMessage, error)
}
AttachmentResolver rewrites multimodal content parts whose only carrier is one the model provider cannot fetch on its own — chiefly vfs_ref-only image/file parts — into a model-deliverable form (an inline data_uri, or a fetchable uri).
It runs on the *assembled request* immediately before the provider call, never on persisted history: a resolved part may carry inlined bytes or a short-lived presigned URL that must not be written back to the store. Resolve must not mutate its input; it returns the (possibly rewritten) messages to send.
The per-turn OBO authority is carried on ctx (set by the runner via tools.WithMemoryAuthority); a resolver that fetches from an authority-scoped service (e.g. data-connectors) reads it there. addr supplies the tenant + workspace the attachment belongs to.
The default wiring is NoopAttachmentResolver, which resolves nothing and logs the parts it leaves undeliverable. The sahara distribution supplies a data-connectors-backed resolver that mints a download URL, materializes the blob into the workspace, and inlines it.
type AuthorityFunc ¶
type AuthorityFunc func(addr protocol.MessageAddress, user protocol.ChatMessage) tools.MemoryAuthority
AuthorityFunc derives a turn's OBO authority from the inbound address+message. Core leaves it unset (zero authority → memory uses its default); the Scitrera distribution wires one that reads the inbound grant.
type AuthzDecision ¶
type AuthzDecision struct {
Outcome AuthzOutcome
Reason string
Scope string
}
AuthzDecision is an authorizer's verdict plus optional metadata (a reason for logs/telemetry, and the grant Scope the user chose when a Prompt is resolved).
type AuthzInput ¶
type AuthzInput struct {
Call protocol.ToolInvokeEnvelope
Addr protocol.MessageAddress
Trust tools.TrustLevel
ProviderID string // "" = local registry tool
}
AuthzInput is the per-call context handed to each authorizer.
type AuthzOutcome ¶
type AuthzOutcome int
AuthzOutcome is a tool authorizer's verdict on a call.
const ( // Abstain: this authorizer has no opinion; the pipeline moves on. Abstain AuthzOutcome = iota // Allow: the call is authorized (subject to a later Deny/escalation). Allow // Deny: the call is refused; short-circuits the pipeline. Deny // Prompt: the call needs human approval before it may run. Prompt )
type BootstrapLoader ¶
type Config ¶
type Config struct {
Store harness.HistoryStore
Loader BootstrapLoader
Registry *tools.Registry
Provider Provider
Publisher EventPublisher
// Assembler is the default context manager. ContextManager, when set,
// overrides it (e.g. a MemoryLayer-backed strategy).
Assembler contextpack.Assembler
ContextManager ContextManager
Model string
// ReasoningEffort is an optional process/user preference. Per-thread
// /reasoning overrides it; model defaults and then provider defaults apply
// when it is empty or disallowed by a model-specific allowlist.
ReasoningEffort string
// DefaultWorkspaceID resolves workspace-less local/client turns. A non-empty
// workspace carried by the turn is never replaced.
DefaultWorkspaceID string
// ModelRegistry is the set of available models + default. When set, the runner
// selects the per-turn model from it (capability-matched), honoring an
// explicit /command override first. nil → the single Model is always used.
ModelRegistry *modelpkg.Registry
// ModelSelector picks the per-turn model from ModelRegistry on the auto path
// (an explicit override wins). nil with a non-nil ModelRegistry →
// model.CapabilityDefault. The distribution plugs in a cost/complexity router
// here (policy stays out of oss).
ModelSelector modelpkg.Selector
// SkillRealizer, when set, materializes a loaded skill's bundle files into the
// sandbox's shared /skills dir (installed on ctx each turn so load_skill can call
// it). nil → no materialization (skills load body-only). The distribution wires
// the source (MemoryLayer bundle files / workspace folders); oss stays agnostic.
SkillRealizer tools.SkillRealizerFunc
// TurnRecorder, when set, receives one LLMCallRecord per successful provider
// call (the assembled prompt + response + usage + latency) for trace/training
// export. nil → no recording (the default; zero overhead). Opt-in because it
// captures the full prompt on every call.
TurnRecorder TurnRecorder
// ProviderResolver, when set, maps the per-turn model to a distinct Provider
// (multi-provider config/models.yaml: a model referencing a named provider is
// served by that upstream). A model with no provider — or any resolution
// failure — falls back to Provider. nil → the single Provider is always used
// (unchanged single-provider behavior). Build it via NewProviderResolver.
ProviderResolver ProviderResolver
// MaxModelAttempts bounds how many distinct models a single turn may try
// before surfacing the error (the initial model plus cross-model fallbacks).
// Only relevant with a ModelSelector that opts into fallback — oss's
// CapabilityDefault never does. <=0 → default 3.
MaxModelAttempts int
// MaxTransientRetries bounds same-model backoff retries for a transient
// provider failure (rate_limit/server/overloaded/timeout/network) before
// escalating to a fallback model or surfacing the error. 0 → default 2;
// negative → disabled (surface immediately, the pre-safety-net behavior).
MaxTransientRetries int
// RetryBackoff returns the wait before transient retry attempt n (1-based).
// nil → an exponential default (250ms, doubling, capped at 8s). The wait is
// context-aware (a cancelled ctx aborts it).
RetryBackoff func(attempt int) time.Duration
// MaxToolIterations caps tool-call rounds per turn. <=0 defaults to 250.
MaxToolIterations int
// Steering delivers a user's mid-turn chat message into the turn already
// running on its thread, at the next input-assembly boundary. It must be the
// SAME inbox the runtime loop parks into. nil disables mid-turn delivery.
Steering *steering.Inbox
Streaming bool
// StreamFlushInterval coalesces streamed token deltas: deltas are buffered and
// emitted as one token_delta at most once per interval (always on the first
// delta; flushed before any other stream event and at finalize). This bounds
// the per-turn message rate so a fast stream stays under the gateway's
// per-identity quota. 0 (default) emits every delta immediately.
StreamFlushInterval time.Duration
// Memory integration (optional). When Memory is set: MemoryAutoCommit appends
// the turn's user+assistant messages to the thread; MemoryAutoRecall injects
// thread-scoped recalled memories into the turn context.
Memory MemoryService
MemoryAutoCommit bool
// MemoryAutoCommitAssistantOnly commits ONLY the assistant message on
// auto-commit. Use it when the host already persists the user message (e.g.
// the workclaw platform-server commits the inbound user turn before
// dispatching the chat task); committing it again here would duplicate the
// turn. Default false: commit user + assistant.
MemoryAutoCommitAssistantOnly bool
// MemoryAutoCommitAsync runs the post-turn commit in a detached goroutine so
// it never blocks turn completion. Use it when the commit transport may stall
// (e.g. a MemoryLayer ProxyHttp whose response does not yet route back through
// the sandbox relay — the write still lands, but the response wait would
// otherwise delay the turn). Only safe for long-lived runtimes (a CLI process
// could exit before the goroutine finishes). Default false: synchronous.
MemoryAutoCommitAsync bool
MemoryAutoRecall bool
MemoryRecallLimit int
MemoryRecallIncludeInput bool
// Daily-notes startup context. When DailyNotes is set, the first turn of a
// thread injects recent <DailyNotesDir>/memory/YYYY-MM-DD*.md as untrusted
// background. Now supplies the clock (defaults to time.Now).
DailyNotes bool
DailyNotesDir string
DailyNotesDays int
Now func() time.Time
// NewThreadID mints a local thread id for a new chat that arrived with no
// thread (and no ThreadRegistrar minted one). Defaults to a random hex id;
// override for deterministic tests.
NewThreadID func() string
// Commands holds discovered workspace slash commands. Reserved built-ins
// (/help, /commands, /clear) are always available; a nil registry just means
// no workspace commands.
Commands *commands.Registry
// ScheduledOperations serves the reserved /schedules and /runs built-ins.
// It is optional and normally configured only by an Aether worker host.
ScheduledOperations ScheduledOperationsCommandProvider
// RefinementAudit serves the reserved /refinements built-in. It is optional;
// local and MemoryLayer-backed stores implement the same bounded query shape.
RefinementAudit RefinementAuditCommandProvider
// ExecutionLedger is the optional branch-aware operational audit. It also
// persists /model pins and serves the model-free /ledger command. Local OSS
// and distributed Aether compositions implement the same neutral contract.
ExecutionLedger ExecutionLedger
// Approvers gate tool calls (first denial wins); Observers watch the tool
// lifecycle (no veto). Both are in-process today but the interfaces are
// transport-agnostic so an external (e.g. Aether-ACL) approver or an OTel
// observer can be plugged in later.
Approvers []hooks.ToolApprover
Observers []hooks.ToolObserver
// TurnObservers watch the TURN lifecycle (start, each model call, compaction,
// finish) — no veto. Peer to Observers (tool sub-lifecycle). A checkpoint/sync
// or audit backend implements hooks.TurnObserver; unset → the turn fires
// nothing. The distribution can also pass its command-hook Runtime here (it
// implements TurnObserver) to drive external UserPromptSubmit/PostCompact hooks.
TurnObservers []hooks.TurnObserver
// Authority derives each turn's OBO authority from the inbound message. Core
// leaves it nil; the distribution wires the grant extractor.
Authority AuthorityFunc
// DedupTrailingUserTurn drops a trailing user message from the loaded history
// when it matches the incoming turn (same id, else same non-empty text) before
// appending it. Use it when the host commits the inbound user turn to the store
// out-of-band before dispatching (so the fetched history can already end with
// it); the incoming turn is then kept as the canonical copy. Default false.
DedupTrailingUserTurn bool
// Approval flow (human-in-the-loop). When Approvals is set, a tool the policy
// gates with "requires approval" emits an approval_request part and blocks for
// an approve/deny control instead of erroring. ApprovalGranter records
// session/always grants; ApprovalTimeout bounds the wait (0 = until ctx);
// ApprovalScopes advertises offered scopes (default once/session/always).
Approvals approval.Awaiter
ApprovalGranter ApprovalGranter
ApprovalTimeout time.Duration
ApprovalScopes []string
// ToolPolicy gates PROVIDER-surfaced tools (e.g. discovered MCP tools) in the
// authorization pipeline: a default-trust provider tool is decided by the
// policy (Allow / Deny / RequiresApproval → prompt), positioned after the
// grant short-circuit (a pre-authorized/durably-granted tool skips it) and
// before the safety authorizer. Optional; nil → provider tools are ungated by
// policy (behavior unchanged). Local (static-registry) tools are NOT gated
// here — they keep the registry's own policy via session.InvokeTool.
ToolPolicy tools.Policy
// GrantStore lets the approval slow-path consult durable "always" grants
// before prompting: when a tool is already durably authorized for the
// workspace, the runner records a session grant and runs it without
// emitting an approval_request. Optional; nil → prompt every time (today's
// behavior). Typically the same store backing ApprovalGranter's
// GrantAlways persistence.
GrantStore tools.GrantStore
// SafetyAuthorizer is the pluggable safety slot in the tool authorization
// pipeline. It runs for EVERY tool (local + provider, including
// pre-authorized) and may Deny a call or escalate an Allow to a prompt.
// Optional; nil → a no-op that abstains (behavior unchanged — this is the
// future safety-classifier seam).
SafetyAuthorizer ToolAuthorizer
// ToolProviders are the generalized per-turn tool sources (unified discovery +
// routing). Each is queried every turn; provided tools merge into the turn's
// model-visible set and their invocations route back to the provider. Earlier
// entries win a name collision among providers; the static registry always
// wins over any provider. Optional; nil → providers-less.
ToolProviders []ToolProvider
// ToolSuggestionProviders contribute only dynamic prompt suggestions. They do
// not change the ordered model tool-schema array or receive direct invocation.
ToolSuggestionProviders []ToolSuggestionProvider
// ToolCatalog is the common protocol-backed operational catalog used for
// provider-surfaced tools. nil constructs an in-process standalone catalog,
// preserving dependency-free OSS operation. A distribution may supply an
// Aether-backed service instead.
ToolCatalog *catalog.LiveService
// ToolCatalogBinding derives trusted subject/context/policy-epoch state for
// catalog query and invocation. nil uses the turn's MemoryAuthority/address,
// with an explicit single-user fallback.
ToolCatalogBinding ToolCatalogBindingFunc
// Attachments, when set, resolves multimodal parts the provider cannot fetch
// itself (vfs_ref-only image/file parts) into a model-deliverable carrier,
// applied to the assembled request just before each provider call. Optional;
// nil → NoopAttachmentResolver (logs undeliverable attachments, resolves
// nothing). The sahara distribution wires a data-connectors-backed resolver.
Attachments AttachmentResolver
// Notifier is the ingress-write seam (channel.Enqueuer) a DETACHED background
// sub-agent uses to push its completion notice back to the parent thread, waking
// a fresh parent turn. Optional; nil → background sub-agents are unavailable and
// spawn_subagent(background:true) falls back to a synchronous run. The web/tui
// channels satisfy it directly; the Aether transport by messaging the parent.
Notifier channel.Enqueuer
// MaxBackgroundSubagents caps concurrent detached background children (excess
// spawns queue on the semaphore). <=0 → default 4.
MaxBackgroundSubagents int
// StreamBackgroundSubagents, when true, streams a background child's activity via
// the Publisher keyed to the child thread id (a client can subscribe to that
// thread to render it). Default false: background children run silently (like
// synchronous sub-agents). Independent of the child's durable commit either way.
StreamBackgroundSubagents bool
// StreamSubagents, when true, streams synchronous child activity via the
// Publisher under the child thread id. Interactive clients can associate it
// with the blocking parent spawn by TaskID. Default false.
StreamSubagents bool
// AuthHandoff hands the parent turn's OBO authority to the woken completion turn
// via a single-use token carried on the notice (the credential never rides the
// message). Optional; nil → a Store is created. The runner consumes the token
// before applying the Authority hook; a distribution may also pre-resolve it
// onto the context while routing across workspace-specific runners.
AuthHandoff *authhandoff.Store
// SubagentObserver receives best-effort authoritative lifecycle transitions
// for durable registry/snapshot projection. Observation failures are logged
// and never fail the child turn itself.
SubagentObserver subagent.LifecycleObserver
// SubagentDefaultWorkspace resolves registry identity when legacy local turns
// omit workspace on their address. It is separate from DefaultWorkspaceID so
// legacy unscoped transcript storage need not move on disk.
SubagentDefaultWorkspace string
// SubagentTasks optionally makes a durable task backend the execution
// authority for child admission, running, terminal state, and restart
// reconciliation. The lifecycle observer remains the session snapshot
// projection. Nil preserves the independently useful local runner.
SubagentTasks subagent.TaskBackend
// SubagentExecutionScopeAuthorizer is called only for a requested binding
// change or write-access expansion. Nil keeps OSS fail-closed while exact
// inheritance and read-only narrowing remain independently useful.
SubagentExecutionScopeAuthorizer subagent.ExecutionScopeAuthorizer
// TurnJournal durably checkpoints parent model/tool execution. It is optional
// so embedders retain the prior in-memory behavior; when set, OwnerIdentity is
// required and active records are scoped to that stable runtime identity.
TurnJournal turnjournal.Store
TurnOwnerIdentity string
// Rubric, when set, runs an OPT-IN post-turn self-grading verifier: after a
// turn finishes, an independent grader checks the just-produced result against
// a declarative rubric and, if it needs revision, enqueues an actionable
// revision follow-up (bounded by its MaxAttempts). Optional; nil → no grading
// (default, behavior unchanged). The runner invokes AfterTurn at end-of-turn
// when it is set.
Rubric *RubricVerifier
// Goals, when set, accounts finalized turns against durable goals and asks a
// host-owned bounded policy whether to enqueue another turn. If Rubric is also
// set it is used as the goal verifier; Rubric's standalone retry loop runs only
// on turns not associated with a goal.
Goals *goal.Runtime
// ContextDecorator, when set, wraps the incoming turn ctx once at the start of
// Run (before the tool loop), keyed off the resolved address. The ACP channel
// uses it to attach per-session fs/terminal client delegates (tools.With*
// Delegate) so the harness file/shell tools route through the editor. Optional;
// nil → the ctx is untouched and turn behavior is byte-for-byte unchanged.
ContextDecorator func(ctx context.Context, addr protocol.MessageAddress) context.Context
}
type ContextManager ¶
type ContextManager interface {
Build(ctx context.Context, bootstrap []bootstrap.File, history []protocol.ChatMessage) ([]protocol.ChatMessage, error)
}
ContextManager assembles the model-visible context for a turn from the bootstrap files and thread history. Implementations may compact, summarize, or augment (e.g. MemoryLayer-backed RAG) however they like. The default is contextpack.Assembler, which satisfies this interface as-is.
type ContextPreparer ¶ added in v0.0.2
ContextPreparer is an optional turn-scoped companion to ContextManager. It resolves authoritative prompt inputs once after workspace and OBO authority are installed, keeping all model calls/retries within a turn deterministic.
type EventPublisher ¶
EventPublisher is the egress half of the transport seam (channel.Publisher).
type ExecutionLedger ¶ added in v0.0.2
type ExecutionLedger interface {
RunExecutionLedgerCommand(
ctx context.Context,
addr protocol.MessageAddress,
user protocol.ChatMessage,
args string,
) (string, error)
PinnedModel(ctx context.Context, addr protocol.MessageAddress) (string, error)
PinModel(ctx context.Context, addr protocol.MessageAddress, model string) error
}
ExecutionLedger is the runner-facing composition seam for branch-aware operational history. Turn lifecycle writes arrive separately through the ordinary hooks.TurnObserver interface; this surface owns operator reads and restart-stable per-thread model pins.
type Grader ¶
type Grader interface {
Grade(ctx context.Context, req RubricRequest) (RubricVerdict, error)
}
Grader evaluates a rubric request and returns a verdict. Injected so tests use a fake and production spawns an independent grader (e.g. a sub-agent).
type LLMCallRecord ¶
type LLMCallRecord struct {
Timestamp string `json:"ts,omitempty"`
Thread string `json:"thread,omitempty"`
Model string `json:"model"`
Messages []protocol.ChatMessage `json:"messages"`
Response protocol.ChatMessage `json:"response"`
Usage provider.Usage `json:"usage"`
LatencyMS int64 `json:"latency_ms"`
}
LLMCallRecord captures one provider call for trace export / training data: the assembled request the model actually saw (post context-assembly, compaction, and attachment resolution) plus its response, token usage, and latency.
type MemoryProviderNamer ¶ added in v0.0.2
type MemoryProviderNamer interface {
MemoryProviderName() string
}
MemoryProviderNamer optionally gives transparency events a stable backend name without coupling the turn core to a concrete memory implementation.
type MemoryService ¶
type MemoryService interface {
Recall(ctx context.Context, auth tools.MemoryAuthority, workspace, query string, limit int) ([]tools.MemoryHit, error)
// ownership selects the storage workspace: "" / "user" folds into the
// backend's user-chat home; "workspace" homes the messages under workspace.
AppendThreadMessages(ctx context.Context, auth tools.MemoryAuthority, workspace, threadID, ownership string, messages []protocol.ChatMessage) error
}
MemoryService is the optional MemoryLayer integration: recall for the auto-injection hook, and append for the auto-commit write path. Workspace + thread are passed per call (not baked into the client) so it is safe to share across concurrent turns.
type NoopAttachmentResolver ¶
type NoopAttachmentResolver struct{}
NoopAttachmentResolver is the default resolver: it returns the messages unchanged and logs any image/file part that has no model-deliverable carrier (vfs_ref-only or empty). The harness core ships no VFS resolver, so this is the canonical "attachment dropped" log — it makes a missing attachment visible even when the distribution wires no real resolver.
func (NoopAttachmentResolver) Resolve ¶
func (NoopAttachmentResolver) Resolve(ctx context.Context, _ protocol.MessageAddress, messages []protocol.ChatMessage) ([]protocol.ChatMessage, error)
type Provider ¶
type Provider interface {
Chat(ctx context.Context, req provider.ChatRequest) (provider.ChatResponse, error)
}
type ProviderAuthResolver ¶ added in v0.0.2
type ProviderAuthResolver interface {
Authenticator(kind, profile string) (llmclient.Authenticator, error)
}
NewProviderResolver builds a ProviderResolver over a model.Registry. It returns nil when reg is nil (the caller then leaves Config.ProviderResolver unset and behavior is unchanged single-provider). def supplies fallback fields (BaseURL, Format, and API key/keyenv) for a provider config that omits them; guard is applied to every constructed provider (e.g. provider.SandboxGuard). Clients are constructed lazily on first use and cached per provider name (thread-safe).
type ProviderResolver ¶
ProviderResolver maps a per-turn model name to the Provider that should serve it, so a single runner can fan out to multiple upstreams (config/models.yaml providers). (_, false) means "no per-model provider — use the runner's default provider", which is also the answer for a nil resolver, a bare model (no provider declared), an unknown provider name, or a construction failure. It never fails the turn: a resolution problem always degrades to the default.
func NewProviderResolver ¶
func NewProviderResolver(reg *modelpkg.Registry, def modelpkg.ProviderConfig, guard provider.Guard, options ...ProviderResolverOption) ProviderResolver
type ProviderResolverOption ¶ added in v0.0.2
type ProviderResolverOption func(*providerResolver)
func WithProviderAuthResolver ¶ added in v0.0.2
func WithProviderAuthResolver(resolver ProviderAuthResolver) ProviderResolverOption
type ProviderRetryPayload ¶ added in v0.0.2
type ProviderRetryPayload struct {
Attempt int `json:"attempt"`
RetryInMS int64 `json:"retry_in_ms"`
Strategy string `json:"strategy"`
FailureKind string `json:"failure_kind,omitempty"`
Model string `json:"model,omitempty"`
NextModel string `json:"next_model,omitempty"`
Message string `json:"message"`
}
ProviderRetryPayload is the dynamic part's body. Milliseconds (not a duration string) because the consumer is a countdown timer.
type RefinementAuditCommandProvider ¶ added in v0.0.2
type RefinementAuditCommandProvider interface {
RunRefinementAuditCommand(
ctx context.Context,
addr protocol.MessageAddress,
user protocol.ChatMessage,
args string,
) (string, error)
}
RefinementAuditCommandProvider is the authority-owned, model-free browsing seam behind /refinements. The full inbound message remains available to a composition policy; authority-aware stores read the OBO context installed by Runner before command resolution.
type RubricRequest ¶
RubricRequest is what the grader receives: the rubric criteria plus a fully constructed grader Prompt whose UNTRUSTED transcript is wrapped in a nonce-bracketed delimiter (Nonce echoes the delimiter's nonce so a caller can verify the framing). The prompt is self-contained — a grader can pass it straight to a model.
type RubricVerdict ¶
type RubricVerdict struct {
Satisfied bool `json:"satisfied"`
Feedback string `json:"feedback,omitempty"`
FailedCriteria []string `json:"failed_criteria,omitempty"`
}
RubricVerdict is the grader's judgement. A Satisfied==true verdict that still names FailedCriteria is structurally invalid (a contradiction); AfterTurn's guard treats it as needs-revision rather than trusting the contradictory pass.
type RubricVerifier ¶
type RubricVerifier struct {
// Criteria is the rubric — the SOLE definition of "done" the grader judges
// against. Free-form lines ("the answer cites its sources", "tests pass", …).
Criteria []string
// Grader is the injected grading backend (a fake in tests; a spawned grader
// sub-agent in production). Required — a nil Grader makes AfterTurn a no-op.
Grader Grader
// Enqueuer pushes the revision follow-up turn back to the thread. Optional;
// nil → AfterTurn only reports the verdict (no automatic retry).
Enqueuer channel.Enqueuer
// MaxAttempts bounds how many revision follow-ups a thread may receive so a
// stubborn turn can't loop forever. <=0 → defaultRubricMaxAttempts.
MaxAttempts int
}
RubricVerifier is an OPT-IN post-turn self-grading verifier (ported from langchain-deepagents' RubricMiddleware). After a turn finishes, an INDEPENDENT grader evaluates the just-produced result against a declarative rubric ("what does done look like"); if it needs revision, an actionable revision follow-up is enqueued back to the same thread so the agent tries again. It never mutates the turn's inner tool loop — it is a pure post-turn hook. Attach it via Config.Rubric; the runner calls AfterTurn at end-of-turn when it is set.
func (*RubricVerifier) AfterTurn ¶
func (v *RubricVerifier) AfterTurn(ctx context.Context, addr protocol.MessageAddress, transcript []protocol.ChatMessage) (RubricVerdict, error)
AfterTurn grades the just-produced transcript against the rubric and, when it needs revision and an Enqueuer is wired, enqueues a bounded revision follow-up to the same thread. It returns the (guard-adjusted) verdict. Best-effort: the runner logs an error and never fails the turn on it. A nil verifier or nil Grader is a no-op (returns a satisfied verdict).
func (*RubricVerifier) VerifyGoal ¶ added in v0.0.2
func (v *RubricVerifier) VerifyGoal(ctx context.Context, request goal.VerificationRequest) (goal.VerificationResult, error)
VerifyGoal adapts the rubric grader to the generic goal verifier without invoking RubricVerifier's own enqueue loop. The durable goal runtime owns the single bounded continuation decision and ledger.
type Runner ¶
type Runner struct {
// contains filtered or unexported fields
}
func (*Runner) AcknowledgeTaskTerminal ¶ added in v0.0.2
AcknowledgeTaskTerminal closes the journal-side outbox record only after the external task lifecycle decorator (or startup reconciler) has confirmed the authoritative task terminal state. It is idempotent for a concurrently acknowledged record and a no-op when this runner did not journal the turn.
func (*Runner) ActiveModelName ¶
ActiveModelName reports the model selected for threadID.
func (*Runner) ActiveReasoningEffort ¶ added in v0.0.2
ActiveReasoningEffort reports the effective effort for a thread's active model. Empty means the request omits the setting and lets the provider decide.
func (*Runner) ActiveTurnExecutions ¶ added in v0.0.2
ActiveTurnExecutions returns the deterministic owner-scoped startup scan. Hosts with workspace routing can use each record's WorkspaceID to select the same runner/catalog that owned the original turn.
func (*Runner) AvailableCommands ¶
AvailableCommands returns runner-owned slash commands for command palettes. The returned slice is detached from the registry and safe for callers to sort or filter.
func (*Runner) ExecuteAssignedSubagent ¶ added in v0.0.2
func (r *Runner) ExecuteAssignedSubagent(ctx context.Context, taskID string, envelope subagent.ExecutionEnvelope, req subagent.Request) (subagent.Result, error)
ExecuteAssignedSubagent runs an already-admitted child without re-entering admission or mutating its Aether task. The assignment owner must claim before calling and must perform the terminal task transition after this returns.
func (*Runner) InterruptTurn ¶ added in v0.0.2
InterruptTurn durably closes an active execution that its host has proven cannot be resumed (for example, the authoritative parent task is already terminal). It never changes a terminal record.
func (*Runner) ResumeTurn ¶ added in v0.0.2
func (r *Runner) ResumeTurn(ctx context.Context, workspaceID, taskID string) (protocol.ChatMessage, error)
ResumeTurn resumes one journaled parent turn. Only an admitted external child (or its already-persisted result) is recoverable. Every other active phase is durably interrupted and returned as ErrRecoveryUnsafe.
func (*Runner) Run ¶
func (r *Runner) Run(ctx context.Context, addr protocol.MessageAddress, user protocol.ChatMessage) (_ protocol.ChatMessage, err error)
func (*Runner) RunSubagent ¶
func (r *Runner) RunSubagent(ctx context.Context, req subagent.Request) (_ subagent.Result, err error)
RunSubagent runs a bounded sub-agent turn on its OWN durable, persisted child thread and returns its final text plus the re-addressable thread handle. It reuses the runner's tools, bootstrap loader, context manager, and model. A new child thread is minted by an optional ThreadRegistrar and its canonical id is adopted; without one it falls back to "<parentThread>::sub::<seq>". Passing Request.ResumeThreadID instead CONTINUES that existing child thread (resume continuity comes for free from the injected store's LoadHistory cold-load). The child thread's task message back-references the spawning message via MessageRef{parent_thread_id, parent_message_id}, so the sub-agent is durable, recallable (a thread query), and re-addressable. The parent's OBO grant is carried through so tools act on behalf of the same principal; no stream egress (the sub-agent is internal) and no auto-recall (resume covers continuity).
This is the in-process backend for the spawn_subagent tool; the subagent.Runner interface lets an Aether-task backend replace it later.
func (*Runner) StartBackground ¶
StartBackground runs a sub-agent DETACHED: it resolves (or resumes) the child thread, launches the run on a cancellation-independent context (so it survives the spawning parent turn ending, mirroring the async memory-commit path), and returns the child thread handle immediately. On completion it pushes a notice back to the parent thread (via the Notifier) that wakes a fresh parent turn, so the parent can react to the actual outcome. Concurrent detached children are capped by bgSem. Returns ErrBackgroundUnsupported when no Notifier is wired.
func (*Runner) SteeringInbox ¶ added in v0.0.2
SteeringInbox returns the inbox this executor drains between model actions. Runtime ingress must park into this exact instance.
type ScheduledOperationsCommandProvider ¶ added in v0.0.2
type ScheduledOperationsCommandProvider interface {
RunScheduledOperationsCommand(
ctx context.Context,
addr protocol.MessageAddress,
user protocol.ChatMessage,
name string,
args string,
) (string, error)
}
ScheduledOperationsCommandProvider is the worker-authoritative operations seam behind /schedules and /runs. The complete inbound message is retained so enterprise hosts can authorize inspection from its authenticated/OBO context; OSS uses a single-user adapter. A nil provider keeps Aether-free runtimes independent and makes the commands report that distributed scheduling is not configured.
type StreamingProvider ¶
type StreamingProvider interface {
ChatStream(ctx context.Context, req provider.ChatRequest, onDelta provider.DeltaFunc) (provider.ChatResponse, error)
}
StreamingProvider is an optional capability: providers that implement it can stream text tokens (delivered to onDelta) while assembling the final message.
type ThreadRegistrar ¶
type ThreadRegistrar interface {
EnsureThread(ctx context.Context, auth tools.MemoryAuthority, spec ThreadSpec) (threadID string, err error)
}
ThreadRegistrar is an OPTIONAL capability a MemoryService (or any backend) may implement to durably DECLARE (and, when asked, MINT) a thread plus its parent relationship, separate from the per-message MessageRef the commit carries. The turn runner calls EnsureThread with an empty ThreadID when a backend-owned id is needed: for a NEW sub-agent child and for a new chat that arrived with no thread. The returned id is canonical and MUST be adopted by the caller. A non-empty ThreadID remains available for backends that can idempotently create-or-get caller-owned ids, but MemoryLayer intentionally owns its thread ids and ignores supplied ids. The capability is best-effort: when absent or unavailable, the runner mints a local id and retains message-level linkage. oss owns this abstraction + the call sites; MemoryLayer-backed distributions implement it against chat_threads.
type ThreadSpec ¶
type ThreadSpec struct {
WorkspaceID string
ThreadID string
ParentThreadID string
// Origin records what created the thread (e.g. "subagent"); a hint only.
Origin string
// Ownership selects where the thread is stored: "" / "user" folds into the
// backend's user-chat home; "workspace" homes the thread under WorkspaceID
// (workspace-homed threads). A hint the registrar MAY honor.
Ownership string
}
ThreadSpec describes a thread to declare durably, independent of its message content. ParentThreadID is the load-bearing hierarchy field. ThreadID is optional: empty asks the backend to mint a canonical id; non-empty asks a backend that supports caller-owned ids to ensure that id exists. The remaining fields are optional hints a backend MAY persist (e.g. as thread metadata) or ignore. It is a struct — not a fixed argument list — so the seam can grow (title, tags, expiry…) without breaking implementers: oss is meant to be a base for backends beyond MemoryLayer.
type ToolAuthorizer ¶
type ToolAuthorizer interface {
Authorize(ctx context.Context, in AuthzInput) AuthzDecision
}
ToolAuthorizer decides whether a tool call may proceed. Authorizers compose in a pipeline (see resolveAuthz): a plug-in safety classifier, the grant short-circuit, and the interactive prompt are all ToolAuthorizers.
type ToolCatalogBindingFunc ¶ added in v0.0.2
type ToolCatalogBindingFunc func( ctx context.Context, addr protocol.MessageAddress, user protocol.ChatMessage, catalogContext spec.ToolCatalogContext, ) (catalog.QueryBinding, error)
ToolCatalogBindingFunc derives the trusted subject and policy epoch for one turn-confined catalog context. The returned binding must carry catalogContext exactly. Hosts use this seam to bind discovery and invocation to their own authenticated identity/ACL epoch; nil selects the OSS single-user default.
type ToolProvider ¶
type ToolProvider interface {
ID() string
Tools(ctx context.Context, addr protocol.MessageAddress, user protocol.ChatMessage) ([]tools.Descriptor, error)
Invoke(ctx context.Context, req tools.Request) (tools.Result, error)
}
ToolProvider is the generalized per-turn tool source: it discovers tools for a turn (Tools) and services their invocations (Invoke). The runner queries every configured provider each turn to assemble the model-visible tool list, then routes an invocation of any provided tool (one not in the static registry) back to the provider that surfaced it. ID names the provider for logs/telemetry. Static-registry specs win a name collision; among providers, earlier-in-list wins. Tools is best-effort — a provider error is logged and skipped (static-only continues). Discovery is best-effort: an error degrades the turn to the static tool set. The per-turn OBO authority is on ctx (tools.MemoryAuthorityFrom) and on req.Authority.
type ToolSuggestionProvider ¶ added in v0.0.2
type ToolSuggestionProvider interface {
ID() string
Suggestions(ctx context.Context, addr protocol.MessageAddress, user protocol.ChatMessage) ([]tools.Descriptor, error)
}
ToolSuggestionProvider discovers names and descriptions for the dynamic system-prompt suffix without adding full schemas to the provider tool array.
func SuggestionsFromToolProvider ¶ added in v0.0.2
func SuggestionsFromToolProvider(provider ToolProvider) ToolSuggestionProvider
SuggestionsFromToolProvider adapts an existing discovery source for stable schema mode. Suggested tools remain invocable only through stable meta-tools.
type TurnRecorder ¶
type TurnRecorder interface {
RecordLLMCall(ctx context.Context, rec LLMCallRecord) error
}
TurnRecorder receives one record per successful provider call — the seam for trace / training-data capture. Implementations must be safe for concurrent use (sub-agents run turns in parallel) and should be cheap/non-blocking.
Wiring one is OPT-IN: the core leaves it nil so recording is a no-op with zero overhead. It captures the FULL assembled prompt on every call, which is only worth its cost when explicitly collecting data — so the reference CLI gates it behind a --record flag and distributions wire it only when a pipeline consumes it.
Source Files
¶
- attachments.go
- authz.go
- command.go
- command_catalog.go
- ephemeral.go
- errors.go
- model_command.go
- multimodal_summary.go
- parallel_tools.go
- part_emitter.go
- provider_recovery.go
- provider_resolver.go
- reasoning_command.go
- recorder.go
- rubric.go
- runner.go
- steering.go
- stream.go
- subagent.go
- subagent_checkpoint.go
- tool_approval.go
- tool_catalog.go
- tool_events.go
- tool_loop.go
- turn_context_helpers.go
- turn_execution.go
- turn_recovery.go
- usage.go
- worldstate_sink.go