agentruntime

package
v0.5.0-rc.1 Latest Latest
Warning

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

Go to latest
Published: Sep 18, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package agentruntime is the shared session-construction layer behind `yottacode run` (internal/oneshot), the TUI (internal/tui), and the ACP server (internal/acp). All three build the same "world" — registry, permissions, adapter, router, LSP manager, skills, subagents, MCP, compaction — and previously did so via two independently-drifting ~370/~850 line inline blocks. Builder.Build is the single place that construction happens now; callers layer their own presentation-specific state (Bubbletea Model, stdout streaming, checkpoints, recall) on top of the returned Runtime.

Index

Constants

This section is empty.

Variables

View Source
var EffortLevels = []string{"", "low", "medium", "high"}

EffortLevels is the closed set RebuildAdapterForEffort accepts, mirroring internal/tui/effort_picker.go's effortEntries — "" means provider default (no reasoning override injected).

Functions

func IsValidEffortLevel

func IsValidEffortLevel(level string) bool

IsValidEffortLevel reports whether level is one of EffortLevels.

func RebuildAdapterForEffort

func RebuildAdapterForEffort(rt *Runtime, level string) error

RebuildAdapterForEffort updates rt.ChatOptions.ReasoningEffort and reconstructs every adapter that bakes reasoning effort in at construction time — rt.Adapter and, when a router pair is configured for this session, rt.RouterAdapters — using exactly Build's own adapter-selection logic (RoutingEnabled → the freshly-rebuilt advisor adapter; otherwise a plain adapter.NewWithConfig), so a mid-session effort change can never silently knock a routed session off its advisor model the way an independent "just call NewWithConfig" rebuild could. Session-only: nothing is persisted to config.toml. No preflight re-probe — matches internal/tui/effort_picker.go's commitEffortChoice, which also skips it (the provider connection already worked at session start).

func RecomposeSystemPrompt

func RecomposeSystemPrompt(sess *session.Session, content string, headBytes int)

RecomposeSystemPrompt rewrites the session's system message. headBytes marks the stable cache prefix (the static base prompt ahead of the memory tail) — see adapter.Message.CacheHeadBytes. Exported because callers with a live prompt at construction time (oneshot) re-score memory against it after Build returns and need to overwrite what Build injected with the prompt-unaware memory.SystemPrompt.

func SetAdvisorRouting

func SetAdvisorRouting(rt *Runtime, enabled bool) error

SetAdvisorRouting toggles whether subagent dispatch and summarization route through rt.RouterAdapters' implementer/advisor pair. Mirrors internal/tui/cmd_router.go's applyRoutingOn/applyRoutingOff exactly — session-only (nothing persisted to config.toml), and deliberately does NOT touch rt.Adapter/rt.Cfg.Adapter: which model serves the main conversation is decided once, at Build time, by fileCfg.Router.RoutingEnabled() (see RebuildAdapterForEffort), not by this toggle. Returns an error when enabling is requested but no advisor/implementer pair is configured for this session — mirrors the TUI's own "/advisor on" guidance to configure one first.

Types

type Builder

type Builder struct{}

