agent

package
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 64 Imported by: 0

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrDepthLimitExceeded = errors.New("sub-turn depth limit exceeded")
	ErrConcurrencyTimeout = errors.New("timeout waiting for concurrency slot")
)
View Source
var ErrToolsRequired = errors.New(
	"The selected model doesn't support tool calls, and this agent requires them. Choose a model that supports tool calling.",
)

ErrToolsRequired ends a turn whose model rejects tool calls when the agent's AGENT.md sets requireTools: true. Without that key the turn retries without tools instead.

Functions

func EstimateMessageTokens

func EstimateMessageTokens(msg providers.Message) int

EstimateMessageTokens estimates the token count for a single message. Delegates to the shared tokenizer package for consistency across agent and seahorse.

func EstimateToolDefsTokens

func EstimateToolDefsTokens(defs []providers.ToolDefinition) int

EstimateToolDefsTokens estimates the total token cost of tool definitions as they appear in the LLM request. Delegates to the shared tokenizer package.

func RegisterBuiltinHook

func RegisterBuiltinHook(name string, factory BuiltinHookFactory) error

RegisterBuiltinHook registers a named in-process hook factory for config-driven mounting.

func RegisterContextManager

func RegisterContextManager(name string, factory ContextManagerFactory) error

RegisterContextManager registers a named ContextManager factory.

func SpawnSubTurn

func SpawnSubTurn(ctx context.Context, cfg SubTurnConfig) (*tools.ToolResult, error)

SpawnSubTurn is the exported entry point for tools to spawn sub-turns. It retrieves AgentLoop and parent turnState from context and delegates to spawnSubTurn.

func TurnStateFromContext

func TurnStateFromContext(ctx context.Context) *turnState

TurnStateFromContext retrieves turnState from context (exported for tools)

func WithAgentLoop

func WithAgentLoop(ctx context.Context, al *AgentLoop) context.Context

WithAgentLoop injects AgentLoop into context for tool access

Types

type ActiveTurnInfo

type ActiveTurnInfo struct {
	TurnID       string
	AgentID      string
	SessionKey   string
	Channel      string
	ChatID       string
	UserMessage  string
	Phase        TurnPhase
	Iteration    int
	StartedAt    time.Time
	Depth        int
	ParentTurnID string
	ChildTurnIDs []string
}

type AgentContextDefinition

type AgentContextDefinition struct {
	Agent *AgentPromptDefinition `json:"agent,omitempty"`
	Soul  *SoulDefinition        `json:"soul,omitempty"`
	User  *UserDefinition        `json:"user,omitempty"`
}

AgentContextDefinition captures the workspace agent definition in a runtime-friendly shape.

type AgentDescriptor

type AgentDescriptor struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description"`
}

AgentDescriptor is the structured discovery payload injected into each agent's system prompt so the LLM can choose a peer by identity.

type AgentFrontmatter

type AgentFrontmatter struct {
	Name        string         `json:"name"`
	Description string         `json:"description"`
	Tools       []string       `json:"tools,omitempty"`
	Model       string         `json:"model,omitempty"`
	Skills      []string       `json:"skills,omitempty"`
	MCPServers  []string       `json:"mcpServers,omitempty"`
	Fields      map[string]any `json:"-"`
}

AgentFrontmatter holds machine-readable AGENT.md configuration.

Known fields are exposed directly for convenience. Fields keeps the full parsed frontmatter so future refactors can read additional keys without changing the loader contract again.

type AgentInstance

type AgentInstance struct {
	ID   string
	Name string
	// Model is the agent's model selection — an exact target
	// "instance-id/model-id" or a model route name — taken from its AGENT.md
	// frontmatter, its agents.list entry or agents.defaults.model_name, in
	// that order. Empty when none is set.
	Model     string
	Workspace string
	// ModuleSummaries are one-line capability summaries for each enabled
	// module, composed into the system prompt so the agent knows what its
	// installed modules can do without loading their full documentation.
	ModuleSummaries []string
	// ModuleKnowledge composes a selected module's overlay and skills for one
	// turn. It is set at init, where module discovery already happens, so a
	// turn never pays to re-describe every installed module.
	//
	// nil when no module is installed, and it returns empty for a turn with no
	// selection -- an installed module costs one line of capability summary
	// until someone points the agent at it.
	ModuleKnowledge func(moduleID string) (overlays string, skills string, warnings []string)
	MaxIterations   int
	MaxTokens       int
	Temperature     float64
	// ThinkingLevel is the extended-thinking level of the agent's first
	// target, from its instance's runtime settings.
	ThinkingLevel             ThinkingLevel
	ThinkingLevelConfigured   bool
	ContextWindow             int
	SummarizeMessageThreshold int
	SummarizeTokenPercent     int
	// Provider is the provider of Candidates[0]; nil when the agent has no
	// model.
	Provider           providers.LLMProvider
	Sessions           session.SessionStore
	ContextBuilder     *ContextBuilder
	Tools              *tools.ToolRegistry
	Definition         AgentContextDefinition
	Subagents          *config.SubagentsConfig
	SkillsFilter       []string
	MCPServerAllowlist map[string]struct{}
	// Candidates are the targets Model resolved to, in failover order: one
	// for an exact target, the route's targets for a route. Empty when the
	// agent has no model.
	Candidates []providers.FallbackCandidate
	// ImageCandidates are the targets of agents.defaults.image_model, which
	// turns carrying images are rerouted to.
	ImageCandidates []providers.FallbackCandidate

	// Router is non-nil when model routing is configured and the light model
	// resolved. It scores each incoming message and decides whether to route
	// to LightCandidates or stay with Candidates.
	Router *routing.Router
	// LightCandidates are the targets of the routing light model selection,
	// resolved at agent creation.
	LightCandidates []providers.FallbackCandidate
	// LightProvider is the provider of LightCandidates[0]. It is only used
	// when routing selects the light tier for a turn.
	LightProvider providers.LLMProvider
	// CandidateProviders and CandidateConfigs hold the provider and the
	// request spec of every candidate above, keyed by
	// FallbackCandidate.StableKey(). A request spec carries the target
	// instance's runtime settings: thinking level, streaming, RPM, timeouts.
	CandidateProviders map[string]providers.LLMProvider
	CandidateConfigs   map[string]*providers.CallSpec
	// contains filtered or unexported fields
}

AgentInstance represents a fully configured agent with its own workspace, session manager, context builder, and tool registry.

func NewAgentInstance

func NewAgentInstance(
	agentCfg *config.AgentConfig,
	defaults *config.AgentDefaults,
	cfg *config.Config,
	provider providers.LLMProvider,
	resolve ModelResolver,
) *AgentInstance

NewAgentInstance creates an agent instance from config. The agent runs on its model selection (see AgentInstance.Model), resolved through resolve; when provider is not nil it serves the default selection instead, with no resolution. A nil resolve uses the default model resolver.

func (*AgentInstance) AllowsMCPServer

func (a *AgentInstance) AllowsMCPServer(serverName string) bool

func (*AgentInstance) Close

func (a *AgentInstance) Close() error

Close releases resources held by the agent's providers and session store. Providers a running turn still uses close when that turn ends.

type AgentLoop

type AgentLoop struct {
	// contains filtered or unexported fields
}

func AgentLoopFromContext

func AgentLoopFromContext(ctx context.Context) *AgentLoop

AgentLoopFromContext retrieves AgentLoop from context

func NewAgentLoop

func NewAgentLoop(
	cfg *config.Config,
	msgBus *bus.MessageBus,
	provider providers.LLMProvider,
	opts ...AgentLoopOption,
) *AgentLoop

NewAgentLoop creates the agent loop over cfg. Each agent runs on its model selection, resolved through the loop's model resolver (see WithModelResolver). provider, when not nil, serves the default selection instead, as a single candidate and with no resolution — the seam embedders and tests inject a provider through. An agent whose selection is empty or does not resolve has no model: its turns without a per-message selection fail with a no-model error.

Example

ExampleNewAgentLoop runs one turn on the models connected in Compa.

package main

import (
	"context"
	"fmt"
	"path/filepath"
	"time"

	"github.com/kayiik/compa/pkg/agent"
	"github.com/kayiik/compa/pkg/bus"
	"github.com/kayiik/compa/pkg/config"
	"github.com/kayiik/compa/pkg/session"
)

