runtime

package
v0.0.1-alpha.2 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2026 License: Apache-2.0 Imports: 30 Imported by: 0

Documentation

Overview

Package runtime is the composition root (change 0002): it assembles the subsystems built in change 0001 into a live session.

The runtime owns no behavior. It resolves configuration, picks adapters, and hands ports to the agent loop — every decision about *how* something works stays in the package that owns it. That is what keeps this from becoming a god object as more subsystems come online.

Index

Constants

View Source
const (
	// DefaultBudget is the assembled-context token ceiling when unset.
	DefaultBudget = 120_000
	// DefaultWindow is the assumed context window when unset.
	DefaultWindow = 200_000
	// DefaultMemoryPath is where the memory database lives when unset.
	DefaultMemoryPath = ".openplus/memory.db"
	// DefaultBaseSystemPrompt is used when the caller passes none.
	DefaultBaseSystemPrompt = "You are OpenPlus, a coding agent."
	// MaxAutoSkills bounds how many skills auto-load into one turn.
	MaxAutoSkills = 3
)

Defaults for values a project need not configure.

View Source
const (
	// DefaultMaxSubagents caps how many subagents one fan-out may launch.
	DefaultMaxSubagents = 8
	// DefaultMaxSubagentParallel caps how many run at once.
	DefaultMaxSubagentParallel = 4
)

Bounds on fan-out. Each subagent is a full agent turn against the provider, so these are cost controls, not just resource ones.

View Source
const DefaultKeepRecent = 6

DefaultKeepRecent is how many trailing messages survive compaction when KeepRecent is unset. Enough that the immediate exchange is never lost.

View Source
const DefaultMaxJudgeIterations = 3

DefaultMaxJudgeIterations is the default cap for judge consults.

View Source
const MemoryTopK = 5

MemoryTopK bounds how many memory chunks are retrieved per turn.

View Source
const PreviousPlaceholder = "{{previous}}"

PreviousPlaceholder is substituted in a phase prompt with the previous phase's output. It is the whole of the hand-off vocabulary: one explicit token, so a prompt without it is passed through verbatim and a reader can see at a glance which phases depend on their predecessor.

View Source
const SummaryCap = 8000

SummaryCap bounds the checkpoint summary in characters. The summary is the transcript verbatim (no model call, no editorial selection), so it needs a ceiling to stay a checkpoint rather than a second copy of the session.

Variables

View Source
var (
	// ErrNoModel means no model was configured or passed.
	ErrNoModel = errors.New("runtime: no model configured")
	// ErrMissingCredential means the selected provider has no resolvable API
	// key and is not a local endpoint.
	ErrMissingCredential = errors.New("runtime: provider credential missing")
)

Sentinel errors from assembly.

Functions

func CoordinatedReport

func CoordinatedReport(backend string, results []CoordinatedResult) string

CoordinatedReport renders coordinated results. It leads with the backend and the fact that this mode commits, because unlike every other path in OpenPlus a coordinated fan-out writes history to the user's repository.

func FanoutReport

func FanoutReport(prompts []string, results []orchestrate.Result) string

FanoutReport renders fan-out results for a user, in input order, marking failures rather than hiding them.

Types

type Command

type Command struct {
	Name    string
	Usage   string
	Summary string
	Run     func(s *Session, args string) (string, error)
}

Command is one slash command. Run takes the session and the raw argument string (everything after the command name, trimmed at the ends only) and returns text to show the user.

Commands return text rather than printing it, so the same command works in the TUI and the one-shot path without either front-end knowing about the other.

type CoordinatedResult

type CoordinatedResult struct {
	ID     string
	Prompt string
	Output string
	Err    error

	Merged        bool
	Blocked       bool
	BlockedBy     string
	BlockedSymbol string
}

CoordinatedResult is one coordinated subagent's outcome. Blocked, failed, and merged are distinct states: a blocked subagent never ran, a failed one ran and errored, and only a merged one changed the codebase.

type Options

type Options struct {
	// Model overrides the configured model ("<provider>/<model>").
	Model string
	// SkipPermissions applies --dangerously-skip-permissions: an allow-all base
	// where explicit rules still win.
	SkipPermissions bool
	// Fake uses the scripted fake provider, so the binary runs with no
	// credential (the offline smoke path).
	Fake bool
	// BaseSystemPrompt precedes the project instructions.
	BaseSystemPrompt string
	// ConfigPath overrides the default <root>/opencode.json. Empty means
	// use the default. Used by --config / -c in the CLI.
	ConfigPath string
	// Goal, when non-empty, makes Run consult Session.Judge after the
	// agent loop returns. Empty Goal skips the judge entirely. Used by
	// --goal in the CLI (T-440..T-445).
	Goal string
	// Judge is the optional goal / stop-condition evaluator (ADR-0006).
	// Nil disables judging even when Goal is set, preserving pre-0007
	// behavior for callers that want a goal field but no judge yet.
	Judge *orchestrate.Judge
	// MaxJudgeIterations caps the number of judge consults in a single
	// Run when the judge keeps replying UNMET. Zero or negative falls
	// back to DefaultMaxJudgeIterations (3).
	MaxJudgeIterations int
}

