Documentation
¶
Index ¶
- Constants
- Variables
- func AbandonedWritesSuppressed() int64
- func GetSSRFChecker(al *AgentLoop) *security.SSRFChecker
- func GetTaskStore(al *AgentLoop) *taskstore.TaskStore
- func RecoverOrphanedToolCalls(store session.SessionStore, sessionKey string, auditLog *audit.Logger) []providers.Message
- func RegisterBuiltinHook(name string, factory BuiltinHookFactory) error
- func SpawnSubTurn(ctx context.Context, cfg SubTurnConfig) (*tools.ToolResult, error)
- func TurnStateFromContext(ctx context.Context) *turnState
- func WithAgentLoop(ctx context.Context, al *AgentLoop) context.Context
- func WithScheduledJobContext(ctx context.Context, jobID, jobName string) context.Context
- type ActiveTurnInfo
- type AdmissionController
- type AgentContextDefinition
- type AgentDefinitionSource
- type AgentFrontmatter
- type AgentInstance
- type AgentLoop
- func (al *AgentLoop) AgentForSession(sessionID string) (*AgentInstance, error)
- func (al *AgentLoop) ApplyAgentModel(agentID, model string) (string, error)
- func (al *AgentLoop) AuditLogger() *audit.Logger
- func (al *AgentLoop) BootstrapRecapPass(ctx context.Context)
- func (al *AgentLoop) Close()
- func (al *AgentLoop) CloseSession(sessionID, trigger string)
- func (al *AgentLoop) ContextBuilderRegistry() *ContextBuilderRegistry
- func (al *AgentLoop) Continue(ctx context.Context, sessionKey, channel, chatID string) (string, error)
- func (al *AgentLoop) EmitNotification(p NotificationPayload)
- func (al *AgentLoop) EmitWhatsAppPairing(channelID string, status channels.PairingStatus, qr, message string)
- func (al *AgentLoop) EventDrops(kind EventKind) int64
- func (al *AgentLoop) ExecProxy() *security.ExecProxy
- func (al *AgentLoop) GetActiveAgentIDs() []string
- func (al *AgentLoop) GetActiveTurn() *ActiveTurnInfo
- func (al *AgentLoop) GetActiveTurnBySession(sessionKey string) *ActiveTurnInfo
- func (al *AgentLoop) GetActiveTurnHookForSession(sessionID string) TurnCancelHook
- func (al *AgentLoop) GetAgentStore(agentID string) *session.UnifiedStore
- func (al *AgentLoop) GetChannelManager() *channels.Manager
- func (al *AgentLoop) GetConfig() *config.Config
- func (al *AgentLoop) GetCurrentSession(agentID string) (string, bool)
- func (al *AgentLoop) GetMediaRefsDropped() int64
- func (al *AgentLoop) GetMediaStore() media.MediaStore
- func (al *AgentLoop) GetRegistry() *AgentRegistry
- func (al *AgentLoop) GetSessionActiveAgent(sessionID string) (string, bool)
- func (al *AgentLoop) GetSessionStore() *session.UnifiedStore
- func (al *AgentLoop) GetStartupInfo() map[string]any
- func (al *AgentLoop) HardAbort(sessionKey string) error
- func (al *AgentLoop) HydrateAgentHistoryFromTranscript(sessionID string) error
- func (al *AgentLoop) InjectFollowUp(msg providers.Message) error
- func (al *AgentLoop) InjectSteering(msg providers.Message) error
- func (al *AgentLoop) InterruptByChannelChat(channel, chatID, hint string) error
- func (al *AgentLoop) InterruptGraceful(hint string) error
- func (al *AgentLoop) InterruptHard() error
- func (al *AgentLoop) InterruptSession(sessionID, hint string) (descendants []string, err error)
- func (al *AgentLoop) InterruptSessionHard(sessionID, hint string) (descendants []string, err error)
- func (al *AgentLoop) ListAllSessions() ([]*session.UnifiedMeta, []error)
- func (al *AgentLoop) MountHook(reg HookRegistration) error
- func (al *AgentLoop) MountProcessHook(ctx context.Context, name string, opts ProcessHookOptions) error
- func (al *AgentLoop) MutateConfig(fn func(*config.Config) error) error
- func (al *AgentLoop) ProcessDirect(ctx context.Context, content, sessionKey string) (string, error)
- func (al *AgentLoop) ProcessDirectWithChannel(ctx context.Context, content, sessionKey, channel, chatID string) (string, error)
- func (al *AgentLoop) ProcessHeartbeat(ctx context.Context, content, channel, chatID string) (string, error)
- func (al *AgentLoop) ProcessScheduled(ctx context.Context, ownerAgentID, sessionID, content, channel, chatID string) (string, error)
- func (al *AgentLoop) PromptGuard() *security.PromptGuard
- func (al *AgentLoop) RateLimiter() *security.RateLimiterRegistry
- func (al *AgentLoop) RecordLastChannel(channel string) error
- func (al *AgentLoop) RecordLastChatID(chatID string) error
- func (al *AgentLoop) RegisterIdleTicker(sessionID string, cancel context.CancelFunc)
- func (al *AgentLoop) RegisterTool(tool tools.Tool)
- func (al *AgentLoop) ReloadProviderAndConfig(ctx context.Context, provider providers.LLMProvider, cfg *config.Config) error
- func (al *AgentLoop) RequestCancel(ctx context.Context, scope CancelScope, canceller CancelCanceller, ...) (CancelOutcome, error)
- func (al *AgentLoop) RequestCancelByChannelChat(ctx context.Context, channelName, chatID, userID string) error
- func (al *AgentLoop) RequestCancelForSession(ctx context.Context, sessionID, userID, channel string) (bool, error)
- func (al *AgentLoop) ResolveSessionStore(sessionID string) *session.UnifiedStore
- func (al *AgentLoop) Run(ctx context.Context) error
- func (al *AgentLoop) SandboxBackend() sandbox.SandboxBackend
- func (al *AgentLoop) SetAllowGodMode(allow bool)
- func (al *AgentLoop) SetAppliedSandboxMode(mode sandbox.Mode)
- func (al *AgentLoop) SetChannelManager(cm *channels.Manager)
- func (al *AgentLoop) SetCurrentSession(agentID, sessionID string)
- func (al *AgentLoop) SetMediaStore(s media.MediaStore)
- func (al *AgentLoop) SetReloadFunc(fn func() error)
- func (al *AgentLoop) SetSteeringMode(mode SteeringMode)
- func (al *AgentLoop) SetSysagentDeps(deps *systools.Deps)
- func (al *AgentLoop) SetToolApprover(a PolicyApprover)
- func (al *AgentLoop) SetTranscriber(t voice.Transcriber)
- func (al *AgentLoop) Steer(msg providers.Message) error
- func (al *AgentLoop) SteeringMode() SteeringMode
- func (al *AgentLoop) Stop()
- func (al *AgentLoop) SubscribeEvents(buffer int) EventSubscription
- func (al *AgentLoop) SwapConfig(newCfg *config.Config)
- func (al *AgentLoop) TriggerReload() error
- func (al *AgentLoop) UnmountHook(name string)
- func (al *AgentLoop) UnsubscribeEvents(id uint64)
- func (al *AgentLoop) WaitForActiveRequests()
- func (al *AgentLoop) WireSysagentDeps(deps *systools.Deps)
- func (al *AgentLoop) WireTier13Deps(deps Tier13Deps)
- type AgentLoopSpawner
- type AgentPromptDefinition
- type AgentRegistry
- func (r *AgentRegistry) CanSpawnSubagent(parentAgentID, targetAgentID string) bool
- func (r *AgentRegistry) Close()
- func (r *AgentRegistry) ForEachTool(name string, fn func(tools.Tool))
- func (r *AgentRegistry) GetAgent(agentID string) (*AgentInstance, bool)
- func (r *AgentRegistry) GetAgentName(agentID string) (string, bool)
- func (r *AgentRegistry) GetDefaultAgent() *AgentInstance
- func (r *AgentRegistry) ListAgentIDs() []string
- func (r *AgentRegistry) ResolveRoute(input routing.RouteInput) routing.ResolvedRoute
- func (r *AgentRegistry) SetDefaultAgentOverride(agentID string)
- type ApprovalDecision
- type ApprovalVerdict
- type BackgroundProcessKillPayload
- type BuiltinHookFactory
- type CancelCanceller
- type CancelHooks
- type CancelOutcome
- type CancelScope
- type CompactionRetryPayload
- type ContextBuilder
- func (cb *ContextBuilder) AddAssistantMessage(messages []providers.Message, content string) []providers.Message
- func (cb *ContextBuilder) AddToolResult(messages []providers.Message, toolCallID, toolName, result string) []providers.Message
- func (cb *ContextBuilder) BuildMessages(history []providers.Message, summary string, currentMessage string, ...) []providers.Message
- func (cb *ContextBuilder) BuildSystemPrompt() string
- func (cb *ContextBuilder) BuildSystemPromptWithCache() string
- func (cb *ContextBuilder) EnvironmentProvider() envcontext.Provider
- func (cb *ContextBuilder) GetEnvironmentContext() string
- func (cb *ContextBuilder) GetSkillsInfo() map[string]any
- func (cb *ContextBuilder) InvalidateCache()
- func (cb *ContextBuilder) ListSkillNames() []string
- func (cb *ContextBuilder) LoadAgentDefinition() AgentContextDefinition
- func (cb *ContextBuilder) LoadBootstrapFiles() string
- func (cb *ContextBuilder) Memory() *MemoryStore
- func (cb *ContextBuilder) ResolveSkillName(name string) (string, bool)
- func (cb *ContextBuilder) WithAgentInfo(id, name string) *ContextBuilder
- func (cb *ContextBuilder) WithEnvironmentProvider(p envcontext.Provider) *ContextBuilder
- func (cb *ContextBuilder) WithResourcesInjector(fn func() string) *ContextBuilder
- func (cb *ContextBuilder) WithSplitOnMarker(enabled bool) *ContextBuilder
- func (cb *ContextBuilder) WithToolDiscovery(useBM25, useRegex bool) *ContextBuilder
- type ContextBuilderRegistry
- type ContextCompressPayload
- type ContextCompressReason
- type EmptyResponseRetryPayload
- type ErrorPayload
- type Event
- type EventBus
- type EventKind
- type EventMeta
- type EventObserver
- type EventSubscription
- type FollowUpQueuedPayload
- type HookAction
- type HookDecision
- type HookManager
- func (hm *HookManager) AfterLLM(ctx context.Context, resp *LLMHookResponse) (*LLMHookResponse, HookDecision)
- func (hm *HookManager) AfterTool(ctx context.Context, result *ToolResultHookResponse) (*ToolResultHookResponse, HookDecision)
- func (hm *HookManager) ApproveTool(ctx context.Context, req *ToolApprovalRequest) ApprovalDecision
- func (hm *HookManager) BeforeLLM(ctx context.Context, req *LLMHookRequest) (*LLMHookRequest, HookDecision)
- func (hm *HookManager) BeforeTool(ctx context.Context, call *ToolCallHookRequest) (*ToolCallHookRequest, HookDecision)
- func (hm *HookManager) Close()
- func (hm *HookManager) ConfigureTimeouts(observer, interceptor, approval time.Duration)
- func (hm *HookManager) Mount(reg HookRegistration) error
- func (hm *HookManager) Unmount(name string)
- type HookRegistration
- type HookSource
- type InterruptKind
- type InterruptReceivedPayload
- type LLMDeltaPayload
- type LLMHookRequest
- type LLMHookResponse
- type LLMInterceptor
- type LLMRequestPayload
- type LLMResponsePayload
- type LLMRetryPayload
- type LongTermEntry
- type MemoryCategory
- type MemoryStore
- func (ms *MemoryStore) AppendLongTerm(content, category string) error
- func (ms *MemoryStore) AppendRetro(sessionID string, r Retro) error
- func (ms *MemoryStore) AppendToday(content string) error
- func (ms *MemoryStore) GetMemoryContext() string
- func (ms *MemoryStore) GetRecentDailyNotes(days int) string
- func (ms *MemoryStore) ReadLastSession() (string, error)
- func (ms *MemoryStore) ReadLongTerm() string
- func (ms *MemoryStore) ReadLongTermEntries() ([]LongTermEntry, error)
- func (ms *MemoryStore) ReadRetros(daysBack int) ([]Retro, error)
- func (ms *MemoryStore) ReadToday() string
- func (ms *MemoryStore) SearchEntries(query string, limit int) ([]LongTermEntry, error)
- func (ms *MemoryStore) SweepRetros(retentionDays int) (int, error)
- func (ms *MemoryStore) WriteLastSession(content string) error
- func (ms *MemoryStore) WriteLongTerm(content string) error
- type MemoryStoreAdapter
- type NotificationPayload
- type PolicyApprovalReq
- type PolicyApprover
- type ProcessHook
- func (ph *ProcessHook) AfterLLM(ctx context.Context, resp *LLMHookResponse) (*LLMHookResponse, HookDecision, error)
- func (ph *ProcessHook) AfterTool(ctx context.Context, result *ToolResultHookResponse) (*ToolResultHookResponse, HookDecision, error)
- func (ph *ProcessHook) ApproveTool(ctx context.Context, req *ToolApprovalRequest) (ApprovalDecision, error)
- func (ph *ProcessHook) BeforeLLM(ctx context.Context, req *LLMHookRequest) (*LLMHookRequest, HookDecision, error)
- func (ph *ProcessHook) BeforeTool(ctx context.Context, call *ToolCallHookRequest) (*ToolCallHookRequest, HookDecision, error)
- func (ph *ProcessHook) Close() error
- func (ph *ProcessHook) OnEvent(ctx context.Context, evt Event) error
- type ProcessHookOptions
- type RateLimitPayload
- type RecapModelBootError
- type RecapTrigger
- type Retro
- type ScheduledJobInfo
- type ServedEntry
- type ServedSubdirs
- func (s *ServedSubdirs) ActiveForAgent(agentID string) (token string, deadline time.Time, ok bool)
- func (s *ServedSubdirs) Evict(agentID string)
- func (s *ServedSubdirs) Lookup(token string) *ServedEntry
- func (s *ServedSubdirs) Register(agentID, absDir string, duration time.Duration) (token string, deadline time.Time, err error)
- func (s *ServedSubdirs) SetOnEvict(fn func(tokens []string))
- func (s *ServedSubdirs) Stop()
- type SessionSummarizePayload
- type SoulDefinition
- type SteeringInjectedPayload
- type SteeringMode
- type SubTurnConfig
- type SubTurnEndPayload
- type SubTurnOrphanPayload
- type SubTurnResultDeliveredPayload
- type SubTurnSpawnPayload
- type SubTurnStatus
- type TaskExecutor
- type ThinkingLevel
- type Tier13Deps
- type ToolApprovalRequest
- type ToolApprover
- type ToolCallHookRequest
- type ToolExecEndPayload
- type ToolExecSkippedPayload
- type ToolExecStartPayload
- type ToolInterceptor
- type ToolResultHookResponse
- type TurnCancelHook
- type TurnEndPayload
- type TurnEndStatus
- type TurnPhase
- type TurnStartPayload
- type TurnTimeoutPayload
- type UserDefinition
- type WhatsAppPairingPayload
Constants ¶
const DefaultAgentID = "main"
DefaultAgentID is the registry key for the default agent. It is the internal identifier for the generic default agent instance used when no specific agent is targeted (e.g., unrouted channel messages).
const NotificationAdminBroadcast = "*admin*"
NotificationAdminBroadcast is the sentinel Recipient value used when a notification could not be routed to a specific user and must reach every admin connection instead (W-7 fallback).
Variables ¶
var ( ErrDepthLimitExceeded = errors.New("sub-turn depth limit exceeded") ErrInvalidSubTurnConfig = errors.New("invalid sub-turn config") ErrConcurrencyTimeout = errors.New("timeout waiting for concurrency slot") )
var ErrReloadNotConfigured = errors.New("reload not configured")
ErrReloadNotConfigured is returned by TriggerReload when no reload function has been registered. This is normal in unit-test environments where the full gateway reload pipeline is not wired. Production always configures the reload function during startup, so callers outside tests should treat this as unexpected and log accordingly.
Functions ¶
func AbandonedWritesSuppressed ¶
func AbandonedWritesSuppressed() int64
AbandonedWritesSuppressed returns the current value of the omnipus_abandoned_writes_suppressed_total counter.
func GetSSRFChecker ¶
func GetSSRFChecker(al *AgentLoop) *security.SSRFChecker
GetSSRFChecker returns the singleton SSRFChecker built from the SSRF policy config at startup (SEC-24). Returns nil when SSRF protection is disabled (sandbox.ssrf.enabled = false in config.json). Gateway handlers that make outbound HTTP calls (e.g. the skills installer) should pass this to their HTTP client constructors so allow_internal is honored consistently.
func GetTaskStore ¶
GetTaskStore returns the shared TaskStore (may be nil in tests).
func RecoverOrphanedToolCalls ¶
func RecoverOrphanedToolCalls( store session.SessionStore, sessionKey string, auditLog *audit.Logger, ) []providers.Message
RecoverOrphanedToolCalls inspects the tail of the session's message history. If orphaned tool calls are found (assistant message with tool_calls but no subsequent tool result), it:
- Appends a synthetic system message to the transcript documenting the ungraceful shutdown recovery (FR-069).
- Returns the rebuilt history with the orphaned assistant message removed (FR-088), so the next LLM call does not observe the half-completed turn.
- Emits audit events for each orphaned tool call.
Parameters:
- store: the session store for the agent.
- sessionKey: the session whose history to inspect and repair.
- auditLog: optional audit logger; if nil, events are skipped.
Returns the cleaned history slice (all messages except the orphaned assistant turn). If no orphaned calls are found, returns the original history unchanged.
Safe to call on every session load — it is idempotent: orphans that already have a synthetic turn_canceled_restart system message are skipped.
func RegisterBuiltinHook ¶
func RegisterBuiltinHook(name string, factory BuiltinHookFactory) error
RegisterBuiltinHook registers a named in-process hook factory for config-driven mounting.
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 ¶
TurnStateFromContext retrieves turnState from context (exported for tools)
func WithAgentLoop ¶
WithAgentLoop injects AgentLoop into context for tool access
func WithScheduledJobContext ¶
WithScheduledJobContext returns a child context carrying the schedule identity. Call this in the cron fire path (pkg/gateway/schedules.go RunScheduled) before calling ProcessScheduled so the auto-deny audit entry can include the job id and name.
Types ¶
type ActiveTurnInfo ¶
type AdmissionController ¶
type AdmissionController struct {
// contains filtered or unexported fields
}
AdmissionController is a soft-cap gate for concurrent session workers.
Phase 1: gates inbound user-message dispatch only. The counter tracks unique active scopes (one per spawned session worker) — not per-turn, so a single chatty session cannot pin admission slots indefinitely. Subagent spawn and task-executor dispatch paths are NOT gated; see the v0.2 follow-up issue for resource-aware admission that covers those paths as well.
func (*AdmissionController) ActiveScopes ¶
func (a *AdmissionController) ActiveScopes() int
ActiveScopes returns the current count of active scopes (worker goroutines that hold an admission slot). Used in tests and observability.
func (*AdmissionController) SoftCap ¶
func (a *AdmissionController) SoftCap() int
SoftCap returns the configured soft cap value.
func (*AdmissionController) TryAdmit ¶
func (a *AdmissionController) TryAdmit(scope string) (bool, func())
TryAdmit atomically claims a slot for scope. Returns (true, release) when the scope is admitted; release MUST be called (typically via defer) when the scope's worker exits.
If scope is already active (follow-up turn in an existing session), the call always succeeds without consuming an additional slot — the slot was already claimed when the worker was first spawned.
Returns (false, nil) when the softCap is reached and scope is a new scope.
type AgentContextDefinition ¶
type AgentContextDefinition struct {
Source AgentDefinitionSource `json:"source,omitempty"`
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 AgentDefinitionSource ¶
type AgentDefinitionSource string
AgentDefinitionSource identifies which agent bootstrap file produced the definition.
const ( // AgentDefinitionSourceAgent indicates the new AGENT.md format. AgentDefinitionSourceAgent AgentDefinitionSource = "AGENT.md" // AgentDefinitionSourceAgents indicates the legacy AGENTS.md format. AgentDefinitionSourceAgents AgentDefinitionSource = "AGENTS.md" )
type AgentFrontmatter ¶
type AgentFrontmatter struct {
Name string `json:"name"`
Description string `json:"description"`
Tools []string `json:"tools,omitempty"`
Model string `json:"model,omitempty"`
MaxTurns *int `json:"maxTurns,omitempty"`
Skills []string `json:"skills,omitempty"`
MCPServers []string `json:"mcpServers,omitempty"`
Fields map[string]any `json:"fields,omitempty"`
}
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 string
Fallbacks []string
Workspace string
MaxIterations int
MaxTokens int
Temperature float64
ThinkingLevel ThinkingLevel
ContextWindow int
SummarizeMessageThreshold int
SummarizeTokenPercent int
Provider providers.LLMProvider
Sessions session.SessionStore
ContextBuilder *ContextBuilder
Tools *tools.ToolRegistry
Subagents *config.SubagentsConfig
SkillsFilter []string
Candidates []providers.FallbackCandidate
// TimeoutSeconds is the per-turn hard timeout. 0 = disabled.
// Populated from AgentDefaults.TimeoutSeconds; per-agent override if available.
TimeoutSeconds int
// AgentType is the resolved type string ("core", "custom", "system") used by
// FilterToolsByPolicy at LLM-call assembly time (FR-003, FR-041). Set once at
// construction; never mutated after creation.
AgentType string
// IsRoutingDefault records whether this agent was marked Default=true in
// its AgentConfig at construction time. Used by GetDefaultAgent to make the
// per-agent routing-default flag the single canonical source of truth (F3),
// so SPA WebSocket chat and channel routing both converge on the same agent.
// Read-only after construction.
IsRoutingDefault bool
// Router is non-nil when model routing is configured and the light model
// was successfully resolved. It scores each incoming message and decides
// whether to route to LightCandidates or stay with Candidates.
Router *routing.Router
// LightCandidates holds the resolved provider candidates for the light model.
// Pre-computed at agent creation to avoid repeated model_list lookups at runtime.
LightCandidates []providers.FallbackCandidate
// LightProvider is the concrete provider instance for the configured light model.
// It is only used when routing selects the light tier for a turn.
LightProvider providers.LLMProvider
// 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, ) *AgentInstance
NewAgentInstance creates an agent instance from config.
func (*AgentInstance) Close ¶
func (a *AgentInstance) Close() error
Close releases resources held by the agent's session store.
func (*AgentInstance) LoadToolPolicy ¶
func (a *AgentInstance) LoadToolPolicy() *tools.ToolPolicyCfg
LoadToolPolicy returns the current tool policy snapshot for this agent. Returns nil when no policy has been stored (defaults to allow-all at call sites). Safe for concurrent access (atomic load, FR-020).
func (*AgentInstance) SetAgentType ¶
func (a *AgentInstance) SetAgentType(agentType string)
SetAgentType updates the resolved agent type. Called by the registry to upgrade runtime-seeded core agents (e.g., Ava, Main) that may not have Type set in config.
func (*AgentInstance) StoreToolPolicy ¶
func (a *AgentInstance) StoreToolPolicy(p *tools.ToolPolicyCfg)
StoreToolPolicy atomically replaces the agent's tool policy (FR-020). Called by ReloadProviderAndConfig on config PUT to propagate the new policy without rebuilding the agent registry. Passing nil resets to allow-all. Safe for concurrent access with ongoing turn assembly.
type AgentLoop ¶
type AgentLoop struct {
// contains filtered or unexported fields
}
func AgentLoopFromContext ¶
AgentLoopFromContext retrieves AgentLoop from context
func NewAgentLoop ¶
func NewAgentLoop( cfg *config.Config, msgBus *bus.MessageBus, provider providers.LLMProvider, ) (*AgentLoop, error)
NewAgentLoop constructs an AgentLoop from the given config, message bus, and LLM provider. Returns (*AgentLoop, nil) on success. Returns (nil, *RecapModelBootError) when AutoRecapEnabled is true and the recap model fails the allow-list gate (FR-029a) — callers should treat this as a fatal configuration error and abort boot.
func (*AgentLoop) AgentForSession ¶
func (al *AgentLoop) AgentForSession(sessionID string) (*AgentInstance, error)
AgentForSession resolves the AgentInstance responsible for the given session. FR-026.
func (*AgentLoop) ApplyAgentModel ¶
ApplyAgentModel switches a live agent instance to a new primary model in place — rebuilding its provider, candidate chain, and thinking level under the instance lock WITHOUT recreating the instance. This preserves the agent's in-memory conversation state and avoids a config hot-reload that would drop the WebSocket (#73). The new model must already be persisted to config (the REST handler writes config.json first) so resolution observes it. Returns the previous primary model. Shared by the switch_model tool and the PUT /api/v1/agents/{id} model-change path.
func (*AgentLoop) AuditLogger ¶
AuditLogger returns the audit logger, or nil if audit logging is disabled. Used by gateway handlers that need to log policy changes.
func (*AgentLoop) BootstrapRecapPass ¶
BootstrapRecapPass (FR-032, FR-032a): on gateway start, scans the shared session store for sessions that lack a retro and are older than 30 minutes, and enqueues a CloseSession("bootstrap") for each.
Early-returns if AutoRecapEnabled or BootstrapRecapEnabled is false. Rate-limits starts to GetBootstrapRecapMaxPerMinute per minute. Caps total estimated cost at GetBootstrapRecapDailyBudgetUSD.
Sessions are a gateway-wide resource, NOT per-agent — so the sessions directory is walked exactly once. Each session's owning agent is resolved via AgentForSession before auditing so the audit entry reflects reality.
func (*AgentLoop) Close ¶
func (al *AgentLoop) Close()
Close releases resources held by agent session stores. Call after Stop.
func (*AgentLoop) CloseSession ¶
CloseSession triggers an async session-end recap if AutoRecapEnabled is set and this sessionID has not already been claimed. Idempotent: duplicate calls for the same sessionID are silently dropped (FR-027).
func (*AgentLoop) ContextBuilderRegistry ¶
func (al *AgentLoop) ContextBuilderRegistry() *ContextBuilderRegistry
ContextBuilderRegistry returns the registry used to broadcast system-prompt cache invalidation when operator config changes (FR-061). Always non-nil after NewAgentLoop.
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) EmitNotification ¶
func (al *AgentLoop) EmitNotification(p NotificationPayload)
EmitNotification publishes a user-facing notification onto the event bus so the recipient's SPA WebSocket connections receive a notification frame (#264). The WS forwarder filters delivery by Recipient (==wsConn.userID), so the payload is not broadcast to every authenticated tab. Safe to call from any goroutine — the bus drops to a full subscriber rather than blocking.
func (*AgentLoop) EmitWhatsAppPairing ¶
func (al *AgentLoop) EmitWhatsAppPairing(channelID string, status channels.PairingStatus, qr, message string)
EmitWhatsAppPairing publishes a WhatsApp native/QR pairing update (QR code or status) onto the event bus so every connected SPA WebSocket client receives a whatsapp_pairing frame (#283). Safe to call from a channel's own goroutine — the bus drops to a full subscriber rather than blocking. Wired into the WhatsApp native channel at gateway boot via SetPairingObserver.
func (*AgentLoop) EventDrops ¶
EventDrops returns the number of dropped events for the given kind.
func (*AgentLoop) ExecProxy ¶
ExecProxy returns the SEC-28 SSRF proxy for exec child processes, or nil when the proxy is disabled or failed to bind. Used by gateway handlers that report the proxy status and by tests that exercise the proxy lifecycle.
func (*AgentLoop) GetActiveAgentIDs ¶
GetActiveAgentIDs returns the IDs of all agents that currently have an active turn. Used by the REST API to report real-time agent status.
func (*AgentLoop) GetActiveTurn ¶
func (al *AgentLoop) GetActiveTurn() *ActiveTurnInfo
func (*AgentLoop) GetActiveTurnBySession ¶
func (al *AgentLoop) GetActiveTurnBySession(sessionKey string) *ActiveTurnInfo
func (*AgentLoop) GetActiveTurnHookForSession ¶
func (al *AgentLoop) GetActiveTurnHookForSession(sessionID string) TurnCancelHook
GetActiveTurnHookForSession returns a TurnCancelHook for the active turn belonging to the given transcript session ID, or nil if none is active. Used by handleCancel to atomically claim the turn and register the post-cancel callback (FR-10, FR-11, FR-15).
H1: When multiple turns share the same transcriptSessionID (a root turn plus one or more sub-turns), the root turn (depth==0 / parentTurnID=="") is preferred so the cancel handler targets the outermost scope. The first match in the sync.Map iteration is returned only as a last-resort fallback (defensive; should not occur in normal operation).
func (*AgentLoop) GetAgentStore ¶
func (al *AgentLoop) GetAgentStore(agentID string) *session.UnifiedStore
GetAgentStore returns the UnifiedStore for a given agent, or nil if not found or if the agent's session store is not a UnifiedStore. Use GetSessionStore() for creating new sessions; GetAgentStore is kept for legacy per-agent session access.
func (*AgentLoop) GetChannelManager ¶
GetChannelManager returns the current channel manager under the read lock (may be nil before channels start, e.g. during onboarding). Exported for pkg/gateway: REST handlers inspect runtime channel state (e.g. FailedChannels), and the scheduled runner validates that a deliver=true target channel is registered before publishing (M2). Set after construction via SetChannelManager, so callers must tolerate nil and re-fetch at use time.
func (*AgentLoop) GetCurrentSession ¶
GetCurrentSession returns the active session ID for the given agent, and whether one was found. Used by the WebSocket lazy-CAS logic (FR-024).
func (*AgentLoop) GetMediaRefsDropped ¶
GetMediaRefsDropped returns the cumulative count of media refs that were dropped because they could not be resolved (unknown ref or file missing on disk). Safe for concurrent access; incremented on the hot turn path.
func (*AgentLoop) GetMediaStore ¶
func (al *AgentLoop) GetMediaStore() media.MediaStore
GetMediaStore returns the currently injected media store. Callers that serve media over HTTP must use this getter (not a cached reference) because the store is replaced on every restartServices — a cached pointer goes stale.
func (*AgentLoop) GetRegistry ¶
func (al *AgentLoop) GetRegistry() *AgentRegistry
GetRegistry returns the current registry (thread-safe)
func (*AgentLoop) GetSessionActiveAgent ¶
GetSessionActiveAgent returns the agent that the handoff tool last switched the given session to. Returns ("", false) if no handoff override is active for this session_id.
func (*AgentLoop) GetSessionStore ¶
func (al *AgentLoop) GetSessionStore() *session.UnifiedStore
GetSessionStore returns the shared UnifiedStore for new sessions. May be nil in tests or when the shared sessions directory could not be initialized.
func (*AgentLoop) GetStartupInfo ¶
GetStartupInfo returns information about loaded tools and skills for logging.
func (*AgentLoop) HardAbort ¶
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"). For graceful interruption that allows the agent to finish the current tool and summarize, use Steer() instead.
func (*AgentLoop) HydrateAgentHistoryFromTranscript ¶
HydrateAgentHistoryFromTranscript reads the transcript for sessionID and rebuilds each owning agent's session.SessionStore history under the key "agent:<agentID>:session:<sessionID>".
The mapping is best-effort: messages with unknown roles or unresolvable agent IDs are skipped. SubTurn entries (orchestrator hand-offs) are ignored at this layer — they are reconstructed by the agent loop's own subturn machinery on demand.
func (*AgentLoop) InjectFollowUp ¶
InjectFollowUp enqueues a message to be automatically processed after the current turn completes. Unlike Steer(), which interrupts the current execution, InjectFollowUp waits for the current turn to finish naturally before processing the message.
This is useful for: - Automated workflows that need to chain multiple turns - Background tasks that should run after the main task completes - Scheduled follow-up actions
The message will be processed via Continue() when the agent becomes idle.
func (*AgentLoop) InjectSteering ¶
InjectSteering is an alias for Steer() to match the design document naming. It injects a steering message into the currently running agent loop.
func (*AgentLoop) InterruptByChannelChat ¶
InterruptByChannelChat gracefully cancels the active root turn (depth==0) whose channel and chatID match the supplied values, then cascades to all sub-turns that share the same transcriptSessionID via InterruptSession.
This is the correct cancellation path for Tier B (text-parsing) channels: inbound messages from those channels carry no explicit SessionID so a direct InterruptSession call would match nothing. Sub-turns inherit their parent's transcriptSessionID but NOT channel/chatID (they are created with empty values), so matching by channel+chatID alone misses them. The two-step strategy — find root by channel+chatID, then cascade by sessionID — covers both parent and all sub-turns.
Returns nil whether or not any matching turn was found — "no active turn" is a valid no-op. Returns a non-nil error only when channel or chatID is empty.
func (*AgentLoop) InterruptGraceful ¶
func (*AgentLoop) InterruptHard ¶
func (*AgentLoop) InterruptSession ¶
InterruptSession gracefully cancels the parent turn AND every sub-turn sharing the given sessionID (transcriptSessionID match). FR-6, FR-10, FR-12a, FR-15.
The cascade walks activeTurnStates and, for each matching turnState, spawns a goroutine that calls requestGracefulInterrupt AND providerCancel in parallel so the in-flight LLM HTTP request is aborted immediately (FR-12a) rather than waiting for the stream to drain naturally within the 3s graceful window.
Returns the list of turn IDs that received the cancel signal (parent + sub-turns). The cancel handler includes this in the turn_canceled audit/transcript entry. Returns an error only if sessionID is empty. A session with no active turns is not an error — cancel handlers treat it as a no-op (was_fired=false).
func (*AgentLoop) InterruptSessionHard ¶
InterruptSessionHard escalates a previously-graceful cancel to a hard abort for every turn matching sessionID. Called at t=3s after InterruptSession per FR-11. See InterruptHard for the legacy single-turn path; this function is session-scoped.
Returns the list of turn IDs that received the hard-abort signal.
func (*AgentLoop) ListAllSessions ¶
func (al *AgentLoop) ListAllSessions() ([]*session.UnifiedMeta, []error)
ListAllSessions returns sessions from the shared store merged with legacy per-agent stores, deduplicated and sorted by UpdatedAt descending. The second return value collects per-store errors so callers can distinguish "no sessions" from "all stores failed". Callers should surface partial errors as warnings rather than treating the entire response as a failure.
func (*AgentLoop) MountHook ¶
func (al *AgentLoop) MountHook(reg HookRegistration) error
MountHook registers an in-process hook on the agent loop.
func (*AgentLoop) MountProcessHook ¶
func (*AgentLoop) MutateConfig ¶
MutateConfig acquires the agent loop write lock and calls fn with the live *config.Config pointer. This serializes sysagent mutations with all REST readers that go through GetConfig (which holds RLock). fn must not call GetConfig or SwapConfig — deadlock would result.
The caller (typically Deps.WithConfig) is responsible for snapshotting and rolling back cfg fields if fn or the subsequent SaveConfig fails.
func (*AgentLoop) ProcessDirect ¶
func (*AgentLoop) ProcessDirectWithChannel ¶
func (*AgentLoop) ProcessHeartbeat ¶
func (al *AgentLoop) ProcessHeartbeat( ctx context.Context, content, channel, chatID string, ) (string, error)
ProcessHeartbeat processes a heartbeat request without session history. Each heartbeat is independent and doesn't accumulate context.
func (*AgentLoop) ProcessScheduled ¶
func (al *AgentLoop) ProcessScheduled( ctx context.Context, ownerAgentID, sessionID, content, channel, chatID string, ) (string, error)
ProcessScheduled runs a fired schedule's message as ownerAgentID against the concrete pre-created sessionID (issue #264, W-1). It is the dedicated headless entry point for the cron → agent fire path and deliberately differs from the human message path:
- It pins ownerAgentID directly via runAgentLoop — it does NOT consult routing or the sessionActiveAgent handoff map, so a human switching agents in this session cannot hijack the scheduled run, and a missing/disabled owner is a hard error (never a default-agent fallback, the core #264 bug).
- It passes the concrete sessionID as TranscriptSessionID so the turn registers under it (GetActiveTurnHookForSession matches by transcriptSessionID) and RequestCancel(CancelScope{SessionID}) can abort it on a caller-imposed deadline. The session key is the per-owner "agent:<owner>:session:<id>" form, collision-free across isolated runs.
- It sets AutoDenyAsk so any `ask`-policy tool call is denied without blocking for approval (FR-009) — no operator is present.
The caller (the Wave-2 gateway runner) resolves the owner + picks the session per session_mode and supplies a concrete sessionID; it imposes the deadline on ctx and calls RequestCancel on timeout. ProcessScheduled only guarantees owner-pinning, cancellability, and prompt return.
Returns the agent's reply and a non-nil error on run failure. An aborted (canceled/deadline) run returns a context-derived error promptly.
func (*AgentLoop) PromptGuard ¶
func (al *AgentLoop) PromptGuard() *security.PromptGuard
PromptGuard returns the SEC-25 prompt-injection guard. Always non-nil after NewAgentLoop — even when no config field is set, the factory returns a medium-strictness guard. Used by runTurn and by gateway status handlers.
func (*AgentLoop) RateLimiter ¶
func (al *AgentLoop) RateLimiter() *security.RateLimiterRegistry
RateLimiter returns the SEC-26 rate limiter registry. Always non-nil after NewAgentLoop. Used by runTurn for per-agent limit checks and by gateway handlers that report the current rate limit / cost status.
func (*AgentLoop) RecordLastChannel ¶
RecordLastChannel records the last active channel for this workspace. This uses the atomic state save mechanism to prevent data loss on crash.
func (*AgentLoop) RecordLastChatID ¶
RecordLastChatID records the last active chat ID for this workspace. This uses the atomic state save mechanism to prevent data loss on crash.
func (*AgentLoop) RegisterIdleTicker ¶
func (al *AgentLoop) RegisterIdleTicker(sessionID string, cancel context.CancelFunc)
RegisterIdleTicker stores a cancel function for the idle ticker of a session. Calling it again for the same session replaces the previous cancel without canceling it — use resetIdleTicker for the reset path.
func (*AgentLoop) RegisterTool ¶
func (*AgentLoop) ReloadProviderAndConfig ¶
func (al *AgentLoop) ReloadProviderAndConfig( ctx context.Context, provider providers.LLMProvider, cfg *config.Config, ) error
ReloadProviderAndConfig atomically swaps the provider and config with proper synchronization. It uses a context to allow timeout control from the caller. Returns an error if the reload fails or context is canceled.
func (*AgentLoop) RequestCancel ¶
func (al *AgentLoop) RequestCancel( ctx context.Context, scope CancelScope, canceller CancelCanceller, hooks CancelHooks, ) (CancelOutcome, error)
RequestCancel is the canonical cancel entry point. All four cancel surfaces (web SPA, Tier A /cancel command, Tier B text-parsing channels, CLI) call this method.
It performs the entire cancel state machine:
- abuse-detection record
- ClaimCancel atomic first-cancel-wins check
- turn_cancel_attempt audit emission (always, even for no-op cancels)
- graceful cascade via InterruptSession / providerCancel
- approval auto-deny (via hooks.CancelPendingApprovals)
- cancel_stage frame emission (via hooks.SendStageFrame)
- session status → interrupted (via hooks.SetSessionInterrupted)
- transcript MarkLastEntryTruncated + turn_canceled entry on Finish
- turn_canceled audit on Finish
- 3s timer → hard abort (InterruptSessionHard)
- 5s timer → detached / MarkAbandoned + turn_cancel_stuck audit
Returns:
- CancelOutcome{Fired: true, Descendants, TurnID} on a successful claim
- CancelOutcome{Fired: false} when no active turn matches OR ClaimCancel found cancelFired==true (double-cancel race)
- error only for parameter validation failures (empty scope)
func (*AgentLoop) RequestCancelByChannelChat ¶
func (al *AgentLoop) RequestCancelByChannelChat(ctx context.Context, channelName, chatID, userID string) error
RequestCancelByChannelChat is a primitive-argument adapter for RequestCancel used by the channels.CancelInterceptor interface. It resolves the session by (channel, chatID) so Tier B text-parsing channels can fire the full cancel state machine without knowing the session ID.
Returns nil when no matching turn exists (no-op). Returns a non-nil error only when channel or chatID is empty.
func (*AgentLoop) RequestCancelForSession ¶
func (al *AgentLoop) RequestCancelForSession(ctx context.Context, sessionID, userID, channel string) (bool, error)
RequestCancelForSession is a primitive-argument adapter for RequestCancel used by the commands.AgentLoopInterface. It avoids importing pkg/agent types in pkg/commands (which would create a circular dependency) by accepting only primitive string arguments.
sessionID must be non-empty. Returns (fired, nil) on success; fired is true when an active turn was claimed.
func (*AgentLoop) ResolveSessionStore ¶
func (al *AgentLoop) ResolveSessionStore(sessionID string) *session.UnifiedStore
ResolveSessionStore finds which UnifiedStore owns the given sessionID. Checks the shared store first, then the main agent's legacy store, then all other per-agent stores. Returns nil if the session cannot be found.
func (*AgentLoop) SandboxBackend ¶
func (al *AgentLoop) SandboxBackend() sandbox.SandboxBackend
SandboxBackend returns the active sandbox backend, or nil if sandboxing is disabled. Used by gateway handlers that report sandbox status.
func (*AgentLoop) SetAllowGodMode ¶
SetAllowGodMode sets the god-mode opt-in flag (latch 2). Must be called before WireTier13Deps so the coercion logic picks up the correct value. If called after WireTier13Deps, the change takes effect on the next hot-reload.
func (*AgentLoop) SetAppliedSandboxMode ¶
SetAppliedSandboxMode stores the mode that the kernel sandbox actually applied at boot (from SandboxApplyResult.Mode). Must be called from the gateway boot path after applySandbox returns successfully, before WireTier13Deps and wireExecToolDeps run, so that ExecToolDeps.SandboxMode reflects the true runtime enforcement level rather than the config file value.
func (*AgentLoop) SetChannelManager ¶
func (*AgentLoop) SetCurrentSession ¶
SetCurrentSession records the active session ID for the given agent. Used by the WebSocket lazy-CAS logic (FR-024).
func (*AgentLoop) SetMediaStore ¶
func (al *AgentLoop) SetMediaStore(s media.MediaStore)
SetMediaStore injects a MediaStore for media lifecycle management.
func (*AgentLoop) SetReloadFunc ¶
SetReloadFunc sets the callback function for triggering config reload.
func (*AgentLoop) SetSteeringMode ¶
func (al *AgentLoop) SetSteeringMode(mode SteeringMode)
SetSteeringMode updates the steering mode.
func (*AgentLoop) SetSysagentDeps ¶
SetSysagentDeps stores the system.* tool dependencies for use by hot-reload. Call WireSysagentDeps after this to immediately register the tools.
func (*AgentLoop) SetToolApprover ¶
func (al *AgentLoop) SetToolApprover(a PolicyApprover)
SetToolApprover injects the gateway's policy-level approval implementation into the loop (FR-011). Must be called before any turns start; safe from any goroutine. Passing nil clears the approver (ask-policy tools treated as allow — open gate).
func (*AgentLoop) SetTranscriber ¶
func (al *AgentLoop) SetTranscriber(t voice.Transcriber)
SetTranscriber injects a voice transcriber for agent-level audio transcription.
func (*AgentLoop) Steer ¶
Steer enqueues a user message to be injected into the currently running agent loop. The message will be picked up after the current tool finishes executing, causing any remaining tool calls in the batch to be skipped.
func (*AgentLoop) SteeringMode ¶
func (al *AgentLoop) SteeringMode() SteeringMode
SteeringMode returns the current steering mode.
func (*AgentLoop) SubscribeEvents ¶
func (al *AgentLoop) SubscribeEvents(buffer int) EventSubscription
SubscribeEvents registers a subscriber for agent-loop events.
func (*AgentLoop) SwapConfig ¶
SwapConfig atomically replaces the in-memory config with the supplied, fully-initialized *config.Config (credentials resolved, sensitive values registered). Callers are responsible for calling credentials.ResolveBundle and cfg.RegisterSensitiveValues before SwapConfig — this method only does the atomic pointer swap.
func (*AgentLoop) TriggerReload ¶
TriggerReload triggers a config reload so the in-memory config picks up changes written to disk by safeUpdateConfigJSON. Called by REST handlers after persisting config changes (agent create/update, token rotate, etc.).
Concurrency: the underlying reloadFunc (set in gateway.go) is guarded by an atomic CompareAndSwap that serializes concurrent calls — only one reload can be in flight at a time. A second concurrent call returns an error ("reload already in progress") rather than queuing a second reload.
func (*AgentLoop) UnmountHook ¶
UnmountHook removes a previously registered in-process hook.
func (*AgentLoop) UnsubscribeEvents ¶
UnsubscribeEvents removes a previously registered event subscriber.
func (*AgentLoop) WaitForActiveRequests ¶
func (al *AgentLoop) WaitForActiveRequests()
WaitForActiveRequests blocks until all in-flight LLM calls tracked by activeRequests have completed. Used by the graceful shutdown sequence to ensure active turns finish before the process exits.
func (*AgentLoop) WireSysagentDeps ¶
WireSysagentDeps registers all 41 system.* tools on every agent in the current registry (FR-001, FR-002). Mirrors the WireTier13Deps pattern: called once at boot after NewAgentLoop, and again on hot-reload. The deps pointer is stashed so hot-reload can re-apply the wiring on the rebuilt registry.
Per-agent policy (seeded via coreagent.SeedConfig) governs which agents may actually invoke these tools at LLM-call time — this registration is the supply side; policy is the demand filter.
func (*AgentLoop) WireTier13Deps ¶
func (al *AgentLoop) WireTier13Deps(deps Tier13Deps)
WireTier13Deps registers the web_serve, workspace.shell, and workspace.shell_bg tools into every non-system agent's tool registry using the shared infrastructure instances created once at gateway boot. Called from gateway.go after NewAgentLoop and after the Tier13Deps registries (DevServerRegistry, ServedSubdirs, EgressProxy) are constructed. The "Tier13" name is historical — Tier 1 (static serve) and Tier 3 (dev-server proxy) used to live in two separate tools (serve_workspace and run_in_workspace); both are now subsumed by web_serve, but the deps struct keeps the legacy name for cross-package callers.
Mirrors the wireExecToolDeps pattern: post-creation injection so the heavy singleton objects (EgressProxy, DevServerRegistry) are not re-created per agent. Nil fields in deps skip the corresponding tool registration (graceful degradation when preview is disabled or Tier 3 unsupported).
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"`
}
AgentPromptDefinition represents the parsed AGENT.md or AGENTS.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, ) *AgentRegistry
NewAgentRegistry creates a registry from config, instantiating all agents.
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 and clears the map (M9).
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) GetAgentName ¶
func (r *AgentRegistry) GetAgentName(agentID string) (string, bool)
GetAgentName returns the display name for agentID and true if the agent exists in the registry. It satisfies the tools.AgentRegistryReader interface used by HandoffTool to avoid an import cycle.
func (*AgentRegistry) GetDefaultAgent ¶
func (r *AgentRegistry) GetDefaultAgent() *AgentInstance
GetDefaultAgent returns the default agent instance.
Resolution order (canonical — matches channel routing's resolveDefaultAgentID):
- An agent whose config has Default==true (the per-agent routing-default flag set by SeedConfig / the Agents-screen "star"). Deterministic: if multiple agents somehow carry Default==true (operator error — F11 repairs this at boot), the one with the lexicographically smallest ID wins.
- The configurable override from config.Agents.Defaults.DefaultAgentID, when the named agent exists in the registry.
- The built-in "main" sentinel agent.
- The lexicographically first registered agent (deterministic fallback, M10).
func (*AgentRegistry) ListAgentIDs ¶
func (r *AgentRegistry) ListAgentIDs() []string
ListAgentIDs returns all registered agent IDs.
func (*AgentRegistry) ResolveRoute ¶
func (r *AgentRegistry) ResolveRoute(input routing.RouteInput) routing.ResolvedRoute
ResolveRoute determines which agent handles the message.
func (*AgentRegistry) SetDefaultAgentOverride ¶
func (r *AgentRegistry) SetDefaultAgentOverride(agentID string)
SetDefaultAgentOverride sets the agent ID to use as the default agent. When set, GetDefaultAgent returns this agent instead of the "main" agent.
type ApprovalDecision ¶
type ApprovalDecision struct {
Verdict ApprovalVerdict `json:"verdict"`
Reason string `json:"reason,omitempty"`
}
ApprovalDecision is the result of a ToolApprover.ApproveTool call. Use IsApproved() to check whether execution is permitted.
func Deny ¶
func Deny(reason string) ApprovalDecision
Deny constructs a denial ApprovalDecision with a guaranteed non-empty reason.
func (ApprovalDecision) IsApproved ¶
func (d ApprovalDecision) IsApproved() bool
IsApproved returns true when the verdict permits tool execution.
type ApprovalVerdict ¶
type ApprovalVerdict string
ApprovalVerdict is a typed string enum for the outcome of a tool approval request.
const ( // VerdictAllow approves the tool call for this invocation only. VerdictAllow ApprovalVerdict = "allow" // VerdictDeny denies the tool call. VerdictDeny ApprovalVerdict = "deny" // VerdictAlways approves the tool call and remembers the preference for this session. VerdictAlways ApprovalVerdict = "always" )
type BackgroundProcessKillPayload ¶
BackgroundProcessKillPayload describes a background process that was force-killed.
type BuiltinHookFactory ¶
BuiltinHookFactory constructs an in-process hook from config.
type CancelCanceller ¶
type CancelCanceller struct {
UserID string // e.g. "@alice", "user_abc123"
Channel string // factory ID: "web" | "cli" | "telegram" | "slack" | ...
}
CancelCanceller is the identity of who issued the cancel. Used for audit attribution and abuse detection.
type CancelHooks ¶
type CancelHooks struct {
// SendStageFrame is called at each timer stage transition
// (stage values: "graceful", "hard", "detached").
SendStageFrame func(sessionID, stage string)
// CancelPendingApprovals auto-denies pending approvals on the canceled
// session (FR-7). Called once at graceful stage.
CancelPendingApprovals func(sessionID, reason string)
// SetSessionInterrupted updates the session meta.json Status to interrupted.
// Called once at graceful stage.
SetSessionInterrupted func(sessionID string)
}
CancelHooks lets callers inject transport-specific side-effects. All fields are optional; nil hooks are silently skipped.
type CancelOutcome ¶
type CancelOutcome struct {
Fired bool // true if a turn was actually targeted (ClaimCancel succeeded)
Descendants []string // turn IDs canceled (parent + sub-turns)
TurnID string // root turn ID; empty when Fired is false
}
CancelOutcome is returned to the caller after a cancel attempt.
type CancelScope ¶
type CancelScope struct {
SessionID string // non-empty → cancel the session directly
Channel string // Tier B: factory ID, e.g. "telegram"
ChatID string // Tier B: platform chat identifier
}
CancelScope identifies what to cancel. Exactly one of SessionID or (Channel + ChatID) must be set.
- SessionID is preferred when known (web SPA, CLI, Tier A /cancel).
- Channel + ChatID is used by Tier B channels that carry no SessionID; RequestCancel resolves the session internally by walking activeTurnStates.
type CompactionRetryPayload ¶
CompactionRetryPayload describes context compaction triggered during a timeout recovery.
type ContextBuilder ¶
type ContextBuilder struct {
// contains filtered or unexported fields
}
func NewContextBuilder ¶
func NewContextBuilder(workspace string) *ContextBuilder
func (*ContextBuilder) AddAssistantMessage ¶
func (cb *ContextBuilder) AddAssistantMessage( messages []providers.Message, content string, ) []providers.Message
AddAssistantMessage appends an assistant message to the message slice. The toolCalls parameter was previously unused and has been removed (M5).
func (*ContextBuilder) AddToolResult ¶
func (*ContextBuilder) BuildMessages ¶
func (*ContextBuilder) BuildSystemPrompt ¶
func (cb *ContextBuilder) BuildSystemPrompt() string
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) EnvironmentProvider ¶
func (cb *ContextBuilder) EnvironmentProvider() envcontext.Provider
EnvironmentProvider returns the currently-configured provider (may be nil). Exposed for subturn inheritance assertions and test doubles.
func (*ContextBuilder) GetEnvironmentContext ¶
func (cb *ContextBuilder) GetEnvironmentContext() string
GetEnvironmentContext renders the env preamble for the current process/ agent. Returns an empty string when no provider is wired, which preserves legacy behavior and makes the parts[0] insertion a no-op in tests that do not exercise env awareness. The real body is installed by lane E via envcontext.Render.
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 bootstrap files.
It prefers the new AGENT.md format and its paired SOUL.md file. When the structured files are absent, it falls back to the legacy AGENTS.md layout so the current runtime can transition incrementally.
func (*ContextBuilder) LoadBootstrapFiles ¶
func (cb *ContextBuilder) LoadBootstrapFiles() string
LoadBootstrapFiles loads the bootstrap files using a fresh LoadAgentDefinition call. Callers that already hold an AgentContextDefinition should use loadBootstrapFilesWithDef to avoid a redundant disk read.
func (*ContextBuilder) Memory ¶
func (cb *ContextBuilder) Memory() *MemoryStore
Memory returns the MemoryStore backing this ContextBuilder. Used by NewAgentInstance to wire memory tools with the same store instance.
func (*ContextBuilder) ResolveSkillName ¶
func (cb *ContextBuilder) ResolveSkillName(name string) (string, bool)
func (*ContextBuilder) WithAgentInfo ¶
func (cb *ContextBuilder) WithAgentInfo(id, name string) *ContextBuilder
func (*ContextBuilder) WithEnvironmentProvider ¶
func (cb *ContextBuilder) WithEnvironmentProvider(p envcontext.Provider) *ContextBuilder
WithEnvironmentProvider wires an envcontext.Provider into the builder so BuildSystemPrompt renders the ## Environment preamble as parts[0]. Fix A (FR-057). Subagents share the same ContextBuilder pointer as their parent, so any provider set here is inherited automatically (see subturn.go).
func (*ContextBuilder) WithResourcesInjector ¶
func (cb *ContextBuilder) WithResourcesInjector(fn func() string) *ContextBuilder
WithResourcesInjector sets a callback that provides additional context sections to inject into the system prompt (e.g., available tools catalog for Ava).
func (*ContextBuilder) WithSplitOnMarker ¶
func (cb *ContextBuilder) WithSplitOnMarker(enabled bool) *ContextBuilder
func (*ContextBuilder) WithToolDiscovery ¶
func (cb *ContextBuilder) WithToolDiscovery(useBM25, useRegex bool) *ContextBuilder
type ContextBuilderRegistry ¶
type ContextBuilderRegistry struct {
// contains filtered or unexported fields
}
ContextBuilderRegistry is a thin, concurrency-safe registry that maps an agent ID to its ContextBuilder. It is the broadcast channel for config-change invalidation (FR-061): when an operator writes a sandbox or dev-mode-bypass setting, the REST handler calls InvalidateAllContextBuilders so every agent's next turn picks up the new preamble without a restart.
Stale entries (agents that have been deleted but whose Unregister was never called) are harmless: InvalidateCache on a no-longer-used ContextBuilder is a cheap no-op.
func NewContextBuilderRegistry ¶
func NewContextBuilderRegistry() *ContextBuilderRegistry
NewContextBuilderRegistry returns an empty, ready-to-use registry.
func (*ContextBuilderRegistry) InvalidateAllContextBuilders ¶
func (r *ContextBuilderRegistry) InvalidateAllContextBuilders()
InvalidateAllContextBuilders iterates every registered ContextBuilder and calls InvalidateCache on each. This forces a full rebuild of the system prompt (including the env preamble) on the next turn. The iteration is lock-free (sync.Map.Range) and safe for concurrent reads and writes.
func (*ContextBuilderRegistry) Register ¶
func (r *ContextBuilderRegistry) Register(agentID string, cb *ContextBuilder)
Register associates agentID with cb. Replaces any existing entry for the same ID (e.g. after a hot-reload creates a new ContextBuilder for an existing agent).
func (*ContextBuilderRegistry) Unregister ¶
func (r *ContextBuilderRegistry) Unregister(agentID string)
Unregister removes the entry for agentID. Safe to call when the entry does not exist.
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" )
type EmptyResponseRetryPayload ¶
EmptyResponseRetryPayload describes a retry triggered by an empty LLM response.
type ErrorPayload ¶
ErrorPayload describes an execution error inside the agent loop.
type Event ¶
type Event struct {
Kind EventKind `json:"Kind"`
Time time.Time `json:"Time"`
Meta EventMeta `json:"Meta"`
Payload any `json:"Payload"`
}
Event is the structured envelope broadcast by the agent EventBus.
type EventBus ¶
type EventBus struct {
// contains filtered or unexported fields
}
EventBus is a lightweight multi-subscriber broadcaster for agent-loop events.
func NewEventBus ¶
func NewEventBus() *EventBus
NewEventBus creates a new in-process event broadcaster.
func (*EventBus) Close ¶
func (b *EventBus) Close()
Close closes all subscriber channels and stops future broadcasts.
func (*EventBus) Emit ¶
Emit broadcasts an event to all current subscribers without blocking. When a subscriber channel is full, the event is dropped for that subscriber.
func (*EventBus) Subscribe ¶
func (b *EventBus) Subscribe(buffer int) EventSubscription
Subscribe registers a new subscriber with the requested channel buffer size. A non-positive buffer uses the default size.
func (*EventBus) Unsubscribe ¶
Unsubscribe removes a subscriber and closes its channel.
type EventKind ¶
type EventKind uint8
EventKind identifies a structured agent-loop event.
MarshalJSON uses a value receiver and UnmarshalJSON uses a pointer receiver — the standard Go JSON codec pair. recvcheck is suppressed here because MarshalJSON cannot use a pointer receiver without breaking fmt.Stringer for value instances (e.g. range-loop variables).
const ( // EventKindTurnStart is emitted when a turn begins processing. EventKindTurnStart EventKind = iota // EventKindTurnEnd is emitted when a turn finishes, successfully or with an error. EventKindTurnEnd // EventKindLLMRequest is emitted before a provider chat request is made. EventKindLLMRequest // EventKindLLMDelta is emitted when a streaming provider yields a partial delta. EventKindLLMDelta // EventKindLLMResponse is emitted after a provider chat response is received. EventKindLLMResponse // EventKindLLMRetry is emitted when an LLM request is retried. EventKindLLMRetry // EventKindContextCompress is emitted when session history is forcibly compressed. EventKindContextCompress // EventKindSessionSummarize is emitted when asynchronous summarization completes. EventKindSessionSummarize // EventKindToolExecStart is emitted immediately before a tool executes. EventKindToolExecStart // EventKindToolExecEnd is emitted immediately after a tool finishes executing. EventKindToolExecEnd // EventKindToolExecSkipped is emitted when a queued tool call is skipped. EventKindToolExecSkipped // EventKindSteeringInjected is emitted when queued steering is injected into context. EventKindSteeringInjected // EventKindFollowUpQueued is emitted when an async tool queues a follow-up system message. EventKindFollowUpQueued // EventKindInterruptReceived is emitted when a soft interrupt message is accepted. EventKindInterruptReceived // EventKindSubTurnSpawn is emitted when a sub-turn is spawned. EventKindSubTurnSpawn // EventKindSubTurnEnd is emitted when a sub-turn finishes. EventKindSubTurnEnd // EventKindSubTurnResultDelivered is emitted when a sub-turn result is delivered. EventKindSubTurnResultDelivered // EventKindSubTurnOrphan is emitted when a sub-turn result cannot be delivered. EventKindSubTurnOrphan // EventKindError is emitted when a turn encounters an execution error. EventKindError // EventKindTurnTimeout is emitted when a turn exceeds its configured timeout. EventKindTurnTimeout // EventKindEmptyResponseRetry is emitted when the LLM returns an empty response and a retry is attempted. EventKindEmptyResponseRetry // EventKindCompactionRetry is emitted when context compaction is triggered due to a timeout. EventKindCompactionRetry // EventKindBackgroundProcessKill is emitted when a background process is force-killed after exceeding its timeout. EventKindBackgroundProcessKill // EventKindRateLimit is emitted when an agent LLM or tool call is denied by a rate limit (SEC-26). EventKindRateLimit // EventKindWhatsAppPairing is emitted when the WhatsApp native channel produces // a linked-device pairing update (QR code or status) to surface in the SPA (#283). EventKindWhatsAppPairing // EventKindNotification is emitted when a user-facing notification is raised // (e.g. a scheduled run failed). Delivered live only to the recipient user's // WebSocket connections (#264). EventKindNotification )
func (EventKind) MarshalJSON ¶
MarshalJSON emits the canonical string form of an EventKind so that subprocess hooks (and any other JSON consumer) see e.g. "tool_exec_start" rather than the underlying uint8 index 8. Without this, the wire payload for hook.event notifications was an integer that subprocess authors had to map manually — and the index would silently shift if a new EventKind was added in the middle of the enum.
Regression guard for #164.
func (*EventKind) UnmarshalJSON ¶
UnmarshalJSON parses the canonical string form back into an EventKind. Round-trips with MarshalJSON. Returns an error for unknown names so a typo or stale schema gets surfaced instead of silently mapping to EventKindTurnStart (the zero value).
type EventMeta ¶
type EventMeta struct {
AgentID string `json:"AgentID"`
TurnID string `json:"TurnID"`
ParentTurnID string `json:"ParentTurnID"`
SessionKey string `json:"SessionKey"`
Iteration int `json:"Iteration"`
TracePath string `json:"TracePath"`
Source string `json:"Source"`
}
EventMeta contains correlation fields shared by all agent-loop events.
type EventSubscription ¶
EventSubscription identifies a subscriber channel returned by EventBus.Subscribe.
type FollowUpQueuedPayload ¶
FollowUpQueuedPayload describes an async follow-up queued back into the inbound bus.
type HookAction ¶
type HookAction string
const ( HookActionContinue HookAction = "continue" HookActionModify HookAction = "modify" 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(eventBus *EventBus) *HookManager
func (*HookManager) AfterLLM ¶
func (hm *HookManager) AfterLLM(ctx context.Context, resp *LLMHookResponse) (*LLMHookResponse, HookDecision)
func (*HookManager) AfterTool ¶
func (hm *HookManager) AfterTool( ctx context.Context, result *ToolResultHookResponse, ) (*ToolResultHookResponse, HookDecision)
func (*HookManager) ApproveTool ¶
func (hm *HookManager) ApproveTool(ctx context.Context, req *ToolApprovalRequest) ApprovalDecision
func (*HookManager) BeforeLLM ¶
func (hm *HookManager) BeforeLLM(ctx context.Context, req *LLMHookRequest) (*LLMHookRequest, HookDecision)
func (*HookManager) BeforeTool ¶
func (hm *HookManager) BeforeTool( ctx context.Context, call *ToolCallHookRequest, ) (*ToolCallHookRequest, HookDecision)
func (*HookManager) Close ¶
func (hm *HookManager) Close()
func (*HookManager) ConfigureTimeouts ¶
func (hm *HookManager) ConfigureTimeouts(observer, interceptor, approval time.Duration)
func (*HookManager) Mount ¶
func (hm *HookManager) Mount(reg HookRegistration) error
func (*HookManager) Unmount ¶
func (hm *HookManager) Unmount(name string)
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 InterruptKind ¶
type InterruptKind string
const ( InterruptKindSteering InterruptKind = "steering" InterruptKindGraceful InterruptKind = "graceful" InterruptKindHard InterruptKind = "hard_abort" )
type InterruptReceivedPayload ¶
type InterruptReceivedPayload struct {
Kind InterruptKind
Role string
ContentLen int
QueueDepth int
HintLen int
}
InterruptReceivedPayload describes accepted turn-control input.
type LLMDeltaPayload ¶
LLMDeltaPayload describes a streamed LLM delta.
type LLMHookRequest ¶
type LLMHookRequest struct {
Meta EventMeta `json:"meta"`
Model string `json:"model"`
Messages []providers.Message `json:"messages,omitempty"`
Tools []providers.ToolDefinition `json:"tools,omitempty"`
Options map[string]any `json:"options,omitempty"`
Channel string `json:"channel,omitempty"`
ChatID string `json:"chat_id,omitempty"`
GracefulTerminal bool `json:"graceful_terminal,omitempty"`
}
func (*LLMHookRequest) Clone ¶
func (r *LLMHookRequest) Clone() *LLMHookRequest
type LLMHookResponse ¶
type LLMHookResponse struct {
Meta EventMeta `json:"meta"`
Model string `json:"model"`
Response *providers.LLMResponse `json:"response,omitempty"`
Channel string `json:"channel,omitempty"`
ChatID string `json:"chat_id,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 ¶
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 LongTermEntry ¶
type LongTermEntry struct {
Timestamp time.Time
Category MemoryCategory
Content string
}
LongTermEntry is a single parsed entry from MEMORY.md.
type MemoryCategory ¶
type MemoryCategory string
MemoryCategory is the closed set of categories an agent may tag a long-term memory entry with. Keeping it typed (rather than a free-form string) makes the domain explicit at every call site and catches drift at compile time.
const ( CategoryKeyDecision MemoryCategory = "key_decision" CategoryReference MemoryCategory = "reference" CategoryLessonLearned MemoryCategory = "lesson_learned" // CategoryLegacy is assigned to entries parsed from pre-structured // MEMORY.md files (no ts/cat header). Not valid on write. CategoryLegacy MemoryCategory = "legacy" // CategoryLastSession / CategoryRetro are synthetic categories applied // to entries surfaced through SearchEntries from non-MEMORY.md sources. CategoryLastSession MemoryCategory = "last_session" CategoryRetro MemoryCategory = "retro" )
func ParseMemoryCategory ¶
func ParseMemoryCategory(s string) (MemoryCategory, error)
ParseMemoryCategory validates and returns a typed category from a string. Accepts only the three AppendLongTerm-legal values; anything else is an error so callers can't silently persist "garbage" as cat=garbage.
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 - Last session: memory/sessions/LAST_SESSION.md - Retrospectives: memory/sessions/YYYY-MM-DD/<sessionID>_retro.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) AppendLongTerm ¶
func (ms *MemoryStore) AppendLongTerm(content, category string) error
AppendLongTerm appends a new entry to MEMORY.md under advisory flock. FR-001: category must be one of key_decision | reference | lesson_learned. FR-002: content must be non-empty, ≤ 4096 runes, and must not contain "<!--". FR-003: NUL bytes are stripped silently.
func (*MemoryStore) AppendRetro ¶
func (ms *MemoryStore) AppendRetro(sessionID string, r Retro) error
AppendRetro writes a structured retrospective to memory/sessions/<YYYY-MM-DD>/<sessionID>_retro.md. FR-009: uses advisory flock. sessionID is validated via validation.EntityID.
func (*MemoryStore) AppendToday ¶
func (ms *MemoryStore) AppendToday(content string) error
AppendToday appends content to today's daily note. If the file doesn't exist, it creates a new file with a date header.
func (*MemoryStore) GetMemoryContext ¶
func (ms *MemoryStore) GetMemoryContext() string
GetMemoryContext returns formatted memory context for the agent system prompt. FR-019: includes LAST_SESSION.md before long-term memory. FR-020: budgets MEMORY.md content at 12000 runes; falls back to newest N entries.
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) ReadLastSession ¶
func (ms *MemoryStore) ReadLastSession() (string, error)
ReadLastSession returns the contents of LAST_SESSION.md, or empty string if absent. FR-008.
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.
func (*MemoryStore) ReadLongTermEntries ¶
func (ms *MemoryStore) ReadLongTermEntries() ([]LongTermEntry, error)
ReadLongTermEntries parses MEMORY.md into typed LongTermEntry values. Results are cached mtime-keyed; the cache is reused when the file has not changed. FR-004: returns newest-first. FR-006: legacy MEMORY.md (no separators) → single entry with cat=legacy, ts=<file mtime>.
func (*MemoryStore) ReadRetros ¶
func (ms *MemoryStore) ReadRetros(daysBack int) ([]Retro, error)
ReadRetros returns structured Retro records from the last daysBack days. Clamps daysBack to 1..365. Files that don't parse are silently skipped. FR-010.
func (*MemoryStore) ReadToday ¶
func (ms *MemoryStore) ReadToday() string
ReadToday reads today's daily note. Returns empty string if the file doesn't exist.
func (*MemoryStore) SearchEntries ¶
func (ms *MemoryStore) SearchEntries(query string, limit int) ([]LongTermEntry, error)
SearchEntries performs a case-insensitive literal substring search across: - MEMORY.md entries - LAST_SESSION.md (as a single entry with cat=last_session) - retrospectives from the last 30 days Results are newest-first. limit defaults to 20 if ≤ 0, max 50. FR-005: no regex — literal substring match only.
func (*MemoryStore) SweepRetros ¶
func (ms *MemoryStore) SweepRetros(retentionDays int) (int, error)
SweepRetros deletes retro files whose enclosing date directory is older than retentionDays days. Returns the count of deleted files. FR-031.
func (*MemoryStore) WriteLastSession ¶
func (ms *MemoryStore) WriteLastSession(content string) error
WriteLastSession atomically writes content to memory/sessions/LAST_SESSION.md. FR-007.
func (*MemoryStore) WriteLongTerm ¶
func (ms *MemoryStore) WriteLongTerm(content string) error
WriteLongTerm writes content to the long-term memory file (MEMORY.md).
type MemoryStoreAdapter ¶
type MemoryStoreAdapter struct {
// contains filtered or unexported fields
}
MemoryStoreAdapter wraps *MemoryStore to implement tools.MemoryAccess. The adapter converts between the agent-side types (LongTermEntry, Retro) and the tools-side mirror types (tools.MemoryEntry, tools.MemoryRetro), keeping pkg/agent and pkg/tools import-cycle-free.
func NewMemoryStoreAdapter ¶
func NewMemoryStoreAdapter(ms *MemoryStore) *MemoryStoreAdapter
NewMemoryStoreAdapter wraps ms in a tools.MemoryAccess implementation.
func (*MemoryStoreAdapter) AppendLongTerm ¶
func (a *MemoryStoreAdapter) AppendLongTerm(content, category string) error
AppendLongTerm delegates to MemoryStore.AppendLongTerm (MemoryWriter).
func (*MemoryStoreAdapter) AppendRetro ¶
func (a *MemoryStoreAdapter) AppendRetro(sessionID string, r tools.MemoryRetro) error
AppendRetro converts tools.MemoryRetro to agent.Retro and delegates. The Trigger field crosses a type boundary here: pkg/tools sees it as a free string (mirror-struct to avoid cycles), pkg/agent keeps it typed so a future refactor can add exhaustive-switch checks on triggers.
func (*MemoryStoreAdapter) SearchEntries ¶
func (a *MemoryStoreAdapter) SearchEntries(query string, limit int) ([]tools.MemoryEntry, error)
SearchEntries delegates to MemoryStore.SearchEntries and converts results. Category crosses the same type boundary as Trigger in AppendRetro.
type NotificationPayload ¶
type NotificationPayload struct {
// Recipient is the username the notification is for, or
// NotificationAdminBroadcast to fan out to all admins.
Recipient string `json:"recipient"`
ID string `json:"id"`
NotificationType string `json:"notification_type"`
Title string `json:"title"`
Body string `json:"body,omitempty"`
Severity string `json:"severity"`
Read bool `json:"read"`
CreatedAtMs int64 `json:"created_at_ms"`
ScheduleID string `json:"schedule_id,omitempty"`
SessionID string `json:"session_id,omitempty"`
AgentID string `json:"agent_id,omitempty"`
}
NotificationPayload carries a user-facing notification for the live WS push (#264). It is delivered ONLY to connections whose userID equals Recipient (or, when Recipient == NotificationAdminBroadcast, to admin-role connections).
type PolicyApprovalReq ¶
type PolicyApprovalReq struct {
ToolCallID string
ToolName string
Args map[string]any
AgentID string
SessionID string
TurnID string
RequiresAdmin bool
}
PolicyApprovalReq carries the fields needed to create and broadcast an approval.
type PolicyApprover ¶
type PolicyApprover interface {
RequestApproval(ctx context.Context, req PolicyApprovalReq) (approved bool, denialReason string)
}
PolicyApprover is implemented by the gateway to wire the central approval registry and WebSocket broadcast into the agent loop (FR-011, FR-082).
This is distinct from the hooks.ToolApprover interface (which is for hook-based interactive approval). PolicyApprover governs the policy-level ask gate from FilterToolsByPolicy, using the in-process approvalRegistryV2.
RequestApproval MUST:
- Create a pending approval entry in the registry.
- Emit a tool_approval_required WS frame (scoped to session owner, FR-073).
- Block until the user approves/denies, the timeout fires, the queue is saturated, or the gateway shuts down.
- Return (true, "") on approve; (false, reason) otherwise.
denialReason matches the Reason field from ApprovalOutcome: "user", "timeout", "saturated", "restart", "cancel", "batch_short_circuit".
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 (ph *ProcessHook) AfterTool( ctx context.Context, result *ToolResultHookResponse, ) (*ToolResultHookResponse, HookDecision, error)
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 (ph *ProcessHook) BeforeTool( ctx context.Context, call *ToolCallHookRequest, ) (*ToolCallHookRequest, HookDecision, error)
func (*ProcessHook) Close ¶
func (ph *ProcessHook) Close() error
type ProcessHookOptions ¶
type RateLimitPayload ¶
type RateLimitPayload struct {
Scope string `json:"scope"`
Resource string `json:"resource"` // "llm_call" or "tool_call"
PolicyRule string `json:"policy_rule"`
RetryAfterSeconds float64 `json:"retry_after_seconds"`
AgentID string `json:"agent_id,omitempty"`
ChatID string `json:"chat_id,omitempty"`
Tool string `json:"tool,omitempty"`
}
RateLimitPayload describes a rate-limit denial for an LLM or tool call (SEC-26). ChatID is required so the WebSocket event forwarder can route the frame to the correct connection via matchesChatID — a rate-limit denial is meaningless without the chat context it applies to.
type RecapModelBootError ¶
RecapModelBootError is returned by NewAgentLoop when AutoRecapEnabled is true and the resolved recap model is not in the cheap-model allow-list (FR-029a). Callers should map this to a non-zero exit code and log the message.
func (*RecapModelBootError) Error ¶
func (e *RecapModelBootError) Error() string
type RecapTrigger ¶
type RecapTrigger string
RecapTrigger is the closed set of triggers recorded on a Retro. Keeping this typed means a future refactor cannot quietly introduce a fourth source without the type system noticing.
const ( TriggerExplicit RecapTrigger = "explicit" TriggerLazy RecapTrigger = "lazy" TriggerIdle RecapTrigger = "idle" TriggerBootstrap RecapTrigger = "bootstrap" TriggerJoined RecapTrigger = "joined" )
type Retro ¶
type Retro struct {
Timestamp time.Time
Trigger RecapTrigger
Fallback bool
FallbackReason string
Recap string
WentWell []string
NeedsImprovement []string
}
Retro is a structured retrospective record.
type ScheduledJobInfo ¶
ScheduledJobInfo carries the schedule/job identity that ProcessScheduled callers inject into the run context via WithScheduledJobContext. The auto-deny path reads it so the emitted audit entry names the responsible schedule (F-13 / O-3 observability requirement, issue #342).
type ServedEntry ¶
type ServedEntry struct {
// AgentID is the ID of the agent that owns this registration.
AgentID string
// AbsDir is the canonicalised absolute path to the served directory.
// It is within the agent's workspace (validated by the web_serve tool).
AbsDir string
// Deadline is when this registration expires.
Deadline time.Time
}
ServedEntry holds a single web_serve static-mode registration.
type ServedSubdirs ¶
type ServedSubdirs struct {
// contains filtered or unexported fields
}
ServedSubdirs is the process-wide registry of active web_serve static-mode registrations. The zero value is not usable — call NewServedSubdirs.
func NewServedSubdirs ¶
func NewServedSubdirs() *ServedSubdirs
NewServedSubdirs creates a registry and launches the 30-second janitor. Call Stop when the gateway shuts down so the goroutine exits cleanly.
func (*ServedSubdirs) ActiveForAgent ¶
ActiveForAgent returns the token and deadline of the currently active registration for agentID, and ok=true if one exists and has not expired. Returns ("", zero, false) otherwise.
func (*ServedSubdirs) Evict ¶
func (s *ServedSubdirs) Evict(agentID string)
Evict removes all registrations for the given agentID. Called on agent deletion so URLs stop resolving immediately rather than waiting for the janitor.
func (*ServedSubdirs) Lookup ¶
func (s *ServedSubdirs) Lookup(token string) *ServedEntry
Lookup returns the ServedEntry for the given token, or nil if the token is unknown or expired. An expired-but-not-yet-janitor-cleaned entry is treated as missing so callers always see consistent state.
func (*ServedSubdirs) Register ¶
func (s *ServedSubdirs) Register( agentID, absDir string, duration time.Duration, ) (token string, deadline time.Time, err error)
Register creates a new web_serve static-mode registration for agentID pointing at absDir with a lifetime of duration. Any previous registration for agentID is atomically replaced (per-agent cap).
Returns the token (for embedding in the URL) and the registration's expiry time.
func (*ServedSubdirs) SetOnEvict ¶
func (s *ServedSubdirs) SetOnEvict(fn func(tokens []string))
SetOnEvict installs a callback that is invoked (outside the registry lock) whenever tokens are evicted. Gateway wires this to purgeFirstServedTokensBulk so the audit firstServedTokens set stays in sync with the registry (F-9). Must be called before any concurrent access.
func (*ServedSubdirs) Stop ¶
func (s *ServedSubdirs) Stop()
Stop signals the janitor goroutine to exit and waits for it to return. Safe to call multiple times (subsequent calls are no-ops — stopCh is already closed and the janitor has already exited).
type SessionSummarizePayload ¶
type SessionSummarizePayload struct {
SummarizedMessages int
KeptMessages int
SummaryLen int
OmittedOversized bool
}
SessionSummarizePayload describes a completed async session summarization.
type SoulDefinition ¶
SoulDefinition represents the resolved SOUL.md file linked to the agent.
type SteeringInjectedPayload ¶
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 {
Model string
Tools []tools.Tool
SystemPrompt string
MaxTokens int
// 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
// MaxContextRunes limits the context size (in runes) passed to the SubTurn.
// This prevents context window overflow by truncating message history before LLM calls.
//
// Values:
// 0 = Auto-calculate based on model's ContextWindow * 0.75 (default, recommended)
// -1 = No limit (disable soft truncation, rely only on hard context errors)
// >0 = Use specified rune limit
//
// The soft limit acts as a first line of defense before hitting the provider's
// hard context window limit. When exceeded, older messages are intelligently
// truncated while preserving system messages and recent context.
MaxContextRunes int
// ActualSystemPrompt is injected as the true 'system' role message for the childAgent.
// The legacy SystemPrompt field is actually used as the first 'user' message (task description).
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
// InitialTokenBudget is a shared atomic counter for tracking remaining tokens.
// If set, the SubTurn will inherit this budget and deduct tokens after each LLM call.
// If nil, the SubTurn will inherit the parent's tokenBudget (if any).
// Used by team tool to enforce token limits across all team members.
InitialTokenBudget *atomic.Int64
// TaskLabel is the optional human-readable label for the sub-turn task.
// Populated by the spawn tool from its "label" argument (FR-H-004).
// Used in SubTurnSpawnPayload.TaskLabel for the WS subagent_start frame.
TaskLabel string
}
SubTurnConfig configures the execution of a child sub-turn.
Usage Examples:
Synchronous sub-turn (Async=false):
cfg := SubTurnConfig{
Model: "gpt-4o-mini",
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{
Model: "gpt-4o-mini",
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 SubTurnStatus
// SpanID is "span_" + ParentSpawnCallID, matching the corresponding SubTurnSpawnPayload.
SpanID string
// ParentSpawnCallID is the ToolCall.ID of the spawn tool call that triggered this sub-turn.
ParentSpawnCallID session.ToolCallID
// DurationMS is the wall-clock duration of the sub-turn in milliseconds.
DurationMS int64
// ChatID is needed so the WS forwarder can route this event to the right connection.
ChatID string
// SessionID is the transcript-store session ID for this turn.
SessionID string
}
SubTurnEndPayload describes the completion of a child turn. FR-H-004: carries span_id, status, duration_ms for the WS forwarder.
type SubTurnOrphanPayload ¶
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
// SpanID is "span_" + ParentSpawnCallID (deterministic, derivable from persisted data).
SpanID string
// ParentSpawnCallID is the ToolCall.ID of the spawn tool call that triggered this sub-turn.
// This is the correlation anchor for the subagent span.
ParentSpawnCallID session.ToolCallID
// TaskLabel is the human-readable label for the sub-turn task (from spawn tool's label param).
TaskLabel string
// ChatID is needed so the WS forwarder can route this event to the right connection.
ChatID string
// SessionID is the transcript-store session ID for this turn.
SessionID string
}
SubTurnSpawnPayload describes the creation of a child turn. FR-H-004: carries span_id, parent_call_id, task_label, agent_id for the WS forwarder.
type SubTurnStatus ¶
type SubTurnStatus string
SubTurnStatus describes the terminal state of a sub-turn. Using a named type prevents accidental use of arbitrary strings at call sites. JSON marshaling is identical to a plain string.
const ( // SubTurnStatusSuccess indicates the sub-turn completed normally. SubTurnStatusSuccess SubTurnStatus = "success" // SubTurnStatusError indicates the sub-turn ended with an error. SubTurnStatusError SubTurnStatus = "error" // SubTurnStatusCancelled indicates the sub-turn was explicitly canceled by the user. // //nolint:misspell // wire value "cancelled" matches frontend TS union in src/store/chat.ts, src/lib/ws.ts SubTurnStatusCancelled SubTurnStatus = "cancelled" // SubTurnStatusInterrupted indicates the sub-turn was interrupted by its parent. SubTurnStatusInterrupted SubTurnStatus = "interrupted" // SubTurnStatusTimeout indicates the sub-turn exceeded its configured timeout. SubTurnStatusTimeout SubTurnStatus = "timeout" )
type TaskExecutor ¶
type TaskExecutor struct {
// contains filtered or unexported fields
}
TaskExecutor runs queued tasks by dispatching them to agent sessions.
func GetTaskExecutor ¶
func GetTaskExecutor(al *AgentLoop) *TaskExecutor
GetTaskExecutor returns the shared TaskExecutor (may be nil in tests).
func (*TaskExecutor) CheckQueuedTasks ¶
func (te *TaskExecutor) CheckQueuedTasks(ctx context.Context)
CheckQueuedTasks picks up the highest-priority queued task per agent and starts it. Called by the heartbeat service.
func (*TaskExecutor) ExecuteTask ¶
func (te *TaskExecutor) ExecuteTask(ctx context.Context, taskID string) error
ExecuteTask starts executing the task identified by taskID. It updates the task's status to "running" and dispatches it to the agent in a goroutine.
func (*TaskExecutor) Stop ¶
func (te *TaskExecutor) Stop()
Stop cancels all running task goroutines.
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 Tier13Deps ¶
type Tier13Deps struct {
// ServedSubdirs is the process-wide web_serve static-mode registration map.
// Non-nil when the gateway has initialized it at boot.
ServedSubdirs *ServedSubdirs
// EgressProxy is the shared Tier 2 / Tier 3 egress HTTP/HTTPS proxy.
// Non-nil when sandbox.NewEgressProxy succeeded at boot.
EgressProxy *sandbox.EgressProxy
// DevServerRegistry is the process-wide web_serve dev-mode registration map.
// Non-nil when the gateway has initialized it at boot.
DevServerRegistry *sandbox.DevServerRegistry
// GatewayBaseURL is the base URL (scheme + host + port) of the running
// gateway's MAIN listener, e.g. "http://localhost:5000".
//
// Deprecated: kept for one release for replay safety on transcripts that
// embedded URLs minted before the two-port topology landed (FR-021). Use
// GatewayPreviewBaseURL for new tool URL emission. Will be removed after
// 2026-Q3.
GatewayBaseURL string
// GatewayPreviewBaseURL is the base URL of the gateway's PREVIEW
// listener, e.g. "http://localhost:3001" or "https://preview.acme.com".
// Sourced from cfg.Gateway.PreviewOrigin when set, otherwise computed
// from cfg.Gateway.Host + cfg.Gateway.PreviewPort at boot.
//
// web_serve and workspace.shell_bg use this to build absolute preview
// URLs returned in tool results — web_serve emits /preview/<agent>/<token>/,
// workspace.shell_bg emits /dev/<agent>/<token>/. The preview origin is
// browser-cross-origin to the SPA's main origin, providing the T-01
// mitigation (parent.localStorage access throws SecurityError).
GatewayPreviewBaseURL string
}
Tier13Deps carries the shared singletons required to register the Tier 1 (web_serve static mode), Tier 2 (build_static), and Tier 3 (web_serve dev mode) tools for non-system agents.
All fields are nullable — a nil registry / proxy means the corresponding tool is not registered (graceful degradation when the gateway skips Tier 2/3 setup, e.g. in unit tests that only need Tier 1).
type ToolApprovalRequest ¶
type ToolApprovalRequest struct {
Meta EventMeta `json:"meta"`
Tool string `json:"tool"`
Arguments map[string]any `json:"arguments,omitempty"`
Channel string `json:"channel,omitempty"`
ChatID string `json:"chat_id,omitempty"`
// SessionID is the transcript-store session for the turn requesting approval.
// Carried on exec_approval_request/expired frames so the SPA can scope them.
SessionID string `json:"session_id,omitempty"`
}
func (*ToolApprovalRequest) Clone ¶
func (r *ToolApprovalRequest) Clone() *ToolApprovalRequest
type ToolApprover ¶
type ToolApprover interface {
ApproveTool(ctx context.Context, req *ToolApprovalRequest) (ApprovalDecision, error)
}
type ToolCallHookRequest ¶
type ToolCallHookRequest struct {
Meta EventMeta `json:"meta"`
Tool string `json:"tool"`
Arguments map[string]any `json:"arguments,omitempty"`
Channel string `json:"channel,omitempty"`
ChatID string `json:"chat_id,omitempty"`
}
func (*ToolCallHookRequest) Clone ¶
func (r *ToolCallHookRequest) Clone() *ToolCallHookRequest
type ToolExecEndPayload ¶
type ToolExecEndPayload struct {
ToolCallID session.ToolCallID
ChatID string
// SessionID is the transcript-store session ID for this turn.
SessionID string
Tool string
Duration time.Duration
ForLLMLen int
ForUserLen int
IsError bool
Async bool
// Result is the tool's ForLLM content, forwarded to the browser via WebSocket
// so rich tool UIs (e.g., browser screenshot preview) can render the result.
Result string
// ParentSpawnCallID is non-empty when this tool call fires inside a sub-turn.
// It equals the parent spawn tool call's ToolCall.ID (FR-H-002).
// The WebSocket forwarder propagates this as parent_call_id on outbound frames (FR-H-005).
ParentSpawnCallID session.ToolCallID
// AgentID is the agent executing this tool call.
// FR-I-008: live tool_call_result frames must carry agent_id to match replay frame parity.
AgentID string
}
ToolExecEndPayload describes the outcome of a tool execution.
type ToolExecSkippedPayload ¶
ToolExecSkippedPayload describes a skipped tool call.
type ToolExecStartPayload ¶
type ToolExecStartPayload struct {
ToolCallID session.ToolCallID
ChatID string
// SessionID is the transcript-store session ID for this turn.
SessionID string
Tool string
Arguments map[string]any
// ParentSpawnCallID is non-empty when this tool call fires inside a sub-turn.
// It equals the parent spawn tool call's ToolCall.ID (FR-H-002).
// The WebSocket forwarder propagates this as parent_call_id on outbound frames (FR-H-005).
ParentSpawnCallID session.ToolCallID
// AgentID is the agent executing this tool call.
// FR-I-008: live tool_call_start frames must carry agent_id to match replay frame parity.
AgentID string
}
ToolExecStartPayload describes a tool execution request.
type ToolInterceptor ¶
type ToolInterceptor interface {
BeforeTool(ctx context.Context, call *ToolCallHookRequest) (*ToolCallHookRequest, HookDecision, error)
AfterTool(ctx context.Context, result *ToolResultHookResponse) (*ToolResultHookResponse, HookDecision, error)
}
type ToolResultHookResponse ¶
type ToolResultHookResponse struct {
Meta EventMeta `json:"meta"`
Tool string `json:"tool"`
Arguments map[string]any `json:"arguments,omitempty"`
Result *tools.ToolResult `json:"result,omitempty"`
Duration time.Duration `json:"duration"`
Channel string `json:"channel,omitempty"`
ChatID string `json:"chat_id,omitempty"`
}
func (*ToolResultHookResponse) Clone ¶
func (r *ToolResultHookResponse) Clone() *ToolResultHookResponse
type TurnCancelHook ¶
type TurnCancelHook interface {
// IsAlive returns true while the turn has not yet finished.
IsAlive() bool
// TurnID returns the turn's unique identifier.
TurnID() string
// SetOnCancelFinish registers a callback invoked by Finish() when the turn
// exits after a cancel. Receives "graceful" or "hard".
SetOnCancelFinish(fn func(cancelMethod string))
// ClaimCancel performs the atomic first-cancel-wins check. Returns true
// if this call is the first to claim the cancel (i.e. cancelFired was false
// and has now been set to true). Returns false if already canceled.
ClaimCancel() bool
// MarkAbandoned sets the abandoned flag so the gateway can stop tracking
// a stuck goroutine (FR-19, FR-20, FR-21).
MarkAbandoned()
}
TurnCancelHook is the exported interface that the gateway's cancel handler uses to interact with an active turn. It exposes only the methods needed for the two-stage cancel timer, preventing gateway code from touching unexported turnState fields.
type TurnEndPayload ¶
type TurnEndPayload struct {
Status TurnEndStatus
Iterations int
Duration time.Duration
FinalContentLen int
// ChatID is the chat session this turn belongs to.
// Populated so the WS watchdog can scope orphan detection to the correct connection.
ChatID string
// SessionID is the transcript-store session ID for this turn.
// Carried end-to-end so the WS forwarder can avoid the sessionIDs reverse-lookup.
SessionID string
// IsRoot is true when this turn has no parent (parentTurnID == "").
// The orphan watchdog only arms on root turn-end to avoid spurious interrupts
// from sibling sub-turn completions.
IsRoot 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 TurnStartPayload ¶
TurnStartPayload describes the start of a turn.
type TurnTimeoutPayload ¶
TurnTimeoutPayload describes a turn that exceeded its configured timeout.
type UserDefinition ¶
UserDefinition represents the resolved USER.md file linked to the workspace.
type WhatsAppPairingPayload ¶
type WhatsAppPairingPayload struct {
ChannelID string `json:"channel_id"`
Status channels.PairingStatus `json:"status"`
QR string `json:"qr,omitempty"`
Message string `json:"message,omitempty"`
}
WhatsAppPairingPayload carries a WhatsApp native/QR linked-device pairing update for the SPA (#283). QR is populated only when Status == channels.PairingStatusCode.
Source Files
¶
- admission.go
- attach_hydrate.go
- audit_bridge.go
- cancel.go
- cancel_abuse.go
- context.go
- context_budget.go
- context_env.go
- context_registry.go
- definition.go
- eventbus.go
- events.go
- hook_mount.go
- hook_process.go
- hooks.go
- instance.go
- instance_policy.go
- loop.go
- loop_env.go
- loop_mcp.go
- loop_media.go
- memory.go
- memory_adapter.go
- model_resolution.go
- prompt_guard.go
- registry.go
- repair.go
- served_subdirs.go
- session_end.go
- session_recovery.go
- session_worker.go
- steering.go
- subturn.go
- task_executor.go
- thinking.go
- tier13deps.go
- tool_approver.go
- turn.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package envcontext provides the "new-office onboarding" preamble that sits above an agent's identity in the system prompt (Fix A, spec v7).
|
Package envcontext provides the "new-office onboarding" preamble that sits above an agent's identity in the system prompt (Fix A, spec v7). |
|
Package testutil provides shared test infrastructure for all Plan-3 PRs.
|
Package testutil provides shared test infrastructure for all Plan-3 PRs. |