func main() {
	// The config the Compa web UI saves: $COMPA_HOME/config.json, by default
	// ~/.compa/config.json. A missing file loads the defaults, which connect
	// no provider.
	cfg, err := config.LoadConfig(filepath.Join(config.GetHome(), "config.json"))
	if err != nil {
		fmt.Println("load config:", err)
		return
	}

	// Optional: run on another model than the default model.
	// cfg.Agents.Defaults.ModelName = "openai/gpt-5.4" // exact target: instance-id/model-id
	// cfg.Agents.Defaults.ModelName = "everyday"       // or the name of a model route

	msgBus := bus.NewMessageBus()
	defer msgBus.Close()

	// A nil provider resolves the model selection against the providers
	// connected in Compa.
	loop := agent.NewAgentLoop(cfg, msgBus, nil)
	defer loop.Close()

	if _, err := loop.DefaultModelStatus(); err != nil {
		fmt.Println(err) // for example: no default model is selected
		return
	}

	// Turns with the same key share one conversation. The loop keeps a key
	// the caller supplies only when it is opaque, so build it with
	// session.BuildOpaqueSessionKey.
	sessionKey := session.BuildOpaqueSessionKey("my-app:demo")

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()

	reply, err := loop.ProcessDirect(ctx, "Say hello in five words.", sessionKey)
	if err != nil {
		fmt.Println("turn failed:", err)
		return
	}
	fmt.Println(reply)
}

func (*AgentLoop) Check

func (al *AgentLoop) Check(ctx context.Context, call approval.Call) error

Check is the agent loop's approval.Gate, for the calls made outside a turn such as a scheduled command: the approval policy decides them as calls from their origin, cron unless the call says otherwise. An ask goes to the registered approvers, or with none to the owner, in the owner's chat.

func (*AgentLoop) Close

func (al *AgentLoop) Close()

Close releases resources held by agent session stores. Call after Stop.

func (*AgentLoop) Continue

func (al *AgentLoop) Continue(ctx context.Context, sessionKey, channel, chatID string) (string, error)

Continue resumes an idle agent by dequeuing any pending steering messages and running them through the agent loop. This is used when the agent's last message was from the assistant (i.e., it has stopped processing) and the user has since enqueued steering messages.

If no steering messages are pending, it returns an empty string.

func (*AgentLoop) DefaultModelStatus

func (al *AgentLoop) DefaultModelStatus() (string, error)

DefaultModelStatus reports the default agent's model selection and, when the agent has no model to run on, why: every turn without a per-message selection then fails with that error.

func (*AgentLoop) GetActiveTurnBySession

func (al *AgentLoop) GetActiveTurnBySession(sessionKey string) *ActiveTurnInfo

func (*AgentLoop) GetConfig

func (al *AgentLoop) GetConfig() *config.Config

func (*AgentLoop) GetRegistry

func (al *AgentLoop) GetRegistry() *AgentRegistry

func (*AgentLoop) GetStartupInfo

func (al *AgentLoop) GetStartupInfo() map[string]any

func (*AgentLoop) HardAbort

func (al *AgentLoop) HardAbort(sessionKey string) error

HardAbort immediately cancels the running agent loop for the given session, cascading the cancellation to all child SubTurns. This is a destructive operation that terminates execution without waiting for graceful cleanup.

Use this when the user explicitly requests immediate termination (e.g., "stop now", "abort"). To redirect a running turn instead, deliver another inbound message for the session: the agent loop queues it as steering for the active turn.

func (*AgentLoop) MountHook

func (al *AgentLoop) MountHook(reg HookRegistration) error

MountHook registers an in-process hook on the agent loop.

func (*AgentLoop) MountProcessHook

func (al *AgentLoop) MountProcessHook(ctx context.Context, name string, opts ProcessHookOptions) error

func (*AgentLoop) ProcessDirect

func (al *AgentLoop) ProcessDirect(
	ctx context.Context,
	content, sessionKey string,
) (string, error)

ProcessDirect runs content as a turn of the terminal's user, the owner: the CLI's direct and interactive modes.

func (*AgentLoop) ProcessDirectWithChannel

func (al *AgentLoop) ProcessDirectWithChannel(
	ctx context.Context,
	content, sessionKey, channel, chatID string,
) (string, error)

ProcessDirectWithChannel runs a scheduled job's message as a turn for the chat it names (cron's JobExecutor). The message is stored text replayed on every run, so slash commands in it never run.

func (*AgentLoop) ProcessHeartbeat

func (al *AgentLoop) ProcessHeartbeat(
	ctx context.Context,
	content, channel, chatID string,
) (string, error)

func (*AgentLoop) ProcessInstanceSelection

func (al *AgentLoop) ProcessInstanceSelection(
	ctx context.Context,
	selection string,
	messages []providers.Message,
	tools []providers.ToolDefinition,
	options map[string]any,
	onChunk func(string),
) (*InstanceSelectionResult, error)

ProcessInstanceSelection invokes one explicit instance target or named route with the given messages, outside any turn or session: the selection resolves against the loop's current config, its targets run in order through core's candidate executor (providers.Failover), and a streaming target that fails after visible output does not fall over.

func (*AgentLoop) PublishResponseIfNeeded

func (al *AgentLoop) PublishResponseIfNeeded(ctx context.Context, channel, chatID, sessionKey, response string)

PublishResponseIfNeeded sends response to the chat, unless the session's agent already wrote to it with its message tool: the cron tool's reply.

func (*AgentLoop) RecordLastChannel

func (al *AgentLoop) RecordLastChannel(channel string) error

func (*AgentLoop) RecordLastChatID

func (al *AgentLoop) RecordLastChatID(chatID string) error

func (*AgentLoop) RegisterExtensionTool

func (al *AgentLoop) RegisterExtensionTool(tool tools.Tool)

RegisterExtensionTool registers a tool contributed from outside Compa, such as a module's, on every agent. It never takes the place of a registered tool, and a built-in tool registered later under its name replaces it.

func (*AgentLoop) RegisterTool

func (al *AgentLoop) RegisterTool(tool tools.Tool)

func (*AgentLoop) ReloadProviderAndConfig

func (al *AgentLoop) ReloadProviderAndConfig(
	ctx context.Context,
	provider providers.LLMProvider,
	cfg *config.Config,
) error

ReloadProviderAndConfig atomically swaps the config and rebuilds every agent from it: each agent resolves its model selection anew, so a changed default model, route or provider instance takes effect. provider, when not nil, serves the default selection instead of a resolution, as in NewAgentLoop. It uses a context to allow timeout control from the caller. Returns an error if the reload fails or context is canceled.

func (*AgentLoop) Run

func (al *AgentLoop) Run(ctx context.Context) error

Run processes inbound messages until ctx ends or Stop is called, and then returns nil. It returns an error only when it cannot process messages at all. Hooks and MCP servers that fail to start do not stop it: their failures are logged.

func (*AgentLoop) RuntimeEventBus

func (al *AgentLoop) RuntimeEventBus() runtimeevents.Bus

RuntimeEventBus returns the runtime event bus used by the agent loop.

func (*AgentLoop) RuntimeEventStats

func (al *AgentLoop) RuntimeEventStats() runtimeevents.Stats

RuntimeEventStats returns runtime event bus counters.

func (*AgentLoop) RuntimeEvents

func (al *AgentLoop) RuntimeEvents() runtimeevents.EventChannel

RuntimeEvents returns the root runtime event channel.

func (*AgentLoop) SetChannelManager

func (al *AgentLoop) SetChannelManager(cm *channels.Manager)

SetChannelManager sets the channel manager turns send through. A reload may call it while turns run: they read it through currentChannelManager.

func (*AgentLoop) SetMediaStore

func (al *AgentLoop) SetMediaStore(s media.MediaStore)

SetMediaStore sets the media store of the loop and its tools. A reload may call it while turns run: they read it through currentMediaStore.

func (*AgentLoop) SetReloadFunc

func (al *AgentLoop) SetReloadFunc(fn func() error)

func (*AgentLoop) SetTerminalChat

func (al *AgentLoop) SetTerminalChat(enabled bool)

SetTerminalChat says whether the terminal shows what the loop posts in its chat, as interactive `compa-kernel agent` does. Then the owner's approval of a terminal turn's call is asked there; otherwise the call is denied, as nobody would see the request.

func (*AgentLoop) SetTerminalEvents

