sol

package module
v0.1.6 Latest Latest
Warning

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

Go to latest
Published: May 7, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

README

sol

Minimal LLM agent loop in Go — embeddable as a library, usable as a CLI utility composable with pipes, jq, and structured JSON output.

The core agent loop is derived from opencode (see NOTICE for attribution), but sol's scope is different: opencode is an interactive coding TUI, sol is a building block — something you call from your own Go code or stitch into shell pipelines.

[!WARNING] Alpha software. Early-stage code with bugs we haven't found yet. We use it ourselves but expect rough edges, especially around uncommon prompts or tool combinations. Please open an issue for anything that breaks.

Install

As a library:

go get github.com/airlockrun/sol

As a CLI:

go install github.com/airlockrun/sol/cmd/sol@latest

Requires Go 1.26+.

CLI

sol "summarize the contents of README.md"
sol -model gpt-4o -agent plan "outline a migration to postgres 17"
echo '{"task": "...", "context": "..."}' | sol < stdin > result.json

Flags:

  • -model — model name (default gpt-4o)
  • -agent — agent type: build, plan, explore, general (default build)
  • -session — session ID for prompt caching (default: auto-generated)

Output is structured for downstream tools (jq, etc.).

Library

The sol package is what airlock embeds for in-process agent execution. Run the agent loop directly, supply your own provider/tools/bus, and stream results into your application.

See cmd/sol/main.go and cmd/toolserver/main.go in this repo for full examples.

Scope

We accept contributions that improve sol's library API, scriptability, structured-output handling, and pipe-friendly UX. We don't accept changes that try to make sol re-converge with opencode's interactive TUI experience — that's not what sol is for. Use opencode if that's what you want. See CONTRIBUTING.md for details.

Companion projects

  • airlock (AGPL-3.0) — self-hosted cyborg agent platform that embeds sol
  • agentsdk (Apache-2.0) — Go SDK for building agents on airlock
  • goai (Apache-2.0) — Go port of the Vercel AI SDK

License

Apache-2.0. The opencode-derived portions are MIT and reproduced under NOTICE.

Contributing

See CONTRIBUTING.md and CODE_OF_CONDUCT.md. A CLA Assistant bot will prompt you to sign on your first PR (one signature covers all airlockrun projects).

Security

Email security@airlock.run. Do not open public issues for vulnerabilities.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildSwitchPrompt

func BuildSwitchPrompt() string

BuildSwitchPrompt returns the build switch prompt.

func ConnectMCPServers

func ConnectMCPServers(ctx context.Context, servers []MCPServer) (*mcp.Client, tool.Set, error)

ConnectMCPServers connects to the configured MCP servers via HTTP and returns the MCP client and the discovered tools. Tool schemas are normalized to match opencode's behavior. The caller must call client.DisconnectAll() when done.

func EnvironmentInfo

func EnvironmentInfo(workDir string) string

EnvironmentInfo returns environment information for agents with custom prompts.

func GenerateTitleAsync

func GenerateTitleAsync(ctx context.Context, userPrompt string, titleModel, apiKey, baseURL string) <-chan TitleResult

GenerateTitleAsync generates a title for the conversation asynchronously. It returns a channel that will receive the result.

func GetPromptForModel

func GetPromptForModel(modelID string) string

GetPromptForModel returns the provider-specific prompt name for logging/debugging.

func MaxStepsPrompt

func MaxStepsPrompt() string

MaxStepsPrompt returns the max steps warning prompt.

func PlanPrompt

func PlanPrompt() string

PlanPrompt returns the plan mode prompt.

func PlanReminderAnthropicPrompt

func PlanReminderAnthropicPrompt() string

PlanReminderAnthropicPrompt returns the Anthropic-specific plan reminder.

func SystemPrompt

func SystemPrompt(modelID, workDir string) string

SystemPrompt generates the system prompt for the agent. It selects the appropriate provider-specific prompt and appends environment info. modelID is the model identifier (e.g., "gpt-4o"), workDir is the working directory.

func TitlePrompt

func TitlePrompt() string

TitlePrompt returns the title generation prompt.

Types

type CompactResult

type CompactResult struct {
	// TokensFreed is the estimated delta between pre- and post-compaction
	// context size. Reported to the UI as the divider label.
	TokensFreed int

	// Summary is the text the model produced. Callers may surface it to the
	// user, log it, or ignore it.
	Summary string
}

CompactResult describes the outcome of a user-triggered compaction.

type CompactionState

type CompactionState struct {
	Messages []goai.Message `json:"messages"` // the compacted messages (excluding system prompt)
}

CompactionState captures the compacted messages when context overflow triggers compaction.

type ExitedRunResult

