cliagents

package
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2026 License: AGPL-3.0 Imports: 38 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// SubagentRosterMaxLines caps the roster entries; overflow collapses into
	// a tail line pointing at the dispatch_tasks agent enum.
	SubagentRosterMaxLines = 8
)

Roster bounds. The "# Subagents" section is appended to the root system prompt, so its parts are clamped to keep the assembled prompt bounded and provider prompt-cache friendly (the section changes only when the roster does).

Variables

View Source
var AdvertisedSessionToolSpecsVar func(ToolTierPlan, *agents.AgentRegistry) []provider.ToolSpec

AdvertisedSessionToolSpecsVar is wired by internal/cli/cliagents_wiring.go to cli's advertisedSessionToolSpecs. Used by advertisedToolSpecs in tool_tiers.go to append the session-owned dispatcher tools to the advertised wire array. The *agents.AgentRegistry argument is the binding's immutable resolved agent snapshot, passed as data (never a global) so dispatch_tasks can advertise its real agent enum and roster at turn zero.

View Source
var BuiltInSlashTokensVar func() map[string]struct{}

BuiltInSlashTokensVar is wired by internal/cli/cliagents_wiring.go to return the set of reserved slash tokens from cli's builtInSlashCommands. Used by loadSessionSkills in model_binding.go to reject skill names that collide with built-in slash commands.

ContextDispatcherForVar is wired at process start by internal/cli/cliagents_wiring.go to cli's contextDispatcherFor.

View Source
var ErrAgentWallClockExceeded = errors.New("agent wall-clock ceiling exceeded")

ErrAgentWallClockExceeded is the typed cause attached when a routed agent exhausts its own wall-clock ceiling. It distinguishes an agent-policy timeout from a caller-imposed task timeout or an operator cancel, all of which otherwise surface as context.DeadlineExceeded.

View Source
var NewSessionDispatcherVar func(SessionDispatcherOpts) (*runtime.Dispatcher, error)

NewSessionDispatcherVar is wired at process start by internal/cli/cliagents_wiring.go to cli's NewSessionDispatcher. Moving code (agent_switch.go, model_binding.go) calls this instead of importing cli directly.

View Source
var RemainderSpoolFromRegistryVar func(*tools.Registry) *remainder.Spool

RemainderSpoolFromRegistryVar is wired at process start by internal/cli/cliagents_wiring.go to cli's RemainderSpoolFromRegistry. cli's read_output.go owns the unexported readOutputTool type and cannot move, so the spool builder stays there.

SummaryWiringVar is wired by internal/cli/cliagents_wiring.go to cli's summaryWiring. Used by refreshSummarizerAfterModelSwitch in model_binding.go.

View Source
var WireWorkflowToolOptionsVar func(*tools.DefaultOptions, string, *config.Resolved, func() *events.Bus, bool, ledger.LedgerRepository)

WireWorkflowToolOptionsVar is wired at process start by internal/cli/cliagents_wiring.go to cli's wireWorkflowToolOptions. chat_workspace.go (moving) calls this to wire the workflow-engine event bus without importing cli. The ledger repository is the owning session's orchestration repo (AgentSessionState.LedgerRepo); nil keeps child-run registration skipped.

Functions

func AddMCPTools

func AddMCPTools(registry *tools.Registry, cfg *config.Resolved, serverIDs []string) (func(), error)

func AdvertisedToolSpecs

func AdvertisedToolSpecs(base *tools.Registry, plan ToolTierPlan, agentReg *agents.AgentRegistry) ([]provider.ToolSpec, int)

AdvertisedToolSpecs is the exported view of advertisedToolSpecs for callers outside cliagents (tests, session catalog introspection).

func AgentNameOf

func AgentNameOf(selected *agents.ResolvedAgent) string

agentNameOf is the empty-safe agent name used to key persisted admissions.

func ApplyDeferredToolPrompt

func ApplyDeferredToolPrompt(sess *chat.Session, res *config.Resolved, plan ToolTierPlan, state *AgentSessionState)

applyDeferredToolPrompt appends the binding's frozen deferred-tool index to the session prompt. It runs once per binding: every later admission republishes this exact prompt, which is what keeps the cached system-prompt prefix intact while the tool array grows.

func ApplySelectedAgentPrompt

func ApplySelectedAgentPrompt(sess *chat.Session, res *config.Resolved, selected *agents.ResolvedAgent, state *AgentSessionState)

applySelectedAgent applies the selected agent's prompt and turn budget to the session. max_turns: nil leaves the session default; 0 means unlimited.

Runs even when selected is nil (plan 77: found via a live smoke test that a bare `mivia chat` with no --agent never reached this far otherwise - chat_command.go's own fallback prompt resolution runs BEFORE the memory store opens and is hardcoded to no injection, so this call, right after configureChatWorkspace, is the only site that recomposes the root session's prompt with the real memory block for a no-agent session).

func ApplySessionAgent

func ApplySessionAgent(sess *chat.Session, res *config.Resolved, state *AgentSessionState, name string, busy bool) error

ApplySessionAgent switches the root agent for the idle session. busy is the TUI waiting flag; active turns and switch guards are checked on sess. It reuses ToolBase for re-scope and rebuilds the dispatcher like model switch.

func ApplyWorkspacePromptGate

func ApplyWorkspacePromptGate(res *config.Resolved, global config.AgentsGlobal)

applyWorkspacePromptGate strips untrusted workspace system prompts when the user gate is off. User config prompts always load.

func AuthorizedAgentTools

func AuthorizedAgentTools(agent *agents.ResolvedAgent, registry *tools.Registry) []string