func (al *AgentLoop) SetTerminalEvents(sink func(TurnEvent))

SetTerminalEvents makes the terminal's turns stream to sink, or stop streaming when sink is nil. Without a sink a terminal turn returns its answer whole, as it always has.

func (*AgentLoop) SetTranscriber

func (al *AgentLoop) SetTranscriber(t asr.Transcriber)

func (*AgentLoop) StateManager

func (al *AgentLoop) StateManager() *state.Manager

StateManager returns the manager the loop records the last chat and the owner's chat in, or nil when it has no default agent. Services that report to those chats share it.

func (*AgentLoop) Stop

func (al *AgentLoop) Stop()

func (*AgentLoop) UnmountHook

func (al *AgentLoop) UnmountHook(name string)

UnmountHook removes a previously registered in-process hook.

type AgentLoopOption

type AgentLoopOption func(*AgentLoop)

AgentLoopOption configures an AgentLoop at construction time.

func WithModelResolver

func WithModelResolver(resolve ModelResolver) AgentLoopOption

WithModelResolver sets how the loop resolves model selections: each agent's model, the image and light models, /switch model, hook model rewrites and per-message selections, always against the loop's current config. nil keeps the default, modelservice.NewResolver().Resolve, which reads the saved catalogs and the auth store.

func WithRuntimeEvents

func WithRuntimeEvents(bus runtimeevents.Bus) AgentLoopOption

WithRuntimeEvents injects the runtime event bus used for new observation APIs.

The injected bus is treated as externally owned and will not be closed by AgentLoop.Close. Passing nil leaves the default owned runtime bus enabled.

func WithToolProviders

func WithToolProviders(providers ...ToolProvider) AgentLoopOption

WithToolProviders injects capability providers at construction time.

Called once per agent during shared-tool registration, in the order given. Summaries accumulate across providers; the LAST non-nil knowledge loader wins, because a single selection resolves to a single provider today and silently merging two loaders would make an ambiguous selection look decided.

Passing none is fully supported and is the standalone-kernel case.

Example

ExampleWithToolProviders adds the host program's own tools to the agent.

package main

import (
	"context"
	"fmt"
	"path/filepath"
	"time"

	"github.com/kayiik/compa/pkg/agent"
	"github.com/kayiik/compa/pkg/bus"
	"github.com/kayiik/compa/pkg/config"
	"github.com/kayiik/compa/pkg/session"
	"github.com/kayiik/compa/pkg/tools"
)

// hostClock is a tool the host program provides. The agent calls it like a
// built-in tool.
type hostClock struct{}

func (hostClock) Name() string        { return "host_time" }
func (hostClock) Description() string { return "Returns the current date and time on this computer." }
func (hostClock) Parameters() map[string]any {
	return map[string]any{"type": "object", "properties": map[string]any{}}
}

func (hostClock) Execute(context.Context, map[string]any) *tools.ToolResult {
	return tools.NewToolResult(time.Now().Format(time.RFC1123))
}

// hostTools gives every agent of the loop the host's tools.
type hostTools struct{}

func (hostTools) RegisterTools(
	workspace string,
	register func(agent.Tool),
) ([]string, func(string) (string, string, []string)) {
	register(hostClock{})
	return nil, nil
}

func main() {
	cfg, err := config.LoadConfig(filepath.Join(config.GetHome(), "config.json"))
	if err != nil {
		fmt.Println("load config:", err)
		return
	}

	msgBus := bus.NewMessageBus()
	defer msgBus.Close()

	loop := agent.NewAgentLoop(cfg, msgBus, nil, agent.WithToolProviders(hostTools{}))
	defer loop.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()

	reply, err := loop.ProcessDirect(ctx, "What time is it?", session.BuildOpaqueSessionKey("my-app:demo"))
	if err != nil {
		fmt.Println("turn failed:", err)
		return
	}
	fmt.Println(reply)
}

type AgentLoopSpawner

type AgentLoopSpawner struct {
	// contains filtered or unexported fields
}

AgentLoopSpawner implements tools.SubTurnSpawner interface. This allows tools to spawn sub-turns without circular dependency.

func NewSubTurnSpawner

func NewSubTurnSpawner(al *AgentLoop) *AgentLoopSpawner

NewSubTurnSpawner creates a SubTurnSpawner for the given AgentLoop.

func (*AgentLoopSpawner) SpawnSubTurn

func (s *AgentLoopSpawner) SpawnSubTurn(
	ctx context.Context,
	cfg tools.SubTurnConfig,
) (*tools.ToolResult, error)

SpawnSubTurn implements tools.SubTurnSpawner interface.

type AgentPromptDefinition

type AgentPromptDefinition struct {
	Path           string           `json:"path"`
	Raw            string           `json:"raw"`
	Body           string           `json:"body"`
	RawFrontmatter string           `json:"raw_frontmatter,omitempty"`
	Frontmatter    AgentFrontmatter `json:"frontmatter"`
	FrontmatterErr string           `json:"frontmatter_error,omitempty"`
}

AgentPromptDefinition represents the parsed AGENT.md prompt file.

type AgentRegistry

type AgentRegistry struct {
	// contains filtered or unexported fields
}

AgentRegistry manages multiple agent instances and routes messages to them.

func NewAgentRegistry

func NewAgentRegistry(
	cfg *config.Config,
	provider providers.LLMProvider,
	resolve ModelResolver,
) *AgentRegistry

NewAgentRegistry creates a registry from config, instantiating all agents. Each agent resolves its model selection through resolve; provider, when not nil, serves the default selection instead (see NewAgentInstance).

func (*AgentRegistry) CanSpawnSubagent

func (r *AgentRegistry) CanSpawnSubagent(parentAgentID, targetAgentID string) bool

CanSpawnSubagent checks if parentAgentID is allowed to spawn targetAgentID.

func (*AgentRegistry) Close

func (r *AgentRegistry) Close()

Close releases resources held by all registered agents.

func (*AgentRegistry) ForEachTool

func (r *AgentRegistry) ForEachTool(name string, fn func(tools.Tool))

ForEachTool calls fn for every tool registered under the given name across all agents. This is useful for propagating dependencies (e.g. MediaStore) to tools after registry construction.

func (*AgentRegistry) GetAgent

func (r *AgentRegistry) GetAgent(agentID string) (*AgentInstance, bool)

GetAgent returns the agent instance for a given ID.

func (*AgentRegistry) GetAgentDescriptor

func (r *AgentRegistry) GetAgentDescriptor(agentID string) (*AgentDescriptor, bool)

GetAgentDescriptor returns the structured discovery payload for one agent.

func (*AgentRegistry) GetDefaultAgent

func (r *AgentRegistry) GetDefaultAgent() *AgentInstance

GetDefaultAgent returns the default agent instance.

func (*AgentRegistry) ListAgentIDs

func (r *AgentRegistry) ListAgentIDs() []string

ListAgentIDs returns all registered agent IDs.

func (*AgentRegistry) ListAgents

func (r *AgentRegistry) ListAgents(workspace string) []AgentDescriptor

ListAgents returns structured descriptors for every agent in the current Compa instance. The current workspace, when provided, is used only to order the matching agent first for prompt readability.

func (*AgentRegistry) ListSpawnableAgents

func (r *AgentRegistry) ListSpawnableAgents(agentID string) []AgentDescriptor

ListSpawnableAgents returns descriptors only when the current agent can call spawn, and only for peers it is allowed to spawn. Restricted peers are intentionally omitted from discovery.

func (*AgentRegistry) ResolveRoute

func (r *AgentRegistry) ResolveRoute(inbound bus.InboundContext) routing.ResolvedRoute

ResolveRoute determines which agent handles the normalized inbound context.

type ApprovalDecision

type ApprovalDecision struct {
	Approved bool   `json:"approved"`
	Reason   string `json:"reason,omitempty"`
}

type ApprovalRequestedPayload

type ApprovalRequestedPayload struct {
	// ApprovalID pairs the request with its ApprovalResolvedPayload.
	ApprovalID string `json:"approval_id"`
	// ActionID is the ID of the tool call in its turn; empty for a call
	// made outside a turn, such as a scheduled command.
	ActionID   string         `json:"action_id"`
	Tool       string         `json:"tool"`
	Parameters map[string]any `json:"parameters,omitempty"`
	// Reason names the part of the policy that asks.
	Reason string `json:"reason"`
	State  string `json:"state"` // "pending", "approved", "rejected", "cancelled"
}