Options are the operator-supplied knobs, overriding configuration.

type Session

type Session struct {
	Root  string
	Model string

	Config       *config.Config
	SystemPrompt string

	Provider    ports.Provider
	Tools       *tool.Registry
	ToolSchemas []ports.ToolSchema

	// Goal is the stop-condition text (Change 0007). Empty disables
	// judging; Run then terminates when the agent's tool-call count hits
	// zero (the pre-0007 behavior).
	Goal string
	// Judge is the optional goal / stop-condition evaluator (ADR-0006).
	// When Goal is non-empty and Judge is non-nil, Run consults Judge
	// after the agent loop returns. MET stops; UNMET appends feedback
	// to history and loops; the loop is bounded by MaxJudgeIterations.
	Judge *orchestrate.Judge
	// MaxJudgeIterations caps the judge loop. Zero or negative falls
	// back to DefaultMaxJudgeIterations (3).
	MaxJudgeIterations int

	// Gate authorizes tool calls. Until a Prompter is wired, an Ask rule
	// degrades to Deny — safe, but not interactive.
	Gate policy.Gate
	// Rules is the decision table behind Gate, exposed so a caller can inspect
	// what a rule decides independently of how Ask is resolved.
	Rules *policy.Rules

	// Memory is nil when no embedder is configured.
	Memory   *memory.Store
	Embedder embed.Embedder

	// LanguageService answers code-intelligence questions (change 0026,
	// ADR-0017). Nil unless the lsp config is enabled with at least one server
	// — and always nil in a fake session, which must never spawn a process.
	LanguageService ports.LanguageService

	Skills   *skills.Index
	Budgeter contextmgr.Budgeter

	// Checkpointer writes and restores checkpoint.md (ADR-0008). Nil when no
	// context window is configured, which disables checkpointing end to end.
	Checkpointer *contextmgr.Checkpointer
	// Tasks is the task tree, restored from the checkpoint at assembly and
	// written back on every checkpoint (milestone subsystem #3).
	Tasks contextmgr.TaskTree

	// OnEvent and OnToolResult are the front-end render hooks, forwarded to the
	// agent loop on every Run. Nil means no rendering (the non-interactive path).
	OnEvent      func(ports.Event)
	OnToolResult func(call ports.ToolCall, result ports.Block)

	// OnCheckpointError reports a failed checkpoint write. The turn itself
	// succeeded, but the session is no longer durable, so the operator needs to
	// know. Nil drops the report rather than failing the turn.
	OnCheckpointError func(error)

	// KeepRecent bounds how many trailing messages survive compaction
	// (change 0010). Zero uses DefaultKeepRecent.
	KeepRecent int

	// MaxSubagents caps how many subagents one fan-out may launch, and
	// MaxSubagentParallel how many run at once (change 0011). Each subagent is a
	// full model turn, so these are cost controls. Zero uses the defaults.
	MaxSubagents        int
	MaxSubagentParallel int
	// OnSubagentDir reports the isolated directory a subagent is running in
	// (empty when running in place). Nil is a no-op.
	OnSubagentDir func(dir string)

	// Workflows holds the registered deterministic workflows (ADR-0006),
	// keyed by name and invoked with /workflow.
	Workflows map[string]orchestrate.Workflow

	// Coordinator locks code symbols before subagents edit them (change 0012).
	// Defaults to NoCoordinator, so coordinated fan-out is unavailable until a
	// real coordinator (grit) is wired and installed.
	Coordinator orchestrate.Coordinator
	// OnCompact reports a compaction as (before, after) message counts, so a
	// front-end can tell the user rather than the context shrinking invisibly.
	// Nil is a no-op.
	OnCompact func(before, after int)

	// Memo is the file-based memory surface: MEMORY.md, notes.md, and
	// tasks/<id>/progress.md under the project root (ADR-0002 #1). /dream
	// appends extracted facts here.
	Memo memo.Files

	// Compose is the active compose session, nil until /compose starts one
	// (ADR-0002 #6). It lives for the process; persisting a phase machine across
	// invocations is deliberately out of scope for change 0009.
	Compose *compose.Session

	// History accumulates this session's turns, so /dream has a transcript to
	// extract from and /distill has runs to mine.
	History []ports.Message
	// Runs records each turn's tool sequence for /distill.
	Runs []improve.Run

	// MaxSamples is the default N for /max (change 0016). Zero uses
	// orchestrate.DefaultSamples; over-cap values are clamped at use.
	MaxSamples int
	// MaxModel is the judge model /max ranks with. Empty judges with the
	// session's own model.
	MaxModel string

	// ConfigPath is the resolved opencode.json this session was loaded from,
	// whether or not the file exists. /theme persists into it.
	ConfigPath string

	// Theme is the attached front-end's palette control (change 0017). Nil when
	// no front-end is attached, which makes /theme report that rather than
	// pretending to switch — theming is a front-end capability.
	Theme Themer

	// MCPWarnings records each declared MCP server that could not be used, by
	// name (change 0015). A broken server is skipped rather than fatal, so the
	// front-end must surface these — otherwise its tools go missing silently.
	MCPWarnings []string
	// contains filtered or unexported fields
}