AuthorizedAgentTools adds discovered MCP tool names for the agent's selected servers. The server selection is the authority. An agent file does not need to repeat volatile remote tool names in its tools list.

func BuildModelBinding

func BuildModelBinding(sess *chat.Session, res *config.Resolved, root, providerName, model string, state *AgentSessionState) (chat.ModelBinding, error)

buildModelBinding prepares a complete provider/model generation without mutating the session. The caller publishes it through Session.SwitchBinding.

state is the live agent session state, not a snapshot of it: a model switch rebuilds the whole surface, and the surface's authority registry, frozen tier split and skill registry exist only there. Passing a context snapshot is what made a /model switch silently narrow this session's authority. In-flight turns keep their captured binding generation.

func BuildSkillCatalogue

func BuildSkillCatalogue(workspaceRoot string) (map[string]agents.SkillCatalogueEntry, []string)

buildSkillCatalogue scans user and project skill roots separately so both origins are visible for allowlist trust decisions (project cannot silently replace a user skill binding).

func ChatBindingFactory

func ChatBindingFactory(sess *chat.Session, res *config.Resolved, root string, state *AgentSessionState) func(string, string) (chat.ModelBinding, error)

chatBindingFactory is the single provider/model construction path every surface rebuild goes through: the REPL and TUI model switches, and a catalog load whose saved session names a different provider or model. It closes over the live agentSessionState so each rebuild sees the current agent, tier plan and admitted set rather than a snapshot taken at startup.

func ConfigureChatWorkspace

func ConfigureChatWorkspace(sess *chat.Session, root string, useTools bool, res *config.Resolved, state *AgentSessionState, quiet bool, fullDisk bool, runRecoverySweep bool) (func(), error)

func ConfiguredProfile

func ConfiguredProfile(res *config.Resolved, providerName, model string) (config.ModelSpec, bool)

func CoreMemoryBlockForOpts

func CoreMemoryBlockForOpts(opts SessionDispatcherOpts) string

CoreMemoryBlockForOpts is CoreMemoryBlockForState for the subagent path (plan 77, E2/E5): opts.Memory is nil for workflow/background callers, which coreMemoryBlock degrades safely to "".

func CoreMemoryBlockForState

func CoreMemoryBlockForState(state *AgentSessionState) string

CoreMemoryBlockForState returns the core-memory injection block scoped to the project for the given session state. Callers hold state.mu per the LedgerRepo/Memory field convention. context.Background() is used because this is always a session-init or turn-boundary call with no request context.

func CurrentAgentName

func CurrentAgentName(state *AgentSessionState) string

CurrentAgentName implements current agent name.

func DeclaredBinding

func DeclaredBinding(definition agents.ResolvedAgent) bool

declared reports whether the agent named its own provider or model rather than following the session.

func DisabledForAgent

func DisabledForAgent(selected *agents.ResolvedAgent, base *tools.Registry) []string

disabledForAgent lists the selected agent's tool names a registry cannot offer. /agent is an entry point, so it may report them; the surface builds it triggers stay silent because they also run mid-turn, under the TUI.

func EnsureMCPServerTools

func EnsureMCPServerTools(registry *tools.Registry, manager *mcp.Manager) func([]string) error

func FilterSkillRegistryForGate

func FilterSkillRegistryForGate(skillReg *skills.Registry, allowProject bool) *skills.Registry

filterSkillRegistryForGate omits project-origin skills when the workspace gate is off. User skills remain registered.

func FilterSkillsForScope

func FilterSkillsForScope(reg *skills.Registry, scope AgentSkillScope) *skills.Registry

filterSkillsForScope returns a registry containing only skills the scope may invoke (name allowlist only; tool subset is checked at invocation).

func FormatAgentCurrent

func FormatAgentCurrent(name string, reg *agents.AgentRegistry) string

FormatAgentCurrent implements format agent current.

func FormatAgentSet

func FormatAgentSet(name string) string

FormatAgentSet implements format agent set.

func FormatSessionAgentStatus

func FormatSessionAgentStatus(state *AgentSessionState, sess *chat.Session) string

func InstallSessionIdentity

func InstallSessionIdentity(sess *chat.Session, state *AgentSessionState)

func LoadSessionSkills

func LoadSessionSkills(root string, allowProject bool) (*skills.Registry, []string, error)

loadSessionSkills loads skill handlers for the session. When allowProject is false, only user skills are discovered so a workspace skill cannot shadow a user skill and then be stripped by the gate (leaving nothing).

func LogDiagnosticsCommandsOnce

func LogDiagnosticsCommandsOnce(w io.Writer, tc config.ToolsConfig, quiet bool)

logDiagnosticsCommandsOnce states every configured get_diagnostics command once at startup, before any tool call: a configured command that runs programs is a disclosure, not a hidden capability (the same contract as the lifecycle-hooks armedNotice). Names are sorted so the line is deterministic across runs. quiet (--quiet) suppresses the line; the tool itself is still registered whenever the workspace declares commands.

func MemoryConfigOf

func MemoryConfigOf(state *AgentSessionState) config.MemoryConfig

MemoryConfigOf returns the resolved memory config from state without requiring the caller to hold state.mu (plan 77, E2).

func MemoryOf

func MemoryOf(state *AgentSessionState) memory.Store

MemoryOf returns the session-lifetime memory store from state without requiring the caller to hold state.mu (plan 77, E2).

func NewLoadToolsTool

func NewLoadToolsTool(sess *chat.Session, candidates []tools.TierCandidate) tools.Tool