ApprovalRequestedPayload describes a tool call the approval policy (tools.approval) asks about, before the approvers or the owner answer.

type ApprovalResolvedPayload

type ApprovalResolvedPayload struct {
	ApprovalID string `json:"approval_id"`
	// Resolution is "cancelled" when the turn or job ended before the answer.
	Resolution string `json:"resolution"` // "approved", "rejected", "cancelled"
	// Reason tells why a call that was not approved may not run.
	Reason string `json:"reason,omitempty"`
}

ApprovalResolvedPayload describes the answer to an approval request.

type ArtifactProducedPayload

type ArtifactProducedPayload struct {
	ArtifactID  string `json:"artifact_id"`
	Kind        string `json:"kind"`
	Module      string `json:"module"`
	Locator     string `json:"locator"`
	MediaType   string `json:"media_type"`
	Bytes       int64  `json:"bytes"`
	Digest      string `json:"digest"`
	Title       string `json:"title,omitempty"`
	ReviewState string `json:"review_state,omitempty"` // "draft", "verified", "reviewed", "approved"
	ToolName    string `json:"tool_name,omitempty"`
}

ArtifactProducedPayload describes an authoritative output emitted by a tool or operation.

type AssembleRequest

type AssembleRequest struct {
	SessionKey string // session identifier
	Budget     int    // context window in tokens
	MaxTokens  int    // max response tokens
	// AgentID is the agent whose turn asks; its session store holds the
	// history. Empty means the agent that owns the session.
	AgentID string
}

AssembleRequest is the input to Assemble.

type AssembleResponse

type AssembleResponse struct {
	History []providers.Message // assembled conversation history for BuildMessagesFromPrompt
	Summary string              // conversation summary embedded into system prompt by BuildMessagesFromPrompt
}

AssembleResponse is the output of Assemble.

type BuiltinHookFactory

type BuiltinHookFactory func(ctx context.Context, spec config.BuiltinHookConfig) (any, error)

BuiltinHookFactory constructs an in-process hook from config.

type CompactRequest

type CompactRequest struct {
	SessionKey string                // session identifier
	Reason     ContextCompressReason // proactive_budget | llm_retry | summarize
	Budget     int                   // context window budget (used for retry aggressive compaction)
	// AgentID is the agent whose turn asks (see AssembleRequest.AgentID).
	AgentID string
}

CompactRequest is the input to Compact.

type ContextBuilder

type ContextBuilder struct {
	// contains filtered or unexported fields
}

func NewContextBuilder

func NewContextBuilder(workspace string) *ContextBuilder

func (*ContextBuilder) BuildMessagesFromPrompt

func (cb *ContextBuilder) BuildMessagesFromPrompt(req PromptBuildRequest) []providers.Message

func (*ContextBuilder) BuildSystemPrompt

func (cb *ContextBuilder) BuildSystemPrompt() string

func (*ContextBuilder) BuildSystemPromptParts

func (cb *ContextBuilder) BuildSystemPromptParts() []PromptPart

func (*ContextBuilder) BuildSystemPromptWithCache

func (cb *ContextBuilder) BuildSystemPromptWithCache() string

BuildSystemPromptWithCache returns the cached system prompt if available and source files haven't changed, otherwise builds and caches it. Source file changes are detected via mtime checks (cheap stat calls).

func (*ContextBuilder) EstimateSystemTokens

func (cb *ContextBuilder) EstimateSystemTokens(summary string, activeSkills []string) int

EstimateSystemTokens estimates the token count of the full system message that would be sent to the LLM, mirroring the composition logic in BuildMessagesFromPrompt. It includes: static prompt, dynamic context, active skills, and summary with wrapping prefixes and separators. This avoids needing all per-request parameters that BuildMessagesFromPrompt requires (media, channel, chatID, sender, etc.).

func (*ContextBuilder) GetSkillsInfo

func (cb *ContextBuilder) GetSkillsInfo() map[string]any

GetSkillsInfo returns information about loaded skills.

func (*ContextBuilder) InvalidateCache

func (cb *ContextBuilder) InvalidateCache()

InvalidateCache clears the cached system prompt. Normally not needed because the cache auto-invalidates via mtime checks, but this is useful for tests or explicit reload commands.

func (*ContextBuilder) ListSkillNames

func (cb *ContextBuilder) ListSkillNames() []string

func (*ContextBuilder) LoadAgentDefinition

func (cb *ContextBuilder) LoadAgentDefinition() AgentContextDefinition

LoadAgentDefinition parses the workspace agent definition files: AGENT.md, SOUL.md and USER.md. Each is optional.

func (*ContextBuilder) LoadBootstrapFiles

func (cb *ContextBuilder) LoadBootstrapFiles() string

func (*ContextBuilder) RegisterPromptContributor

func (cb *ContextBuilder) RegisterPromptContributor(contributor PromptContributor) error

func (*ContextBuilder) RegisterPromptSource

func (cb *ContextBuilder) RegisterPromptSource(desc PromptSourceDescriptor) error

func (*ContextBuilder) ResolveActiveSkillsForContext

func (cb *ContextBuilder) ResolveActiveSkillsForContext(skillNames []string) []string

func (*ContextBuilder) ResolveSkillName

func (cb *ContextBuilder) ResolveSkillName(name string) (string, bool)

func (*ContextBuilder) WithAgentDiscovery

func (cb *ContextBuilder) WithAgentDiscovery(
	agentID string,
	discover func(agentID string) []AgentDescriptor,
) *ContextBuilder

func (*ContextBuilder) WithSplitOnMarker

func (cb *ContextBuilder) WithSplitOnMarker(enabled bool) *ContextBuilder

func (*ContextBuilder) WithToolDiscovery

func (cb *ContextBuilder) WithToolDiscovery(useBM25, useRegex bool) *ContextBuilder

type ContextCompressPayload

type ContextCompressPayload struct {
	Reason            ContextCompressReason
	DroppedMessages   int
	RemainingMessages int
}

ContextCompressPayload describes a forced history compression.

type ContextCompressReason

type ContextCompressReason string

ContextCompressReason identifies why emergency compression ran.

const (
	// ContextCompressReasonProactive indicates compression before the first LLM call.
	ContextCompressReasonProactive ContextCompressReason = "proactive_budget"
	// ContextCompressReasonRetry indicates compression during context-error retry handling.
	ContextCompressReasonRetry ContextCompressReason = "llm_retry"
	// ContextCompressReasonSummarize indicates post-turn async summarization.
	ContextCompressReasonSummarize ContextCompressReason = "summarize"
)

type ContextManager

type ContextManager interface {
	// Assemble builds budget-aware context from the ContextManager's own storage.
	// Called before BuildMessagesFromPrompt. Returns assembled messages ready for LLM.
	Assemble(ctx context.Context, req *AssembleRequest) (*AssembleResponse, error)

	// Compact compresses conversation history.
	// Called after turn completes (may be async internally) and on context overflow (sync).
	Compact(ctx context.Context, req *CompactRequest) error

	// Ingest records a message into the ContextManager's own storage.
	// Called after each message is persisted to session JSONL.
	Ingest(ctx context.Context, req *IngestRequest) error

	// Clear removes all stored context for a session (messages, summaries, etc.).
	// Called when the user issues /clear or /reset.
	Clear(ctx context.Context, sessionKey string) error
}

ContextManager manages conversation context via a pluggable strategy. Exactly ONE ContextManager is active per AgentLoop, selected by config. The built-in default summarizes and compresses the session history in place.

type ContextManagerFactory

type ContextManagerFactory func(cfg json.RawMessage, al *AgentLoop) (ContextManager, error)

ContextManagerFactory constructs a ContextManager from config. al provides access to the AgentLoop's runtime resources (provider, model, workspace, etc.) cfg is the raw JSON configuration from config.json (may be nil).

type Control

type Control int
const (
	// ControlContinue tells the coordinator to jump back to the top of the turn loop
	// (equivalent to the original "goto turnLoop").
	ControlContinue Control = iota
	// ControlBreak tells the coordinator to exit the turn loop and proceed to Finalize.
	ControlBreak
	// ControlToolLoop tells the coordinator to execute the tool loop.
	ControlToolLoop
)

type DispatchRequest

type DispatchRequest struct {
	SessionKey     string
	InboundContext *bus.InboundContext
	RouteResult    *routing.ResolvedRoute
	SessionScope   *session.SessionScope
	UserMessage    string
	Media          []string
}