Session is an assembled, ready-to-run agent session. Fields are ports, not concrete adapters, except where a caller legitimately needs the concrete type (Memory, for its lifecycle).

func Assemble

func Assemble(root string, opts Options) (*Session, error)

Assemble builds a Session from a project root. It fails rather than degrade: a missing credential, an unknown model prefix, or an unreadable project is an error, not a silently reduced session.

func (*Session) AssembleContext

func (s *Session) AssembleContext(ctx context.Context, userMsg string, history []ports.Message) (Turn, error)

AssembleContext builds the context for one turn (ADR-0008): it retrieves relevant memory, auto-loads relevant skills, budgets the result in priority order, and returns the system prompt plus the history to send.

Retrieval failures are not fatal. Memory and skills are enrichment — losing them degrades the answer, whereas refusing the turn loses it entirely.

func (*Session) Close

func (s *Session) Close() error

Close releases the session's resources: the memory store and every MCP server it started. Safe to call when memory was never opened, and idempotent.

Both are closed even if the first fails — a leaked subprocess or an unflushed store is worse than a lost error message, so only the first error is returned.

func (*Session) Dispatch

func (s *Session) Dispatch(ctx context.Context, input string) (output string, handled bool, err error)

Dispatch runs input as a command when it begins with "/".

handled reports whether input was a command *attempt*: it is false only for input the caller should run as a normal turn. An unknown command is handled (with an error) rather than falling through, because silently sending "/typo" to the model is worse than saying the command does not exist.

func (*Session) Fanout

func (s *Session) Fanout(ctx context.Context, prompts []string) ([]orchestrate.Result, error)

Fanout runs each prompt as a parallel subagent, isolated in its own git worktree when the project is a repo, and returns the results in input order (ADR-0002 #4).

A subagent's failure is carried in its own Result and never aborts its siblings — losing three good answers because a fourth failed would be worse than reporting the one failure.

func (*Session) FanoutCoordinated

func (s *Session) FanoutCoordinated(ctx context.Context, tasks []SubagentTask) ([]CoordinatedResult, error)

FanoutCoordinated claims each task's symbols, runs the granted subagents in their coordinated worktrees, and merges each on success (change 0012, via grit's claim→work→done model).

Blocked tasks are reported and not run. Running a subagent whose claim was refused would produce work that must then be thrown away — the waste coordination is meant to eliminate.

func (*Session) MCPClients

func (s *Session) MCPClients() []*mcp.Client

MCPClients returns the connected MCP clients (diagnostics and tests).

func (*Session) Run

func (s *Session) Run(ctx context.Context, userMsg string, history []ports.Message) ([]ports.Message, error)

Run assembles context and drives one agent loop to completion, returning the resulting history. When memory is configured the exchange is persisted so a later session can retrieve it.

When Session.Goal is non-empty AND Session.Judge is non-nil, Run consults the judge after the agent loop returns (Change 0007 / T-440..T-445). MET stops; UNMET appends the judge's feedback to history and re-runs the agent loop. The loop is bounded by Session.MaxJudgeIterations (default DefaultMaxJudgeIterations = 3) so an unsatisfiable goal can't run forever.

func (*Session) SetPrompter

func (s *Session) SetPrompter(p policy.Prompter)

SetPrompter wires an interactive prompter so Ask decisions can be resolved by the operator instead of degrading to Deny. It is a no-op under --dangerously-skip-permissions, where nothing prompts by design.

type SubagentTask

type SubagentTask struct {
	Prompt  string
	Symbols []string
}

SubagentTask is one coordinated subagent: what to do, and which code symbols it will edit.

Symbols are stated by the caller, never inferred from the prompt. A wrong guess would claim locks the subagent does not need — blocking other agents — or miss ones it does, which is the conflict coordination exists to prevent.

type Themer

type Themer interface {
	// ThemeNames lists the selectable palettes, in presentation order.
	ThemeNames() []string
	// Theme reports the active palette's name.
	Theme() string
	// SetTheme switches the active palette. An unknown name is an error.
	SetTheme(name string) error
}

Themer is the front-end theme seam (change 0017, ADR-0012). internal/tui implements it; the runtime depends on this interface so no front-end type reaches the session.

type Turn

type Turn struct {
	System  string
	History []ports.Message
	// Used is the budgeter's estimate of the assembled context's token cost.
	// It is what the checkpoint high-water decision is measured against.
	Used int
}

Turn is the assembled context for one turn: the system prompt the model will see, and the message history to send with it.

Jump to

Keyboard shortcuts

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