NewLoadToolsTool constructs a load_tools tool wired to sess. Pass nil, nil to build a schema-only instance for the tool catalog.

func NewProviderCompleter

func NewProviderCompleter(res *config.Resolved, providerName, model string) (provider.Completer, error)

newProviderCompleter constructs a completer for one provider-qualified model. provider.NewForProvider is fail-closed: it rejects a provider with no configured runtime and one with no credential, both before any client is built, so this is the point where an agent's declared provider is really authorized (the parse-time name check is only a spelling check).

func NewProviderCompleterFactory

func NewProviderCompleterFactory(res *config.Resolved) func(string, string) (provider.Completer, error)

newProviderCompleterFactory adapts newProviderCompleter to the dispatcher's factory seam. It returns nil when there is no configuration to build from, which leaves a routed agent's foreign provider failing closed rather than falling back to the session's completer.

func NewSessionDispatcher

func NewSessionDispatcher(opts SessionDispatcherOpts) (*runtime.Dispatcher, error)

NewSessionDispatcher builds a runtime.Dispatcher for agent sessions. NewSessionDispatcherVar must be non-nil; wired by internal/cli/cliagents_wiring.go.

func NewSurfaceWidener

func NewSurfaceWidener(sess *chat.Session, res *config.Resolved, state *AgentSessionState) chat.SurfaceWidener

newSurfaceWidener returns the host-owned publisher for staged tool admissions (plan tools/05 D7). internal/chat cannot build a session dispatcher, so the session records intent and calls back here at the turn boundary.

The candidate surface is built first and published only if every precondition still holds, checked atomically with the swap inside TryPublishAgentSurface. A refused publication closes the candidate dispatcher it never installed and leaves the stage pending.

func OpenMemoryStoreWithReadOnly

func OpenMemoryStoreWithReadOnly(root string, mc config.MemoryConfig, readOnly bool) (memory.Store, error)

OpenMemoryStoreWithReadOnly resolves the memory config to a memory store. readOnly controls memory.Config.ReadOnly. Exported so internal/cli's openMemoryStore and openMemoryStoreReadOnly can delegate here without duplicating the path-resolution logic.

func PinAttachAdvertisedToolSpecs

func PinAttachAdvertisedToolSpecs(sess *chat.Session, selected *agents.ResolvedAgent, plan ToolTierPlan, agentReg *agents.AgentRegistry)

pinAttachAdvertisedToolSpecs computes and pins the initial-attach binding's advertised union BEFORE sess.Tools is narrowed to its core-tier execution registry: the union must be built from the full pre-scope base (plan tools-advertising/01), same as buildSurfaceFromBase does for /agent and /model. agentReg is the session's immutable resolved registry snapshot, threaded into the advertised session-tool schemas. Must be called before sess.Tools is reassigned.

func RecordSchemaMass

func RecordSchemaMass(sess *chat.Session, state *AgentSessionState, plan ToolTierPlan, admitted []string, agentName, publication string)

recordSchemaMass measures and records the session's current advertised schema mass. It takes state.mu itself; callers already holding it use recordSchemaMassLocked. admitted is passed in rather than read back off the session: the admission path records this measurement before it commits the new admitted set, so asking the session would price the surface it just replaced.

func RefreshSummarizerAfterModelSwitch

func RefreshSummarizerAfterModelSwitch(sess *chat.Session, res *config.Resolved)

RefreshSummarizerAfterModelSwitch is the exported view of refreshSummarizerAfterModelSwitch for callers outside cliagents.

func RegisterSessionTool

func RegisterSessionTool(d *runtime.Dispatcher, reg *tools.Registry, tool tools.Tool) error

RegisterSessionTool registers a privileged session-owned tool on both the dispatcher and the tool registry. It fails fast if the tool name is already present so registration conflicts surface at startup rather than at runtime.

func RootSystemPromptWithRoster added in v0.1.1

func RootSystemPromptWithRoster(prompt string, registry *agents.AgentRegistry) string

RootSystemPromptWithRoster appends the roster section ADDITIVELY to the root system prompt - the compiled fallback or an operator's custom chat.system_prompt alike - because the roster is environment fact, not user content. It lives at the prompt-assignment point, never inside buildAgentPrompt, so a customized prompt still gets the announcement. An empty section returns the prompt unchanged byte-for-byte.

func RoutedIdentity

func RoutedIdentity(definition agents.ResolvedAgent, instanceID string, generation uint64) *events.Identity

RoutedIdentity builds an events.Identity for a routed agent invocation.

func RunAgents

func RunAgents(args []string) error

runAgents handles the provider-independent agent catalog commands.

func RunAgentsWithIO

func RunAgentsWithIO(args []string, stdout, stderr io.Writer) error

RunAgentsWithIO is the exported view of runAgentsWithIO for callers outside cliagents (tests, integration harnesses).

func SafeCatalogText

func SafeCatalogText(value string, max int) string

func SameFilePath added in v0.1.1

func SameFilePath(goos, a, b string) bool

SameFilePath reports whether two path spellings name the same file: both are normalized (backslashes go to slashes, dot-dot and double-slash segments resolve away), then compared - case-folded on Windows, whose filesystems match case-insensitively, and byte-exact elsewhere. goos is a parameter, not runtime.GOOS, so the Windows branch stays testable from every OS. Normalization is done by hand because filepath.Clean is host-dependent (it treats backslashes as plain bytes on Unix), which a GOOS-parameterized comparison cannot lean on. Accepted limits, each benign in direction (a miss only skips the chmod of the file the store is opening anyway, never hardens a wrong file): a literal backslash inside a Unix filename reads as a separator, and a symlink or 8.3 short-name alias of the temp path compares unequal - both require deliberately spelling out our hash-named TempStorePath, which the default-filled path never does. Resolving them needs live-filesystem calls, which would give back the host-dependence this pure seam exists to remove. This is deliberately NOT config.sameFilePath (internal/config/agents_io.go), which resolves symlinks on the live filesystem; this one is pure, so a path that does not exist yet still compares. Empty paths never match. SameFilePath is the shared gate helper for ad-hoc store hardening: the clichat and cliworkflow orchestration-ledger gates call this exported form so all three packages compare paths by one contract.