Builder constructs a Runtime from a SessionSpec. Stateless — safe to share across concurrent Build calls (each call only touches its own local state and the spec/session it's given).

func NewBuilder

func NewBuilder() *Builder

NewBuilder returns a Builder. A constructor exists (rather than a bare struct literal at call sites) so adding builder-level config later (e.g. injected clocks for tests) doesn't churn every caller.

func (*Builder) Build

func (b *Builder) Build(ctx context.Context, spec SessionSpec) (*Runtime, error)

Build constructs the full session world: session, memory, adapter, router, permissions, registry + tools, MCP, subagents, and the LoopConfig ready to hand to agent.Turn. It never calls os.Getwd()/os.Chdir() — every constructor here takes spec.Cwd explicitly, which is what makes this safe to call concurrently for N different sessions with different working directories (see SessionSpec.Cwd's doc comment).

type Runtime

type Runtime struct {
	Session *session.Session
	Fresh   bool

	Cfg      agent.LoopConfig
	Registry *agent.Registry

	Permissions   *permissions.Permissions
	AgentTool     *agent.AgentTool
	SubagentTasks *subagents.Registry
	MCPManager    *mcppkg.Manager

	Skills    []skills.Skill
	SkillTool *agent.SkillTool

	LSPManager      *lsp.Manager
	CodeMapProvider codemap.Provider

	Adapter        adapter.Client
	RouterAdapters *cli.RouterAdapters

	// ChatOptions is the live, as-constructed options snapshot Adapter
	// (and RouterAdapters) were built from — mirrors internal/tui's own
	// m.opts. Build seeds it (including any advisor-model override
	// applied to spec.ChatOptions.Model below); RebuildAdapterForEffort
	// mutates ChatOptions.ReasoningEffort and reconstructs every adapter
	// that bakes reasoning effort in at construction time. Session-only —
	// never written back to config.toml.
	ChatOptions cli.ChatOptions

	// RoutingAuto is the session-live mirror of fileCfg.Router.RoutingAuto()
	// at construction, ANDed with the pair having actually resolved —
	// whether subagent dispatch/summarization currently route through
	// RouterAdapters' implementer/advisor pair. A persisted mode="auto"
	// whose pair failed to resolve (see Build's warning) reports false
	// here rather than claiming a toggle that silently does nothing.
	// Distinct from FileCfg.Router.Mode (the on-disk setting Build read
	// once): this field is what SetAdvisorRouting toggles, session-only,
	// matching internal/tui/cmd_router.go's own
	// m.routerMode/RouterModeAuto split between "what's persisted" and
	// "what's live."
	RoutingAuto bool

	// GHClient is the typed GitHub client backing the pr_*/issue_*/
	// git_push composite tools (see Build). Lazy and side-effect-free
	// to construct — the real auth/network cost is paid on first tool
	// call, not here — so every caller gets it unconditionally, same as
	// MCPManager. Exposed on Runtime because TUI's status bar also
	// reads it directly (resolveCurrentPRCmd) for a display-only
	// current-PR lookup that has no ACP equivalent.
	GHClient githubapi.Interface

	// RecallIndex backs the model-callable session_recall tool (see
	// Build) — a full-text/semantic index over every saved session at
	// ~/.yottacode/index.sqlite, shared across every caller (it's one
	// file on disk, not per-session state; SQLite's WAL mode is what
	// makes concurrent handles from oneshot/tui/acp safe at once). Nil
	// when recall.Open fails (non-fatal — see rt.Warnings); callers
	// must Close it themselves when the session ends (TUI's own
	// teardown does this already; CloseSession/Shutdown do it for ACP).
	// Backfilling the corpus (session.List + re-index) stays a
	// TUI-only background job — see internal/tui/run.go — since it's a
	// startup-time catch-up most valuable for the interactive,
	// long-running case; oneshot/ACP sessions still search whatever a
	// prior TUI/backfill run already indexed, they just don't trigger
	// a fresh corpus-wide catch-up themselves.
	RecallIndex *recall.Index

	// Model is the effective model name after Build's own resolution —
	// specifically, when advisor routing is enabled and overrides the
	// requested model, this is the *overridden* name. spec.Model (i.e.
	// spec.ChatOptions.Model) stays whatever the caller originally
	// passed in; callers that display or persist the active model (the
	// TUI's status bar, session bookkeeping) must read this field, not
	// spec.Model, or they'll show a stale name under routing.
	Model string

	PlanMode    *agent.PlanModeState
	AutoMode    *agent.AutoModeState
	YoloMode    *agent.YoloModeState
	LoopControl *agent.LoopControlState

	PlanStore *agent.PlanStore
	CwdRef    *agent.CwdRef

	// CmdSandbox is the optional session-scoped command sandbox backing
	// run_bash and worktree/dispatch sandbox inheritance. Nil preserves
	// HostSandbox behavior.
	CmdSandbox agent.Sandbox

	// SandboxManager owns lazy per-profile containers when sandboxing is enabled.
	SandboxManager *SandboxManager

	FileCfg         config.Config
	ExperimentalSet *experimental.Set
	Mem             memory.Loaded
	EmbedClient     *memory.EmbedClient

	// BaseSystemPrompt is the pre-memory composed prompt (profile framing
	// + skills section, before memory injection). Callers that know the
	// live user prompt at construction time (oneshot) can re-score memory
	// against it and overwrite the session's system message via
	// RecomposeSystemPrompt; callers that don't (TUI, ACP — the prompt
	// arrives later, via session/prompt) just keep what Build already
	// injected with the prompt-unaware memory.SystemPrompt.
	BaseSystemPrompt string

	// RawSystemPrompt is the base system prompt *before*
	// composeSystemPrompt/appendSkillsSection are applied — opts.
	// SystemPrompt (or defaultSystemPrompt) plus the dispatch addendum,
	// nothing else. TUI needs this raw form, not BaseSystemPrompt: its
	// own per-turn/reload paths (rebuildSystemPromptForTurn,
	// reloadMemoryNow, recomposeSystemPromptWithSkills) call
	// composeSystemPrompt/appendSkillsSection themselves, so handing them
	// the already-composed BaseSystemPrompt would double the profile
	// framing and skills section on every turn.
	RawSystemPrompt string

	// Warnings collects non-fatal construction issues (router degrade,
	// embedding model unreachable, MCP server start failures, unknown
	// experimental flags, skills/subagents load warnings) as pre-formatted
	// lines. The caller decides how to surface them — TUI startup
	// notices, oneshot stderr, an ACP diagnostic message.
	Warnings []string
}

Runtime is everything Builder.Build constructs for one session. Callers layer their own presentation-specific state on top: the Bubbletea Model/Config (TUI), stdout streaming (oneshot), or ACP protocol plumbing (internal/acp). Deliberately excluded (stay caller-specific): checkpoints, recall/FTS index, workspace trust, sensitivity posture, and the GitHub PR/Issue tool suite — all TUI-interactive or ghClient-coupled concerns with no ACP v1 equivalent yet.

func (*Runtime) Close

func (rt *Runtime) Close(ctx context.Context)

Close releases every process/goroutine resource owned by this Runtime. It is intentionally safe to call more than once: callers use it from both normal session-close paths and error/defer cleanup paths. Persistence stays caller-owned because TUI, oneshot, and ACP each have different save gates.

type SandboxConfigReloader

type SandboxConfigReloader func() (config.Config, error)

SandboxConfigReloader returns the latest config.toml snapshot before a lazy profile is created. It lets a session recover from a bad documents_image by editing config and retrying, without rebuilding the entire tool registry.

type SandboxConstructor

type SandboxConstructor func(ctx context.Context, cfg config.SandboxConfig, id, mountRoot string) (agent.Sandbox, error)

SandboxConstructor is the injectable container factory used by SandboxManager. Production wiring points it at sandbox.NewPodmanSandbox; tests replace it with spy sandboxes so profile routing can be exercised without Podman.

type SandboxHandler

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

SandboxHandler is the stable sandbox value registered with tools. It routes ordinary Sandbox.Command calls to the default profile and profile-aware calls to the requested profile.

func (SandboxHandler) Close

func (h SandboxHandler) Close() error

func (SandboxHandler) Command

func (h SandboxHandler) Command(ctx context.Context, command, cwd string) *exec.Cmd

func (SandboxHandler) CommandProfile

func (h SandboxHandler) CommandProfile(ctx context.Context, profile agent.SandboxProfile, command, cwd string) *exec.Cmd

func (SandboxHandler) Label

func (h SandboxHandler) Label() string

func (SandboxHandler) LabelProfile

func (h SandboxHandler) LabelProfile(profile agent.SandboxProfile) string

func (SandboxHandler) LiveProfiles

func (h SandboxHandler) LiveProfiles() []agent.SandboxProfile

LiveProfiles exposes the manager's mounted profiles to worktree guards. An empty result means the lazy handler exists but no container has mounted the original cwd yet, so an in-session worktree swap is still safe.

func (SandboxHandler) VisiblePath

func (h SandboxHandler) VisiblePath(path string) bool

type SandboxManager

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

SandboxManager owns the command-sandbox lifecycle for one session or dispatch worker. It lazily creates one container per requested profile and never tracks a user-facing "active" profile: tools route each command by intent.

func NewSandboxManager

func NewSandboxManager(cfg config.SandboxConfig, sessionID, mountRoot string, constructor SandboxConstructor) *SandboxManager

NewSandboxManager returns a manager for an enabled sandbox backend. The first container is not started until a tool actually requests its profile, which avoids paying for the documents image in sessions that never touch documents.

func (*SandboxManager) Close

func (m *SandboxManager) Close() error

Close tears down every profile container created by this manager. It is best-effort and returns a joined error for callers that choose to log it.

func (*SandboxManager) Command

func (m *SandboxManager) Command(ctx context.Context, profile agent.SandboxProfile, command, cwd string) *exec.Cmd

Command returns a command routed through the requested profile's container, creating that container on first use. A creation failure is encoded as an exec.Cmd that fails from Run, matching the Sandbox interface's no-error shape without falling back to host execution.

func (*SandboxManager) Handler

func (m *SandboxManager) Handler() agent.Sandbox

Handler returns the stable agent.Sandbox implementation injected into tools. The handler is intentionally tiny: it preserves the old Sandbox interface for run_bash while exposing ProfiledSandbox for document-tool routing.

func (*SandboxManager) Label

func (m *SandboxManager) Label(profile agent.SandboxProfile) string

Label reports the profile-specific sandbox label used in approval previews.

func (*SandboxManager) LiveProfiles

func (m *SandboxManager) LiveProfiles() []agent.SandboxProfile

LiveProfiles returns the profiles that have created containers. It is used by worktree guards so changing cwd cannot strand live containers with unseen mounts.

func (*SandboxManager) SetConfigReloader

func (m *SandboxManager) SetConfigReloader(reloader SandboxConfigReloader)

SetConfigReloader installs an optional live config reload hook used only for profiles that have not yet created a container. Existing live containers keep their startup config until the session ends, preserving mount/image stability.

func (*SandboxManager) VisiblePath

func (m *SandboxManager) VisiblePath(path string) bool

VisiblePath reports whether a sandboxed command can run with path as its cwd. The Podman sandbox mounts both the session root and this repo's managed worktree storage, so an in-session enter_worktree is safe for yottacode- managed worktrees even after a profile container is live.

type SessionSpec

type SessionSpec struct {
	cli.ChatOptions

	// Cwd is the session's working directory. ChatOptions carries no cwd
	// field — oneshot/tui both derive it from os.Getwd() today. ACP must
	// derive it from session/new's cwd parameter instead, and Build must
	// never call os.Getwd()/os.Chdir() itself: it hosts N concurrent
	// sessions with potentially different cwds in one process.
	Cwd string

	// MCPServers is session-scoped and additive on top of the global
	// fileCfg.MCPServers Build loads from config.toml. Empty for
	// oneshot/tui (they only ever use the global set); ACP's session/new
	// carries this from the client's mcpServers param. On name collision
	// a session-scoped entry overrides the global one of the same name.
	MCPServers []config.MCPServer

	// DisableWorktreeTools excludes EnterWorktreeTool/ExitWorktreeTool
	// from the registry. Both tools call process-global os.Chdir() in
	// addition to updating the session's CwdRef (see
	// EnterWorktreeTool.swapCwd) — safe when one process hosts one
	// session at a time (oneshot, TUI), unsafe when one process hosts N
	// concurrent sessions with different cwds (ACP). oneshot/tui pass
	// false; acp passes true.
	DisableWorktreeTools bool

	// SupportsBackgroundDispatch controls AgentTool.AllowBackground and
	// DispatchTool.SupportsBackground. oneshot passes false (no
	// long-lived session to host async completions — its own rationale,
	// documented at the oneshot call site historically). tui and acp
	// both pass true: both are long-lived processes that can host
	// detached background work and surface its completion later.
	SupportsBackgroundDispatch bool

	// PreCompact is the optional pre-compaction snapshot hook threaded
	// into LoopConfig.Compaction.PreCompact. tui supplies its snapshot
	// writer (writePreSummarySnapshot); oneshot and acp leave this nil,
	// which agent.CompactionConfig treats as "no snapshot taken."
	PreCompact func(history []adapter.Message) (string, error)

	// DeferMCPStart skips starting the MCP manager and registering its
	// tools inside Build. Build still constructs Runtime.MCPManager (via
	// mcp.NewManager) so the caller has something to Start() later — it
	// just doesn't block Build on it. TUI sets this: it deliberately
	// starts MCP servers asynchronously via its own Bubbletea tea.Cmd
	// (internal/tui/cmd_mcp.go) so slow/hanging npx-based servers don't
	// delay first paint, and that existing machinery already knows how
	// to Start() the manager and register tools once it has a
	// *mcp.Manager and *agent.Registry to work with — nothing new is
	// needed there. oneshot and acp both leave this false: neither has a
	// progressive-UI concept, so Build's synchronous start-and-register
	// is exactly what they want (acp's session/new must return only
	// once the session is actually ready).
	DeferMCPStart bool

	// SupportsLiveRouterToggle controls whether Build attempts
	// cli.BuildRouterAdapters even when [router].mode is "off". TUI and
	// ACP are long-lived and let a user flip routing on mid-session
	// (/advisor on, or ACP's "Advisor routing" session config option) —
	// both need the advisor/implementer pair pre-resolved at startup so
	// that toggle can work instantly and so the pair's mere presence can
	// be checked (see internal/tui/cmd_router.go's routerOn and
	// internal/acp/config_options.go's sessionConfigOptions). oneshot has
	// no such toggle — a single `yottacode run` never flips [router].mode
	// mid-session — so it leaves this false: with routing off, Build
	// skips resolving the pair entirely rather than surfacing a stale or
	// misconfigured advisor pair's resolution error as a confusing
	// warning for a session that would never use it anyway.
	SupportsLiveRouterToggle bool
}

SessionSpec is the input to Builder.Build. It wraps cli.ChatOptions (the struct both current callers already resolve from flags/env/config via cli.Resolve) rather than re-deriving a hand-picked subset, plus the handful of fields ChatOptions has no room for.

Jump to

Keyboard shortcuts

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