agentapp

package
v0.2.0-alpha.8 Latest Latest
Warning

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

Go to latest
Published: Sep 6, 2026 License: Apache-2.0 Imports: 37 Imported by: 0

Documentation

Index

Constants

View Source
const AgentsMdFilename = "AGENTS.md"

AgentsMdFilename is the name of the workspace-level agent instructions file per the agents.md convention (https://agents.md/).

View Source
const DefaultSystemPrompt = `` /* 4627-byte string literal not displayed */

DefaultSystemPrompt is the default system message for the BuildMax CLI agent.

View Source
const MaxAdditionalSystemPromptChars = agent.MaxUserAuthoredSystemPromptChars

MaxAdditionalSystemPromptChars bounds the additional system prompt. It sits in the system prompt, which is re-sent in full on every call and has no trimming path, so it is bounded when it is resolved rather than degraded later.

Variables

View Source
var ErrTurnActive = errors.New("a turn is already running for this session")

ErrTurnActive reports that a session already has a running turn. Callers that can wait should queue behind the run (surfaces already do); nothing may run a second turn concurrently.

Functions

func BuildAgentTypes

func BuildAgentTypes(registry llm.ToolRegistry, userDefs []subagent.Def) map[string]tools.AgentTypeConfig

BuildAgentTypes merges built-in sub-agent definitions with caller-provided user defs into an AgentTypeConfig map ready for tools.NewTask.

func BuildEffectiveSystemPrompt

func BuildEffectiveSystemPrompt(workspaceDir, modelName, additionalSystemPrompt string, caps PromptCapabilities) string

BuildEffectiveSystemPrompt builds the agent system prompt for a workspace, an optional model name, and an optional additional system prompt.

The local layers run from least to most specific, and every one is additive:

  1. the runtime prompt, which carries the tool-usage conventions
  2. ~/.buildmax/AGENTS.md — personal rules
  3. <ws>/AGENTS.md — project rules
  4. the additional system prompt — this run's user-authored identity and constraints

Portal workers additionally insert their Space instructions before layer 4. Together the layers form the cacheable prefix for one run. The compaction summary changes, and RunLoop appends it after them; it is never added here.

Pass an empty modelName when it is not yet known, and empty additional text when the run has none.

func BuildSystemPromptWithLayers

func BuildSystemPromptWithLayers(workspaceDir, modelName, additionalSystemPrompt string, caps PromptCapabilities) (string, []agent.PromptLayer)

BuildSystemPromptWithLayers builds the prompt and reports which layers contributed to it. The layer list goes into the run trace, so a finished run can say what it was told before the conversation began rather than leaving it to be inferred from behaviour.

func DefaultModelName

func DefaultModelName(settings config.Settings) string

func NewConfiguredPolicy

func NewConfiguredPolicy(res config.PermissionResolution, fallback agent.ToolPolicy) agent.ToolPolicy

NewConfiguredPolicy layers settings.yaml rules over a surface policy. Invalid actions are logged once and skipped: one bad rule must not stop the agent.

func ReadAgentsMd

func ReadAgentsMd(dir string) (string, error)

ReadAgentsMd reads AGENTS.md from the given directory. Returns ("", nil) when the file does not exist.

func ResolveAgentTypeTools

func ResolveAgentTypeTools(agentName string, toolNames []string, registry llm.ToolRegistry) []llm.Tool

ResolveAgentTypeTools resolves tool names from a registry; skips unknowns with a warning.

func ValidateAdditionalSystemPrompt

func ValidateAdditionalSystemPrompt(text string) error

ValidateAdditionalSystemPrompt rejects text that does not fit the budget. The error names the size and the limit so whoever supplied it — a flag, a file, or an agent record — can see what to cut.

func ValidateInstructionLayers

func ValidateInstructionLayers(spaceInstructions, additionalSystemPrompt string) error

ValidateInstructionLayers bounds the complete user-authored instruction prefix. Space and Agent instructions are both permanent on every model call, so they share one budget rather than each quietly doubling it.

Types

type AgentApp

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

func NewAgentApp

func NewAgentApp(cfg AppConfig) (*AgentApp, error)

func (*AgentApp) AgentDefs

func (a *AgentApp) AgentDefs() []subagent.Def

AgentDefs returns the user-defined sub-agent definitions for this workspace.

func (*AgentApp) Close

func (a *AgentApp) Close() error

func (*AgentApp) CloseSession

func (a *AgentApp) CloseSession(sess *SessionContext)

CloseSession releases a finished session and fires the SessionEnd hook.

Every OpenSession must be paired with one of these. An open session holds the writer lock and its journal file, so leaving one open keeps the session unopenable by anything else — including this process, which is how a second open of the same id fails rather than waits. Safe to call with a nil session, and safe to call twice.

func (*AgentApp) CompactSession

func (a *AgentApp) CompactSession(ctx context.Context, sess *SessionContext) (CompactResult, error)

CompactSession compacts a session's context on demand.

It is the same pass RunLoop makes on its own when the window fills, run without the fill test, and it takes the session's turn lock for the same reason a turn does: it rewrites the model-visible history, and doing that under a running turn would race it.

func (*AgentApp) DefaultModelName

func (a *AgentApp) DefaultModelName() string

func (*AgentApp) EstimateRunUsage

func (a *AgentApp) EstimateRunUsage(sess *SessionContext) (RunUsage, error)

func (*AgentApp) GenerateTurnDigest

func (a *AgentApp) GenerateTurnDigest(ctx context.Context, sess *SessionContext, client llm.LLMClient, summary TurnSummary) (TurnDigest, error)

GenerateTurnDigest asks the model to describe the finished turn. It returns the empty digest with no error whenever there is nothing to ask about, so a caller can call it unconditionally.

It spends money, so it is bounded twice over: the config may switch either half off, and worthRecapping/asksUser keep the call off turns that could not produce anything. The usage it does spend is folded into the session like the title call's, because a cost the user cannot see in /stats is a cost BuildMax reported wrong.

func (*AgentApp) Jobs

func (a *AgentApp) Jobs() *job.Manager

Jobs returns the app's background job manager, or nil where background jobs are disabled. One manager per AgentApp: jobs are process-scoped but owned by this workspace's runtime, and closing the app stops them.

func (*AgentApp) ListSessions

func (a *AgentApp) ListSessions() ([]session.ItemSummary, error)

func (*AgentApp) MCPStatus

func (a *AgentApp) MCPStatus() MCPStatus

func (*AgentApp) ManagedServerURL

func (a *AgentApp) ManagedServerURL() string

ManagedServerURL is the deployment serving this app's models, or empty when they are called directly from this machine. It is the app's mode, and a surface names it wherever it tells the user where a prompt goes.

func (*AgentApp) MemoryOverview

func (a *AgentApp) MemoryOverview() MemoryOverview

MemoryOverview is this run's view of its own Project memories.

func (*AgentApp) MemoryStatus

func (a *AgentApp) MemoryStatus() MemoryReport

MemoryStatus reports what this app's memory store looks like right now, without putting any body where the model can see it.

func (*AgentApp) ModelConfigs

func (a *AgentApp) ModelConfigs() []ModelConfig

func (*AgentApp) OpenOrCreateSession

func (a *AgentApp) OpenOrCreateSession(sessionID string) (*SessionContext, error)

OpenOrCreateSession loads sessionID when it has been persisted, or creates a new session with that ID. Remote task runs use this because the server assigns a session ID before the worker has written the first session file.

func (*AgentApp) OpenSession

func (a *AgentApp) OpenSession(sessionID string) (*SessionContext, error)

func (*AgentApp) PermissionIssues

func (a *AgentApp) PermissionIssues() []string

PermissionIssues returns rules that were ignored because their action was not recognised. A rule silently dropped looks exactly like one that is in force.

func (*AgentApp) PermissionRules

func (a *AgentApp) PermissionRules() []config.PermissionEntry

PermissionRules returns the configured rules in resolution order, for display alongside ToolEntries. Rules naming a dispatch target have no tool row of their own.

func (*AgentApp) Plugins

func (a *AgentApp) Plugins() PluginSnapshot

Plugins returns the plugin inventory this runtime was assembled with.

func (*AgentApp) Project

func (a *AgentApp) Project() localproject.Project

Project is the local Project this app's sessions belong to, or the zero value for a projectless run.

It does not move when the workspace does: entering a worktree changes the root and everything derived from it, and leaves the Project -- and the memory that hangs off it -- alone. See docs/design/local-project-memory.md §6.2.

func (*AgentApp) ReadSession

func (a *AgentApp) ReadSession(sessionID string) (*SessionContext, error)

ReadSession loads a session without taking its writer lock, for callers that only display it.

A status view must work while a turn is running, so it cannot be the thing that takes the lock. The result cannot be written to: it is a read model, and its commit paths are no-ops, which is why it is a different call rather than a flag on OpenSession.

func (*AgentApp) RefreshMCP

func (a *AgentApp) RefreshMCP(ctx context.Context) (MCPStatus, error)

func (*AgentApp) RunBackgroundEvent

func (a *AgentApp) RunBackgroundEvent(ctx context.Context, sess *SessionContext, ev BackgroundEvent, opts RunPromptOpts) (RunResult, error)

RunBackgroundEvent runs one serialized turn caused by a background job event rather than a user prompt. The appended message carries the event's non-user Source and an envelope framing the payload as untrusted observation; UserPromptSubmit does not fire, because nothing here is a user prompt. Serialization against the session is the same as RunPrompt's.

func (*AgentApp) RunPrompt

func (a *AgentApp) RunPrompt(ctx context.Context, sess *SessionContext, prompt string, opts RunPromptOpts) (RunResult, error)

func (*AgentApp) Sandbox

func (a *AgentApp) Sandbox() agent.SandboxView

Sandbox returns the SandboxView the agent will run with. In Phase A this is always NoopSandbox; Phase B will install the OS-backed manager.

func (*AgentApp) SandboxResolution

func (a *AgentApp) SandboxResolution() config.SandboxResolution

SandboxResolution returns the resolved config plus the per-layer source chain. Surfaced by `buildmax sandbox status`.

func (*AgentApp) SandboxStatus

func (a *AgentApp) SandboxStatus() SandboxStatus

SandboxStatus returns the resolved sandbox config plus runtime state.

func (*AgentApp) SessionModelName

func (a *AgentApp) SessionModelName(sessionID string) string

SessionModelName is the model a persisted conversation runs under: its own recorded selection, or the app default when it records none or is not found.

It exists because a conversation's model is per-session state, not the app default: once one conversation's model is switched, the picker must show that conversation's model rather than whatever the app default happens to be.

func (*AgentApp) SessionsDir

func (a *AgentApp) SessionsDir() string

func (*AgentApp) SetDefaultModel

func (a *AgentApp) SetDefaultModel(name string)

SetDefaultModel overrides the model used for new turns in this AgentApp.

func (*AgentApp) SetSessionModel

func (a *AgentApp) SetSessionModel(sessionID, modelName string) error

SetSessionModel records the model an existing conversation runs under, so its next turn resolves to that model rather than the one it was created with.

It writes the session's metadata directly, without opening it for writing: the model is a current selection, like a rename, so it never touches history. A conversation with a run in flight holds the writer lock and reports session.ErrLocked here — its live turn already fixed its model, and the switch lands on the next one.

func (*AgentApp) SkillEntries

func (a *AgentApp) SkillEntries() []tools.SkillEntry

func (*AgentApp) StartupNotices

func (a *AgentApp) StartupNotices(relinkCommand string) []string

StartupNotices are the things a surface should print once, before the first turn: a Project registered for a directory that may be a moved repository, and memory files that will be silently absent from every run until repaired.

A source missing for a whole session without anyone being told is the failure this exists to prevent, and `doctor` is not where a person looks mid-task.

func (*AgentApp) ToolEntries

func (a *AgentApp) ToolEntries() []ToolEntry

ToolEntries returns the name and description of every tool available to the agent. It reuses the cached tool registry when available; otherwise it builds one.

func (*AgentApp) Workspace

func (a *AgentApp) Workspace() util.Workspace

Workspace returns the root itself, for a surface that must keep following it rather than read it once. A nil AgentApp reports an empty root instead of nil, so callers never have to guard the interface value.

func (*AgentApp) WorkspaceRoot

func (a *AgentApp) WorkspaceRoot() string

func (*AgentApp) Worktrees

func (a *AgentApp) Worktrees() *worktree.Manager

Worktrees returns the worktree lifecycle for this runtime, nil on a surface that does not offer it.

type AppConfig

type AppConfig struct {
	WorkspaceDir string
	EnableMCP    bool
	// ModelEntries overrides settings.yaml models for this AgentApp. It is how a
	// surface in managed mode supplies what the deployment offers, and how a
	// worker receives the server's resolved model without writing credentials to
	// a run directory that is later persisted as an artifact.
	ModelEntries []config.ModelEntry
	// DefaultModel names the entry in ModelEntries a new session starts with.
	// Read only when ModelEntries is set; otherwise settings.yaml says.
	DefaultModel string
	// ManagedServerURL says these models are served by that deployment rather
	// than called from this machine. Empty means direct: the models are the ones
	// in settings.yaml and each carries its own provider credential.
	//
	// It is a property of the app rather than of an entry because a list has one
	// source. A surface is in one mode or the other, and the mode decides where
	// every prompt goes. See docs/design/client-modes.md section 4.
	ManagedServerURL string
	// Policy is the surface's tool permission baseline, under the user's
	// tools.permissions rules. Every surface states its own — CLI, TUI, Desktop,
	// a Portal turn, and a task run all pass one — and nil is the library's
	// nil-safe floor rather than a surface's choice: it allows every tool, which
	// is only correct for a surface that meant to.
	Policy agent.ToolPolicy
	// SandboxSurface picks the per-surface default sandbox baseline (see
	// config.SandboxSurfaceCLI / SandboxSurfaceWorker). Empty means
	// SandboxSurfaceCLI.
	SandboxSurface config.SandboxSurface
	// SandboxRunOverride enables the sandbox or selects its approval mode for
	// this AgentApp only. It cannot disable confinement or outrank policy.yaml.
	SandboxRunOverride config.SandboxRunOverride
	// SandboxNetworkTier and SandboxFilesystemTier are this run's agent-
	// declared sandbox tiers (see docs/design/agent-sandbox-policy.md).
	// Empty means the strictest tier on that axis. Only a worker run sets
	// these; every other surface leaves them empty and gets today's
	// behavior unchanged.
	SandboxNetworkTier    config.SandboxNetworkTier
	SandboxFilesystemTier config.SandboxFilesystemTier
	// SandboxSharedPaths supplies the deployment-configured paths
	// SandboxFilesystemTier's non-workspace tiers add. See
	// config.SandboxSharedPaths.
	SandboxSharedPaths config.SandboxSharedPaths
	// SecretEnvNames are the environment variable names this run declared as
	// Space Secret grants. The sandbox admits them past its secret-shaped
	// denylist, so a grant like GH_TOKEN reaches the agent's commands.
	// BuildMax's own credentials are never admitted. Empty on every surface
	// that consumes no Secret. See docs/design/space-secrets.md §13.1.
	SecretEnvNames []string
	// SecretEnvValues are the corresponding grant values, registered with the
	// run's trace redactor so they do not drift into a durable trace. Defense
	// in depth, not a boundary. See docs/design/space-secrets.md §12.
	SecretEnvValues []string
	// MaxIterations caps this AgentApp's model calls per run, outranking
	// settings.yaml. Zero takes the configured value. A surface exposes it for
	// the run whose length nobody configured for: a benchmark task or an
	// unattended job is asked once and has to finish inside whatever the
	// machine was already set to, and the run that hits the cap is recorded as
	// an agent failure rather than as the budget it actually was.
	MaxIterations int
	// ManagedToken supplies the BuildMax credential for models configured with
	// transport "buildmax". Leaving it nil means this surface offers no managed
	// inference, and such an entry fails with a clear error instead of falling
	// back to a direct provider call.
	ManagedToken ManagedTokenFunc
	// ManagedHTTPClient is the HTTP client managed inference uses to reach the
	// gateway. Nil uses http.DefaultClient. A worker sets it to the client that
	// carries its server trust, so managed calls verify the worker listener the
	// same way its other calls do.
	ManagedHTTPClient *http.Client
	// ManagedTaskRunID makes managed calls from this app run-scoped: they go to
	// the worker route, carrying a run token instead of a login, and the server
	// derives user and space from it. Empty means managed calls are space-scoped,
	// which is what CLI, TUI, and Desktop do.
	ManagedTaskRunID string
	// Surface labels managed calls for correlation, e.g. "cli" or "desktop".
	Surface string
	// AdditionalSystemPrompt is free text appended to the system prompt as its last stable
	// layer: the user-authored identity and constraints for this run. It holds the prompt text
	// itself, not the name of anything. It is additive and never replaces the runtime prompt,
	// because replacing that would strip the tool-usage conventions the agent depends on and
	// the failure would look like a bad model rather than a bad configuration.
	//
	// Whoever assembles the run resolves it — a CLI flag, a named definition file, or the
	// agent record a task run names — and the last writer wins. It is bounded because it
	// lives in the system prompt, which is re-sent in full on every call and never trimmed.
	AdditionalSystemPrompt string
	// AdditionalSystemPromptLayer names this configured text in run provenance.
	// Empty keeps the generic additional_system_prompt name; Portal workers set
	// agent_instructions because the text came from a stored Agent definition.
	AdditionalSystemPromptLayer string
	// SpaceAgentInstructions are the Space-level instructions inherited by a
	// Portal background run. They form their own prompt layer before the
	// selected Agent's AdditionalSystemPrompt. Local surfaces leave this empty.
	SpaceAgentInstructions string
	// ArtifactPublisher gives this surface the artifact capability. Nil means it
	// has none — a session running straight against a model provider, with no
	// BuildMax server — and no artifact tool is registered at all.
	ArtifactPublisher tools.ArtifactPublisher
	// IssueClient scopes this run to the one Issue it is working. Nil means the
	// run has no Issue, or no way to reach the one it has, and neither Issue
	// tool is registered. It is never a client the model may point elsewhere:
	// the Issue is fixed when this is built. See docs/design/issue-agent-access.md.
	IssueClient tools.IssueClient
	// EnableBackgroundJobs turns on local background jobs: Bash gains
	// run_in_background and the Job tools are registered. Only interactive
	// surfaces (TUI, Desktop) set it — print mode has no host process to own
	// a job, and eval and workers have no unattended lifecycle for one, per
	// docs/design/local-background-jobs.md.
	EnableBackgroundJobs bool

	// EnableWorktrees lets a session create Git worktrees and move its own
	// workspace root into them. CLI and TUI set it; a worker run does not,
	// because its directory is run-scoped and is not the user's to branch.
	// See docs/design/workspace-root-and-worktrees.md D8.
	EnableWorktrees bool

	// EnableLocalProject resolves the workspace to a local Project and stamps
	// it on every session this app creates. CLI, TUI, and Desktop set it.
	//
	// A worker or eval run does not: its directory is run-scoped and belongs to
	// nobody's local catalog, and registering one would fill that catalog with
	// Projects for directories that no longer exist. Such a run is projectless,
	// which later costs it the project-memory context and tool by construction
	// rather than by a second flag. See docs/design/local-project-memory.md §9.4.
	EnableLocalProject bool

	// DisableProjectMemory turns off project memory for this run: no block is
	// rendered and the write tool is not registered. Turning off reading is
	// what turns off writing -- a run must not be able to mutate a source it
	// was not allowed to inspect. It has no effect on a run with no Project,
	// which never had either.
	DisableProjectMemory bool
}

type BackgroundEvent

type BackgroundEvent struct {
	// Source is one of the llm.MessageSource* values.
	Source string
	JobID  string
	// Title is the short human label — the command or the delegation
	// description.
	Title string
	// Payload is the observed text: result summary, reply, or line.
	Payload string
}

BackgroundEvent is one background-job fact delivered into a session as its own serialized turn: a finished command, a subagent's final reply, or a monitor line. It is not user input and never runs user-prompt hooks.

func CompletionEvent

func CompletionEvent(m *job.Manager, j job.Job) BackgroundEvent

CompletionEvent shapes a finished job's requested delivery: the terminal state plus the reply (subagent) or a recent-output tail (command). Both surfaces build deliveries here so they cannot drift apart.

func MonitorLineEvent

func MonitorLineEvent(ev job.Event) BackgroundEvent

MonitorLineEvent shapes one react-monitor line for delivery.

type CompactResult

type CompactResult struct {
	Summarized int
	Kept       int
	Reason     string
	// BeforeTokens is the estimated context size the session carried into the
	// compaction, so a surface can say what the pass actually freed. The size
	// afterwards is Status.ContextTokens.
	BeforeTokens int
	// Status is the session's usage re-estimated after the boundary moved. A
	// surface holding a context gauge would otherwise keep showing the size the
	// compaction just removed.
	Status RunUsage
}

CompactResult is what one compaction the user asked for did to a session.

Summarized == 0 with no error means the pass found nothing worth replacing, and Reason says why; the caller reports that rather than a failure.

type HistoryPoint

type HistoryPoint struct {
	ItemID  string
	Role    string
	Content string
	// Source is non-empty when a user-role message was a background event
	// rather than something the person typed, which a picker should not
	// present as their own words.
	Source string
}

HistoryPoint is one message a picker offers, for a rewind or for a fork.

func ForkPoints

func ForkPoints(sess *SessionContext) []HistoryPoint

ForkPoints lists the messages in a session a fork could branch from, newest last, paired with the journal item id a caller passes to Fork.

Only user and assistant messages are offered. A tool result is a message the model sees, but "branch off from the output of that command" is not a place a person thinks of starting from, and offering it would put entries in the list that only make sense to the machine.

An assistant message that asked for tools is excluded for a harder reason than taste. A branch ending there holds a tool call with no result: the fork prefix stops before the result, and recovery does not answer a call that never entered its tool. Only the Anthropic adapter prunes the unanswered half; the OpenAI and Ollama adapters send it and the provider refuses the request. Since the loop continues a turn only when the model asked for tools, excluding those messages is exactly excluding the mid-turn ones, and what remains is the reply that ended each turn.

The last point is the head. Forking from where the conversation already is is the common case — branch off from here — which is why fork offers it and rewind does not.

func RewindPoints

func RewindPoints(sess *SessionContext) []HistoryPoint

RewindPoints lists the prompts a rewind could take back, newest last, paired with the journal item id a caller passes to Rewind.

Only what the person typed is offered, and rewind removes it: the message returns to the input box to be edited and sent again, so an assistant reply is not a point — there is nothing to hand back — and neither is a background event, which arrives as a user message the person never wrote.

The first message of the session is not offered either. Rewinding it would ask for a branch with no records on it at all; a new session says that more honestly than an empty one does. See session.RewindLanding.

type HookManager

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

HookManager owns the merged hooks configuration, the per-type driver registry, and the matcher cache. It is the single object the agent runtime interacts with (via agent.HookRunner). Driver polymorphism is invisible above this layer.

Concurrency: HookManager is safe to call from multiple goroutines. The matcher cache uses a mutex; per-call execution is otherwise stateless.

func NewHookManager

func NewHookManager(cfg corehook.Config, drivers map[string]hook.Driver) *HookManager

NewHookManager constructs a manager from the already-merged hooks config and a driver registry. A nil registry is treated as empty; entries whose resolved type has no driver are skipped with a warning at dispatch time (logged once per event invocation).

func (*HookManager) Refresh

func (m *HookManager) Refresh(cfg corehook.Config)

Refresh swaps the merged config without rebuilding driver instances. The matcher cache is preserved so previously compiled regexes are still hot. Drivers that watch their own dependencies (HTTP transport, MCP catalog) pick up changes via their Deps.

func (*HookManager) Run

Run implements agent.HookRunner. See docs/design/hook-system.md §8.2 for the dispatch flow. The first matching entry that returns a block decision wins for the gate; every other matching entry still executes so audit hooks see every event.

func (*HookManager) Status

func (m *HookManager) Status() HookStatus

Status returns a snapshot describing what the manager currently dispatches.

type HookStatus

type HookStatus struct {
	EventCounts map[string]int `json:"event_counts"`
	Types       []string       `json:"types"`
	TotalHooks  int            `json:"total_hooks"`
}

HookStatus describes the visible state of the manager — counts per event and which transport types are configured. Suitable for a future `buildmax hooks` inspector or desktop activity view.

type LLMClientCache

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

func (*LLMClientCache) Get

func (r *LLMClientCache) Get(modelName string) (cllm.LLMClient, error)

type LLMCompactor

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

LLMCompactor implements agent.ContextCompactor using the same LLM client as the agent run. It calls the model once with a summarize prompt over the messages to compact.

func NewLLMCompactor

func NewLLMCompactor(client llm.LLMClient) *LLMCompactor

NewLLMCompactor creates a compactor backed by the given LLM client.

func (*LLMCompactor) Compact

func (c *LLMCompactor) Compact(ctx context.Context, msgs []llm.Message) (string, llm.Usage, error)

Compact summarizes msgs into a short text suitable for injection into the system prompt.

type MCPManager

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

func NewMCPManager

func NewMCPManager(ctx context.Context, cfg *mcpcfg.ConfigRoot) (*MCPManager, error)

NewMCPManager performs an initial Refresh with the provided config. Config load failures are returned as errors; individual server connection failures are surfaced only via Status().

func (*MCPManager) Close

func (m *MCPManager) Close() error

func (*MCPManager) Refresh

func (m *MCPManager) Refresh(ctx context.Context, cfg *mcpcfg.ConfigRoot) error

Refresh reconnects to all servers in cfg. Individual server connection failures are non-fatal and are recorded in Status() instead.

func (*MCPManager) Registry

func (m *MCPManager) Registry() *mcp.Registry

func (*MCPManager) Status

func (m *MCPManager) Status() MCPStatus

type MCPStatus

type MCPStatus struct {
	LoadError string
	Servers   []mcp.MCPServerStatus
}

type ManagedTokenFunc

type ManagedTokenFunc func(serverURL string) (string, error)

ManagedTokenFunc returns the BuildMax credential to use for serverURL. It is expected to refuse when the stored login belongs to a different server.

type MemoryOverview

type MemoryOverview struct {
	// Project is the scope these belong to. Its ID is empty when this run has
	// none, which is one of the reasons Memories can be empty.
	Project localproject.Project
	// Disabled reports that the user turned memory off for this run. The
	// memories below still exist; this run is simply not carrying them.
	Disabled bool
	// Unavailable is set when the store could not be read at all.
	Unavailable string
	Memories    []localproject.Memory
	Skipped     []localproject.SkippedMemory
	// IndexChars is what the rendered index costs on every model call, and
	// IndexBudget what it may cost. A person deciding whether to prune is
	// deciding against this pair, not against the count.
	IndexChars  int
	IndexBudget int
}

MemoryOverview is what a surface shows a person about a Project's memories: the store as it stands, plus why it might be empty.

It carries bodies. That is the difference between this and what the model sees — a person asking to look at their own memories is not paying a per-call context cost, and the whole reason the model gets an index is that it does. Surfaces still show bodies on request rather than all at once, because a list of twenty full bodies is not a list.

type MemoryReport

type MemoryReport struct {
	// Unavailable is set when the store could not be read at all. Neither the
	// index nor either tool is offered for the run.
	Unavailable string
	// Skipped names the files that could not be used, with the reason.
	Skipped []localproject.SkippedMemory
}

MemoryReport is what a surface says at run start about a store that is not wholly usable.

It exists because a source silently missing for a whole session is the failure this reporting prevents, and `doctor` is not where a person looks mid-task. Nothing here carries memory content.

func (MemoryReport) Empty

func (r MemoryReport) Empty() bool

Empty reports whether there is nothing to say.

func (MemoryReport) Lines

func (r MemoryReport) Lines() []string

Lines renders the report for a surface, one line each.

type ModelConfig

type ModelConfig struct {
	Name          string
	ProviderModel string
	BaseURL       string
	APIKey        string
	ContextWindow int // 0 = no windowing; from settings.yaml model entry
	CallTimeout   int // seconds; 0 = uses DefaultCallTimeoutSecs
	MaxTokens     int // 0 = the adapter's own default
	// Reasoning is the effort level (config.Reasoning*); off means none.
	Reasoning string
	// CacheControl is the resolved prompt-cache policy: which calls ask the
	// provider to cache the stable prefix, and for how long. Resolved here
	// rather than in the client so an entry that chose nothing takes the
	// default once, in front of every protocol.
	CacheControl config.CacheControl
	// Pricing is what this model charges. Zero means the entry configured no
	// prices, and a run against it reports its cost as unavailable rather than
	// as zero — BuildMax does not know what any provider charges.
	Pricing cllm.Pricing
	// PricingErr is why an entry's prices could not be read, empty when they
	// could. Carried rather than returned because a malformed price must not
	// stop a model from answering: the run still works, it just cannot be
	// costed, and the surface says so instead of failing the turn.
	PricingErr string
	// Integration names a qualified OpenAI-compatible gateway; empty is the
	// normal case.
	Integration string
	// Vision says this model accepts image input.
	Vision bool
	// KeepAlive is how long a local runtime keeps the model loaded between
	// calls. Only a local provider has one to keep.
	KeepAlive string
	// Provider is the wire protocol this model speaks. Empty means
	// cllm.ProviderOpenAICompatible. In managed mode it is ignored: the
	// operator's catalog decides which protocol serves the call.
	//
	// There is no transport here. Where a prompt goes is a property of the app's
	// mode, not of one model — see AppConfig.ManagedServerURL.
	Provider string
}

ModelConfig is one resolved model entry usable for client creation.

func DefaultModelConfig

func DefaultModelConfig(settings config.Settings) (ModelConfig, bool)

DefaultModelConfig is the model a new session starts with: the one default_model names, or the first entry when it names none.

A default_model matching nothing falls through to the first entry rather than failing here, because a model picker that returns nothing is worse than one that returns the wrong first choice. `buildmax doctor` reports the mismatch.

func FindModelConfig

func FindModelConfig(settings config.Settings, name string) (ModelConfig, bool)

func ModelConfigFromEntry

func ModelConfigFromEntry(entry config.ModelEntry) ModelConfig

ModelConfigFromEntry resolves one settings.yaml model entry. Surfaces use it to describe a model without building a client for it.

type MovableRoot

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

MovableRoot is the workspace root the runtime's tools resolve against, held as session state rather than captured when the runtime was assembled.

It implements tool.Workspace. Every tool consults it per call, so moving the root moves the whole runtime at once instead of leaving each tool to remember where it started. The surface that moves it — entering and leaving a worktree — is phase 2 of docs/design/workspace-root-and-worktrees.md; this type is what phase 2 sets.

Reads are concurrent: read-only tools run in parallel, per docs/design/parallel-tool-execution.md.

func NewMovableRoot

func NewMovableRoot(dir string) *MovableRoot

NewMovableRoot returns a root starting at dir, which must already be absolute and cleaned — resolveWorkspaceRoot is the one place that decides what a workspace directory means.

func (*MovableRoot) Root

func (r *MovableRoot) Root() string

Root implements tool.Workspace.

func (*MovableRoot) Set

func (r *MovableRoot) Set(dir string)

Set moves the root. dir is cleaned but not otherwise checked: whether a directory may be entered at all is decided before this is called, by the containment rule in docs/design/workspace-root-and-worktrees.md D3.

type NoteCheckpointer

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

NoteCheckpointer implements agent.StateCheckpointer. Before a compaction discards messages, it gives the model one bounded turn to move what matters into durable session state.

It is a separate model call rather than a job handed to the summarizer: the summarizer is answering "what happened", which is a different question from "what will I still need", and it answers it from a context that does not include the run's own notes.

func NewNoteCheckpointer

func NewNoteCheckpointer(client llm.LLMClient) *NoteCheckpointer

NewNoteCheckpointer creates a checkpointer backed by the given LLM client. Its tool set is deliberately just the two state-writing tools: with a file or shell tool in reach the model treats the checkpoint as a turn to keep working.

func (*NoteCheckpointer) Checkpoint

func (c *NoteCheckpointer) Checkpoint(ctx context.Context, discarded []llm.Message) error

Checkpoint runs the checkpoint turn. It is a no-op when the run keeps no durable state or when there is nothing to look at.

type PluginSnapshot

type PluginSnapshot struct {
	Discovery config.PluginDiscovery

	// Findings gathers every problem, from the directory scan and from
	// resolving each kind of content. A collision names the plugins involved,
	// so the messages stay meaningful once they are mixed together.
	Findings []plugin.Finding

	// Shadowed lists plugin definitions a higher layer replaced, so a plugin is
	// not shown as fully active when part of it never loads.
	Shadowed []plugin.Shadowed
	// contains filtered or unexported fields
}

PluginSnapshot is the plugin inventory one runtime resolved when it was assembled, together with everything resolving it noticed.

It is fixed for the life of the runtime. A clone, pull, install, update, disable, or removal while a run is in flight must not change what that run is doing: the CLI picks up the change on its next invocation, and Desktop rebuilds its runtime after a managed plugin action.

func (PluginSnapshot) HasErrors

func (s PluginSnapshot) HasErrors() bool

HasErrors reports whether anything in the plugin layer failed to load.

func (PluginSnapshot) Loadable

func (s PluginSnapshot) Loadable() []config.DiscoveredPlugin

Loadable returns the plugins that contributed to this runtime.

func (PluginSnapshot) Provenance

func (s PluginSnapshot) Provenance(ctx context.Context) []plugin.Provenance

Provenance is the inventory to record for one run.

A repository's commit and dirty flag are read here rather than reused from assembly: a working tree can change a file between the two, and saying which input was still mutable is the record's whole purpose. Everything else is already fixed. A read that fails leaves the entry without a commit rather than failing the run.

func (PluginSnapshot) ShadowedNames

func (s PluginSnapshot) ShadowedNames(name string) []string

ShadowedNames lists what a higher layer overrode for one plugin, so a surface can say a plugin is partly inactive without knowing how layering works.

type ProjectManager

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

ProjectManager resolves a Workspace to the local Project that owns it. It is the one place CLI and Desktop both go, so the two surfaces cannot disagree about what repository a session belongs to.

func NewProjectManager

func NewProjectManager(dir string) *ProjectManager

NewProjectManager returns a manager over the Project bundles in dir.

func (*ProjectManager) Dir

func (m *ProjectManager) Dir() string

Dir is the projects root this manager writes under.

func (*ProjectManager) Lookup

func (m *ProjectManager) Lookup(ctx context.Context, workspace string) (localproject.Project, error)

Lookup returns the Project already registered for workspace, or ErrNotFound.

It registers nothing. A diagnostic reporting which Project a directory belongs to must not be the thing that decides it belongs to one.

func (*ProjectManager) MemoryOverviewFor

func (m *ProjectManager) MemoryOverviewFor(ctx context.Context, project localproject.Project) MemoryOverview

MemoryOverviewFor returns the overview for a Project. It is on the manager rather than on AgentApp so a command with no runtime -- `buildmax info` for a session that is not open -- can ask the same question the TUI panel asks.

func (m *ProjectManager) Relink(ctx context.Context, projectID, workspace string) (localproject.Project, error)

Relink points an existing Project at the Workspace given, after the user has chosen which one. Nothing infers this: a heuristic that joined two memory domains would be undetectable afterwards.

func (*ProjectManager) Resolve

func (m *ProjectManager) Resolve(ctx context.Context, workspace string) (localproject.Project, error)

Resolve returns the Project for workspace, registering one when this is the first time BuildMax has been run there.

Creating on demand is safe because it writes only metadata under BUILDMAX_HOME and never touches the repository. A failure to write is not: it stops the caller rather than letting a session start under an identity that was not persisted, since that session's memory would have nowhere to live and its Project would be reinvented on the next run.

func (*ProjectManager) ResolveReporting

func (m *ProjectManager) ResolveReporting(ctx context.Context, workspace string) (localproject.Project, ProjectReport, error)

ResolveReporting is Resolve plus what the surface has to say about it.

A moved repository misses lookup, so a second Project with empty memory is created before the user could have asked for anything -- and the duplicate looks like the feature working. Creation therefore announces itself when the catalog holds Projects whose locators no longer resolve. The run is never blocked on a naming decision; it is only told, so the recovery path can be found. See docs/design/local-project-memory.md §7.2.

func (*ProjectManager) Store

func (m *ProjectManager) Store() localproject.Store

Store exposes the Project store for the surfaces that list, rename, relink, or delete Projects.

type ProjectReport

type ProjectReport struct {
	// Created is set when this run registered a new Project.
	Created bool
	// Unresolved are Projects whose locator no longer names anything here. One
	// of them may be the repository that just moved.
	Unresolved []localproject.Summary
}

ProjectReport is what a surface says about a resolution the user did not ask for and would otherwise not notice.

func (ProjectReport) Empty

func (r ProjectReport) Empty() bool

Empty reports whether there is nothing worth saying.

func (ProjectReport) Lines

func (r ProjectReport) Lines(relinkCommand string) []string

Lines renders the report for a surface.

type PromptCapabilities

type PromptCapabilities struct {
	// Artifacts is true when this surface registered the artifact tool.
	Artifacts bool
}

PromptCapabilities are runtime facts that change what the agent should be told, as distinct from text a person authored.

A capability the surface does not have contributes nothing, so a session with no server is never told about a tool it does not have.

type RewindOutcome

type RewindOutcome struct {
	// Abandoned is the work the conversation no longer mentions and the world
	// still has. It counts the rewound message itself, which left the branch
	// with everything after it.
	Abandoned session.AbandonedWork
	// Prompt is the text of the rewound message, for the surface to put back
	// in its input box. Taking a prompt away without returning it would make
	// rewind destructive of the one thing the user meant to keep.
	Prompt string
	// Attachments counts images the rewound message carried. Only the text
	// comes back, so a surface that showed the prompt returning has to say
	// these did not.
	Attachments int
}

RewindOutcome is everything a surface needs after a rewind: what the move left in place, and what it has to hand back.

type RunPromptOpts

type RunPromptOpts struct {
	// Stream receives content deltas. Nil runs the LLM in blocking mode.
	Stream cllm.StreamSink
	// Approval resolves tool calls the policy sends to "ask". Nil collapses ask
	// to deny, which is what a surface with nobody to ask should do.
	Approval agent.ApprovalHandler
	// EventSink receives runtime events. Nil disables the caller's leg only — the
	// durable trace records the run either way.
	EventSink func(agent.Event)
	// Pending carries messages the user submits while this run is working. They
	// are appended to the history at the next iteration boundary instead of
	// waiting for the run to finish. Nil disables mid-run injection, leaving the
	// surface to drain its own queue between runs.
	Pending agent.PendingInput
	// Digest asks for a TurnDigest once the turn is done, returned on
	// RunResult.Digest. It costs one extra model call, so only a surface with
	// somewhere to show it sets this; see turn_digest.go for what it produces
	// and what keeps it from running on turns it could not describe.
	Digest bool
}

RunPromptOpts is the optional per-run wiring a surface supplies. The zero value is a valid non-interactive run: no streaming, no approvals, no events.

type RunResult

type RunResult struct {
	Reply                 string
	Duration              time.Duration
	ToolCalls             int
	PromptTokens          int
	CompletionTokens      int
	TotalPromptTokens     int
	TotalCompletionTokens int
	// Cache counts are the provider-reported cached parts of the prompt totals
	// beside them, not extra tokens. Zero means the provider reported none,
	// which is not the same fact as a cache miss.
	CacheReadTokens       int
	CacheWriteTokens      int
	TotalCacheReadTokens  int
	TotalCacheWriteTokens int
	// Cost is the session's estimated spend so far, nil when nothing in it
	// could be priced. It is the session total rather than this turn's,
	// because that is what the session file accumulates: a per-turn figure
	// would need rates that may have changed since the turn ran.
	Cost *cllm.Cost
	// CostIncomplete says part of the session could not be priced, so the
	// total above understates it.
	CostIncomplete bool
	ContextTokens  int
	ContextWindow  int
	SessionID      string
	Workspace      string
	ModelName      string
	// TraceID identifies the durable run trace written for this run, or "" when
	// tracing is disabled or failed to start. Points at
	// <DataDir>/traces/<session_id>/<trace_id>.jsonl.
	TraceID string
	// TracePath is that file's path on disk, or "" when no trace was written.
	// Callers that persist a reference to the trace use this instead of
	// rebuilding the layout from TraceID, so the stored path and the written
	// file cannot disagree.
	TracePath string
	// Digest is the after-the-turn account written for the user, empty unless
	// RunPromptOpts.Digest asked for one and the turn earned something to say.
	// It is not part of the conversation and never reaches the model again.
	Digest TurnDigest
}

type RunUsage

type RunUsage struct {
	ContextTokens         int `json:"context_tokens"`
	ContextWindow         int `json:"context_window"`
	PromptTokens          int `json:"prompt_tokens"`
	CompletionTokens      int `json:"completion_tokens"`
	TotalPromptTokens     int `json:"total_prompt_tokens"`
	TotalCompletionTokens int `json:"total_completion_tokens"`
	// Cache counts break the prompt counts beside them down; summing them with
	// the prompt total counts the same tokens twice.
	CacheReadTokens       int `json:"cache_read_tokens"`
	CacheWriteTokens      int `json:"cache_write_tokens"`
	TotalCacheReadTokens  int `json:"total_cache_read_tokens"`
	TotalCacheWriteTokens int `json:"total_cache_write_tokens"`
	// Cost is the session's estimated spend, absent when nothing could be
	// priced. CostIncomplete says the figure is missing part of the session.
	Cost           *cllm.Cost `json:"cost,omitempty"`
	CostIncomplete bool       `json:"cost_incomplete,omitempty"`
}

RunUsage is the current context occupancy and token/cost accounting for an agent run. Task lifecycle state is coretask.RunStatus; this type carries none.

type SandboxStatus

type SandboxStatus struct {
	Resolution   config.SandboxResolution
	Deps         sandbox.DepsReport
	Backend      string              // backend currently active ("bwrap", "seatbelt", "none")
	Enabled      bool                // SandboxView.Enabled() — false when backend unavailable
	Mode         string              // "auto_allow" | "regular" | "" when disabled
	ProxyAddress string              // in-process HTTP proxy address ("" when not running)
	ProxyAllows  uint64              // cumulative allow decisions since proxy start
	ProxyDenies  uint64              // cumulative deny decisions since proxy start
	Recent       []sandbox.Violation // latest entries from the violation store
}

SandboxStatus is the snapshot returned by AgentApp.SandboxStatus(), used by `buildmax sandbox status` / `deps`. Mirrors what is shown by Claude Code's /sandbox panel: resolved config, source chain, backend, deps.

type SessionContext

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

SessionContext is one open session: the writer that owns it, the branch reduced to a read model, and the runtime selections a turn needs.

It is the committing context §14 describes. Every change that a resumed turn would have to see goes through a method here and reaches the journal before the method returns; nothing mutates the read model on its own. A caller therefore cannot change resumable state without committing it, because there is no exported field to change.

A SessionContext with no writer is unpersisted: it accumulates in memory and commits nothing. That is what a throwaway session for an estimate or a one-shot run gets, and it is why every commit path checks for a nil writer rather than assuming one.

func NewSessionContext

func NewSessionContext(defaultModel string) *SessionContext

NewSessionContext returns an unpersisted session, for a run whose history nobody will resume.

func (*SessionContext) AbandonedBy

func (s *SessionContext) AbandonedBy(targetID string) (session.AbandonedWork, error)

AbandonedBy reports what happened after targetID, without changing anything.

It exists so a surface can show the consequence before the user commits to it. A fork asks it directly: the span it names is what the copy will not know about. A rewind asks RewindPreview instead, because the message it is about to take back belongs to the span it removes.

func (*SessionContext) AddCompaction

func (s *SessionContext) AddCompaction(summary string, summarizedCount int) error

AddCompaction advances the compaction boundary and stores the summary.

The record names the item it covers rather than a count, so the boundary stays meaningful on a branch: a count would be read against whatever messages a later reader happened to have.

func (*SessionContext) AddUsage

func (s *SessionContext) AddUsage(update session.MetaUpdate)

AddUsage folds one turn's usage and cost into the session's running totals. These live in metadata, so they do not touch the journal.

func (*SessionContext) AdditionalPrompt

func (s *SessionContext) AdditionalPrompt() string

AdditionalPrompt is the extra system-prompt text this session runs under.

func (*SessionContext) Append

func (s *SessionContext) Append(m llm.Message) error

Append commits one message and returns only once it is durable.

func (*SessionContext) AppendToolResult

func (s *SessionContext) AppendToolResult(out agent.ToolOutcome) error

AppendToolResult commits one call's outcome and projects it into the conversation as the tool-role message provider adapters expect.

func (*SessionContext) BeginTurn

func (s *SessionContext) BeginTurn(runID, model, workspace string, contextWindow int, inputKind string) error

BeginTurn opens a turn, recording the runtime identity it ran under. runID correlates the turn with its trace.

func (*SessionContext) CacheReadTokens

func (s *SessionContext) CacheReadTokens() int

func (*SessionContext) CacheWriteTokens

func (s *SessionContext) CacheWriteTokens() int

func (*SessionContext) Close

func (s *SessionContext) Close() error

Close releases the writer lock. Safe on an unpersisted session.

func (*SessionContext) Closed

func (s *SessionContext) Closed() bool

Closed reports whether this session has already been released, so a caller that closes early can let a deferred close stand without it firing twice.

func (*SessionContext) CompletionTokens

func (s *SessionContext) CompletionTokens() int

func (*SessionContext) Cost

func (s *SessionContext) Cost() *llm.Cost

func (*SessionContext) CostIncomplete

func (s *SessionContext) CostIncomplete() bool

func (*SessionContext) CreatedAt

func (s *SessionContext) CreatedAt() time.Time

func (*SessionContext) FinishTurn

func (s *SessionContext) FinishTurn(status, errorClass string) error

FinishTurn closes the turn with a terminal status. A turn closed here is not recovered on the next open, because a process that was alive to write this knew what it had done.

func (*SessionContext) HistoryMessages

func (s *SessionContext) HistoryMessages() []llm.Message

HistoryMessages returns the model-visible messages: the suffix after the compaction boundary.

func (*SessionContext) ID

func (s *SessionContext) ID() string

func (*SessionContext) MessageIDs

func (s *SessionContext) MessageIDs() []string

MessageIDs are the journal item ids behind Messages, positionally aligned, so a caller offering a rewind can name the item a message came from.

func (*SessionContext) Messages

func (s *SessionContext) Messages() []llm.Message

Messages returns the whole branch, compacted prefix included, for callers that summarise a session rather than send it to a model.

func (*SessionContext) Meta

func (s *SessionContext) Meta() session.Meta

func (*SessionContext) ModelName

func (s *SessionContext) ModelName(fallback string) string

ModelName returns the selected model for this session, or fallback.

func (*SessionContext) Notes

func (s *SessionContext) Notes() []agent.Note

func (*SessionContext) Persisted

func (s *SessionContext) Persisted() bool

Persisted reports whether this session commits anything.

func (*SessionContext) PriorSummary

func (s *SessionContext) PriorSummary() string

func (*SessionContext) PromptTokens

func (s *SessionContext) PromptTokens() int

func (*SessionContext) Recovery

func (s *SessionContext) Recovery() session.Recovery

Recovery is what the last open found interrupted, or the zero value when it found nothing to repair.

func (*SessionContext) Rewind

func (s *SessionContext) Rewind(messageID string) (RewindOutcome, error)

Rewind removes messageID and everything after it, and reports what it left in place.

It is exclusive: the message a person picks is the prompt they want to edit and send again, so it leaves the branch rather than staying at the end of it, and comes back in the outcome. session.RewindLanding decides which record the head then names.

The report is not optional decoration. Rewind returns the model's history to an earlier point and does not touch files, processes, or anything a network call reached (§8.1), so a caller that does not show the result is telling the user the opposite of what happened — and leaving the model to reason from a workspace picture that is no longer true. It is returned rather than logged for that reason: a caller has to receive it to ignore it.

The report is computed before the append, because afterwards the branch it describes is no longer the live one.

func (*SessionContext) RewindPreview

func (s *SessionContext) RewindPreview(messageID string) (session.AbandonedWork, error)

RewindPreview reports what rewinding messageID would remove, without doing it.

A choice made without knowing what it leaves behind is the thing §8.1 says rewind must not hide, so this answers the same question Rewind does, before the user commits to it rather than after.

func (*SessionContext) SetAdditionalPrompt

func (s *SessionContext) SetAdditionalPrompt(text string) error

SetAdditionalPrompt records the additional system prompt this turn resolved. It is durable state the next turn must see, so it commits.

func (*SessionContext) SetModel

func (s *SessionContext) SetModel(name string)

SetModel updates the selected model for the next turn. It is a current selection, so it changes metadata and appends nothing to history.

func (*SessionContext) SetNotes

func (s *SessionContext) SetNotes(notes []agent.Note, iter int) error

func (*SessionContext) SetTitle

func (s *SessionContext) SetTitle(title string)

SetTitle records a title. Presentation only, so metadata rather than history.

func (*SessionContext) SetTodos

func (s *SessionContext) SetTodos(todos []agent.Todo, iter int) error

func (*SessionContext) SetWorkspace

func (s *SessionContext) SetWorkspace(dir string)

SetWorkspace records where the next turn runs.

func (*SessionContext) Snapshot

func (s *SessionContext) Snapshot() session.Loaded

Snapshot is the live session as a Loaded value, for callers that summarise it. It reads the in-memory state rather than the file: a turn commits as it runs, but metadata lands at the end, so re-reading the bundle mid-turn would answer about the turn before the one on screen.

func (*SessionContext) Title

func (s *SessionContext) Title() string

func (*SessionContext) Todos

func (s *SessionContext) Todos() []agent.Todo

func (*SessionContext) ToolExecutionStarted

func (s *SessionContext) ToolExecutionStarted(calls []agent.ToolCallStart) error

ToolExecutionStarted records that approved calls are about to enter their tools. It commits before returning, which is the whole point: a record that arrived after the tool would not distinguish anything.

type SessionManager

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

SessionManager owns session lifecycle for AgentApp: creating, opening, listing, and the post-turn commit. It holds the policy about when state is committed; the store beneath it holds the durability.

func NewSessionManager

func NewSessionManager(dir string) *SessionManager

NewSessionManager returns a manager over the session bundles in dir. Sessions it creates belong to no local Project; a surface that has one says so with ForProject.

func (*SessionManager) Create

func (s *SessionManager) Create(defaultModel string) (*SessionContext, error)

Create makes a new session and opens it for writing. The caller owns the writer lock until it calls Close on the returned context.

func (*SessionManager) CreateSubagent

func (s *SessionManager) CreateSubagent(defaultModel string, lineage session.Meta) (*SessionContext, error)

CreateSubagent makes a hidden session for one subagent run, recording the lineage §9 requires: which session, run, and tool call delegated to it, what agent type it is, and how deep the delegation went.

Only the lineage fields of the argument are read; identity, kind, and timestamps are this method's to set, so a caller cannot accidentally create a visible session or reuse an id by filling in the wrong field.

func (*SessionManager) CreateWithID

func (s *SessionManager) CreateWithID(id, defaultModel string) (*SessionContext, error)

CreateWithID makes a session under an id chosen elsewhere and opens it.

A remote task run needs this: the server assigns the session id before the worker has written anything, so the worker cannot be the one to mint it.

func (*SessionManager) Delete

func (s *SessionManager) Delete(id string) error

Delete removes a session bundle: its journal, metadata, traces, and artifacts. Because it is destructive and irreversible, it names one session rather than matching a pattern.

func (*SessionManager) DeleteByProject

func (s *SessionManager) DeleteByProject(projectID string) ([]string, error)

DeleteByProject removes every visible session belonging to projectID, returning the ids it deleted.

Membership decides, not the path a session recorded. Matching on paths meant a session that had moved between spellings of one directory survived a clear that named it, and one started in a sibling directory could be taken by a clear that did not. An empty projectID deletes nothing: a session that belongs to no Project is not evidence of belonging to this one.

func (*SessionManager) Dir

func (s *SessionManager) Dir() string

Dir is the sessions root this manager writes under.

func (*SessionManager) Finalize

func (s *SessionManager) Finalize(ctx context.Context, client llm.LLMClient, sess *SessionContext, workspace string, stats agent.RunStats, pricing llm.Pricing) (TurnFinalizeResult, error)

Finalize runs the post-turn flow: fold this turn's usage into the session's totals, persist metadata, and generate a title if one is not set yet.

The conversation itself is already durable — every message, tool boundary and state change committed as it happened — so this writes metadata only, and a failure here loses reporting rather than the turn.

func (*SessionManager) ForProject

func (s *SessionManager) ForProject(projectID string) *SessionManager

ForProject returns a manager over the same sessions whose new ones belong to projectID.

It is a copy rather than a setter because the Project a session belongs to is immutable: a manager that could be repointed would make "which Project did this session start in" depend on when the question was asked.

func (*SessionManager) Fork

func (s *SessionManager) Fork(parent *SessionContext, throughItemID, defaultModel string) (*SessionContext, error)

Fork creates an independent session holding the parent's history through throughItemID, and returns it open for writing.

The parent must be open, which is how §12's "no forking from an unstable head" is enforced: holding its writer lock is what makes the branch being copied stand still. The parent is left untouched and still open — forking is not leaving.

The child is a copy, not a reference. That costs O(n) once and buys the properties §8.3 wants: deleting the parent cannot break the child, loading the child never walks another session, and retention stays session-local with no reference counting to get wrong.

func (*SessionManager) GenerateTitle

func (s *SessionManager) GenerateTitle(ctx context.Context, client llm.LLMClient, sess *SessionContext) (string, llm.Usage, error)

GenerateTitle asks the model for a short title from the opening exchange.

func (*SessionManager) List

func (s *SessionManager) List() ([]session.ItemSummary, error)

List returns the picker projection: user-visible sessions only.

func (*SessionManager) Load

func (s *SessionManager) Load(id string, mode session.LoadMode) (session.Loaded, error)

Load reads a session without taking the writer lock, for callers that only display it.

func (*SessionManager) Open

func (s *SessionManager) Open(id, defaultModel string) (*SessionContext, error)

Open acquires the writer lock for id and returns it as a committing context. A session already open in another process reports session.ErrLocked.

func (*SessionManager) ProjectID

func (s *SessionManager) ProjectID() string

ProjectID is the Project this manager's new sessions belong to, or "" when it has none.

func (*SessionManager) Read

func (s *SessionManager) Read(id, defaultModel string) (*SessionContext, error)

Read loads a session as a read model, without taking its writer lock.

It answers the same questions an open session does — its messages, their ids, what a rewind from here would leave behind — for surfaces that only ask them. Nothing it returns can be committed, which is why this is a separate call rather than a flag on Open: a caller cannot hold a read model and later discover it has been writing to nothing.

func (*SessionManager) Rename

func (s *SessionManager) Rename(id, title string) error

Rename records a new title. Presentation only, so it never touches history.

func (*SessionManager) SetPinned

func (s *SessionManager) SetPinned(id string, pinned bool) error

SetPinned records a pin. Presentation only.

func (*SessionManager) SetSessionModel

func (s *SessionManager) SetSessionModel(id, modelName string) error

SetSessionModel records the model a session runs under, without opening it for writing. Like Rename, it is a metadata change the next open reads back, so a model switched between turns takes effect on the following turn.

type SessionStats

type SessionStats struct {
	ID        string    `json:"id"`
	Title     string    `json:"title,omitempty"`
	Workspace string    `json:"workspace,omitempty"`
	CreatedAt time.Time `json:"created_at"`

	// Usage and Cost are the session's own accumulated totals.
	Usage llm.Usage `json:"usage"`
	Cost  *llm.Cost `json:"cost,omitempty"`
	// CostIncomplete says part of the session could not be priced, so Cost
	// understates it rather than covering it.
	CostIncomplete bool `json:"cost_incomplete,omitempty"`

	// Conversation is the shape of the stored history.
	Conversation session.ConversationStats `json:"conversation"`
	// Runs is the fold over this session's traces. Runs.Runs == 0 means no
	// trace was found, which is not the same as a session that never ran.
	Runs trace.SessionSummary `json:"runs"`
}

SessionStats is one session's statistics, assembled from the two records that hold them.

The session file is authoritative for tokens and money: it accumulated them turn by turn at the rates in force for each, and no later read can restate that. The traces are authoritative for everything time-shaped, and for the per-run detail the session file never kept — durations, denials, which model ran, how much a delegation did.

They are kept apart rather than merged into one flat number because they can legitimately disagree: a run that died before writing run_end is in the session's totals and missing from the trace fold, and a reader shown one blended figure would have no way to notice.

func LoadSessionStats

func LoadSessionStats(sessionsDir, id string) (SessionStats, error)

LoadSessionStats assembles one session's statistics. sessionsDir and tracesDir are the two roots; id names the session.

A missing trace directory is not an error — tracing is fail-open and nothing prunes it today, so its absence is a normal state that the returned Runs reports rather than a failure to load the session.

func NewSessionStats

func NewSessionStats(loaded session.Loaded, sessionsDir string) (SessionStats, error)

NewSessionStats assembles statistics for a session already in memory.

A surface holding the live session uses this rather than LoadSessionStats: a session is persisted after each assistant reply, so reading it back from disk mid-turn answers about the turn before the one on screen.

func (SessionStats) CacheSaved

func (s SessionStats) CacheSaved() (int64, bool)

CacheSaved is what prompt caching is estimated to have saved this session, and ok=false when nothing here was priced or caching cost more than it saved. Reporting a saving on a session that only ever wrote cache entries would be the false claim the whole cost path avoids.

func (SessionStats) ContextPeakShare

func (s SessionStats) ContextPeakShare() (float64, bool)

ContextPeakShare is how close the session came to its context window, and ok=false when the traces recorded no window to compare against.

func (SessionStats) ModelTime

func (s SessionStats) ModelTime() (time.Duration, bool)

ModelTime is the part of a session's wall clock that was not a tool call: model latency plus the loop's own work. ok is false when the traces did not measure enough to answer — no completed run, or tool time exceeding the wall clock, which parallel tool execution makes possible and which would turn into a negative answer.

type SkillRegistry

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

func (*SkillRegistry) Entries

func (s *SkillRegistry) Entries() []tools.SkillEntry

func (*SkillRegistry) Load

func (s *SkillRegistry) Load(workspace string, plugins []config.DiscoveredPlugin) error

func (*SkillRegistry) NewTool

func (s *SkillRegistry) NewTool() *tools.SkillTool

type SubAgentRegistry

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

func (*SubAgentRegistry) Definitions

func (s *SubAgentRegistry) Definitions() []subagent.Def

func (*SubAgentRegistry) Load

func (s *SubAgentRegistry) Load(workspace string, plugins []config.DiscoveredPlugin) error

type ToolEntry

type ToolEntry struct {
	Name        string
	Description string
	// Access is what the tool says the call does: "read-only" or "write".
	Access string
	// Action is what the call resolves to with no arguments and a human
	// present: "allow", "ask", or "deny". Argument-dependent tools can resolve
	// differently for a real call — Bash asks only for a risky command — so
	// this is the category answer, not a promise about every invocation.
	Action string
	// Source names where Action came from: "settings" or "derived".
	Source string
}

ToolEntry is a name+description pair for a tool available to the agent.

type TurnDigest

type TurnDigest struct {
	// Recap is a short account of what the turn did, for the user only.
	Recap string
	// Suggestion is the answer the user is likely about to give, written in
	// their voice. Empty unless the reply ended by asking them to decide.
	Suggestion string
}

TurnDigest is what the side call produced. Either field may be empty, which means the turn earned nothing to say there.

func (TurnDigest) Empty

func (d TurnDigest) Empty() bool

Empty reports whether the digest carries nothing worth showing.

type TurnFinalizeResult

type TurnFinalizeResult struct {
	Title            string
	PromptTokens     int
	CompletionTokens int
	CacheReadTokens  int
	CacheWriteTokens int
}

type TurnSummary

type TurnSummary struct {
	// Prompt is what started the turn: the user's message, or the rendered
	// background event for a turn nobody typed.
	Prompt string
	// Reply is the assistant's final text.
	Reply string
	// Transcript is this turn's messages, clipped per message and in order.
	Transcript string
	// ToolCalls is how many tool calls the turn made.
	ToolCalls int
}

TurnSummary is what one finished turn looked like, reduced to values.

It is captured inside the turn rather than read back from the session afterwards: the session has exactly one writer, and a surface that went back for the messages once the next turn had started would race it.

Directories

Path Synopsis
Package job owns the local background jobs of one AgentApp: identity, state, bounded output, stop, lifecycle events, and shutdown.
Package job owns the local background jobs of one AgentApp: identity, state, bounded output, stop, lifecycle events, and shutdown.
Package taskrun provides task-run execution.
Package taskrun provides task-run execution.

Jump to

Keyboard shortcuts

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