func ScopedRootRegistry

func ScopedRootRegistry(registry *tools.Registry, selected *agents.ResolvedAgent, extraDenylist []string) (*tools.Registry, []string)

scopedRootRegistry intersects a registry with the selected agent's effective tools using ScopeRoot (privileged/delegation tools kept). It returns the scoped registry and the agent's tool names this build could not honour.

It reports rather than prints: this runs on EVERY surface build, including the ones a tool admission performs mid-turn, and a raw stderr write while the TUI owns the terminal corrupts the rendered frame. Only the attach and /agent entry points turn the report into a diagnostic, via warnDisabledAgentTools.

func SelectableModel

func SelectableModel(catalog []config.ProviderModelGroup, providerName, model string) (config.ModelSpec, bool)

SelectableModel returns the catalog profile for one provider-qualified model. Unlike the previous modelInCatalog helper it reports the profile, so the caller can bind the routed model's own context window instead of inheriting the session model's.

func SessionIdentity

func SessionIdentity(sess *chat.Session, state *AgentSessionState, generation uint64) *events.Identity

SessionIdentity resolves the current agent identity (name and source) for a session generation. Shared with internal/legacytui's TUI event handling.

func SessionMCPConfig

func SessionMCPConfig(res *config.Resolved) config.MCPConfig

func SetupSessionMCPTools

func SetupSessionMCPTools(registry *tools.Registry, cfg *config.Resolved, selected *agents.ResolvedAgent) (*mcp.Manager, func(), error)

func SubagentRosterSection added in v0.1.1

func SubagentRosterSection(registry *agents.AgentRegistry) string

SubagentRosterSection renders the "# Subagents" prompt section: one line per agent in registry order (file-backed agents first, then compiled built-ins), capped at SubagentRosterMaxLines with an overflow tail. Empty when there is nothing to announce. A pure function of the immutable registry snapshot.

func SwitchModelCommand

func SwitchModelCommand(sess *chat.Session, res *config.Resolved, providerName, model string) (reasoning.Level, error)

SwitchModelCommand publishes a model generation and reports the /effort choice the switch took away, if any. Three surfaces run this switch and each words its own confirmation; deciding here what was lost is what stops them from disagreeing about whether anything was.

Only a CHOICE counts as a loss. An untouched dial reads the outgoing model's default, and the incoming model declaring a different one is that model describing itself, not a preference being dropped. Whether a choice exists is the session's fact, not something the levels can be subtracted to reveal: a user may deliberately pick the level their model already defaults to.

The before/after reading straddles the publication because only the session knows whether the new generation kept the override, and it is also what keeps a dropped choice the incoming model happens to default to quiet - nothing the user can observe changed there.

func TieredRootRegistry

func TieredRootRegistry(base *tools.Registry, selected *agents.ResolvedAgent, extraDenylist []string, plan ToolTierPlan, admitted []string) *tools.Registry

tieredRootRegistry materializes the core tier in base order and appends the admitted tools as a tail (plan tools/05 D8). An inert plan falls through to the ordinary root scope so a zero-config session is byte-identical to a build without deferred loading.

func WarnAgentLoad

func WarnAgentLoad(warnings []string)

func WarnDisabledAgentTools

func WarnDisabledAgentTools(selected *agents.ResolvedAgent, disabled []string)

warnDisabledAgentTools reports the selected agent's tool names the live registry cannot offer. Call it only from a session entry point the operator initiated (attach, /agent), never from a surface rebuild.

func WarnHookLoad

func WarnHookLoad(warnings []string)

warnHookLoad surfaces lifecycle-hook diagnostics at startup, not in debug output. A silently ignored hook is how someone concludes hooks are broken.

func WarnSkillLoad

func WarnSkillLoad(warnings []string)

func WireSessionMemory

func WireSessionMemory(opts *tools.DefaultOptions, root string, res *config.Resolved) error

WireSessionMemory wires the memory store into the session tool options so memory_save and memory_search register (plan 68). A nil res or a disabled memory section leaves opts.Memory unchanged.

func WorkflowMCPServers

func WorkflowMCPServers(wf *definition.CompiledWorkflow, registry *agents.AgentRegistry) []string

WorkflowMCPServers returns the MCP server IDs referenced by workflow steps, in registry order. Only agents actually used in a step are included. See cli/workflow_run_build.go for the caller.

func WriteAgentCatalog

func WriteAgentCatalog(w io.Writer, view AgentCatalogView, diagnostics io.Writer)

Types

type AgentBinding

type AgentBinding struct {
	ProviderName string
	Model        string
	Completer    provider.Completer

	// maxTokens caps one provider response. It is the tighter of the agent's
	// own ceiling and the operator's session cap, so an agent file can lower
	// the ceiling but never raise it above what the operator allowed.
	MaxTokens int

	// reasoning is the routed MODEL's dial, not the session's. An agent
	// pinned to another model must think at the depth that model declares;
	// inheriting the session's would send one model's wire fields to another.
	Reasoning reasoning.Setting
	// contains filtered or unexported fields
}