DispatchRequest is the normalized runtime input passed into the agent loop after routing and session allocation have completed.

func (DispatchRequest) Channel

func (r DispatchRequest) Channel() string

func (DispatchRequest) ChatID

func (r DispatchRequest) ChatID() string

func (DispatchRequest) MessageID

func (r DispatchRequest) MessageID() string

func (DispatchRequest) ModelSelection

func (r DispatchRequest) ModelSelection() string

ModelSelection is the model selection the message chose — an exact target or a model route name — or empty when it runs on the agent's model.

func (DispatchRequest) ReplyToMessageID

func (r DispatchRequest) ReplyToMessageID() string

func (DispatchRequest) SelectedModule

func (r DispatchRequest) SelectedModule() string

SelectedModule is the module the user pointed the agent at for this turn.

Empty when no selection was made, which is the common case: an installed module contributes its capability summaries always, and its overlay and skills only when someone asks for it.

func (DispatchRequest) SenderID

func (r DispatchRequest) SenderID() string

type ErrorPayload

type ErrorPayload struct {
	Stage   string
	Message string
}

ErrorPayload describes an execution error inside the agent loop.

type FollowUpQueuedPayload

type FollowUpQueuedPayload struct {
	SourceTool string
	ContentLen int
}

FollowUpQueuedPayload describes an async follow-up queued back into the inbound bus.

type HookAction

type HookAction string
const (
	HookActionContinue  HookAction = "continue"
	HookActionModify    HookAction = "modify"
	HookActionRespond   HookAction = "respond" // Return result directly, skip tool execution. SECURITY: This bypasses the approval policy (tools.approval), allowing hooks to return results for any tool (including sensitive ones like bash) without approval. Use with caution.
	HookActionDenyTool  HookAction = "deny_tool"
	HookActionAbortTurn HookAction = "abort_turn"
	HookActionHardAbort HookAction = "hard_abort"
)

type HookDecision

type HookDecision struct {
	Action HookAction `json:"action"`
	Reason string     `json:"reason,omitempty"`
}

type HookManager

type HookManager struct {
	// contains filtered or unexported fields
}

func NewHookManager

func NewHookManager(runtimeEvents runtimeevents.EventChannel) *HookManager

func (*HookManager) AfterLLM

func (*HookManager) AfterTool

func (*HookManager) ApproveTool

func (hm *HookManager) ApproveTool(ctx context.Context, req *ToolApprovalRequest) (decision ApprovalDecision, decided bool)

ApproveTool asks every registered approver, in order, about a call: the first denial decides, and all approving approve it. An approver that fails or times out denies. decided is false when no approver is registered: then the caller asks the owner.

func (*HookManager) BeforeLLM

func (*HookManager) BeforeTool

func (*HookManager) Close

func (hm *HookManager) Close()

func (*HookManager) ConfigureTimeouts

func (hm *HookManager) ConfigureTimeouts(observer, interceptor, approval time.Duration)

ConfigureTimeouts sets the hook timeouts; a zero or negative one is left unchanged. Safe while hooks run.

func (*HookManager) Mount

func (hm *HookManager) Mount(reg HookRegistration) error

func (*HookManager) Unmount

func (hm *HookManager) Unmount(name string)

type HookMeta

type HookMeta struct {
	AgentID      string
	TurnID       string
	ParentTurnID string
	SessionKey   string
	Iteration    int
	TracePath    string
	Source       string
	// contains filtered or unexported fields
}

HookMeta contains correlation fields shared by agent hook requests and runtime events emitted from turn processing.

type HookRegistration

type HookRegistration struct {
	Name     string
	Priority int
	Source   HookSource
	Hook     any
}

func NamedHook

func NamedHook(name string, hook any) HookRegistration

type HookSource

type HookSource uint8
const (
	HookSourceInProcess HookSource = iota
	HookSourceProcess
)

type IngestRequest

type IngestRequest struct {
	SessionKey string            // session identifier
	Message    providers.Message // the message just persisted
}

IngestRequest is the input to Ingest.

type InstanceSelectionResult

type InstanceSelectionResult struct {
	Response       *providers.LLMResponse
	ServedIdentity string
	ExactTarget    string
}

type InterruptKind

type InterruptKind string
const (
	InterruptKindSteering InterruptKind = "steering"
)

type InterruptReceivedPayload

type InterruptReceivedPayload struct {
	Kind       InterruptKind
	Role       string
	ContentLen int
	QueueDepth int
}

InterruptReceivedPayload describes accepted turn-control input.

type LLMDeltaPayload

type LLMDeltaPayload struct {
	ContentDeltaLen   int
	ReasoningDeltaLen int
}

LLMDeltaPayload describes a streamed LLM delta.

type LLMHookRequest

type LLMHookRequest struct {
	Meta     HookMeta                   `json:"meta"`
	Context  *TurnContext               `json:"context,omitempty"`
	Model    string                     `json:"model"`
	Messages []providers.Message        `json:"messages,omitempty"`
	Tools    []providers.ToolDefinition `json:"tools,omitempty"`
	Options  map[string]any             `json:"options,omitempty"`
}

func (*LLMHookRequest) Clone

func (r *LLMHookRequest) Clone() *LLMHookRequest

type LLMHookResponse

type LLMHookResponse struct {
	Meta     HookMeta               `json:"meta"`
	Context  *TurnContext           `json:"context,omitempty"`
	Model    string                 `json:"model"`
	Response *providers.LLMResponse `json:"response,omitempty"`
}

func (*LLMHookResponse) Clone

func (r *LLMHookResponse) Clone() *LLMHookResponse

type LLMInterceptor

type LLMInterceptor interface {
	BeforeLLM(ctx context.Context, req *LLMHookRequest) (*LLMHookRequest, HookDecision, error)
	AfterLLM(ctx context.Context, resp *LLMHookResponse) (*LLMHookResponse, HookDecision, error)
}

type LLMRequestPayload

type LLMRequestPayload struct {
	Model         string
	MessagesCount int
	ToolsCount    int
	MaxTokens     int
	Temperature   float64
}

LLMRequestPayload describes an outbound LLM request.

type LLMResponsePayload

type LLMResponsePayload struct {
	ContentLen   int
	ToolCalls    int
	HasReasoning bool
}

LLMResponsePayload describes an inbound LLM response.

type LLMRetryPayload

type LLMRetryPayload struct {
	Attempt    int
	MaxRetries int
	Reason     string
	Error      string
	Backoff    time.Duration
}

LLMRetryPayload describes a retry of an LLM request.

type MemoryStore

type MemoryStore struct {
	// contains filtered or unexported fields
}

MemoryStore manages persistent memory for the agent. - Long-term memory: memory/MEMORY.md - Daily notes: memory/YYYYMM/YYYYMMDD.md

func NewMemoryStore

func NewMemoryStore(workspace string) *MemoryStore

NewMemoryStore creates a new MemoryStore with the given workspace path. It ensures the memory directory exists.

func (*MemoryStore) GetMemoryContext

func (ms *MemoryStore) GetMemoryContext() string

GetMemoryContext returns formatted memory context for the agent prompt. Includes long-term memory and recent daily notes, together at most memoryContextLimit bytes.

func (*MemoryStore) GetRecentDailyNotes

func (ms *MemoryStore) GetRecentDailyNotes(days int) string

GetRecentDailyNotes returns daily notes from the last N days. Contents are joined with "---" separator.

func (*MemoryStore) ReadLongTerm

func (ms *MemoryStore) ReadLongTerm() string

ReadLongTerm reads the long-term memory (MEMORY.md). Returns empty string if the file doesn't exist.

type ModelResolver

type ModelResolver func(cfg *config.Config, selection string) (*providers.InstanceResolution, error)

ModelResolver resolves a model selection — an exact target "instance-id/model-id" or a model route name — against cfg into ordered candidates, each running on its own provider instance. The loop resolves every selection through it: the agents' models, the image and light models, /switch model, hook rewrites and per-message selections.

type Pipeline

type Pipeline struct {
	Bus            interfaces.MessageBus
	Cfg            *config.Config
	ContextManager ContextManager
	Hooks          *HookManager
	Failover       *providers.Failover
	ChannelManager interfaces.ChannelManager
	MediaStore     media.MediaStore
	// contains filtered or unexported fields
}