type ExitedRunResult struct {
	*RunResult

	// Exit holds whatever the agent passed to the exit tool. When the run
	// terminated for any other reason (provider error, ctx cancel, max
	// nudges exceeded), Exit.Called() returns false and the result-level
	// Status indicates the actual outcome.
	Exit *tools.ExitState

	// Nudges is the count of "you must call exit" re-drives sent before
	// the run terminated. 0 means the agent called exit (or errored) on
	// the first attempt.
	Nudges int
}

ExitedRunResult bundles the underlying RunResult with the agent-reported exit state and the number of nudges sent. Returned by RunUntilExit.

func (*ExitedRunResult) ExitCode

func (r *ExitedRunResult) ExitCode() int

ExitCode maps an ExitedRunResult onto a stable process exit code, suitable for sol-CLI / CI consumers that pipe sol invocations and want machine-readable outcomes.

0 — success: agent called exit{status:"success"}
1 — error:   agent called exit{status:"error"} (LLM-reported failure)
2 — no-exit: agent stopped without calling exit after all nudges
3 — run-failure: provider error, model panic, validation, etc.
4 — cancelled: ctx cancellation propagated

Stable wire contract — do not renumber.

type MCPServer

type MCPServer struct {
	// Name is a unique identifier for this server (used as tool name prefix).
	Name string

	// URL is the HTTP endpoint for the MCP server.
	URL string

	// Headers are HTTP headers (e.g., Authorization).
	Headers map[string]string
}

MCPServer configures an HTTP MCP server connection.

type RunResult

type RunResult struct {
	AgentName string        `json:"agentName"`
	TotalText string        `json:"totalText"`
	Steps     []*StepResult `json:"-"`
	Error     error         `json:"-"`

	Status            RunStatus          `json:"status"`
	Messages          []goai.Message     `json:"messages"`
	NewMessages       []goai.Message     `json:"newMessages,omitempty"`
	CompactionState   *CompactionState   `json:"compactionState,omitempty"`
	SuspensionContext *SuspensionContext `json:"suspensionContext,omitempty"`

	// Usage is the sum of every step's LLM usage over the run. InputTotal
	// and OutputTotal reflect total tokens billed for all API calls this
	// run made (tool loops included). Callers publishing billing / display
	// totals should read from here.
	Usage stream.Usage `json:"usage,omitempty"`
}

RunResult contains the results of an agent run.

func (*RunResult) GetTotalText

func (r *RunResult) GetTotalText() string

GetTotalText returns the total text output. This implements tools.SubagentResult interface.

type RunStatus

type RunStatus string

RunStatus represents the outcome of a run.

const (
	RunCompleted RunStatus = "completed"
	RunSuspended RunStatus = "suspended"
	RunFailed    RunStatus = "failed"
	RunCancelled RunStatus = "cancelled"
	// RunExited is set when the agent invoked the exit tool. The caller
	// reads RunnerOptions.ExitState to learn the agent-reported status
	// (success/error) and the accompanying message. Only emitted when the
	// runner was constructed with a non-nil ExitState.
	RunExited RunStatus = "exited"
)

type RunUntilExitOptions

type RunUntilExitOptions struct {
	// MaxNudges caps how many times the runner re-drives the agent with
	// a reminder when it stops without calling exit. Default 2 when zero.
	MaxNudges int

	// NudgeMessage is the user message appended on each nudge. A sane
	// default is used when empty.
	NudgeMessage string
}

RunUntilExitOptions configures the auto-nudge loop in RunUntilExit.

type Runner

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

Runner executes an agent session.

func NewRunner

func NewRunner(opts RunnerOptions) *Runner

NewRunner creates a new agent runner.

func (*Runner) AgentName

func (r *Runner) AgentName() string

AgentName returns the name of the current agent. This implements tools.SubagentSpawner interface.

func (*Runner) Bus

func (r *Runner) Bus() *bus.Bus

Bus returns the runner's scoped event bus.

func (*Runner) Compact

func (r *Runner) Compact(ctx context.Context) (*CompactResult, error)

Compact runs LLM-backed summarization without stepping the thinking loop. Used when the user explicitly asks for compaction (e.g. /compact) rather than letting the context-overflow path fire on its own. Requires a SessionStore — in-memory sessions have no one to persist the checkpoint to.

func (*Runner) Continue

func (r *Runner) Continue(ctx context.Context, prompt string) (*RunResult, error)

Continue adds a new prompt and continues the thread. Must be called after Run has been called at least once.

func (*Runner) PermissionManager

func (r *Runner) PermissionManager() *bus.PermissionManager

PermissionManager returns the runner's scoped permission manager.

func (*Runner) QuestionManager

func (r *Runner) QuestionManager() *bus.QuestionManager

QuestionManager returns the runner's scoped question manager.

func (*Runner) Run

func (r *Runner) Run(ctx context.Context, prompt string) (*RunResult, error)

Run executes the agent with the given prompt and returns the result.

func (*Runner) RunUntilExit

func (r *Runner) RunUntilExit(ctx context.Context, prompt string, opts RunUntilExitOptions) (*ExitedRunResult, error)