AgentBinding is one routed agent's immutable execution target. It is resolved once, at dispatcher construction, and never mutated afterwards - concurrent Invokes on the shared handler only read it.

func ResolveAgentBinding

func ResolveAgentBinding(definition agents.ResolvedAgent, opts SessionDispatcherOpts) (AgentBinding, error)

ResolveAgentBinding resolves and validates one agent's execution target.

It is deliberately eager: registerAgentHandlers calls it at dispatcher construction so a mistyped provider or model surfaces at startup rather than twenty minutes into a run. A failure is stored on the handler and returned on invoke, so one bad definition cannot take down an otherwise usable session.

func ResolvePinnedAgentBinding

func ResolvePinnedAgentBinding(definition agents.ResolvedAgent, opts SessionDispatcherOpts, providerName, model string) (AgentBinding, error)

ResolvePinnedAgentBinding re-authorizes a provider/model pair restored from a ledger row. The pair is descriptive metadata, never a handler selector: the current catalog and provider factory must authorize it again.

func (AgentBinding) ContextBudget

func (b AgentBinding) ContextBudget() int

ContextBudget is the prompt budget this agent may actually use.

func (AgentBinding) EffectiveReasoning

func (b AgentBinding) EffectiveReasoning() reasoning.Setting

EffectiveReasoning is the dial this agent's requests carry.

func (AgentBinding) WithWallClock

func (b AgentBinding) WithWallClock(ctx context.Context, agentName string) (context.Context, context.CancelFunc, error)

WithWallClock applies the agent's wall-clock ceiling as a parent of whatever bound the caller already imposed. Layering it rather than replacing the caller's timeout matches the handler contract: MultiStepHandler and OneShotHandler clamp a handler-level TotalTimeout against the per-task timeout and keep the tighter of the two, so this parent context composes with the handler bound instead of fighting it - the tightest deadline always wins.

The cause is a fresh per-invocation error value, not the shared sentinel, so ownership is unambiguous: an ancestor that breached ITS ceiling propagates its own cause down, and comparing by identity keeps this agent from reporting a breach it never had. It still wraps ErrAgentWallClockExceeded so callers can match the class with errors.Is.

type AgentCatalogRow

type AgentCatalogRow struct {
	Name        string
	Source      string
	State       string
	Tools       string
	Model       string
	Turns       string
	Description string
	// Limits renders the per-agent resource ceilings that bound an agent even
	// when its turns are unlimited.
	Limits string
}

type AgentCatalogView

type AgentCatalogView struct {
	Report agents.InspectionReport
	Rows   []AgentCatalogRow
}

func LoadAgentCatalog

func LoadAgentCatalog(workspaceRoot string) (AgentCatalogView, error)

func (AgentCatalogView) ReportCollection

func (v AgentCatalogView) ReportCollection() string

func (AgentCatalogView) ReportCollectionState

func (v AgentCatalogView) ReportCollectionState() config.AgentCollectionState

type AgentListRow

type AgentListRow struct {
	Name        string
	Description string
	Current     bool
}

AgentListRow is one selectable entry for the /agent dialog and listings.

type AgentLoadResult

type AgentLoadResult struct {
	Registry *agents.AgentRegistry
	Global   config.AgentsGlobal
	Selected *agents.ResolvedAgent // nil when no agent selected
	Warnings []string
}

AgentLoadResult is Layer-B output: resolved definitions and the user gate.

func LoadAgentDefinitions

func LoadAgentDefinitions(workspaceRoot, agentFlag string, skillReg *skills.Registry) (AgentLoadResult, error)

LoadAgentDefinitions discovers and resolves file-backed agents. skillReg may be nil (no skill collision check). When non-nil, agent names that collide with skills fail closed.

When agentFlag is empty and a definition named config.DefaultAgentName ("mivia") exists, that definition is selected as the root session agent. That replaces the former .mivia/agent-prompt.md load path. A flag equal to config.RootAgentName restores the compiled root surface (no selection).

type AgentSessionContext

type AgentSessionContext struct {
	Global   config.AgentsGlobal
	Selected *agents.ResolvedAgent
	Registry *agents.AgentRegistry
	// AllowProjectSkills is true when workspace skill handlers may register.
	AllowProjectSkills bool
}

AgentSessionContext is a value snapshot for dispatcher construction (startup and model switch). Mutable session state lives in agentSessionState.

type AgentSessionState