Pipeline holds the runtime dependencies used by Pipeline methods. It is constructed by runTurn via NewPipeline and passed to sub-methods so that the coordinator can delegate phase execution.

func NewPipeline

func NewPipeline(al *AgentLoop) *Pipeline

NewPipeline creates a Pipeline from an AgentLoop instance: a snapshot of the loop's config, failover, context manager, channel manager and media store, read together under the loop's lock. A turn runs on its snapshot even when a reload replaces them meanwhile.

func (*Pipeline) CallLLM

func (p *Pipeline) CallLLM(
	ctx context.Context,
	turnCtx context.Context,
	ts *turnState,
	exec *turnExecution,
	iteration int,
) (Control, error)

CallLLM performs an LLM call with fallback support, hook invocation, and retry logic. It handles PreLLM setup, the actual LLM invocation with retry, and AfterLLM processing. Returns Control indicating what the coordinator should do next.

func (*Pipeline) ExecuteTools

func (p *Pipeline) ExecuteTools(
	ctx context.Context,
	turnCtx context.Context,
	ts *turnState,
	exec *turnExecution,
	iteration int,
) ToolControl

ExecuteTools executes the tool loop, handling BeforeTool/AfterTool hooks, the approval policy, tool execution with async callbacks, media delivery, and steering injection. Returns ToolControl indicating what the coordinator should do next:

  • ToolControlContinue: all tool results handled, pendingMessages or steering exists, continue turn
  • ToolControlBreak: tool loop exited, proceed to coordinator's hardAbort/finalContent/finalize

func (*Pipeline) Finalize

func (p *Pipeline) Finalize(
	ctx context.Context,
	turnCtx context.Context,
	ts *turnState,
	exec *turnExecution,
	turnStatus TurnEndStatus,
	finalContent string,
) (turnResult, error)

Finalize handles turn finalization, either: - Early return when allResponsesHandled=true (ExecuteTools already finalized) - Normal finalization for allResponsesHandled=false (sets finalContent, saves session, compact)

func (*Pipeline) SetupTurn

func (p *Pipeline) SetupTurn(ctx context.Context, ts *turnState) (*turnExecution, error)

SetupTurn extracts the one-time initialization phase, returning a turnExecution populated with history, messages, and candidate selection. A turn runs on the message's own model selection when it has one, and on the agent's model otherwise; with neither it fails with a no-model error before anything is recorded.

type ProcessHook

type ProcessHook struct {
	// contains filtered or unexported fields
}

func NewProcessHook

func NewProcessHook(ctx context.Context, name string, opts ProcessHookOptions) (*ProcessHook, error)

func (*ProcessHook) AfterLLM

func (ph *ProcessHook) AfterLLM(
	ctx context.Context,
	resp *LLMHookResponse,
) (*LLMHookResponse, HookDecision, error)

func (*ProcessHook) AfterTool

func (*ProcessHook) ApproveTool

func (ph *ProcessHook) ApproveTool(ctx context.Context, req *ToolApprovalRequest) (ApprovalDecision, error)

func (*ProcessHook) BeforeLLM

func (ph *ProcessHook) BeforeLLM(
	ctx context.Context,
	req *LLMHookRequest,
) (*LLMHookRequest, HookDecision, error)

func (*ProcessHook) BeforeTool

func (*ProcessHook) Close

func (ph *ProcessHook) Close() error

func (*ProcessHook) OnRuntimeEvent

func (ph *ProcessHook) OnRuntimeEvent(ctx context.Context, evt runtimeevents.Event) error

type ProcessHookOptions

type ProcessHookOptions struct {
	Command       []string
	Dir           string
	Env           []string
	Observe       bool
	ObserveKinds  []string
	InterceptLLM  bool
	InterceptTool bool
	ApproveTool   bool
}

type PromptBuildRequest

type PromptBuildRequest struct {
	History []providers.Message
	Summary string

	CurrentMessage string
	Media          []string

	Channel           string
	ChatID            string
	SenderID          string
	SenderDisplayName string

	ActiveSkills []string
	Overlays     []PromptPart

	SuppressDefaultSystemPrompt bool
	SuppressSkillContext        bool
	// ModuleSummaries are one-line capability summaries for the enabled
	// detached modules, carried per turn so the agent's context reflects what
	// is installed right now rather than what was installed at startup.
	ModuleSummaries     []string
	SuppressToolUseRule bool
	AllowedSkills       []string
	AllowedTools        []string
	ToolUseFallback     bool
}

type PromptCachePolicy

type PromptCachePolicy string
const (
	PromptCacheDefault   PromptCachePolicy = ""
	PromptCacheEphemeral PromptCachePolicy = "ephemeral"
	PromptCacheNone      PromptCachePolicy = "none"
)

type PromptContributor

type PromptContributor interface {
	PromptSource() PromptSourceDescriptor
	ContributePrompt(ctx context.Context, req PromptBuildRequest) ([]PromptPart, error)
}

type PromptLayer

type PromptLayer string
const (
	PromptLayerKernel      PromptLayer = "kernel"
	PromptLayerInstruction PromptLayer = "instruction"
	PromptLayerCapability  PromptLayer = "capability"
	PromptLayerContext     PromptLayer = "context"
	PromptLayerTurn        PromptLayer = "turn"
)

type PromptPart

type PromptPart struct {
	ID      string
	Layer   PromptLayer
	Slot    PromptSlot
	Source  PromptSource
	Title   string
	Content string
	Stable  bool
	Cache   PromptCachePolicy
}

type PromptPlacement

type PromptPlacement struct {
	Layer PromptLayer
	Slot  PromptSlot
}

type PromptRegistry

type PromptRegistry struct {
	// contains filtered or unexported fields
}

func NewPromptRegistry

func NewPromptRegistry() *PromptRegistry

func (*PromptRegistry) Collect

func (*PromptRegistry) RegisterContributor

func (r *PromptRegistry) RegisterContributor(contributor PromptContributor) error

func (*PromptRegistry) RegisterSource

func (r *PromptRegistry) RegisterSource(desc PromptSourceDescriptor) error

func (*PromptRegistry) ValidatePart

func (r *PromptRegistry) ValidatePart(part PromptPart) error

type PromptSlot

type PromptSlot string
const (
	PromptSlotIdentity     PromptSlot = "identity"
	PromptSlotHierarchy    PromptSlot = "hierarchy"
	PromptSlotWorkspace    PromptSlot = "workspace"
	PromptSlotTooling      PromptSlot = "tooling"
	PromptSlotMCP          PromptSlot = "mcp"
	PromptSlotSkillCatalog PromptSlot = "skill_catalog"
	PromptSlotActiveSkill  PromptSlot = "active_skill"
	PromptSlotMemory       PromptSlot = "memory"
	PromptSlotRuntime      PromptSlot = "runtime"
	PromptSlotSummary      PromptSlot = "summary"
	PromptSlotMessage      PromptSlot = "message"
	PromptSlotSteering     PromptSlot = "steering"
	PromptSlotSubTurn      PromptSlot = "subturn"
	PromptSlotToolResult   PromptSlot = "tool_result"
	PromptSlotOutput       PromptSlot = "output"
)

type PromptSource

type PromptSource struct {
	ID   PromptSourceID
	Name string
	Path string
}

type PromptSourceDescriptor

type PromptSourceDescriptor struct {
	ID              PromptSourceID
	Owner           string
	Description     string
	Allowed         []PromptPlacement
	StableByDefault bool
}

type PromptSourceID

type PromptSourceID string
const (
	PromptSourceKernel         PromptSourceID = "runtime.kernel"
	PromptSourceHierarchy      PromptSourceID = "runtime.hierarchy"
	PromptSourceWorkspace      PromptSourceID = "workspace.definition"
	PromptSourceRuntime        PromptSourceID = "runtime.context"
	PromptSourceSummary        PromptSourceID = "context.summary"
	PromptSourceMemory         PromptSourceID = "memory:workspace"
	PromptSourceSkillCatalog   PromptSourceID = "skill:index"
	PromptSourceActiveSkills   PromptSourceID = "skill:active"
	PromptSourceAgentDiscovery PromptSourceID = "agent:discovery"
	PromptSourceToolRegistry   PromptSourceID = "tool_registry:native"
	// PromptSourceModuleKnowledge attributes a selected module's overlay and
	// skills, which are module-authored and enter context only on selection.
	PromptSourceModuleKnowledge PromptSourceID = "module:knowledge"
	PromptSourceToolDiscovery   PromptSourceID = "tool_registry:discovery"
	PromptSourceOutputPolicy    PromptSourceID = "runtime.output"
	PromptSourceSubTurnProfile  PromptSourceID = "subturn.profile"
	PromptSourceUserMessage     PromptSourceID = "turn:user_message"
	PromptSourceSteering        PromptSourceID = "turn:steering"
	PromptSourceSubTurnResult   PromptSourceID = "turn:subturn_result"
	PromptSourceToolResult      PromptSourceID = "turn:tool_result"
)