RunUntilExit runs the agent and re-drives it with a reminder if it stops without calling the exit tool. RunnerOptions.ExitState must have been set when constructing the runner. Errors (provider failures, ctx cancellation) surface immediately — only RunCompleted-without-exit triggers a nudge.

Intended for autonomous, non-interactive runs where the caller wants a structured outcome rather than parsing the model's text.

func (*Runner) SpawnSubagent

func (r *Runner) SpawnSubagent(ctx context.Context, agentName string, prompt string) (tools.SubagentResult, error)

SpawnSubagent creates and runs a subagent. This implements tools.SubagentSpawner interface.

type RunnerOptions

type RunnerOptions struct {
	// Agent defines the agent to run (model, tools, prompt, etc.).
	Agent *agent.Agent

	// APIKey is the API key for the provider implied by Agent.Model.
	APIKey string

	// BaseURL is an optional override for OpenAI-compatible endpoints.
	BaseURL string

	// WorkDir is the tool execution working directory.
	WorkDir string

	// Executor handles tool execution. When nil, goai falls back to
	// tool.NewLocalExecutor (tools run in-process). Set this to use remote
	// execution via containers.
	Executor tool.Executor

	// Bus is a scoped event bus for this runner. If nil, a new bus is created.
	Bus *bus.Bus

	// InitialMessages, when set, replaces the default [system, user] message
	// initialization in Run(). The prompt parameter becomes a new user message
	// appended at the end. If prompt is empty, no user message is appended.
	// Ignored when SessionStore is set.
	InitialMessages []goai.Message

	// SessionStore provides pluggable message persistence. When set, messages
	// are loaded from the store (instead of InitialMessages) and new messages
	// are persisted after each step. The store must be pre-scoped to a single
	// conversation by the caller.
	SessionStore session.SessionStore

	// CompactionConfig overrides the default compaction configuration.
	// Use this to set a custom PrunedMessage callback.
	CompactionConfig *session.CompactionConfig

	// Quiet suppresses logging output.
	Quiet bool

	// Model overrides the model instance. If nil, created from Agent.Model + APIKey.
	// Used for testing with mock models.
	Model stream.Model

	// ExitState, when set, opts the runner into "agent must call exit"
	// semantics. After every step the runner checks ExitState.Called() and,
	// if true, breaks the loop with RunResult.Status = RunExited. The exit
	// tool itself is auto-injected into the agent's tool set if not
	// already present, so callers only need to set this field.
	//
	// Intended for autonomous/non-interactive runs (CI, builds, sol-CLI).
	// Use RunUntilExit if you also want the runner to nudge the model when
	// it stops without calling exit.
	ExitState *tools.ExitState
}

RunnerOptions configures a new runner.

type StepResult

type StepResult struct {
	Text         string
	ToolCalls    []stream.ToolCall
	ToolResults  []stream.ToolResultEvent
	FinishReason stream.FinishReason
	Usage        stream.Usage
}

StepResult contains the results of a single step.

type SuspensionContext

type SuspensionContext struct {
	Reason           string                   `json:"reason"`
	Data             any                      `json:"data,omitempty"`
	PendingToolCalls []stream.ToolCall        `json:"pendingToolCalls,omitempty"`
	CompletedResults []stream.ToolResultEvent `json:"completedResults,omitempty"`
}

SuspensionContext captures state when a run is suspended.

type TitleResult

type TitleResult struct {
	Title string
	Error error
}

TitleResult holds the generated title and any error

Directories

Path Synopsis
Package agent provides the agent system for Sol, enabling multiple specialized agents with different capabilities and system prompts.
Package agent provides the agent system for Sol, enabling multiple specialized agents with different capabilities and system prompts.
Package bus provides a typed event bus for inter-component communication.
Package bus provides a typed event bus for inter-component communication.
cmd
sol command
Sol CLI - Minimal thinking loop in Go (matching OpenCode behavior)
Sol CLI - Minimal thinking loop in Go (matching OpenCode behavior)
toolserver command
Command toolserver runs Sol's tools in a container, serving requests via WebSocket.
Command toolserver runs Sol's tools in a container, serving requests via WebSocket.
Package executor provides remote tool execution capabilities.
Package executor provides remote tool execution capabilities.
Package provider implements the multi-provider system for sol.
Package provider implements the multi-provider system for sol.
Package session manages the agent session lifecycle including message history, token tracking, and context compaction.
Package session manages the agent session lifecycle including message history, token tracking, and context compaction.
Package tools provides the tool implementations for Sol.
Package tools provides the tool implementations for Sol.
Package toolutil provides utilities used by tools.
Package toolutil provides utilities used by tools.
Package webfetch provides URL fetching with HTML-to-markdown conversion.
Package webfetch provides URL fetching with HTML-to-markdown conversion.
Package websearch provides a web search client with multiple provider backends.
Package websearch provides a web search client with multiple provider backends.

Jump to

Keyboard shortcuts

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