type AgentSessionState struct {
	Global             config.AgentsGlobal
	Selected           *agents.ResolvedAgent
	AllowProjectSkills bool
	Registry           *agents.AgentRegistry
	WorkspaceRoot      string
	// ToolBase is the post-dispatcher, pre-scope registry for re-scoping.
	// Nil when tools are off.
	ToolBase *tools.Registry
	// MCPManager owns the session-wide MCP clients. Agent switches borrow it to
	// discover newly selected server tools without starting another client.
	MCPManager *mcp.Manager
	// SkillScope is the immutable per-instance skill policy for the selected
	// root agent, including the final live tool registry snapshot (plan 43).
	// Set at dispatcher attach and agent switch; read by the TUI slash path.
	SkillScope AgentSkillScope
	// TierPlan is the frozen core/deferred tool split for the current agent
	// binding (plan tools/05 D8). Computed once per binding; never recomputed
	// while it lives, so the prompt index it feeds stays byte-stable.
	TierPlan ToolTierPlan
	// SkillRegFull is the current binding's unfiltered skill registry. Surface
	// widening reuses it so admitting a tool performs no skill disk I/O.
	SkillRegFull *skills.Registry
	// LedgerRepo is the session-lifetime ledger repository every surface rebuild
	// passes to NewSessionDispatcherVar. It exists so no dispatcher ever OWNS a
	// ledger store: a republished surface carries the live remainder spool, the
	// spool captured its ContentStore at construction, and publication closes
	// the dispatcher it replaced. A per-dispatcher store would therefore be
	// closed out from under the spool by the first tool admission. Nil means the
	// caller supplied a shared store (the dispatcher borrows it) or tools are off.
	LedgerRepo ledger.LedgerRepository

	// LastSchemaMass is the most recent advertised schema-mass measurement for
	// this session's surface (plan tools/05 D5 telemetry). It is written by the
	// three publications that can change the split or the admitted tail: attach,
	// /agent switch and tool admission. A /model rebuild republishes the same
	// frozen tiers with the same admitted tail, so it deliberately leaves this
	// measurement alone rather than re-emitting an identical one.
	LastSchemaMass   SchemaMass
	BaselinePrompt   string
	BaselineMaxSteps int
	BaselineCaptured bool
	// Memory is the session-lifetime memory store, opened once by
	// configureChatWorkspace and never closed here - the same store
	// tools.DefaultOptions.Memory wires into memory_save/memory_search (plan
	// 77, E1). Nil when memory is disabled or tools are off. Callers hold
	// state.mu (applySessionAgent does), so the field is read directly,
	// matching the LedgerRepo convention above.
	Memory memory.Store
	// MemoryConfig is the resolved [memory] section, read alongside Memory
	// to build the core-tier injection block (coreMemoryBlock).
	MemoryConfig config.MemoryConfig
	// contains filtered or unexported fields
}

AgentSessionState is the mid-session mutable agent context. Startup and /agent switch share this so model-switch rebuilds keep the selected agent.

var ClassicAgentState *AgentSessionState

ClassicAgentState is the root agent context for the classic REPL/one-shot chat path. The TUI stores the same pointer on TUIModel.agentState.

func (*AgentSessionState) AdoptLedgerRepo

func (s *AgentSessionState) AdoptLedgerRepo(repo ledger.LedgerRepository, owned *ledger.StorageLedgerRepository)

AdoptLedgerRepo sets the session-lifetime ledger repository and owned store. Used by cli.adoptSessionLedgerRepo so the field stays package-private here.

func (*AgentSessionState) CloseOwnedLedgerStore

func (s *AgentSessionState) CloseOwnedLedgerStore()

CloseOwnedLedgerStore closes the session's durable ledger store. Called by sessionSurfaceCleanup after the dispatcher is torn down.

func (*AgentSessionState) Context

Context returns a snapshot of the agent session context. Thread-safe.

func (*AgentSessionState) DisplayName

func (s *AgentSessionState) DisplayName() string

DisplayName is the status dialog's "agent" row: the locked, nil-safe read of the currently selected agent's name. Exported for internal/legacytui's dialog rendering, which cannot lock the unexported mu field directly.

func (*AgentSessionState) DisplaySource

func (s *AgentSessionState) DisplaySource() string

DisplaySource is the status dialog's "source" row: the locked, nil-safe read of the currently selected agent's provenance source. Exported for internal/legacytui's dialog rendering, which cannot lock the unexported mu field directly.

func (*AgentSessionState) Lock

func (s *AgentSessionState) Lock()

Lock acquires the session state mutex. Use with Unlock for cross-package atomic reads/writes (tests, legacytui). Production paths hold mu directly.

func (*AgentSessionState) OwnedLedgerStore

func (s *AgentSessionState) OwnedLedgerStore() *ledger.StorageLedgerRepository

OwnedLedgerStore returns the session-owned durable ledger store or nil. Used by tests outside this package that cannot read the unexported field.

func (*AgentSessionState) ReleaseOwnedLedgerRepo

func (s *AgentSessionState) ReleaseOwnedLedgerRepo()

ReleaseOwnedLedgerRepo closes and forgets the store adoptSessionLedgerRepo opened. Used by cli.releaseSessionLedgerRepo on the error path.

func (*AgentSessionState) SchemaMassSnapshot

func (s *AgentSessionState) SchemaMassSnapshot() SchemaMass

SchemaMassSnapshot returns the last recorded measurement for display.

func (*AgentSessionState) SetSkillScope

func (s *AgentSessionState) SetSkillScope(scope AgentSkillScope)

SetSkillScope stores the selected root agent's skill policy. Writers that already hold s.mu (applySessionAgent) assign the field directly.

func (*AgentSessionState) SkillScopeSnapshot

func (s *AgentSessionState) SkillScopeSnapshot() AgentSkillScope

SkillScopeSnapshot returns a copy of the current root skill policy for the TUI slash path. A nil state or unset scope yields the open zero value.

func (*AgentSessionState) Unlock

func (s *AgentSessionState) Unlock()

Unlock releases the session state mutex acquired by Lock.

type AgentSkillScope

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

AgentSkillScope is an immutable per-instance skill policy snapshot for the selected root agent. Built once at dispatcher construction; never shared across concurrent agents or model switches as a mutable registry.

Zero value is unrestricted (backward compatible for dispatchers built without an agent). Explicit empty allowlists always set restricted=true with a non-nil allowed map.

v1 scope is root fan-out only: nested multi_step agents do not receive privileged dispatch_tasks/spawn_agent, so they cannot synthesize skill tasks. Resume/retry re-enters skill handlers which re-check this scope.

func SkillScopeFromAgent

func SkillScopeFromAgent(selected *agents.ResolvedAgent) AgentSkillScope