type PromptStack

type PromptStack struct {
	// contains filtered or unexported fields
}

func NewPromptStack

func NewPromptStack(registry *PromptRegistry) *PromptStack

func (*PromptStack) Add

func (s *PromptStack) Add(part PromptPart) error

func (*PromptStack) Parts

func (s *PromptStack) Parts() []PromptPart

func (*PromptStack) Seal

func (s *PromptStack) Seal()

type RuntimeEventObserver

type RuntimeEventObserver interface {
	OnRuntimeEvent(ctx context.Context, evt runtimeevents.Event) error
}

type SessionSummarizePayload

type SessionSummarizePayload struct {
	SummarizedMessages int
	KeptMessages       int
	SummaryLen         int
	OmittedOversized   bool
}

SessionSummarizePayload describes a completed async session summarization.

type SkillContextSnapshot

type SkillContextSnapshot struct {
	Sequence   int      `json:"sequence"`
	Trigger    string   `json:"trigger"`
	SkillNames []string `json:"skill_names,omitempty"`
}

type SoulDefinition

type SoulDefinition struct {
	Path    string `json:"path"`
	Content string `json:"content"`
}

SoulDefinition represents the resolved SOUL.md file linked to the agent.

type SteeringInjectedPayload

type SteeringInjectedPayload struct {
	Count           int
	TotalContentLen int
}

SteeringInjectedPayload describes steering messages appended before the next LLM call.

type SteeringMode

type SteeringMode string

SteeringMode controls how queued steering messages are dequeued.

const (
	// SteeringOneAtATime dequeues only the first queued message per poll.
	SteeringOneAtATime SteeringMode = "one-at-a-time"
	// SteeringAll drains the entire queue in a single poll.
	SteeringAll SteeringMode = "all"
	// MaxQueueSize number of possible messages in the Steering Queue
	MaxQueueSize = 10
)

type SubTurnConfig

type SubTurnConfig struct {
	// SystemPrompt, despite its name, carries the task description: it is
	// sent as the first 'user' message (see ActualSystemPrompt).
	SystemPrompt string

	// Async controls the result delivery mechanism:
	//
	// When Async = false (synchronous sub-turn):
	//   - The caller blocks until the sub-turn completes
	//   - The result is ONLY returned via the function return value
	//   - The result is NOT delivered to the parent's pendingResults channel
	//   - This prevents double delivery: caller gets result immediately, no need for channel
	//   - Use case: When the caller needs the result immediately to continue execution
	//   - Example: A tool that needs to process the sub-turn result before returning
	//
	// When Async = true (asynchronous sub-turn):
	//   - The sub-turn runs in the background (still blocks the caller, but semantically async)
	//   - The result is delivered to the parent's pendingResults channel
	//   - The result is ALSO returned via the function return value (for consistency)
	//   - The parent turn can poll pendingResults in later iterations to process results
	//   - Use case: Fire-and-forget operations, or when results are processed in batches
	//   - Example: Spawning multiple sub-turns in parallel and collecting results later
	//
	// IMPORTANT: The Async flag does NOT make the call non-blocking. It only controls
	// whether the result is delivered via the channel. For true non-blocking execution,
	// the caller must spawn the sub-turn in a separate goroutine.
	Async bool

	// Critical indicates this SubTurn's result is important and should continue
	// running even after the parent turn finishes gracefully.
	//
	// When parent finishes gracefully (Finish(false)):
	//   - Critical=true: SubTurn continues running, delivers result as orphan
	//   - Critical=false: SubTurn exits gracefully without error
	//
	// When parent finishes with hard abort (Finish(true)):
	//   - All SubTurns are canceled regardless of Critical flag
	Critical bool

	// Timeout is the maximum duration for this SubTurn.
	// If the SubTurn runs longer than this, it will be canceled.
	// Default is 5 minutes (defaultSubTurnTimeout) if not specified.
	Timeout time.Duration

	// ActualSystemPrompt is injected as the true 'system' role message for the childAgent.
	// SystemPrompt, despite its name, carries the task description and is sent as
	// the first 'user' message.
	ActualSystemPrompt string

	// InitialMessages preloads the ephemeral session history before the agent loop starts.
	// Used by evaluator-optimizer patterns to pass the full worker context across multiple iterations.
	InitialMessages []providers.Message

	// TargetAgentID, when set, runs the sub-turn as the specified agent.
	// The target agent's workspace, model, tools, and system prompt are used
	// instead of the caller's. If empty, the sub-turn runs as the parent agent.
	TargetAgentID string
}

SubTurnConfig configures the execution of a child sub-turn. The child runs on its agent's model — or, running as its parent's agent, on the model the parent's message selected.

Usage Examples:

Synchronous sub-turn (Async=false):

cfg := SubTurnConfig{
    SystemPrompt: "Analyze this code",
    Async: false,  // Result returned immediately
}
result, err := SpawnSubTurn(ctx, cfg)
// Use result directly here
processResult(result)

Asynchronous sub-turn (Async=true):

cfg := SubTurnConfig{
    SystemPrompt: "Background analysis",
    Async: true,  // Result delivered to channel
}
result, err := SpawnSubTurn(ctx, cfg)
// Result also available in parent's pendingResults channel
// Parent turn will poll and process it in a later iteration

type SubTurnEndPayload

type SubTurnEndPayload struct {
	AgentID string
	Status  string
}

SubTurnEndPayload describes the completion of a child turn.

type SubTurnOrphanPayload

type SubTurnOrphanPayload struct {
	ParentTurnID string
	ChildTurnID  string
	Reason       string
}

SubTurnOrphanPayload describes a sub-turn result that could not be delivered.

type SubTurnResultDeliveredPayload

type SubTurnResultDeliveredPayload struct {
	TargetChannel string
	TargetChatID  string
	ContentLen    int
}

SubTurnResultDeliveredPayload describes delivery of a sub-turn result.

type SubTurnSpawnPayload

type SubTurnSpawnPayload struct {
	AgentID      string
	Label        string
	ParentTurnID string
}

SubTurnSpawnPayload describes the creation of a child turn.

type ThinkingLevel

type ThinkingLevel string

ThinkingLevel controls how the provider sends thinking parameters.

  • "adaptive": sends {thinking: {type: "adaptive"}} + output_config.effort (Claude 4.6+)
  • "low"/"medium"/"high"/"xhigh": sends {thinking: {type: "enabled", budget_tokens: N}} (all models)
  • "off": disables thinking
const (
	ThinkingOff      ThinkingLevel = "off"
	ThinkingLow      ThinkingLevel = "low"
	ThinkingMedium   ThinkingLevel = "medium"
	ThinkingHigh     ThinkingLevel = "high"
	ThinkingXHigh    ThinkingLevel = "xhigh"
	ThinkingAdaptive ThinkingLevel = "adaptive"
)

type Tool

type Tool = tools.Tool

Tool is the kernel's public tool contract, re-exported so an embedder does not have to reach into pkg/tools/shared to satisfy ToolProvider.

A four-method interface (Name, Description, Parameters, Execute) and nothing more. Keeping the embedding surface this small is what lets a standalone product bind its own capabilities natively.

type ToolApprovalRequest

type ToolApprovalRequest struct {
	Meta      HookMeta       `json:"meta"`
	Context   *TurnContext   `json:"context,omitempty"`
	Tool      string         `json:"tool"`
	Arguments map[string]any `json:"arguments,omitempty"`
	// Origin is where the turn that makes the call came from.
	Origin approval.Origin `json:"origin"`
	// Info is the tool's source, its name there, and what it declares.
	Info approval.Info `json:"info"`
	// Decision is the policy's decision, with the rule that matched.
	Decision approval.Decision `json:"decision"`
	// Job is the scheduled job a call made outside a turn runs for.
	Job string `json:"job,omitempty"`
}

