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
- Variables
- func CoordinatedReport(backend string, results []CoordinatedResult) string
- func FanoutReport(prompts []string, results []orchestrate.Result) string
- type Command
- type CoordinatedResult
- type Options
- type Session
- func (s *Session) AssembleContext(ctx context.Context, userMsg string, history []ports.Message) (Turn, error)
- func (s *Session) Close() error
- func (s *Session) Dispatch(ctx context.Context, input string) (output string, handled bool, err error)
- func (s *Session) Fanout(ctx context.Context, prompts []string) ([]orchestrate.Result, error)
- func (s *Session) FanoutCoordinated(ctx context.Context, tasks []SubagentTask) ([]CoordinatedResult, error)
- func (s *Session) MCPClients() []*mcp.Client
- func (s *Session) Run(ctx context.Context, userMsg string, history []ports.Message) ([]ports.Message, error)
- func (s *Session) SetPrompter(p policy.Prompter)
- type SubagentTask
- type Themer
- type Turn
Constants ¶
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.
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.
const DefaultKeepRecent = 6
DefaultKeepRecent is how many trailing messages survive compaction when KeepRecent is unset. Enough that the immediate exchange is never lost.
const DefaultMaxJudgeIterations = 3
DefaultMaxJudgeIterations is the default cap for judge consults.
const MemoryTopK = 5
MemoryTopK bounds how many memory chunks are retrieved per turn.
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.