skillScopeFromAgent snapshots the selected agent's skill allowlist and tools. A nil agent means the compiled default root (all skills, no tool subset gate).

func SkillScopeFromAgentAndRegistry

func SkillScopeFromAgentAndRegistry(selected *agents.ResolvedAgent, reg *tools.Registry) AgentSkillScope

skillScopeFromAgentAndRegistry is skillScopeFromAgent plus the final live tool registry snapshot (after disable/deny filtering). A nil agent stays unrestricted (compiled default root owns the full catalogue); a nil registry leaves the live check disabled. SkillScopeFromAgentAndRegistry builds a scope from an agent and its registry.

func (AgentSkillScope) CheckSkillDefinition

func (s AgentSkillScope) CheckSkillDefinition(def skills.Definition) error

CheckSkillDefinition enforces the full plan 43 policy for one skill definition: allowlist, declared-tool subset of the agent's effective tools and the live registry, and origin fail-closed (a runtime definition whose origin differs from the allowlist-bound origin for the same name is an authorization event).

type AgentSurface

type AgentSurface struct {
	// Dispatcher is the built tool dispatcher. The caller must close it when
	// it is no longer needed or will not be published to a session.
	Dispatcher *runtime.Dispatcher
}

AgentSurface is the exported view of a widened agent surface. It exposes the built dispatcher so callers outside cliagents can close it or probe it.

func BuildWidenedWith

func BuildWidenedWith(sess *chat.Session, res *config.Resolved, state *AgentSessionState, admitted []string) (*AgentSurface, error)

BuildWidenedWith derives the same binding's surface with admitted appended as a tail and returns it for the caller to inspect or publish. See buildWidenedWith.

type ContextDispatcherWiring

type ContextDispatcherWiring struct {
	Preparation      contextmgr.PreparationManager
	PreparationInput contextmgr.PrepareInput
	SharedSQLite     *storage.SQLite
}

ContextDispatcherWiring carries the context-preparation state from the session setup into the agent surface builders. The function that assembles it lives in internal/cli (contextDispatcherFor) and is injected here via ContextDispatcherForVar so the import direction stays inward. See internal/cli/cliagents_wiring.go for the wiring init.

type SchemaMass

type SchemaMass struct {
	Advertised   int
	Tokens       int
	Locked       int
	LockedTokens int
	AgentName    string
	Publication  string
}

SchemaMass is the advertised tool-schema cost of one agent surface: the number every request pays before a single message is added.

Advertised/Tokens price the pinned wire snapshot (plan tools-advertising/01): core tier PLUS every deferred candidate, since admission now changes execution authority only, never what is advertised. Locked/LockedTokens are the subset of that snapshot which is authorized and visible but not yet admitted for execution - "deferred" no longer means "withheld", it means "locked until loaded with load_tools". LockedTokens prices the same shortened (one-line) description advertisedToolSpecs actually ships for a locked tool, not its full Description(), so it stays consistent with Tokens.

func (SchemaMass) String

func (m SchemaMass) String() string

String renders the operator-facing one-liner used by /tools and diagnostics.

type SessionDispatcherOpts