ToolApprovalRequest asks the approvers about a call the approval policy (tools.approval) says to ask about.

func (*ToolApprovalRequest) Clone

type ToolApprover

type ToolApprover interface {
	ApproveTool(ctx context.Context, req *ToolApprovalRequest) (ApprovalDecision, error)
}

ToolApprover decides the calls the approval policy says to ask about.

type ToolCallHookRequest

type ToolCallHookRequest struct {
	Meta       HookMeta          `json:"meta"`
	Context    *TurnContext      `json:"context,omitempty"`
	Tool       string            `json:"tool"`
	Arguments  map[string]any    `json:"arguments,omitempty"`
	Channel    string            `json:"channel,omitempty"`
	ChatID     string            `json:"chat_id,omitempty"`
	HookResult *tools.ToolResult `json:"hook_result,omitempty"` // Result returned directly by hook (for respond action). Media is supported - see Media handling section in docs.
}

func (*ToolCallHookRequest) Clone

type ToolControl

type ToolControl int

ToolControl signals returned from ExecuteTools to drive tool loop iteration.

const (
	// ToolControlContinue tells the tool loop to jump to the next iteration
	// (pendingMessages arrived, SubTurn results, etc.).
	ToolControlContinue ToolControl = iota
	// ToolControlBreak tells the tool loop to exit and return to the coordinator.
	ToolControlBreak
)

type ToolExecEndPayload

type ToolExecEndPayload struct {
	Tool       string
	Duration   time.Duration
	ForLLMLen  int
	ForUserLen int
	IsError    bool
	Async      bool
}

ToolExecEndPayload describes the outcome of a tool execution.

type ToolExecSkippedPayload

type ToolExecSkippedPayload struct {
	Tool   string
	Reason string
}

ToolExecSkippedPayload describes a skipped tool call.

type ToolExecStartPayload

type ToolExecStartPayload struct {
	Tool      string
	Arguments map[string]any
}

ToolExecStartPayload describes a tool execution request.

type ToolExecutionRecord

type ToolExecutionRecord struct {
	Name         string   `json:"name"`
	Success      bool     `json:"success"`
	ErrorSummary string   `json:"error_summary,omitempty"`
	SkillNames   []string `json:"skill_names,omitempty"`
}

type ToolInterceptor

type ToolInterceptor interface {
	BeforeTool(ctx context.Context, call *ToolCallHookRequest) (*ToolCallHookRequest, HookDecision, error)
	AfterTool(ctx context.Context, result *ToolResultHookResponse) (*ToolResultHookResponse, HookDecision, error)
}

type ToolProvider

type ToolProvider interface {
	// RegisterTools contributes tools via register and returns one-line
	// summaries for the agent's context.
	//
	// workspace is the AGENT'S OWN working directory, and it is passed rather
	// than captured because it is resolved PER AGENT inside the registration
	// loop -- the composition root does not know it. An earlier version of this
	// interface omitted it, which only surfaced when wiring the call sites:
	// the provider would have had to invent a workspace, and every agent would
	// have shared one.
	//
	// The returned knowledge function is optional -- nil is valid, and means
	// this provider has no per-selection knowledge to load. It maps a
	// provider-defined identifier to overlays, skills and warnings.
	RegisterTools(workspace string, register func(Tool)) (summaries []string,
		knowledge func(id string) (overlays string, skills string, warnings []string))
}

ToolProvider contributes tools to an agent from outside the kernel.

THIS IS THE KERNEL'S ONLY DOOR FOR NON-GENERIC CAPABILITIES, and it exists so the kernel can stay Layer 1.

The kernel previously imported internal/moduletools directly and called it from registerSharedTools. That was a Layer 1 -> Layer 2 inversion, disqualifying twice over: architecturally a standalone product embedding the kernel dragged in module discovery, the v2 contract gate and bounded subprocess execution it would never use; and mechanically internal/ packages CANNOT BE IMPORTED by an external module, so any sibling embedding the kernel would simply fail to compile.

That one import pulled in six Layer-2/3 packages transitively -- the module host, subprocess execution, both wire protocols, the contract gate, and the cockpit's artefact view vocabulary. Removing it drops all six at once; the audit found the other 55 of 56 transitive packages already generic.

WHAT CROSSES THE BOUNDARY IS DELIBERATELY PLAIN. Tools arrive as tools.Tool, a four-method interface; the descriptive lines are []string; the knowledge loader is a plain func. No provider type reaches the agent struct, which is what makes the kernel independently consumable rather than merely separately compiled.

Three shapes this serves:

full Compa  -- supplies a provider backed by the detached-module host
standalone   -- supplies a NATIVE, in-process provider binding its own
                capabilities, paying no subprocess or protocol cost to
                host itself
bare kernel  -- supplies none, and is a plain conversational runtime

type ToolResultHookResponse

type ToolResultHookResponse struct {
	Meta      HookMeta          `json:"meta"`
	Context   *TurnContext      `json:"context,omitempty"`
	Tool      string            `json:"tool"`
	Arguments map[string]any    `json:"arguments,omitempty"`
	Result    *tools.ToolResult `json:"result,omitempty"`
	Duration  time.Duration     `json:"duration"`
}

func (*ToolResultHookResponse) Clone

type TurnContext

type TurnContext struct {
	Inbound *bus.InboundContext    `json:"inbound,omitempty"`
	Route   *routing.ResolvedRoute `json:"route,omitempty"`
	Scope   *session.SessionScope  `json:"scope,omitempty"`
}

TurnContext carries normalized turn-scoped facts that can be shared across events, hooks, and other runtime observers without re-parsing raw message fields.

type TurnEndPayload

type TurnEndPayload struct {
	Status                TurnEndStatus
	Workspace             string
	Iterations            int
	Duration              time.Duration
	FinalContentLen       int
	UserMessage           string
	FinalContent          string
	ActiveSkills          []string
	AttemptedSkills       []string
	FinalSuccessfulPath   []string
	SkillContextSnapshots []SkillContextSnapshot
	ToolKinds             []string
	ToolExecutions        []ToolExecutionRecord
	// Error says why a turn that ended with TurnEndStatusError failed.
	Error string
	// FromOwner reports that the turn's message came from the instance's
	// owner (see inboundFromOwner).
	FromOwner bool
}

TurnEndPayload describes the completion of a turn.

type TurnEndStatus

type TurnEndStatus string

TurnEndStatus describes the terminal state of a turn.

const (
	// TurnEndStatusCompleted indicates the turn finished normally.
	TurnEndStatusCompleted TurnEndStatus = "completed"
	// TurnEndStatusError indicates the turn ended because of an error.
	TurnEndStatusError TurnEndStatus = "error"
	// TurnEndStatusAborted indicates the turn was hard-aborted and rolled back.
	TurnEndStatusAborted TurnEndStatus = "aborted"
)

type TurnEvent

type TurnEvent struct {
	// Type is "segment" (a new model call starts writing), "reset" (the
	// provider revised the text so far; start the segment over), "delta" or
	// "tool".
	Type string `json:"type"`
	// Text is, for a delta, the text just written.
	Text string `json:"text,omitempty"`
	// Tool, State and Detail describe a tool call: State is "start", "end" or
	// "error", and Detail says what a started call was asked to do.
	Tool   string `json:"tool,omitempty"`
	State  string `json:"state,omitempty"`
	Detail string `json:"detail,omitempty"`
}

TurnEvent is something a one-shot terminal turn does while it runs: the answer being written, a tool being used. A caller that installs a sink with SetTerminalEvents gets the answer as it is written instead of only whole.

type TurnPhase

type TurnPhase string
const (
	TurnPhaseSetup      TurnPhase = "setup"
	TurnPhaseRunning    TurnPhase = "running"
	TurnPhaseTools      TurnPhase = "tools"
	TurnPhaseFinalizing TurnPhase = "finalizing"
	TurnPhaseCompleted  TurnPhase = "completed"
	TurnPhaseAborted    TurnPhase = "aborted"
)

type TurnStartPayload

type TurnStartPayload struct {
	UserMessage string
	MediaCount  int
}

TurnStartPayload describes the start of a turn.

type UserDefinition

type UserDefinition struct {
	Path    string `json:"path"`
	Content string `json:"content"`
}

UserDefinition represents the resolved USER.md file linked to the workspace.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL