Documentation
¶
Overview ¶
Approval broker — routes tool-approval prompts to whatever interface can actually answer them.
Mutating commands and tools prompt for approval (y/N/a). Historically that prompt was a terminal read (askLine), which works for the REPL but hangs headless (no terminal) and can't reach the web UI. The broker abstracts "ask the user to approve X" so it can be answered from the terminal OR the browser, depending on which front-end is driving.
- Terminal mode (default, plain REPL): prompts via askLine as before.
- Web mode (headless, or -serve-write when chosen): emits an EvApproval event the dashboard renders as Approve/Deny/Always buttons, and blocks on a response delivered via POST /api/approve.
Session budgets — a gentle ceiling on time and tokens.
The per-turn iteration cap stops ONE runaway turn. This stops the other failure mode: twenty reasonable turns that quietly add up to ninety minutes and a novel's worth of generated tokens before you notice. On a local box the cost isn't dollars, it's your afternoon and your GPU — so the budget is a WARNING, never a hard stop. Aborting a session mid-thought to even enforce a ceiling would be worse than the overrun.
Config: "budget_minutes" and/or "budget_ktokens" (thousands of generated + reasoning tokens — reasoning counts because on a reasoning model it's most of the spend). Zero/unset = that dimension is off. Crossing a threshold prints one warning; every further 50% prints a reminder. /budget shows current usage against the ceilings anytime.
Git checkpointing — repo-level protection where .bak is file-level.
Before every model turn, `git stash create` snapshots the working tree into an unreferenced commit WITHOUT touching the worktree or the stash list — the cheapest possible checkpoint. /rewind restores any turn's state with `git checkout <hash> -- .`.
Honest limitations, stated because they matter: `git stash create` only records TRACKED files, so a brand-new untracked file the model created after a checkpoint is not deleted by rewinding (its content is simply not managed); and the snapshot commits are unreferenced, so git gc can eventually collect them — checkpoints are session-scale protection, not history. /commit is the durable path, and its message generation enforces the Conventional Commits standard the system prompt already mandates.
Command tools — user-defined tools without recompiling.
MCP is the heavyweight plugin path (a running server, its own protocol); this is the lightweight one: drop a manifest in .agent/tools/<name>.json describing a tool and the executable that backs it, and it joins the registry at startup. Wrap a PowerShell deploy script, a curl one-liner, a Python analyzer — anything runnable — as a first-class tool the model can call, with typed arguments it can't typo.
.agent/tools/deploy_status.json
{
"name": "deploy_status",
"description": "Check the deploy status of a service in an environment.",
"command": ["pwsh", "-File", "scripts/DeployStatus.ps1"],
"parameters": {
"service": {"type": "string", "description": "Service name", "required": true},
"env": {"type": "string", "description": "dev|stage|prod"}
},
"read_only": true,
"timeout_sec": 60
}
The model's typed arguments are passed to the executable two ways, so the script can consume whichever is convenient: as --key value flags appended to the command, AND as a JSON object on stdin. The command runs in the workdir; stdout+stderr (capped) come back as the tool result.
Safety: read_only tools skip the approval prompt and may run in plan mode (declare it only for genuinely side-effect-free scripts). Everything else goes through the same y/N approval as run_command. Manifests are the user's own files under their repo — trusted like a Makefile, not like model output.
Custom slash commands — prompt templates as files.
Your recurring runbook-style prompts become commands: a markdown file at <repo>/.agent/commands/<name>.md (or ~/.agent/commands/<name>.md for personal ones) turns into /<name>. The file body is the prompt; the placeholder $ARGUMENTS is replaced with whatever follows the command:
.agent/commands/migrate.md: Migrate $ARGUMENTS to the reusable build workflow. Read the current workflow first, keep OTCOM versioning intact, and verify with a dry run. > /migrate proj5
Repo-local commands shadow personal ones of the same name. Built-in commands always win — a template can't override /verify. Discovered at startup; shown in /help and Tab-completed like any other command.
Shared slash-command dispatch for the read-only / informational commands.
The REPL historically handled every slash command inline in its input loop, printing directly. That's fine for the REPL but leaves the TUI unable to run any command (it has no access to that loop). This file extracts the SAFE subset — commands that only READ and report state — into runInfoCommand, which returns its output as a string instead of printing. The REPL prints the string; the TUI emits it to the bus. One source of truth, and the TUI gains real slash-command support for everything that doesn't mutate session state.
STATEFUL commands (/plan, /commit, /rewind, /compact, /fork, /reload, /model, /init, /undo, /allow, /ctx, /resume, /diff, /verify) are NOT here: they mutate messages/session/tree and are entangled with the REPL loop. Routing those through a shared dispatcher safely is a larger refactor; they remain REPL-only, and the TUI reports as much for them.
Context construction v2 — relevance over recency, opt-in.
v1 (the default) sends the whole transcript, append-only, and trims with pruning/shrinking/compaction. Its virtue is KV-cache prefix reuse: within and across turns the server reprocesses only new suffixes (~25s measured for a cold 13k prompt, near-zero warm). Its vice is accumulation: a long session drags every stale tool output behind it forever.
v2 REBUILDS the wire context at each turn boundary from what matters now:
[system prompt] [session state: modified files, checklist, elision note] [the session's original goal, if it scrolled out of the window] [recent dialogue: user messages + final assistant replies only] [the new user instruction]
Old tool outputs are deliberately absent — the model re-reads what it needs, and reads are cheap. The trade is a full prompt reprocess once per turn (bounded, small prompt) versus v1's growing-but-cached prompt. Which wins depends on session length and hardware; that's why this is a live toggle (/ctx v2, /ctx v1) and the per-turn cost prints dim — measure, then decide.
INVARIANTS the distiller guarantees: never emits tool-role messages or assistant messages carrying tool_calls (so the wire can't contain orphaned halves of a call/result pair), always includes the newest user message, and never touches the canonical transcript — sessions persist full fidelity regardless of mode. One honest cost: mid-turn crash persistence is weaker in v2 (the turn's messages land in the session file at turn end, not per-iteration).
A real diff engine — first-class change display.
The previous "diff preview" printed all old lines then all new lines: a six-line struct edit showed twelve lines of which eleven were identical noise, and the actual String→string change was a spot-the-difference puzzle. This produces proper unified-style hunks: unchanged context dim, removals red, additions green, and — for modified line pairs — the changed SPAN inside the line highlighted, so a one-token edit reads at a glance.
Used by: edit_file and write_file previews (terminal AND the tool result, so the model sees exactly what its edit did), and the /diff command (session changes per file, current content vs the pre-session .bak).
Algorithm: trim common prefix/suffix lines, LCS-align the middle when it's small enough (the overwhelmingly common case for tool edits — a single contiguous region), degrade to one replace-hunk when it isn't. O(n) for the trim, bounded DP for the middle, no dependencies.
One-shot mode (-p) and the eval harness (-eval).
One-shot makes the agent pipeable and scriptable:
agent -p "fix the failing test" -yes ~/proj && go test ./...
The eval harness is how harness changes stop being judged by vibes: a JSON file of cases, each a prompt plus a shell check, run N times against the current model and harness. "Did the loop breaker help" and "is Gemma better than Qwen at this" become pass rates instead of impressions.
Eval file format (JSON array):
[
{"name": "add-position", "prompt": "set the position column on insert in cmd/add.go",
"check": "go build ./... && grep -q position cmd/add.go"},
...
]
Each run gets a FRESH context (system prompt rebuilt, empty history) and a fresh Sandbox, but shares the working directory — checks that mutate state should clean up after themselves (e.g. via git checkout in the check).
stdout subscriber — renders bus events to the terminal.
This is the first (and during migration, primary) event subscriber. It reproduces the exact terminal styling the direct fmt.Println sites use, so when a print site is migrated from fmt.Println("x") to emitLine("x") the visible output is unchanged. The TUI and dashboard are alternate subscribers that will render the same events differently.
The color hint on an event maps to the same ANSI codes tint() uses. A subscriber may be silenced (the TUI installs itself and silences stdout, since it owns the screen).
In-place lines (EvOverwrite, e.g. a spinner or progress counter) are a special case: at most one is "open" at a time. Opening one erases and replaces whatever was there before; any OTHER event first erases it, the same way term.go's spinner erases itself the moment real output starts, so a fast-moving status line never leaves stale text mixed into the transcript.
A tool-calling coding agent in pure Go (stdlib only), speaking the OpenAI-compatible chat completions API. Works with LM Studio, llama.cpp's llama-server, vLLM, Ollama's /v1 endpoint, or any other OpenAI-compatible server.
v7 design philosophy — built for a capable model (Gemma 4 12B class):
- Trust the model: minimal system prompt (identity, workdir, hard rules), no sampling overrides, no iteration cap, no call-pattern babysitting.
- Guards are tripwires, not walls: backups, empty-write rejection, the repeat-call loop breaker, and y/N gating on mutating commands protect against consequences without distorting what the model attempts.
- Close the loop against reality: a configured verify command runs after any turn that modified files, and failures are fed back automatically. Hooks enforce user invariants (gofmt, lint gates) without model turns.
- Repo-level safety: a git checkpoint before every turn, /rewind to any of them, /commit with a model-written Conventional Commits message.
- Plan mode (/plan): read-only exploration → numbered plan → your approval → execution. Cheaper to review a plan than flailed edits.
- Context is managed, not just accumulated: stale reads are pruned and old tool outputs elided — in rare big batches, because history edits invalidate the server's KV-cache prefix (see session.go).
- Streaming: tokens print as they generate — no dead air.
Layout:
main.go flags, config, system prompt, REPL loop.go the agent loop (runTurn), verify loop, spawn_task tools.go tool registry + handlers, command approval session.go session persistence, /compact, context management api.go OpenAI-compatible types, streaming chat, SSE parsing mcp.go MCP client (stdio transport) — external tool servers eval.go one-shot mode (-p) and the eval harness (-eval) term.go stdin ownership, colors, spinner, input reading
Usage:
go run . [flags] [working-dir] go run . -url http://172.22.208.1:1235 -model google/gemma-4-12b ~/myproject go run . -p "fix the failing test" -yes ~/myproject (one-shot) go run . -eval evals.json -runs 3 ~/myproject (benchmark)
Flags:
-url base URL of the server (default: auto-detect Windows host in WSL2)
-model model id (default: first non-embedding model from GET /v1/models)
-resume resume a session: 'latest' or a name from /sessions
-p run one prompt non-interactively and exit
-yes auto-approve mutating commands and fetches (for -p / scripts)
-eval run an eval file (JSON array of {name,prompt,check}) and report
-runs runs per eval case (default 1)
~/.agent/config.json (all optional): url, model, compact_tokens, command_timeout_sec, verify_command, mcp_servers.
Input: single lines as usual; type """ alone to start a multi-line block (paste code freely), and """ alone again to send it.
Trace legend:
⚙ tool call requested by the model ✏ file written or edited (absolute path shown) ✗ write/edit rejected by a guard (nothing touched disk) $ command executed (auto-approved) or awaiting your y/N ⧉ subtask running in a fresh context ✓ verify command passed
MCP (Model Context Protocol) client — built on the official Go SDK (github.com/modelcontextprotocol/go-sdk/mcp).
This used to be a hand-rolled JSON-RPC client. It now delegates to the canonical SDK, which tracks the spec (protocol version negotiation, both transports, session management, notifications, cancellation) so we don't chase it by hand. The config surface and registry behavior are unchanged: each configured server's tools/list result is registered alongside the built-ins, calls proxy through as tools/call, and the model can't tell an MCP tool from a native one.
Config (~/.agent/config.json):
"mcp_servers": {
"pg": {"command": "npx", "args": ["-y", "@modelcontextprotocol/server-postgres", "postgres://..."]},
"engram": {"url": "http://localhost:8088/mcp/", "no_prefix": true, "prefer": true},
"sbx": {"command": "sandbox", "args": ["mcp", "-image", "python:3-alpine"], "prefer": true,
"prefer_hint": "a locked-down, network-less sandbox. Run untrusted or unfamiliar code here (sbx_run_script / sbx_run_sandbox) before running it on the host."}
}
Two transports: "command" (stdio child process) or "url" (Streamable HTTP). Tool names are prefixed with the server name (pg_query) unless "no_prefix" is set. "prefer" surfaces the server in the system prompt; "prefer_hint" overrides the default notes-server wording for non-notes servers (e.g. a sandbox).
Notifications — because at local token rates a turn can take minutes, and the worst UX in this agent is alt-tabbing away and missing a y/N prompt.
Two layers: a terminal BEL (\a — Windows Terminal flashes/badges the tab) and a Windows toast via powershell.exe interop, WSL's bridge to the host. The toast uses a NotifyIcon balloon — no modules to install, works on a stock Windows box. It's fired async and best-effort: a notification is never worth blocking the agent for, and never worth an error message.
Config: "notify_sec" — a turn longer than this notifies on completion (default 10; 0 disables everything including approval pings). Quick turns stay silent: you're still looking at the terminal anyway.
Prompt construction — every model-facing prompt string lives here, separate from the execution loop that decides WHEN to send it. Extracted from loop.go, session.go, context.go, and checkpoint.go as a pure text-building layer: no I/O, no globals mutated, explicit inputs. See docs/PHASE4_LOGIC_DECOUPLING.md for the rationale and rollout plan.
buildSystemPrompt (main.go) stays where it is — it was already its own function and moving it would be pure churn.
Named agent roles + auxiliary model routing.
ROLES — .agent/agents/<name>.md (repo) or ~/.agent/agents/<name>.md (personal; repo shadows personal) define specialists the model can delegate to via spawn_task's "role" argument:
.agent/agents/reviewer.md: model: qwen2.5-coder-14b-instruct You are a strict code reviewer. Point out bugs, race conditions, and deviations from the repo's conventions. Do not fix anything — report.
The optional "model: <id>" header routes the subtask to a different model on the same server; the rest of the file is appended to the subtask's system prompt. /agents lists what's loaded.
AUX MODEL — "aux_model" in config routes housekeeping (compaction summaries, session titles, commit messages) to a smaller/faster model. None of those need the 12B, and every VRAM-second they don't take is one the main model gets.
/stats — throughput made visible.
You're tuning VRAM, speculative decoding, and KV-cache behavior by feel; this turns feel into numbers. Everything here is measured at the harness (token counts are the same chars/4 estimate the context budget uses, so they're consistent with /context), because local servers are inconsistent about reporting usage on streamed responses.
The number that matters most on local hardware: TTFB (time to first byte of generation) ≈ prompt processing time. Append-only turns keep it small via KV-cache prefix reuse; anything that edited history (shrink, prune, compact, resume) shows up here as a spike. When /stats says the last request spent 90 seconds before the first token, that was the cache-invalidation bill.
Index ¶
- Constants
- Variables
- func AnswerWebApproval(id string, d ApprovalDecision) bool
- func ClosestLines(content, needle string) string
- func CompleteLine(line string, pos int, key rune) (string, int, bool)
- func ConfigPath() (string, error)
- func CurBaseURL() string
- func DeleteTool(name string)
- func ExecArgv(dir string, name string, args ...string) (string, int, error)
- func ExecShell(cmdStr, dir string, live bool) (string, int, error)
- func HTTPClient() *http.Client
- func Hooks() map[string]string
- func IsProtected(rel string) bool
- func MarkReadOnly(name string)
- func RebuildToolSchemas()
- func RegisterTools(ts ...Tool)
- func ResetRegistry()
- func RestoreRegistry(s RegistrySnapshot)
- func Run(opts Options) int
- func RunHook(name string, repl map[string]string) (string, bool)
- func RunInfoCommand(cmd, baseURL, model string, messages []Message, st *SessionStore) (string, bool)
- func SaveConfig(cfg Config) (backup string, err error)
- func SetActiveSessionStore(st *SessionStore)
- func SetApprovalMode(m approvalModeT)
- func SetApprovalWeb()
- func SetHooks(h map[string]string)
- func SetTodos(items []TodoItem)
- func SilenceStdout(silent bool)
- func Tail(s string, n int) string
- func UnregisterTools(names ...string)
- func VerifyCommand() string
- func WriteAtomic(path string, data []byte) error
- type ApprovalDecision
- type ChatError
- type ChatErrorKind
- type ChatRequest
- type Config
- type Event
- type EventKind
- type InputKind
- type MCPServerConfig
- type Message
- type Options
- type RegistrySnapshot
- type Sandbox
- type SessionStore
- func (st *SessionStore) Append(messages []Message)
- func (st *SessionStore) Fork(messages []Message) (string, error)
- func (st *SessionStore) Path() string
- func (st *SessionStore) Rotate()
- func (st *SessionStore) SessionSummaries() []SessionSummary
- func (st *SessionStore) SessionTranscript(name string) ([]Message, error)
- type SessionSummary
- type StatsSnapshot
- type Subscriber
- type SubscriberFunc
- type TodoItem
- type Tool
- type ToolArgs
- type ToolCall
Constants ¶
const ( EvLine = core.EvLine EvStatus = core.EvStatus EvError = core.EvError EvToken = core.EvToken EvThinking = core.EvThinking EvBusy = core.EvBusy EvUser = core.EvUser EvAssistant = core.EvAssistant EvToolCall = core.EvToolCall EvToolDone = core.EvToolDone EvStats = core.EvStats EvApproval = core.EvApproval EvOverwrite = core.EvOverwrite )
--- event-kind constants ---
const ( ApproveOnce = approveOnce ApproveAlways = approveAlways ApproveDeny = approveDeny )
Approval decision constants for the web UI.
Variables ¶
var ToolRegistrations []func()
ToolRegistrations are functions that register tools into the engine at startup. Tool packages append their registrar here (via cmd wiring) so the engine never imports them. Run invokes each after built-ins are registered.
var UseTTY = useTTY
Functions ¶
func AnswerWebApproval ¶
func AnswerWebApproval(id string, d ApprovalDecision) bool
AnswerWebApproval resolves a pending browser-routed approval. Returns true if the id matched a pending request.
func ClosestLines ¶
ClosestLines returns the lines in content most similar to needle (for edit rejection diagnostics).
func CompleteLine ¶
CompleteLine exposes the REPL tab-completer for the TUI input.
func ConfigPath ¶ added in v0.3.0
ConfigPath returns the absolute path to the agent config file (~/.agent/config.json), creating no files. It is the single source of truth for the config location, used by both the agent and the `config` command.
func ExecArgv ¶ added in v0.2.0
execShell runs a command via bash in dir with the shared timeout, killing the WHOLE process group on timeout — exec.CommandContext alone kills only bash itself, leaving grandchildren (a hung test binary, a dev server) running orphaned. With live=true, output streams to the terminal (dimmed) as it happens — no dead air during a two-minute build — while still being captured in full for the tool result. Returns combined output and the exit code; exitCode is -1 for timeouts and other non-exit errors. (Setpgid is Linux/WSL2-only, which is where this agent lives.) ExecArgv runs a command as a direct argv vector — NO shell — so arguments are passed verbatim and shell metacharacters (;, |, `, $(), &) in an argument are inert. Use this (not ExecShell) for tools that invoke a FIXED program with data arguments derived from model/user input (e.g. the git tools): it eliminates command injection by construction. Returns combined stdout+stderr, the exit code, and any error. The command timeout and the VetCommand backstop still apply.
func HTTPClient ¶
HTTPClient is the shared HTTP client (long timeout for model calls), exported for tool packages that make their own requests.
func IsProtected ¶
IsProtected reports whether a repo-relative path is protected from edits.
func MarkReadOnly ¶
func MarkReadOnly(name string)
MarkReadOnly flags a tool name as read-only (safe under plan mode, eligible for parallel execution).
func RebuildToolSchemas ¶
func RebuildToolSchemas()
RebuildToolSchemas regenerates the cached JSON schemas after tools are registered. Tool packages call this at the end of their registrar.
func RegisterTools ¶
func RegisterTools(ts ...Tool)
RegisterTools adds tools to the engine's registry (idempotent by name).
func ResetRegistry ¶
func ResetRegistry()
ResetRegistry clears the registry (tests set up a known state after).
func RestoreRegistry ¶
func RestoreRegistry(s RegistrySnapshot)
RestoreRegistry restores a previously captured registry state.
func Run ¶
Run is the agent entry point. It preserves the exact behavior of the former func main(): resolve config, apply globals, register tools, start MCP, then dispatch to eval / one-shot / TUI / headless / REPL. Returns a process exit code (0 = normal). The cmd/ layer populates Options and calls this.
func RunInfoCommand ¶
func RunInfoCommand(cmd, baseURL, model string, messages []Message, st *SessionStore) (string, bool)
runInfoCommand executes a read-only command and returns (output, handled). handled is false if the command isn't a known read-only one (the caller should then treat it as a stateful/REPL command). It needs a bit of context (model, messages, session) to render some reports.
func SaveConfig ¶ added in v0.3.0
SaveConfig writes cfg to ConfigPath as indented JSON, creating ~/.agent if needed. If the existing file contains keys the struct doesn't model (e.g. "// comment" keys or fields from another version), the current file is first copied to <path>.bak so a clean rewrite never silently drops data. Returns the backup path if one was made (else "").
func SetActiveSessionStore ¶ added in v0.2.0
func SetActiveSessionStore(st *SessionStore)
SetActiveSessionStore records the running agent's session store. Called once at startup.
func SetApprovalMode ¶
func SetApprovalMode(m approvalModeT)
setApprovalMode is called at startup once the front-end is known.
func SetApprovalWeb ¶
func SetApprovalWeb()
SetApprovalWeb routes tool approvals to the browser (headless mode).
func SetTodos ¶
func SetTodos(items []TodoItem)
SetTodos replaces the current todo list. Primarily for tests and headless setup; normal updates flow through the update_todos tool.
func SilenceStdout ¶
func SilenceStdout(silent bool)
SilenceStdout tells the stdout subscriber whether to suppress output (the TUI owns the screen and silences it).
func Tail ¶
tail returns the last n bytes of s (for showing the end of long output, where the actual error usually lives).
func UnregisterTools ¶
func UnregisterTools(names ...string)
UnregisterTools removes tools by name from the registry (used by tests and the tool-dedup path).
func VerifyCommand ¶
func VerifyCommand() string
VerifyCommand returns the configured post-turn verify command ("" if none).
func WriteAtomic ¶
WriteAtomic writes data to path atomically (temp + rename).
Types ¶
type ApprovalDecision ¶
type ApprovalDecision = approvalDecision
ApprovalDecision mirrors the internal approval outcome type for the web UI.
type ChatError ¶ added in v0.4.0
type ChatError struct {
Kind ChatErrorKind
Retryable bool
Status int // HTTP status code, 0 if not applicable
Err error // the underlying error
}
ChatError wraps a chat() failure with enough context for the caller to decide retryable vs. fatal without string-matching error messages.
type ChatErrorKind ¶ added in v0.4.0
type ChatErrorKind int
ChatErrorKind classifies why a chat request failed, so callers can decide whether retrying is worth it instead of treating every failure alike.
const ( ErrKindUnknown ChatErrorKind = iota ErrKindEncode // building the request failed — a code/config bug, not the network ErrKindConnection // dial/TLS/DNS failure reaching the server ErrKindTimeout // context deadline exceeded ErrKindClient // HTTP 4xx — the request itself is bad ErrKindRateLimit // HTTP 429 — bad timing, not a bad request ErrKindServer // HTTP 5xx — the server is unhealthy ErrKindStream // connection dropped mid-SSE-stream ErrKindModel // server reported an error object mid-stream (e.g. context length) )
type ChatRequest ¶
type ChatRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
Tools []map[string]any `json:"tools,omitempty"`
Stream bool `json:"stream"`
MaxTokens int `json:"max_tokens,omitempty"`
ReasoningEffort string `json:"reasoning_effort,omitempty"` // low|medium|high — attacks the measured ~90%-of-wall-time thinking cost
}
type Config ¶
type Config struct {
URL string `json:"url,omitempty"`
APIKey string `json:"api_key,omitempty"` // Bearer token for authenticated endpoints (cloud/proxied OpenAI-compatible); empty for local servers
Model string `json:"model,omitempty"`
CompactTokens int `json:"compact_tokens,omitempty"`
CommandTimeoutSec int `json:"command_timeout_sec,omitempty"` // run_command limit (default 300)
MaxTokens int `json:"max_tokens,omitempty"` // per-generation cap (default 8192)
MaxTurnIters int `json:"max_turn_iters,omitempty"` // hard per-turn tool-call budget (default 40)
Protected []string `json:"protected,omitempty"` // extra write-protected glob patterns (e.g. ".env", "secrets/*")
ContextV2 bool `json:"context_v2,omitempty"` // distilled per-turn wire context (see context.go) — experimental
BudgetMinutes int `json:"budget_minutes,omitempty"` // warn when a session exceeds this wall-clock (0 = off)
BudgetKTokens int `json:"budget_ktokens,omitempty"` // warn when generated+reasoning tokens exceed this many thousand (0 = off)
VerifyCommand string `json:"verify_command,omitempty"` // e.g. "go build ./... && go test ./..."
AuxModel string `json:"aux_model,omitempty"` // smaller model for compaction/titles/commit messages
PlanModel string `json:"plan_model,omitempty"` // stronger model for /plan turns; falls back to the main model when unset
FastModel string `json:"fast_model,omitempty"` // cheaper/faster model for trivial follow-up turns (empty = always use the main model)
PriceInPerM float64 `json:"price_in_per_m,omitempty"` // USD per 1M input (prompt) tokens — enables session cost in /stats (0 = off, e.g. local)
PriceOutPerM float64 `json:"price_out_per_m,omitempty"` // USD per 1M output (generated+reasoning) tokens
EmbedModel string `json:"embed_model,omitempty"` // embedding model id — enables semantic code_search
ReasoningEffort string `json:"reasoning_effort,omitempty"` // low|medium|high — thinking budget for normal turns
PlanEffort string `json:"plan_reasoning_effort,omitempty"` // thinking budget for /plan turns (deep thinking earns its time there)
NotifySec *int `json:"notify_sec,omitempty"` // toast+bell for turns longer than this (default 10; 0 = off)
NoCheckpoints bool `json:"no_checkpoints,omitempty"` // disable per-turn git snapshots
Hooks map[string]string `json:"hooks,omitempty"` // post_edit ({file}), pre_command ({cmd}), post_turn
MCPServers map[string]MCPServerConfig `json:"mcp_servers,omitempty"`
MaxFixAttempts int `json:"max_fix_attempts,omitempty"` // verify-loop fix retries (default 2)
ChatRetryAttempts int `json:"chat_retry_attempts,omitempty"` // streamChat retry attempts on transient failure (default 2)
CapNudgeLimit int `json:"cap_nudge_limit,omitempty"` // cap-stall recovery nudges per turn (default 2)
ResumeTail int `json:"resume_tail,omitempty"` // messages restored by -resume (default 30)
SubtaskMaxDepth int `json:"subtask_max_depth,omitempty"` // subtask nesting cap (default 1)
ContextV2Budget int `json:"context_v2_budget,omitempty"` // v2 distilled-context token budget (default 6000)
ContextV2Window int `json:"context_v2_window,omitempty"` // v2 recent-dialogue message window (default 12)
}
~/.agent/config.json persists defaults so you don't pass flags every launch. Command-line flags always override the file. Every field optional.
func LoadConfigFrom ¶ added in v0.3.0
LoadConfigFrom reads and parses the config at ConfigPath. A missing file is not an error — it returns a zero Config and found=false. A malformed file is reported so the caller (the `config` command) can warn instead of silently discarding it, unlike the agent's startup path which tolerates it.
type InputKind ¶
type InputKind int
InputKind classifies a submitted line so any front-end (REPL, TUI, headless web) routes it the same way.
func ClassifyInput ¶
ClassifyInput determines how a submitted line should be handled.
type MCPServerConfig ¶
type MCPServerConfig struct {
// stdio transport: a child process speaking JSON-RPC on stdin/stdout.
Command string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
Env map[string]string `json:"env,omitempty"` // extra environment for the child (e.g. SEARXNG_URL, API keys)
// HTTP (Streamable HTTP) transport: a URL instead of a command.
URL string `json:"url,omitempty"`
Token string `json:"token,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Insecure bool `json:"insecure,omitempty"` // skip TLS verify (self-signed loopback certs)
NoPrefix bool `json:"no_prefix,omitempty"` // register tools under their own names
Prefer bool `json:"prefer,omitempty"` // steer the model to this server in the system prompt
// PreferHint overrides the default (notes-server) steering text used when
// Prefer is set, so a non-notes server (e.g. a code sandbox) can describe
// how the model should use it. Empty = the default knowledge/notes wording.
PreferHint string `json:"prefer_hint,omitempty"`
}
type Message ¶
--- core data types (moved to core) ---
func Compact ¶
func Compact(baseURL, model string, messages []Message, st *SessionStore) []Message
compact distills the conversation into a summary written by the model itself, then restarts the context (and the session file) from that summary. The full transcript remains on disk in the rotated-out file. This resets the KV-cache prefix — the first request after a compact reprocesses from scratch, which is the one-time price of a small context.
func RunTurn ¶
func RunTurn(baseURL, model string, sb *Sandbox, st *SessionStore, messages []Message) []Message
runTurn is the agent loop: send the conversation, stream the reply, execute any tool calls (read-only ones concurrently), append results, and repeat until the model answers with plain text. No iteration cap by design — the loop breaker and the user's Ctrl+C are the exits.
func RunVerifyLoop ¶
func RunVerifyLoop(baseURL, model string, sb *Sandbox, st *SessionStore, messages []Message, modifiedBefore int) []Message
runVerifyLoop closes the loop against ground truth: when a turn modified files and a verify command is configured, the HARNESS (not the model) runs it, and failures are fed back as a new turn — up to maxFixAttempts times. This converts "a model that edits files" into "a system that converges on working code": the model's claim of being done is checked, every time. If a fix attempt modifies nothing, retrying is pointless and control returns to the user immediately.
type Options ¶
type Options struct {
URL string // -url
Model string // -model
Resume string // -resume
Prompt string // -p (one-shot)
Yes bool // -yes
Eval string // -eval
ForceEval bool // -force-eval
Runs int // -runs
Serve string // -serve
ServeWrite bool // -serve-write
Tui bool // -tui
Headless bool // -headless
PositionalRoot string // optional workdir arg (was flag.Arg(0))
// Presentation hooks, supplied by the cmd layer so the engine never
// imports tui/web (which would create an import cycle). Any may be nil.
StartDashboard func(addr string)
RunTUI func(baseURL, model string, sb *Sandbox, st *SessionStore, messages []Message)
RunHeadless func(baseURL, model string, sb *Sandbox, st *SessionStore, messages []Message)
SetDashWrite func(bool)
}
Options carries the resolved CLI inputs from the cmd/ (Cobra) layer into the agent. It replaces the old flag.* globals; each field maps 1:1 to a former flag. PositionalRoot is the optional working-directory argument.
type RegistrySnapshot ¶
type RegistrySnapshot struct {
// contains filtered or unexported fields
}
RegistrySnapshot captures the tool registry state for save/restore in tests.
func SnapshotRegistry ¶
func SnapshotRegistry() RegistrySnapshot
SnapshotRegistry returns the current registry state.
type Sandbox ¶
type Sandbox struct {
Root string
Modified []string // absolute paths of every file written this session
}
func (*Sandbox) Backup ¶
Backup snapshots a file before modification (for undo). Exported for out-of-package tool handlers.
func (*Sandbox) Execute ¶
Execute dispatches a tool call by name through the registry. A panic in a handler is converted to an ERROR result instead of killing the process — the model sees the failure and the session (and its context) survives.
type SessionStore ¶
type SessionStore struct {
// contains filtered or unexported fields
}
func ActiveSessionStore ¶ added in v0.2.0
func ActiveSessionStore() *SessionStore
ActiveSessionStore returns the running agent's session store, or nil if none is set (e.g. persistence unavailable).
func NewSessionStore ¶
func NewSessionStore(root string) *SessionStore
NewSessionStore creates a session store rooted at the given directory.
func (*SessionStore) Append ¶
func (st *SessionStore) Append(messages []Message)
Append persists any messages beyond what's already on disk.
func (*SessionStore) Fork ¶
func (st *SessionStore) Fork(messages []Message) (string, error)
Fork starts a NEW session file seeded with the current conversation and switches persistence to it — the original file stays frozen where it is, so a risky direction can be explored and abandoned by /resume-ing the original. Returns the new session name.
func (*SessionStore) Path ¶
func (st *SessionStore) Path() string
func (*SessionStore) Rotate ¶
func (st *SessionStore) Rotate()
Rotate closes the current file so the next Append starts a fresh session (used by /compact so the compacted context begins a new transcript).
func (*SessionStore) SessionSummaries ¶ added in v0.2.0
func (st *SessionStore) SessionSummaries() []SessionSummary
SessionSummaries returns the persisted sessions for this store's directory, newest first — the data behind the dashboard's session browser. Returns nil when persistence is unavailable or there are no sessions.
func (*SessionStore) SessionTranscript ¶ added in v0.2.0
func (st *SessionStore) SessionTranscript(name string) ([]Message, error)
SessionTranscript returns the messages of a named session (without the .jsonl suffix) for display in the dashboard. The name is validated to be a bare session name — no path separators — so it can't escape the store dir.
type SessionSummary ¶ added in v0.2.0
type SessionSummary struct {
Name string `json:"name"` // session file name without .jsonl
Title string `json:"title"` // model-written title, or a prompt excerpt
Messages int `json:"messages"` // message count
Latest bool `json:"latest"` // true for the newest session
}
SessionSummary is a structured, exported view of one persisted session for surfaces outside the agent package (the web dashboard). It mirrors what the CLI's `/sessions` list shows, but as data instead of printed text.
type StatsSnapshot ¶
type StatsSnapshot struct {
Requests int
PromptTk int
GenTk int
ThinkTk int
LastTTFBms int64
CostUSD float64 // estimated session cost; 0 when pricing isn't configured
}
StatsSnapshot is an immutable view of the cumulative request stats, safe to read from another package without touching the recorder's lock.
func Stats ¶
func Stats() StatsSnapshot
Stats returns a locked snapshot of the current stats recorder.
type Subscriber ¶
type Subscriber = core.Subscriber
type SubscriberFunc ¶
type SubscriberFunc = core.SubscriberFunc
type Tool ¶
type Tool struct {
Name string
Desc string
Props map[string]any
Required []string
// RawSchema, when set, is used verbatim as the tool's JSON-schema
// parameters (MCP servers ship their own); Props/Required are ignored.
RawSchema map[string]any
Handler func(s *Sandbox, args toolArgs) string
}
func LookupTool ¶
LookupTool returns a registered tool by name.