type SessionDispatcherOpts struct {
	// Registry is the advertised surface: what the root model is shown and what
	// the root loop may invoke. Under a deferred tool tier this is only the core
	// block plus whatever has been admitted.
	Registry *tools.Registry
	// AuthorityRegistry is the root-scoped FULL authorized tool set, deferred
	// tier included. Delegation authority is not an advertising decision: a
	// routed agent, a skill and a nested multi-step loop are scoped from this,
	// so narrowing what the root model sees never narrows what it may delegate.
	// Nil defaults to Registry, which is the correct answer whenever nothing is
	// deferred.
	AuthorityRegistry *tools.Registry
	Completer         provider.Completer
	Model             string
	ProviderName      string
	// AllowWorkspaceAgentProviders is the user-owned opt-in for static workflow
	// panel provider routing.
	AllowWorkspaceAgentProviders bool
	ModelGeneration              uint64
	// ModelGenerationFunc is evaluated when a routed task starts. Candidate
	// dispatchers are built before a binding is published, so a fixed
	// generation here can be stale after a concurrent switch.
	ModelGenerationFunc func() uint64
	ModelCatalog        []config.ProviderModelGroup
	// CompleterFactory builds a completer bound to one provider. It is required
	// before a routed agent may execute on a provider other than the session's;
	// when it is absent such an agent fails closed rather than silently
	// downgrading onto the session completer. Completers are provider-scoped
	// (the model travels per request), so the model argument is advisory.
	CompleterFactory func(providerName, model string) (provider.Completer, error)
	Config           config.SubagentConfig
	MCP              config.MCPConfig
	// EnsureMCPTools lazily adds wrappers for an authorized routed agent.
	// The session owns the manager and registry that this callback uses.
	EnsureMCPTools     func([]string) error
	ToolResultCapBytes int
	// ToolRunTimeout is the [tools] tool_run_timeout_seconds knob applied to
	// every nested sub-agent loop's SDK tool registry: the registry-wide run
	// backstop for tools with no declared Capability.Timeout. <= 0 = no
	// registry-wide cap (the SDK's TimeoutNone).
	ToolRunTimeout time.Duration
	// BatchResultBudgetBytes is the [tools] batch_result_budget_bytes knob,
	// applied to every nested sub-agent loop the same way it applies to the
	// session loop. 0 = off.
	BatchResultBudgetBytes int
	// RefOnlyTools is the [tools] ref_only_tools knob for every nested sub-agent loop; empty = off.
	RefOnlyTools []string
	// WorkspaceRoot is the directory lifecycle hooks execute in. Empty means
	// no hooks are wired, which is what every non-chat caller wants.
	WorkspaceRoot string

	// Memory is the session's memory store (plan 77, E2), the same instance
	// agentSessionState.Memory holds - not a second Open. Nil for every
	// non-chat caller (workflow/background paths); CoreMemoryBlockForState
	// already treats a nil store as "", so subagent prompt composition
	// degrades safely with no caller-side nil check required.
	Memory memory.Store
	// MemoryConfig is the resolved [memory] section read alongside Memory.
	MemoryConfig config.MemoryConfig

	// Repo, if set, is used as-is and its lifetime is caller-owned.
	// If nil, the constructor opens a store from Config (with the
	// memory-backend fallback) and owns its Close via dispatcher.OnClose.
	Repo ledger.LedgerRepository

	// MaxContextTokens / MaxTokens configure the nested subagent handlers.
	// Zero values mean "unset" (handler defaults apply).
	MaxContextTokens int
	MaxTokens        *int
	// WorkLimits are session limits. Each task combines these limits with its
	// agent, model, task, and parent-panel limits.
	WorkLimits runtime.WorkLimits

	// Budget, if non-nil, is the live session budget provider read by nested
	// handlers when invoked (so /budget applies without rebuilding).
	Budget func() int

	// Reasoning, if non-nil, is the live session dial read by nested handlers
	// when invoked (so /effort applies without rebuilding). It supersedes the
	// dial resolved from ModelCatalog for every path that follows the session.
	Reasoning func() reasoning.Setting

	// SharedSQLite is a caller-owned SQLite pointer. When supplied, the ledger
	// adapter borrows it and the dispatcher never closes it.
	SharedSQLite *storage.SQLite

	// ContextPreparationManager is a preparation-only capability for nested
	// loops. The dispatcher never receives a checkpoint publisher or store.
	ContextPreparationManager contextmgr.PreparationManager
	ContextPreparationInput   contextmgr.PrepareInput

	// SkillReg, if non-nil, registers each skill as a Subagent handler.
	SkillReg *skills.Registry
	// WorkflowSkillSnapshots pins workflow skill content for every workflow
	// invocation. A nil map means this is not a workflow dispatcher.
	WorkflowSkillSnapshots map[string]workflowledger.RefSnapshot

	// SkillScope is the immutable per-instance skill policy for the selected
	// root agent (plan 06). Zero value allows all skills (no agent selected).
	SkillScope AgentSkillScope

	// AgentRegistry is the caller-authorized immutable catalogue whose names
	// are the only task routing targets.
	AgentRegistry *agents.AgentRegistry

	// DeferredTools is this agent binding's frozen deferred set (plan
	// tools/05). Non-empty registers load_tools as a privileged session tool;
	// empty leaves the surface byte-identical to a build without the feature.
	DeferredTools []tools.TierCandidate
	// Session is the session whose tool surface load_tools stages against.
	// Required whenever DeferredTools is non-empty.
	Session *chat.Session

	// RemainderSpool is the live spool of an EXISTING session whose surface is
	// being rebuilt. Visibility grants for truncated output live in the spool
	// instance while the bytes live in a shared store, so minting a new spool
	// for a republished surface would turn every earlier ref into "denied" for
	// the session that produced it. Nil mints one, which is what a genuinely
	// new session wants.
	RemainderSpool *remainder.Spool

	// Sink, when set, receives one runtime.Event per invocation lifecycle
	// step (started, retrying, completed) with bounded audit metadata. Nil
	// disables sink delivery and keeps every other caller unchanged. The sink
	// runs on the invoking goroutine, so it must be cheap and safe for
	// concurrent calls.
	Sink func(runtime.Event)
}

SessionDispatcherOpts carries every input the session dispatcher needs. Repo and Budget are optional; their absence selects the legacy defaults (open a SQLite store from Config, no live budget provider). Moved here from internal/cli/dispatcher.go so agent_binding.go and other cliagents code can reference the type without importing cli. internal/cli/dispatcher.go re-exports it via a type alias.

func (SessionDispatcherOpts) Authority

func (o SessionDispatcherOpts) Authority() *tools.Registry

Authority resolves the full authorized set nested principals are scoped from. AuthorityRegistry takes precedence; Registry is the fallback when nothing is deferred.

type ToolTierPlan

type ToolTierPlan struct {
	Tiers tools.Tiers
	// Candidates are the deferred tools with their one-line descriptions, in
	// live-registry order. Empty means the plan is inert.
	Candidates []tools.TierCandidate
	// Digest fingerprints the tier split a persisted admitted set was made
	// against, so a resumed session can drop a stale set fail-closed.
	Digest string
}

ToolTierPlan is one agent binding's frozen deferred-tool decision (plan tools/05 D8). It is computed once per binding, before any admission, and never recomputed while that binding lives: the prompt index it feeds must stay byte-stable across admissions, so it is stale by design afterwards.

func PlanToolTiers

func PlanToolTiers(base *tools.Registry, selected *agents.ResolvedAgent, res *config.Resolved) ToolTierPlan

planToolTiers splits the authorized tool set of the live pre-scope registry into core and deferred tiers. Authority is unchanged: the split only decides which authorized schemas ship on every request and which wait for load_tools.

func (ToolTierPlan) Deferred

func (p ToolTierPlan) Deferred() bool

Deferred reports whether this binding defers anything at all. A plan that defers nothing must behave byte-identically to a build without the feature: no load_tools tool, no prompt index, no tail ordering.

Jump to

Keyboard shortcuts

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