tools

package
v1.5.2 Latest Latest
Warning

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

Go to latest
Published: Jul 1, 2026 License: MIT Imports: 39 Imported by: 0

Documentation

Overview

Package tools provides built-in tool implementations for the octo agentic loop. Each tool implements agent.ToolExecutor and exposes a Definition() method that returns the agent.ToolDefinition the LLM sees.

Index

Constants

View Source
const AsyncModeNotice = "" /* 396-byte string literal not displayed */

AsyncModeNotice is the model-facing instruction appended to an async background-launch tool result. Wrapped in <system-reminder> so StripRemindersForDisplay strips it from UI cards (TUI and web).

View Source
const GlobMaxResults = 200

GlobMaxResults caps the number of paths a single glob call returns. The LLM rarely needs more than this, and a missing cap could surface tens of thousands of paths on large repos (node_modules, vendor/) and blow up context.

View Source
const GrepMaxLines = 200

GrepMaxLines caps how many output lines a single grep call returns. Without it a broad pattern (e.g. `func`) on a large repo floods the LLM context with thousands of hits. Past the cap the output is truncated with a marker naming the total, so the model narrows the pattern instead of assuming it saw everything. Mirrors GlobMaxResults' role for glob.

View Source
const InteractiveModeNotice = "" /* 359-byte string literal not displayed */

InteractiveModeNotice is the model-facing instruction appended to an interactive background-launch tool result. Wrapped in <system-reminder> so UI card renderers strip it automatically.

View Source
const JinaReaderHost = "https://r.jina.ai/"

JinaReaderHost is the public Jina AI Reader endpoint. Sending a URL via `https://r.jina.ai/<URL>` returns the page rendered to Markdown — handles JavaScript-rendered pages, paywalls (where Jina has access), and most of the noisy chrome a raw curl would dump on the LLM.

View Source
const MaxLoopLifetime = 12 * time.Hour

MaxLoopLifetime bounds how long an in-session loop may keep ticking, measured from its first wakeup. A runaway loop — the model re-arming forever, or an interval loop nobody stops — must not tick indefinitely, especially on the server (web/IM) where no one watches it spend tokens. All three surfaces (TUI, web, IM) enforce this same bound via LoopExpired: once a loop has run this long it stops instead of re-arming. For a schedule that must outlive this, use a persistent cron task (cron-task-creator) instead.

View Source
const ReadFileImageMaxBytes = 5 * 1024 * 1024 // 5 MB

ReadFileImageMaxBytes is the maximum image file size read_file will embed for multimodal models. Past this the user gets an error suggesting they resize or compress the image.

View Source
const ReadFileMaxLines = 2000

ReadFileMaxLines caps how many lines a single read_file call returns. Past this, the caller must paginate via offset/limit. Matches the cap Claude Code's Read tool uses (helps keep LLM context sane).

View Source
const TerminalSpillBytes = 16 * 1024

TerminalSpillBytes is the size past which terminal output is written to a temp file instead of being returned to the LLM inline. ~16 KB (~4k tokens) is comfortably "too long" for a single tool result; below it the output is handed back unchanged.

View Source
const WebFetchInlineBytes = 64 * 1024

WebFetchInlineBytes is the size up to which a fetched body is returned inline. Larger responses (up to WebFetchMaxBytes) are written to a temp file and summarised with an outline + head preview, so a big page never floods the model's context while its full content stays one read_file away.

View Source
const WebFetchMaxBytes = 5 * 1024 * 1024 // 5 MB

WebFetchMaxBytes is the absolute ceiling on a single fetched body, whether returned inline or spilled to a temp file. It bounds memory and disk for a pathological response; past it the body is truncated with a clear marker. Set well above any real page so the spilled file holds the full content in practice.

View Source
const WebSearchDefaultMax = 5

WebSearchDefaultMax is the default number of results returned per search. Most tasks need ≤10; capped at 20 to keep responses small.

View Source
const WebSearchHardMax = 20

WebSearchHardMax is the upper bound for max_results regardless of what the caller asks for.

Variables

View Source
var TerminalTimeout = 120 * time.Second

TerminalTimeout is the maximum time a single terminal command may run synchronously before it is automatically promoted to a background process.

Functions

func ActiveMCPRegistry

func ActiveMCPRegistry() *mcp.Registry

ActiveMCPRegistry returns the registered registry or nil if MCP is off.

func ArtifactContentType

func ArtifactContentType(path string) (ctype string, ok bool)

ArtifactContentType returns the Content-Type for a previewable artifact path, or ok=false when the extension isn't previewable.

func BgCommand

func BgCommand(id string) (string, bool)

BgCommand returns the original command string for a background process id on the default manager, or ("", false) if unknown.

func BrowserHealerSet

func BrowserHealerSet() bool

BrowserHealerSet / BrowserSkillGeneratorSet report whether the LLM-backed browser helpers are wired (for tests/diagnostics).

func BrowserSkillGeneratorSet

func BrowserSkillGeneratorSet() bool

func BrowserSkillsDir

func BrowserSkillsDir() string

BrowserSkillsDir is where recorded browser skills live (editable YAML).

func BrowserVisionEnabled

func BrowserVisionEnabled() bool

BrowserVisionEnabled reports the current setting (for tests/diagnostics).

func CleanSpillFiles

func CleanSpillFiles()

CleanSpillFiles removes this process's spill files. Wire it into session shutdown next to KillAllBackground so a normal exit leaves no leftovers.

func CloseSessionBackgroundManager

func CloseSessionBackgroundManager(id string)

CloseSessionBackgroundManager kills every process tracked for a session and drops its manager. Call when a session is deleted so its background daemons don't leak until daemon shutdown. No-op for an unknown id.

func CloseSessionSubAgentManager

func CloseSessionSubAgentManager(id string)

CloseSessionSubAgentManager kills every sub-agent tracked for a session and drops its manager. No-op for an unknown id.

func CloseSessionWorkflowManager

func CloseSessionWorkflowManager(id string)

CloseSessionWorkflowManager cancels every workflow tracked for a session and drops its manager. No-op for an unknown id.

func DefaultTools

func DefaultTools() []agent.ToolDefinition

DefaultTools returns the tool list with no model context — equivalent to DefaultToolsFor(""). MCP schemas are always uploaded in full (the Tool Search bridge needs the model to evaluate its auto threshold), so unmigrated callers keep the original behaviour.

func DefaultToolsFor

func DefaultToolsFor(model string) []agent.ToolDefinition

DefaultToolsFor returns the slice of ToolDefinitions sent to the LLM when `--tools` is on, for the given model. Order matches allTools. Each capability-gated tool is withheld unless the corresponding registration call has been made — SkillTool needs SetSkills, sub-agent tools need a SubAgentManager. Advertising a tool that can only error wastes a slot and confuses the model.

MCP surfaces ride alongside the built-ins. When Tool Search is active for this model (see toolSearchActive) the full per-tool catalog is replaced by the three search/describe/call bridge tools, so the model's tools array carries three small schemas instead of every MCP tool's schema every turn.

func DetectToolchain

func DetectToolchain() (present, missing []string)

DetectToolchain reports which curated developer tools resolve on the current PATH. Presence-only via exec.LookPath — a filesystem lookup, not a subprocess — so it is cheap enough to call on every context build, including the server's per-turn recompose. Versions are deliberately not probed; the agent runs `<tool> --version` on demand when it actually needs one. On Windows LookPath honours PATHEXT, so npm.cmd / npx.cmd resolve as expected.

func FormatBgNote

func FormatBgNote(e BgExit) string

FormatBgNote renders a background-process completion as a <system-reminder> block. It rides the steer path of whichever frontend wires it (CLI/TUI: Inbox.Enqueue; server: Inbox or steer queue; IM: the session agent's Inbox), so the model reads it as an environment event rather than user speech. Wrapping in <system-reminder> matches octo's convention for injected, non-user context — UIs strip these spans from user-visible text.

func FormatBgNoteWithSummary

func FormatBgNoteWithSummary(mgr *BackgroundManager, e BgExit) string

FormatBgNoteWithSummary renders a completion notice plus a summary of other background processes still running. The summary helps the model track in-flight async/interactive work without a dedicated process-list tool. Pass nil for mgr to omit the summary (e.g. unit tests that only verify the basic note format).

func FormatSubAgentNote

func FormatSubAgentNote(ev SubAgentNotification) string

FormatSubAgentNote renders a sub-agent completion notification as a <system-reminder> block. It rides the existing steer path (Agent.Steer → folded into the next tool_result, or prepended to the next turn — see turncore.go), so the model reads it as an environment event rather than user speech.

func FormatTaskList

func FormatTaskList(items []tasks.Task) string

FormatTaskList renders a slice of tasks for display. Used both by the task_list tool's Execute and by the REPL's /tasks slash command.

Layout: a one-line header followed by one row per task, status-prefixed so the user can scan at a glance:

Tasks: 1 in progress, 2 pending
  ▶ #3  Migrating auth middleware
  ○ #1  Add migration test
  ○ #4  Update README

func FormatWorkflowNote

func FormatWorkflowNote(ev WorkflowNotification) string

FormatWorkflowNote renders a background workflow completion as a <system-reminder> note for the model, mirroring FormatSubAgentNote. It lets the transport nudge the model when a detached run finishes instead of relying on the model to poll workflow_status.

func HasActiveSubAgentSync

func HasActiveSubAgentSync() bool

HasActiveSubAgentSync reports whether the default manager has a synchronous sub-agent running. Used by the TUI to conditionally show the Ctrl+B hint.

func HasActiveSync

func HasActiveSync() bool

HasActiveSync reports whether the default manager has a sync terminal polling. Used by the TUI to conditionally show the Ctrl+B hint.

func IsSubAgent

func IsSubAgent(ctx context.Context) bool

IsSubAgent reports whether ctx is currently inside a sub-agent's run.

func KillAllBackground

func KillAllBackground()

KillAllBackground terminates every tracked background process — the default manager AND every per-session manager. Wire it into session/REPL/daemon shutdown to avoid orphans regardless of which session launched a process.

func KillAllSessionSubAgents

func KillAllSessionSubAgents()

KillAllSessionSubAgents terminates every sub-agent across all sessions. Called on daemon shutdown, mirroring KillAllBackground.

func KillDefaultWorkflows

func KillDefaultWorkflows()

KillDefaultWorkflows cancels every background workflow on the process-global manager — called on CLI/TUI exit so a detached run doesn't linger.

func LoopExpired

func LoopExpired(start time.Time) bool

LoopExpired reports whether a loop that first armed at start has run past the anti-leak lifetime and must stop. Shared by every surface so the bound is identical everywhere; a zero start (no loop yet) is never expired.

func MaybeSpillOutput

func MaybeSpillOutput(id, body string) string

MaybeSpillOutput returns body unchanged when it is small enough to give the LLM directly. When body exceeds TerminalSpillBytes — and has more lines than the preview would show — it writes the full body to a temp file and returns a head+tail preview plus the file path and a one-line read hint, so the agent decides how to read the rest (read_file with offset/limit, or grep) instead of having the whole blob flood its context.

id names the source (e.g. a background process id) and is woven into the temp filename. On any write failure it degrades to returning body unchanged: losing context is worse than a missing file.

func NetworkAllowed

func NetworkAllowed() bool

NetworkAllowed reports whether the active sandbox policy permits network access. When no sandbox is active (nil policy) network is allowed.

func PromoteCurrentSubAgentSync

func PromoteCurrentSubAgentSync()

PromoteCurrentSubAgentSync signals the default manager's synchronous sub-agent to promote. Called by the TUI Ctrl+B handler.

func PromoteCurrentSync

func PromoteCurrentSync()

PromoteCurrentSync signals the default manager's sync terminal to promote. Called by the TUI Ctrl+B handler.

func ResetBrowserSession

func ResetBrowserSession()

ResetBrowserSession closes and clears the active browser session.

func SendFileToolDef

func SendFileToolDef() agent.ToolDefinition

SendFileToolDef is the definition runChannelTurns appends to the IM tool list. Exported so the server can advertise the tool without re-deriving it.

func SetAsker

func SetAsker(a Asker)

SetAsker registers the asker the ask_user_question tool delegates to. Pass nil to disable (the tool then doesn't appear in DefaultTools).

func SetBackgroundOnExit

func SetBackgroundOnExit(fn func(BgExit))

SetBackgroundOnExit registers the completion hook on the default manager (the one the built-in terminal tool uses). The REPL wires this to push a "background finished" notice into the conversation + UI. Pass nil to clear.

func SetBrowserHealer

func SetBrowserHealer(h browser.Healer)

SetBrowserHealer injects the LLM-backed step healer used by run_skill.

func SetBrowserSession

func SetBrowserSession(b *browser.Browser, p *browser.Page)

SetBrowserSession injects a pre-connected session (tests).

func SetBrowserSkillGenerator

func SetBrowserSkillGenerator(g browser.SkillGenerator)

SetBrowserSkillGenerator injects the LLM-backed skill distiller used by record_stop (nil falls back to deterministic compilation).

func SetBrowserVision

func SetBrowserVision(on bool)

SetBrowserVision enables/disables handing images to the model.

func SetDefaultSubAgentManager

func SetDefaultSubAgentManager(m *SubAgentManager)

SetDefaultSubAgentManager registers the global manager used by AgentTool when no local manager is set.

func SetDefaultWorkflowOnDone

func SetDefaultWorkflowOnDone(fn func(WorkflowNotification))

SetDefaultWorkflowOnDone wires the completion hook on the process-global workflow manager so a finished background run reaches the CLI/TUI agent (parity with SetBackgroundOnExit / SubAgentManager.SetOnExit). Pass nil to clear.

func SetDefaultWorkflowOnEvent

func SetDefaultWorkflowOnEvent(fn func(WorkflowEvent))

SetDefaultWorkflowOnEvent wires the live-progress hook on the process-global workflow manager so the CLI/TUI can show a running-workflow panel. Pass nil to clear.

func SetMCPRegistry

func SetMCPRegistry(r *mcp.Registry)

SetMCPRegistry installs the active registry for this session. Passing nil clears it (used by defer in cmd/octo so the next session starts clean). Idempotent.

func SetRestarter

func SetRestarter(f func(reason string))

SetRestarter registers the function the restart_server tool delegates to. Pass nil to disable (the tool then doesn't appear in DefaultToolsFor). Mirrors SetAsker: process-global, set once at server start.

func SetSandbox

func SetSandbox(p *sandbox.Policy)

SetSandbox enables OS-level command confinement for the terminal tools. Pass nil to disable. cmd/octo calls this when --sandbox is requested.

func SetSkills

func SetSkills(r *skills.Registry)

SetSkills registers the skills the `skill` tool serves and that DefaultTools uses to decide whether to advertise the tool. cmd/octo calls this at session start. Pass nil (or an empty registry) to disable.

func SetSpawner

func SetSpawner(s Spawner)

SetSpawner registers the function the Agent tool delegates to. Pass nil to disable (the tool then doesn't appear in DefaultTools).

func SetTaskStore

func SetTaskStore(s TaskStore)

SetTaskStore registers the store the task_* tools delegate to. Pass nil to disable; the three tools then drop out of DefaultTools.

func SetToolSearchConfig

func SetToolSearchConfig(c ToolSearchConfig)

SetToolSearchConfig installs the active tool_search configuration. Fields left at their zero value fall back to the documented defaults so a partial config block behaves sensibly.

func SetWakerSupported

func SetWakerSupported(v bool)

SetWakerSupported toggles whether schedule_wakeup is advertised. Pass true from the TUI and server entry points; leave it false (the default) for the headless one-shot.

func ShellEnvNote

func ShellEnvNote() string

ShellEnvNote returns platform-shell guidance for the session environment context, or "" on platforms where the default POSIX assumptions hold.

func SubAgentEventSink

func SubAgentEventSink(ctx context.Context) func(SubAgentEvent)

SubAgentEventSink returns the sink stamped by WithSubAgentEventSink, or nil when none is set (headless, tests, or a manager with no onEvent hook).

func ToolCallTarget

func ToolCallTarget(name string, input map[string]any) (realName string, realInput map[string]any, ok bool)

ToolCallTarget unwraps an mcp_call bridge invocation into the real MCP tool name and its arguments, so permission checks and hooks key on the real tool rather than the "mcp_call" wrapper. ok is false when name isn't an mcp_call (or carries no inner tool name), in which case the caller uses name/input unchanged.

func ToolchainNote

func ToolchainNote() string

ToolchainNote renders the detected/missing toolchain as a session-context block, pairing with ShellEnvNote (which says how to install on this platform) so the model knows both what is missing and how to add it. Returns "" only in the degenerate case where nothing was probed.

func WithAsker

func WithAsker(ctx context.Context, a Asker) context.Context

WithAsker stamps a turn-scoped asker that takes precedence over the process-global one for the duration of this turn.

func WithBackgroundManager

func WithBackgroundManager(ctx context.Context, mgr *BackgroundManager) context.Context

WithBackgroundManager returns ctx carrying mgr as the manager the terminal tools dispatch to for this turn.

func WithChannelSender

func WithChannelSender(ctx context.Context, s ChannelFileSender) context.Context

WithChannelSender stamps the file sender for the duration of an IM turn.

func WithSubAgentEventSink

func WithSubAgentEventSink(ctx context.Context, sink func(SubAgentEvent)) context.Context

WithSubAgentEventSink returns a context carrying sink, which the execution layer (the agentSpawner) pulls out to forward a child's runtime events. The SubAgentManager stamps this in before calling Spawn/Continue, so the Spawner interface itself stays unchanged and the non-TUI path (no sink) emits nothing.

func WithSubAgentManager

func WithSubAgentManager(ctx context.Context, mgr *SubAgentManager) context.Context

WithSubAgentManager returns ctx carrying mgr for the sub-agent tools to find.

func WithSubAgentMarker

func WithSubAgentMarker(ctx context.Context) context.Context

WithSubAgentMarker stamps ctx so descendants are detectable as sub-agent work. The Spawner implementation calls this when invoking the child loop.

func WithTaskStore

func WithTaskStore(ctx context.Context, store TaskStore) context.Context

WithTaskStore returns ctx carrying store for the task_* tools to find.

func WithWaker

func WithWaker(ctx context.Context, w Waker) context.Context

WithWaker stamps the session's Waker for the duration of an interactive turn.

func WithWorkflowManager

func WithWorkflowManager(ctx context.Context, mgr *WorkflowManager) context.Context

WithWorkflowManager stamps ctx with the per-session workflow manager the workflow tools dispatch to (web/IM). CLI/TUI leave it unset and fall back to defaultWorkflowMgr.

func WithWorkingDir

func WithWorkingDir(ctx context.Context, dir string) context.Context

WithWorkingDir returns a context that roots terminal commands at dir. An empty dir is a no-op (returns ctx unchanged).

func WorkingDir

func WorkingDir(ctx context.Context) string

WorkingDir returns the working directory stamped into ctx, or "" if none.

Types

type AgentKillTool

type AgentKillTool struct{}

AgentKillTool terminates an async sub-agent.

func (AgentKillTool) Definition

func (AgentKillTool) Definition() agent.ToolDefinition

func (AgentKillTool) Execute

func (AgentKillTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type AgentSendTool

type AgentSendTool struct{}

AgentSendTool delivers a follow-up message to an existing sub-agent.

func (AgentSendTool) Definition

func (AgentSendTool) Definition() agent.ToolDefinition

func (AgentSendTool) Execute

func (AgentSendTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type AgentStatusTool

type AgentStatusTool struct{}

AgentStatusTool reports one sub-agent's state or lists the running set.

func (AgentStatusTool) Definition

func (AgentStatusTool) Definition() agent.ToolDefinition

func (AgentStatusTool) Execute

func (AgentStatusTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type AgentTool

type AgentTool struct{}

AgentTool is the unified sub-agent tool. It replaces the previous explore_agent / plan_agent / general_agent / code_review_agent split with a single tool controlled by parameters.

Parameters:

  • description: short label for UI/logging
  • prompt: the task (self-contained — the child can't see this conversation)
  • subagent_type: optional agent type (explore, plan, general, code-review). Omit to fork yourself — the child inherits your full conversation context.
  • run_in_background: when true the agent runs async and you are notified on completion. When false (default) it blocks and returns the result.
  • model: optional model override
  • tools: optional tool-name allowlist for the child

The tool is advertised only when a SubAgentManager is registered.

func (AgentTool) Definition

func (AgentTool) Definition() agent.ToolDefinition

func (AgentTool) Execute

func (AgentTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type AskRequest

type AskRequest struct {
	// Question is the prompt shown to the user, complete with punctuation.
	Question string

	// Options are the mutually-exclusive (or, when MultiSelect, jointly
	// selectable) labels. Empty means "free text answer" (the asker prompts
	// for an open-ended response with no list).
	Options []string

	// MultiSelect lets the user pick more than one option. The asker is
	// responsible for parsing a comma-separated selection and returning all
	// chosen labels.
	MultiSelect bool

	// Header is an optional short tag (≤12 chars) shown in the prompt UI.
	// Helpful when several questions share visual real estate.
	Header string
}

AskRequest is the structured question, parsed from the tool input.

type AskResponse

type AskResponse struct {
	// Choices are the labels the user selected from Options. Empty when
	// the user picked "Other" (then Custom carries their free text) or
	// when the question had no Options (free text only).
	Choices []string

	// Custom is the free-text answer for "Other" picks or option-less
	// questions. Empty for plain selections.
	Custom string

	// Cancelled reports user dismissal. The tool surfaces this to the LLM
	// as a non-error result so the model can decide what to do next.
	Cancelled bool
}

AskResponse is what the user provided. Cancelled is true when the user dismissed the prompt (Ctrl-C, empty enter on a forced-pick, etc.); in that case Choices and Custom are empty.

type AskUserQuestionTool

type AskUserQuestionTool struct{}

AskUserQuestionTool lets the model ask the user a single structured clarifying question. The point isn't to chat with the user — it's to resolve a branch where the model genuinely doesn't have enough information to pick a default and asking via free-form prose would produce a sloppy, hard-to-parse answer.

The declared schema mirrors Claude Code's AskUserQuestion natively — a `questions` array of {question, header, multiSelect, options:[{label, description}]} — so the shape the model was trained to emit and the shape we advertise are the same. That alignment is the point: a flat snake_case schema drifted from the model's prior, and it would intermittently revert to the CC shape, fail validation, and the prompt would never reach the user (the "web modal didn't pop up" bug). We still cap at ONE question per call (maxItems:1, per the M11-prep design — simpler REPL/web/IM UX; the model fires multiple calls when it needs multiple), and Execute still tolerates the old flat shape as a fallback. See normalizeAskInput.

func (AskUserQuestionTool) Definition

func (AskUserQuestionTool) Execute

func (AskUserQuestionTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type Asker

type Asker interface {
	Ask(ctx context.Context, q AskRequest) (AskResponse, error)
}

Asker presents a structured question to the user and waits for their answer. Implementations live in cmd/octo (the REPL prompt reader); tests substitute fakes. Like Spawner, this interface stays free of the stdin/terminal mechanics so the tools package doesn't depend on cmd/octo.

type BackgroundManager

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

BackgroundManager owns the set of detached background processes for a session. Methods are safe for concurrent use.

func DefaultBackgroundManager

func DefaultBackgroundManager() *BackgroundManager

DefaultBackgroundManager returns the process-wide background manager used by the CLI/TUI and by tools when no per-session manager is injected.

func NewBackgroundManager

func NewBackgroundManager() *BackgroundManager

NewBackgroundManager returns an empty manager.

func SessionBackgroundManager

func SessionBackgroundManager(id string) *BackgroundManager

SessionBackgroundManager returns the per-session manager for id, creating and registering it on first use.

func (*BackgroundManager) BeginSync

func (m *BackgroundManager) BeginSync() *SyncSession

BeginSync registers a new SyncSession for the current synchronous terminal command. Call EndSync (via defer) when the command finishes or is promoted. At most one sync terminal runs per manager at a time — the agent loop is serial — so there is only one slot.

func (*BackgroundManager) Command

func (m *BackgroundManager) Command(id string) (string, bool)

Command returns the original command string for a background process id, or ("", false) if the id is unknown or has been removed.

func (*BackgroundManager) EndSync

func (m *BackgroundManager) EndSync()

EndSync clears the current SyncSession.

func (*BackgroundManager) FireExitHook

func (m *BackgroundManager) FireExitHook(e BgExit)

FireExitHook calls the onExit hook directly with e, as if a tracked process had just exited. No-op when no hook is registered. Used in tests to trigger the notification path without starting a real shell process.

func (*BackgroundManager) HasSync

func (m *BackgroundManager) HasSync() bool

HasSync reports whether a sync terminal is currently polling.

func (*BackgroundManager) Kill

func (m *BackgroundManager) Kill(id string) bool

Kill terminates the process for id with SIGKILL. Returns false when id is unknown.

func (*BackgroundManager) KillAll

func (m *BackgroundManager) KillAll()

KillAll terminates every tracked process. Called on session shutdown so no background command is orphaned.

func (*BackgroundManager) KillWithSignal

func (m *BackgroundManager) KillWithSignal(id string, sigName string) bool

KillWithSignal terminates the process for id with the named signal. Supported signals: SIGKILL, SIGTERM, SIGINT. Returns false when id is unknown.

func (*BackgroundManager) List

func (m *BackgroundManager) List() []BgInfo

List returns every visible tracked process — running AND exited-but-not-yet reaped — oldest first, so the TUI panel and completion summaries can see what has finished. Invisible (sync, pre-timeout) processes are excluded.

func (*BackgroundManager) ListRunning

func (m *BackgroundManager) ListRunning() []BgInfo

ListRunning returns the visible processes that haven't exited yet, oldest first. Processes started invisibly (e.g. sync mode) are excluded until promoted.

func (*BackgroundManager) Mode

Mode returns the background mode for id, or ("", false) if unknown.

func (*BackgroundManager) Promote

func (m *BackgroundManager) Promote(id string) bool

Promote makes a background process visible in ListRunning. Used when a sync-started process times out and becomes a true background task.

func (*BackgroundManager) PromoteSync

func (m *BackgroundManager) PromoteSync()

PromoteSync signals the current sync terminal to promote itself to a visible background process. No-op if no sync terminal is running.

func (*BackgroundManager) Read

func (m *BackgroundManager) Read(id string) (output, status string, found bool, blocked bool, mode BackgroundMode)

Read returns output produced since the last call for id, plus a status string. found is false when id is unknown. blocked is true when the caller has polled too many times without new output while the process is still running — this forces the LLM to stop polling. mode reports the process's background mode (empty when not found).

func (*BackgroundManager) Remove

func (m *BackgroundManager) Remove(id string)

Remove drops a process from the tracking map, releasing its retained output buffer. Used by the synchronous terminal path to reap a hidden command once it has exited and its output has been returned to the caller — otherwise every synchronous command would leak a bgProcess (up to maxBgOutputBytes each) for the life of the session. Visible background tasks are NOT reaped this way: their output stays readable via terminal_output after they exit.

func (*BackgroundManager) SetOnExit

func (m *BackgroundManager) SetOnExit(fn func(BgExit))

SetOnExit registers a completion hook fired once per process when it exits, carrying its final status and any output not yet read. Pass nil to clear. The hook runs on the process's waiter goroutine (not under the manager lock), so it may call back into the manager (e.g. Read) without deadlocking. The CLI uses it to push a "background finished" notice into the conversation + UI; the default (nil) keeps the original poll-only behaviour.

func (*BackgroundManager) Start

func (m *BackgroundManager) Start(command string, mode BackgroundMode, opts ...StartOption) (string, error)

Start launches command detached (via `sh -c`), with no timeout, and returns its background id. Output streams into a capped buffer; the process is killed if its context is cancelled (Kill / KillAll). The mode argument determines whether the process is an async one-shot task or an interactive service/REPL.

func (*BackgroundManager) Tail

func (m *BackgroundManager) Tail(id string, lines int) (output, status string, found bool, blocked bool, mode BackgroundMode)

Tail returns a non-destructive snapshot of the last `lines` lines of a process's output (lines <= 0 = all retained), plus its status. found is false when id is unknown. Unlike Read it does not advance the cursor — it's for on-demand progress peeks (the terminal_output tool). repeated calls return the same bytes, but repeated empty snapshots of a running process are counted as polling; once blocked is true, callers should tell the model to stop. mode reports the process's background mode (empty when not found).

func (*BackgroundManager) WriteStdin

func (m *BackgroundManager) WriteStdin(id string, input string) error

WriteStdin sends text to the stdin of a running background process. Returns an error if the process is unknown or has already exited.

func (*BackgroundManager) WriteStdinAndClose

func (m *BackgroundManager) WriteStdinAndClose(id string, input string) error

WriteStdinAndClose sends text to the process's stdin and then closes it, signalling EOF. Use for one-shot initial stdin (e.g. piping a PR body through --body-file - instead of embedding it in the shell command where backticks and quotes would be interpreted by the shell).

type BackgroundMode

type BackgroundMode string

BackgroundMode distinguishes the two kinds of tracked background processes the terminal tool can launch. Async processes run a one-shot task and notify the model on completion; interactive processes are long-running services or REPLs that the model may observe or feed via terminal_output / terminal_input.

const (
	// BgModeAsync is for one-shot tasks (tests, builds, installs). The model
	// must not poll them; completion is pushed automatically.
	BgModeAsync BackgroundMode = "async"
	// BgModeInteractive is for long-running services and REPLs (rails c,
	// octo serve). The model may use terminal_output and terminal_input.
	BgModeInteractive BackgroundMode = "interactive"
)

type BgExit

type BgExit struct {
	ID        string
	Command   string
	Status    string // "exited: 0" / "exited: <err>" — same shape readNew returns
	NewOutput string
}

BgExit is delivered to a BackgroundManager's onExit hook when a detached process finishes. NewOutput is whatever hadn't been consumed by Read yet at exit (so a push notification and a later terminal_output poll don't double-report the same bytes — the readNew cursor advances either way).

type BgInfo

type BgInfo struct {
	ID      string
	Command string
	Mode    BackgroundMode
	Start   time.Time
	End     time.Time // zero value while running; set when the process exits
	Status  string    // "running" / "exited: …"
}

BgInfo is a snapshot of a tracked background process — used by the TUI's live "background (N running)" panel and by background-completion summaries.

func RunningBackground

func RunningBackground() []BgInfo

RunningBackground lists the still-running processes on the default manager, for the TUI's live "background (N running)" panel.

type BrowserTool

type BrowserTool struct{}

BrowserTool drives a real Chrome over CDP: navigate, click, type, wait, capture downloads, etc. One tool, action-multiplexed, like computer-use.

func (BrowserTool) Definition

func (BrowserTool) Definition() agent.ToolDefinition

func (BrowserTool) Execute

func (BrowserTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type ChannelFileSender

type ChannelFileSender interface {
	// SendFile uploads path to the bound chat under the display name name
	// (empty name falls back to the file's basename). The adapter picks the
	// wire type (image / video / file) from the extension.
	SendFile(path, name string) error
}

ChannelFileSender delivers a local file to the IM chat the current turn is bound to. It is implemented by the server (wrapping the platform adapter's SendFile) and stamped into the turn ctx via WithChannelSender — the same ctx-scoping pattern as Asker, since only IM turns have a chat to push to.

type DefaultRegistry

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

DefaultRegistry is the agent.ToolExecutor used when `octo --tools` is enabled. It dispatches each tool call by name to the matching entry in allTools, returning a clean error for unknown names.

When tracker is non-nil it enforces read-before-write: write_file / edit_file calls to an existing file are refused unless the file was read (and is unchanged) this session. The zero value (DefaultRegistry{}) has a nil tracker and so enforces nothing — preserved for tests and callers that don't want the discipline. Use NewDefaultRegistry for the enforced variant.

func NewDefaultRegistry

func NewDefaultRegistry() DefaultRegistry

NewDefaultRegistry returns a registry with read-before-write enforcement backed by a fresh per-session ReadTracker.

func (DefaultRegistry) Execute

func (r DefaultRegistry) Execute(ctx context.Context, name string, input map[string]any) (agent.ToolResult, error)

Execute implements agent.ToolExecutor.

type EditFileTool

type EditFileTool struct{}

EditFileTool replaces an exact substring inside an existing file. The match must be unique unless replace_all is true. Refuses to create the file if it doesn't exist — use write_file for that.

func (EditFileTool) Definition

func (EditFileTool) Definition() agent.ToolDefinition

func (EditFileTool) Execute

func (EditFileTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type GlobTool

type GlobTool struct{}

GlobTool returns paths matching a glob pattern, sorted by modification time descending so the most recently touched files appear first.

File enumeration is delegated to ripgrep (`rg --files`), which respects .gitignore / .ignore and prunes ignored subtrees efficiently — the same engine grep uses, so the two tools agree on what's "in" the project. The glob pattern itself is then matched in-process against each path so the semantics below are exactly preserved regardless of ripgrep's own glob rules.

Supports `**` for "any directory depth" (e.g. `src/**/*.go`). Other segments use the standard path.Match semantics: `*` matches any run of non-separator characters, `?` matches a single character, character classes via `[…]`.

func (GlobTool) Definition

func (GlobTool) Definition() agent.ToolDefinition

func (GlobTool) Execute

func (GlobTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type GrepTool

type GrepTool struct{}

GrepTool is a thin wrapper over `ripgrep` (`rg`). It accepts a regex pattern and one of three output modes: full content lines, file paths only, or per-file match counts.

We deliberately depend on a working `rg` binary rather than reimplement in pure Go. Ripgrep handles .gitignore, binary detection, and parallel I/O — duplicating those is months of work, and `rg` is a near-universal install on developer machines.

func (GrepTool) Definition

func (GrepTool) Definition() agent.ToolDefinition

func (GrepTool) Execute

func (GrepTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type KillShellTool

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

KillShellTool terminates a background process started by TerminalTool with run_in_background:"async" or "interactive" and returns its final output — the counterpart to terminal_output, which only reads. Split out from terminal_output's old kill:true flag so "stop this process" is a first-class, obvious action.

func (KillShellTool) Definition

func (KillShellTool) Definition() agent.ToolDefinition

Definition describes the required "id".

func (KillShellTool) Execute

func (t KillShellTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

Execute kills the process, then returns its final remaining output. An unknown id is an error (Kill reports it); an already-exited process is a no-op kill and still returns its last output.

type ReadFileTool

type ReadFileTool struct{}

ReadFileTool reads a UTF-8 text file, optionally a window of it, and returns its content with each line prefixed by its 1-based line number (the `cat -n` format the LLM is already familiar with).

For image files (.png, .jpg, .jpeg, .gif, .webp, .bmp, .tiff, .heic, .ico) the tool reads the raw bytes and returns them as an image content block for multimodal model consumption alongside a short text description.

func (ReadFileTool) Definition

func (ReadFileTool) Definition() agent.ToolDefinition

func (ReadFileTool) Execute

func (ReadFileTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type ReadTracker

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

ReadTracker enforces the read-before-write discipline within a session: the agent may only write to (or edit) a file it has already read, and only while its on-disk mtime still matches what was seen at read time. This stops the LLM from blindly overwriting a file it half-remembers, or clobbering an edit made out-of-band since it last looked.

State is per-session (one tracker per Registry). All methods are safe for concurrent use, though the agent loop dispatches tools sequentially.

func NewReadTracker

func NewReadTracker() *ReadTracker

NewReadTracker returns an empty tracker.

func (*ReadTracker) CheckWritable

func (rt *ReadTracker) CheckWritable(absPath string) error

CheckWritable reports whether absPath may be written/edited right now.

Rules:

  • A path that does NOT exist on disk is always writable (creating a new file needs no prior read — you can't read what isn't there).
  • An existing path must have been read this session, else the LLM is writing blind → refuse.
  • An existing, previously-read path whose mtime advanced since the read was changed out-of-band → refuse and force a re-read.

The returned error text mirrors Claude Code's wording so the LLM reacts the way it's been trained to (re-read, then retry).

func (*ReadTracker) RecordRead

func (rt *ReadTracker) RecordRead(absPath string)

RecordRead notes that absPath was read (or written) and stamps it with the file's current mtime. A failed stat is silently ignored — if we can't tell the file's mtime there's nothing to enforce against later, and recording a zero time would wrongly trip the "modified since read" guard.

type RestartServerTool

type RestartServerTool struct{}

RestartServerTool lets the model restart the octo server process — after a binary upgrade or a config change that is only read at startup. The restart is scheduled, not immediate: the server drains in-flight turns (including the one executing this tool), so the model's final reply still reaches the user before the process exits and the supervisor respawns it.

func (RestartServerTool) Definition

func (RestartServerTool) Definition() agent.ToolDefinition

func (RestartServerTool) Execute

func (RestartServerTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type ScheduleWakeupTool

type ScheduleWakeupTool struct{}

ScheduleWakeupTool lets the model schedule its own next turn — the mechanism behind the loop skill. The model calls it at the end of a turn to come back later (dynamic, self-paced mode) or to keep a fixed cadence (repeat = interval mode); NOT calling it ends the loop. It only works inside a live session that can be re-entered — the interactive TUI or a server-managed web/IM session — so it is withheld from the headless one-shot (wakerEnabled gates it in DefaultToolsFor) and the per-session Waker is injected into the turn ctx.

func (ScheduleWakeupTool) Definition

func (ScheduleWakeupTool) Execute

func (ScheduleWakeupTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type SendFileTool

type SendFileTool struct{}

SendFileTool delivers a local file to the user through the active IM channel (WeChat, Telegram, Discord, …). It is the only way the model can put a file in front of an IM user: a chat reply carries text, so a generated image, chart, document, or any other artifact must be pushed as a file.

The tool lives in allTools so DefaultRegistry can dispatch it, but it is withheld from the advertised tool list everywhere except IM turns — see DefaultToolsFor, which always skips it, and runChannelTurns, which appends its definition and injects the sender. On a non-IM turn channelSenderFrom returns nil and the tool reports a clean, model-readable error.

func (SendFileTool) Definition

func (SendFileTool) Definition() agent.ToolDefinition

func (SendFileTool) Execute

func (SendFileTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type ShowArtifactTool

type ShowArtifactTool struct{}

ShowArtifactTool surfaces an existing file to the user as a previewable artifact. write_file/edit_file payloads already feed the web Artifacts panel automatically; this tool covers files produced any other way — build scripts (e.g. web-artifacts-builder's bundle.html), generators, downloads. Its tool_use block also lands in the session transcript, which is what authorizes the artifact endpoint to serve the path.

func (ShowArtifactTool) Definition

func (ShowArtifactTool) Definition() agent.ToolDefinition

func (ShowArtifactTool) Execute

func (ShowArtifactTool) Execute(_ context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type SkillTool

type SkillTool struct{}

SkillTool loads a skill's full SKILL.md body on demand. The model calls it after spotting a matching skill in the system-prompt "Available skills" manifest; the body returns as a tool_result, landing in history rather than the frozen system prefix. The zero value reads from the package-level registry set by SetSkills.

func (SkillTool) Definition

func (SkillTool) Definition() agent.ToolDefinition

func (SkillTool) Execute

func (SkillTool) Execute(_ context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type SpawnRequest

type SpawnRequest struct {
	// Description is a short human-readable label for logging / progress UI.
	Description string

	// AgentType is the subagent_type the caller selected (e.g. "explore",
	// "general"), shown alongside Description in the live panels. Empty for an
	// untyped fork.
	AgentType string

	// Prompt is the sub-agent's user message. It carries the task.
	Prompt string

	// ForkConversation, when true, seeds the child's history with the parent's
	// conversation so far (a true fork), rather than starting fresh. The
	// spawner trims the in-flight tool_use turn that spawned the child so the
	// copied history ends cleanly. Set by the sub_agent tool when no
	// subagent_type is given; workflow agents leave it false.
	ForkConversation bool

	// Tools, when non-empty, restricts the child to this subset of the
	// parent's tool list. nil/empty means "inherit all of parent's tools
	// except Agent itself" — the spawn implementation handles the
	// recursion filter, not the tool.
	Tools []string

	// DisallowedTools is subtracted from the child's inherited tool set
	// (frontmatter `disallowed_tools`). Applied on top of Tools/ReadOnly.
	DisallowedTools []string

	// Model, when non-empty, overrides the parent's model for this child.
	Model string

	// SystemSuffix, when non-empty, is appended to the child's system prompt
	// (after the shared parent System) to give a preset agent its persona.
	SystemSuffix string

	// ReadOnly, when true, strips the mutating tools (write_file, edit_file)
	// from the child's toolbelt on top of the always-dropped Agent tool.
	ReadOnly bool

	// LeanContext, when true, runs the child on the parent's lite model and
	// seeds it with the parent's lean system prompt (skills manifest + memory
	// dropped). Set for cheap read-only presets (explore/plan). Falls back to
	// the parent's model/system when no lite model / lean system is configured.
	LeanContext bool

	// Schema, when non-empty, is a JSON Schema (as a JSON string) the child's
	// reply must satisfy. The spawner instructs the child to emit only matching
	// JSON, strips any markdown fences, and re-prompts once if the reply isn't
	// valid JSON. The returned Reply is the cleaned JSON text.
	Schema string

	// Isolation, when "worktree", runs the child in a fresh git worktree so its
	// file/terminal changes don't touch the main checkout. Changes are left on a
	// dedicated branch for the caller to review; an unchanged run is cleaned up.
	// Requires a git repository.
	Isolation string

	// SessionDir, when non-empty, tells the spawner to persist the sub-agent's
	// full conversation transcript to <SessionDir>/<agent-id>.jsonl so it can
	// be inspected after a failure.
	SessionDir string
}

SpawnRequest is the LLM-supplied Agent tool payload, parsed.

type SpawnResult

type SpawnResult struct {
	// AgentID addresses the sub-agent for a later Continue. Spawn returns
	// a non-empty id when the implementation keeps the child alive.
	AgentID      string
	Reply        string
	InputTokens  int
	OutputTokens int
	// Turns is the number of provider round-trips the sub-agent executed.
	Turns int
	// StopReason carries why the sub-agent stopped. Empty for normal
	// completion; "max_turns" when the loop budget was exhausted.
	StopReason string
}

SpawnResult is the sub-agent's final output, plus its token usage so the parent can roll it into the session total.

type Spawner

type Spawner interface {
	Spawn(ctx context.Context, req SpawnRequest) (SpawnResult, error)
	// Continue re-runs a still-alive sub-agent (one a prior Spawn kept in the
	// implementation's live registry) with a new message and returns its next
	// reply. The sub-agent's prior history carries over. An unknown or
	// already-evicted agentID returns an error whose text tells the model to
	// launch a fresh sub-agent instead. Concurrent Continue calls on the SAME
	// agentID must be serialized by the implementation — a sub-agent's history
	// can't take two interleaved turns.
	Continue(ctx context.Context, agentID, message string) (SpawnResult, error)
}

Spawner runs one sub-agent task to completion and returns the final reply. Implementations live outside the tools package (cmd/octo wires the real one, tests substitute fakes) so this package stays free of agent-construction concerns. Multiple Spawner calls may run concurrently from one parent tool_use batch (see parallel dispatch in agent.dispatchTools), so the implementation MUST be safe for concurrent invocation on distinct requests.

func ActiveSpawner

func ActiveSpawner() Spawner

ActiveSpawner returns the currently registered Spawner, or nil if none.

type StartOption

type StartOption func(*bgProcess)

StartOption is a functional option for BackgroundManager.Start.

func WithOnLine

func WithOnLine(fn func(string)) StartOption

WithOnLine registers a callback that receives each line of output as it is produced. Used by the synchronous path to forward output to the progress callback in real time.

func WithVisible

func WithVisible(v bool) StartOption

WithVisible sets the process visibility in ListRunning. Sync-started processes start hidden (visible=false) and are promoted to visible=true when they time out.

type SubAgentEvent

type SubAgentEvent struct {
	AgentID     string // manager handle, e.g. "agent_1"
	Description string // human-readable label from sub_agent
	AgentType   string // subagent_type, e.g. "explore" (empty for an untyped fork)
	// Kind is one of:
	//   "started"    — the sub-agent began a task (or a Continue round)
	//   "tool"       — it dispatched a tool (ToolName set)
	//   "tool_error" — a tool returned an error (ToolName set)
	//   "done"       — the round finished (sync return or async completion);
	//                  live panels drop the entry on this
	Kind      string
	ToolName  string
	ToolInput map[string]any // optional: the tool's input arguments for UI display
}

SubAgentEvent is a runtime progress event from a sub-agent, forwarded to a SubAgentManager's onEvent hook for live display. It is distinct from SubAgentNotification, which is the one-shot completion record: events stream while the sub-agent works, the notification fires once when it finishes.

Only tool-level activity is carried — not per-token text — so a live panel can show each sub-agent's tool-call chain without the event volume of N concurrent sub-agents streaming their prose.

type SubAgentInfo

type SubAgentInfo struct {
	ID          string
	Description string
	Start       time.Time
	Busy        bool
}

SubAgentInfo is a snapshot of a sub-agent for listing.

type SubAgentManager

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

SubAgentManager owns the set of async sub-agents for a session. Methods are safe for concurrent use.

func NewSubAgentManager

func NewSubAgentManager(spawner Spawner) *SubAgentManager

NewSubAgentManager returns an empty manager.

func SessionSubAgentManager

func SessionSubAgentManager(id string, mkSpawner func() Spawner) *SubAgentManager

SessionSubAgentManager returns the per-session sub-agent manager for id, creating it via mkSpawner on first use. Subsequent calls reuse the existing manager (and its spawner), so mkSpawner is only invoked once per session. If mkSpawner is nil and no manager exists for id, nil is returned — callers that only need to signal an already-running sync sub-agent (e.g. a WebSocket promote message) can use this safely without constructing a manager.

func (*SubAgentManager) BeginSync

func (m *SubAgentManager) BeginSync() *SyncSession

BeginSync registers a new SyncSession for the current synchronous sub-agent run. Call EndSync with the returned session (via defer) when the run finishes or is promoted. At most one synchronous sub-agent runs per manager at a time — the agent loop is serial — so there is only one slot.

func (*SubAgentManager) ContinueSync

func (m *SubAgentManager) ContinueSync(ctx context.Context, agentID, message string) (SpawnResult, error)

ContinueSync re-runs a still-alive sub-agent (addressed by its spawner-side id) with a new message and blocks until it replies. The synchronous counterpart of Send.

func (*SubAgentManager) EndSync

func (m *SubAgentManager) EndSync(s *SyncSession)

EndSync clears the current SyncSession only if it is still the one returned by BeginSync. This protects against a deferred EndSync racing with a later synchronous run in tests or if the manager is ever used concurrently.

func (*SubAgentManager) HasSync

func (m *SubAgentManager) HasSync() bool

HasSync reports whether a synchronous sub-agent is currently running.

func (*SubAgentManager) Kill

func (m *SubAgentManager) Kill(id string) bool

Kill terminates the sub-agent for id. Returns false when id is unknown.

func (*SubAgentManager) KillAll

func (m *SubAgentManager) KillAll()

KillAll terminates every tracked sub-agent. Called on session shutdown.

func (*SubAgentManager) ListRunning

func (m *SubAgentManager) ListRunning() []SubAgentInfo

ListRunning returns the agents that haven't exited yet, oldest first.

func (*SubAgentManager) PromoteSync

func (m *SubAgentManager) PromoteSync()

PromoteSync signals the current synchronous sub-agent to promote itself to a background agent. No-op if no synchronous sub-agent is running.

func (*SubAgentManager) Read

func (m *SubAgentManager) Read(id string) (result, status string, found bool)

Read returns the latest result and status for an agent. found is false when id is unknown.

func (*SubAgentManager) RunSync

func (m *SubAgentManager) RunSync(ctx context.Context, req SpawnRequest) (SpawnResult, error)

RunSync spawns a sub-agent and blocks until it completes, returning its reply. Used by the synchronous sub_agent path; the spawner stamps the sub-agent marker and keeps the child resumable for a later ContinueSync. When an onEvent hook is registered the child's tool-level activity is streamed the same way async sub-agents are, so live panels work for both sync and async modes.

A synchronous run can be manually promoted to a background agent while it is running (TUI Ctrl+B, Web "Background" button). The same agent_N id is kept, the goroutine continues, and the result arrives via the onExit hook.

func (*SubAgentManager) Send

func (m *SubAgentManager) Send(agentID, message string) error

Send delivers a message to an existing sub-agent asynchronously. Returns immediately; reply arrives via onExit.

func (*SubAgentManager) SetOnEvent

func (m *SubAgentManager) SetOnEvent(fn func(SubAgentEvent))

SetOnEvent registers a runtime-event hook fired as a sub-agent works (started + per-tool activity), for live display. Pass nil to clear. Distinct from onExit, which fires once on completion.

func (*SubAgentManager) SetOnExit

func (m *SubAgentManager) SetOnExit(fn func(SubAgentNotification))

SetOnExit registers a completion hook fired once per sub-agent when it finishes processing. Pass nil to clear.

func (*SubAgentManager) SetSynchronous

func (m *SubAgentManager) SetSynchronous(v bool)

SetSynchronous selects the sub_agent dispatch model. The default (false) is the interactive async path: Start returns immediately and the reply arrives via onExit, which the REPL/TUI re-injects as a follow-up turn. A request/response transport (HTTP server, IM bridge) has no follow-up-turn channel, so it sets this true: sub_agent then blocks the turn on RunSync and returns the child's reply directly as the tool_result. Set once at startup, before any turn runs.

func (*SubAgentManager) Start

func (m *SubAgentManager) Start(req SpawnRequest) (string, error)

Start creates a new sub-agent and runs it asynchronously. Returns the agent_id immediately; result arrives via onExit.

func (*SubAgentManager) Synchronous

func (m *SubAgentManager) Synchronous() bool

Synchronous reports whether the manager runs sub-agents inline (see SetSynchronous).

type SubAgentNotification

type SubAgentNotification struct {
	AgentID      string // e.g. "agent_1"
	Description  string // human-readable label from sub_agent
	Kind         string // "spawn_done" | "message_reply"
	Result       string // final reply text
	InputTokens  int
	OutputTokens int
	// StopReason is empty for normal completion, "max_turns" when the sub-agent
	// hit its loop budget. The parent uses this to decide whether to continue.
	StopReason string
}

SubAgentNotification is delivered to the onExit hook when a sub-agent finishes a task (spawn) or replies to a message (continue).

type SyncSession

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

SyncSession is the promote handle for one in-flight synchronous terminal command. Closing the channel (via Signal) unblocks the polling loop so the process is promoted to a visible background task before the timer fires. safe for concurrent calls (sync.Once).

func (*SyncSession) C

func (s *SyncSession) C() <-chan struct{}

C returns the receive-only channel the terminal polling loop selects on.

func (*SyncSession) Signal

func (s *SyncSession) Signal()

Signal closes the channel, unblocking any select waiting on C(). Safe to call multiple times — only the first call takes effect.

type TaskCreateTool

type TaskCreateTool struct{}

TaskCreateTool adds a new pending task to the session's task list.

Tool name: task_create.

func (TaskCreateTool) Definition

func (TaskCreateTool) Definition() agent.ToolDefinition

func (TaskCreateTool) Execute

func (TaskCreateTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type TaskListTool

type TaskListTool struct{}

TaskListTool returns the current task list, grouped by status.

Tool name: task_list.

func (TaskListTool) Definition

func (TaskListTool) Definition() agent.ToolDefinition

func (TaskListTool) Execute

func (TaskListTool) Execute(ctx context.Context, _ string, _ map[string]any) (agent.ToolResult, error)

type TaskStore

type TaskStore interface {
	Create(subject, description, activeForm string) (int, error)
	Update(id int, u tasks.UpdateField) (tasks.Task, error)
	List() []tasks.Task
	Get(id int) (tasks.Task, bool)
	Summary() string
}

TaskStore is the surface the task_* tools use to talk to the underlying store. internal/tasks.Store satisfies this directly; tests can substitute fakes without dragging in the tasks package.

func ActiveTaskStore

func ActiveTaskStore() TaskStore

ActiveTaskStore returns the currently registered store, or nil. Used by cmd/octo's /tasks REPL command (and the post-tool summary line) without going through a LLM-driven tool call.

type TaskUpdateTool

type TaskUpdateTool struct{}

TaskUpdateTool mutates an existing task. Most commonly the model uses it to move tasks through the pending → in_progress → completed lifecycle.

Tool name: task_update.

func (TaskUpdateTool) Definition

func (TaskUpdateTool) Definition() agent.ToolDefinition

func (TaskUpdateTool) Execute

func (TaskUpdateTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type TerminalInputTool

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

TerminalInputTool sends text to the stdin of a running INTERACTIVE background process launched with terminal run_in_background:"interactive". Use to interact with long-running interactive applications (REPLs, configuration wizards, servers that accept commands via stdin).

func (TerminalInputTool) Definition

func (TerminalInputTool) Definition() agent.ToolDefinition

Definition describes the required "id" and "input".

func (TerminalInputTool) Execute

func (t TerminalInputTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

Execute writes input to the process's stdin. Unknown or exited id is an error. Reject async processes: terminal_input is only meaningful for interactive background tasks.

type TerminalOutputTool

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

TerminalOutputTool reads new output (and status) from an INTERACTIVE background process launched with terminal run_in_background:"interactive".

func (TerminalOutputTool) Definition

Definition describes the required "id". Reading is non-destructive; to stop a process use the kill_shell tool.

func (TerminalOutputTool) Execute

func (t TerminalOutputTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

Execute returns a snapshot of the process's last N lines plus a status line. Read-only and non-advancing — it never terminates the process (that's kill_shell) and never moves a read cursor, so it can't "block" and there is no polling incentive.

type TerminalTool

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

TerminalTool is an agent.ToolExecutor that runs shell commands through the system shell (`sh -c` on macOS/Linux, PowerShell on Windows; see shellCommand). Stdout and stderr are combined and returned as the tool result. Non-zero exit codes are reported as extra metadata in the result text rather than as a tool error, so the LLM can see the failure output and adapt.

The LLM-facing tool name is "terminal" — calling it "bash" would imply a hard /bin/bash dependency, but the executor shells out via the platform shell (the model is told which one via the environment context).

mgr, when non-nil, is the BackgroundManager used for run_in_background launches; nil falls back to the process-wide default manager. The field exists so tests can inject an isolated manager.

func (TerminalTool) Definition

func (TerminalTool) Definition() agent.ToolDefinition

Definition returns the agent.ToolDefinition the LLM receives in the tools list. The JSON Schema describes a required "command" string and an optional "run_in_background" enum.

func (TerminalTool) Execute

func (t TerminalTool) Execute(ctx context.Context, name string, input map[string]any) (agent.ToolResult, error)

Execute runs the command and returns combined output. A non-zero exit code is appended to the output as `[exit: <error>]` rather than being surfaced as an error, giving the LLM visibility into what went wrong.

Internally this delegates to ExecuteStream with a nil progress callback so both code paths share the same exec/scanner pipeline — only the streaming behavior changes.

func (TerminalTool) ExecuteStream

func (t TerminalTool) ExecuteStream(
	ctx context.Context,
	_ string,
	input map[string]any,
	progress func(chunk string),
) (agent.ToolResult, error)

ExecuteStream runs the command and forwards each output line to progress as it arrives, returning the full aggregated stdout+stderr at the end. progress may be nil — in that case the behaviour is identical to Execute.

stdout and stderr are merged into a single stream so the LLM sees them in chronological order (the same way they'd appear in an interactive terminal). Scanner buffer cap is 1 MiB per line — commands that emit a single 10MB- long line will get their final line truncated, but the more usual case of many short lines is unaffected.

Timeout promotion: if the command exceeds TerminalTimeout (120 s) the original process continues running in the background (no restart). The caller receives the output produced so far plus a background id and a clear instruction to wait for the completion notification.

type ToolSearchConfig

type ToolSearchConfig struct {
	Mode           ToolSearchMode
	ThresholdPct   int // auto-mode activation threshold, percent of context window
	SearchLimit    int // default number of hits tool_search returns
	MaxSearchLimit int // upper bound on the caller-supplied limit
}

ToolSearchConfig is the tools-package view of the user's tool_search config. cmd/octo maps the ~/.octo/config.yml block onto this and installs it via SetToolSearchConfig, mirroring SetSandbox / SetMCPRegistry.

type ToolSearchMode

type ToolSearchMode int

ToolSearchMode selects when the bridge replaces full MCP schema upload.

const (
	// ToolSearchAuto activates the bridge only when deferred MCP schemas would
	// occupy at least ThresholdPct% of the model's context window.
	ToolSearchAuto ToolSearchMode = iota
	// ToolSearchOn activates the bridge whenever any MCP tool is present.
	ToolSearchOn
	// ToolSearchOff never activates the bridge — MCP schemas upload in full.
	ToolSearchOff
)

type Waker

type Waker interface {
	// ScheduleWakeup arranges for prompt to run as a fresh user turn in the
	// current session after delay. When repeat is true the wakeup re-arms on
	// the same cadence (interval mode); otherwise it fires once and the model
	// re-arms by calling the tool again (dynamic mode). reason is a short
	// human-facing note. Any wakeup already pending for the session is
	// replaced — a session has at most one armed wakeup at a time.
	ScheduleWakeup(delay time.Duration, prompt, reason string, repeat bool) error

	// CancelWakeup stops any pending wakeup for the session — the explicit way
	// to end an interval loop (the dynamic mode ends by simply not re-arming).
	// No-op when nothing is armed.
	CancelWakeup() error
}

Waker schedules a delayed re-entry into the current interactive session: a prompt delivered as a fresh user turn after a delay. It backs the loop skill's two modes — dynamic (the model re-arms each turn) and interval (the wakeup re-arms itself) — and is stamped into the turn ctx per surface. The TUI (cmd/octo) and the HTTP/IM server (internal/server) each implement it differently, the same ctx-scoping the Asker and ChannelFileSender use. A headless one-shot turn stamps none, so schedule_wakeup reports a clean error there rather than pretending to loop in a process that exits after one turn.

type WebFetchTool

type WebFetchTool struct{}

WebFetchTool fetches a URL and returns its body as Markdown. It prefers the Jina AI Reader proxy for JS-rendered pages and clean HTML-to-Markdown conversion, but falls back to a direct HTTP fetch when the proxy fails.

func (WebFetchTool) Definition

func (WebFetchTool) Definition() agent.ToolDefinition

func (WebFetchTool) Execute

func (WebFetchTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type WebSearchResponse

type WebSearchResponse struct {
	Query    string            `json:"query"`
	Results  []WebSearchResult `json:"results"`
	Count    int               `json:"count"`
	Provider string            `json:"provider"`
	Error    string            `json:"error,omitempty"`
}

WebSearchResponse is what gets serialised back to the LLM. Provider records which backend actually produced the results, so the LLM knows whether to trust the corpus (Brave/Google index vs. DDG/Bing HTML scrape).

type WebSearchResult

type WebSearchResult struct {
	Title   string `json:"title"`
	URL     string `json:"url"`
	Snippet string `json:"snippet"`
}

WebSearchResult is a single normalised search hit. All five backends flatten their wire-format response into this shape so the LLM sees the same contract regardless of which backend produced the result.

type WebSearchTool

type WebSearchTool struct{}

WebSearchTool searches the web. Backend priority (descending):

  1. Brave Search API (env BRAVE_SEARCH_API_KEY)
  2. Tavily API (env TAVILY_API_KEY)
  3. Serper.dev (env SERPER_API_KEY)
  4. DuckDuckGo HTML (zero key, default)
  5. Bing HTML (zero key, fallback when DDG returns nothing)

The tool never panics — every backend failure becomes an Error field in the returned response, and the next backend is tried.

func (WebSearchTool) Definition

func (WebSearchTool) Definition() agent.ToolDefinition

func (WebSearchTool) Execute

func (WebSearchTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type WorkflowEvent

type WorkflowEvent struct {
	RunID       string
	Description string
	Kind        string
	Line        string // the progress/log line for Kind=="progress"
	Status      string // "running" | "done" | "error" for Kind=="done"
}

WorkflowEvent is emitted as a background run progresses, for live display (the web panel). Kind is "started" | "progress" | "done".

type WorkflowKillTool

type WorkflowKillTool struct{}

WorkflowKillTool cancels a running background workflow by id — for a run that has stalled (workflow_status shows a large, growing "last activity" gap) or is no longer wanted.

func (WorkflowKillTool) Definition

func (WorkflowKillTool) Definition() agent.ToolDefinition

func (WorkflowKillTool) Execute

func (WorkflowKillTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type WorkflowManager

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

WorkflowManager owns background workflow runs for one scope (process-global for CLI/TUI, or per-session for web/IM). It mirrors SubAgentManager: runs outlive the turn that launched them under a detached context.

func NewWorkflowManager

func NewWorkflowManager() *WorkflowManager

NewWorkflowManager returns an empty manager.

func SessionWorkflowManager

func SessionWorkflowManager(id string) *WorkflowManager

SessionWorkflowManager returns the per-session workflow manager for id, creating it on first use.

func (*WorkflowManager) Kill

func (m *WorkflowManager) Kill(id string) (found, wasRunning bool)

Kill cancels one running workflow by id. Returns (found, wasRunning): found is false for an unknown id; wasRunning is false when the run had already finished (a no-op cancel). The run's detached context is cancelled, which propagates to its in-flight sub-agents and unwinds the script.

func (*WorkflowManager) KillAll

func (m *WorkflowManager) KillAll()

KillAll cancels every running workflow this manager owns. Called on session close so a detached run doesn't outlive its conversation.

func (*WorkflowManager) List

List returns snapshots of every run this manager knows, oldest first.

func (*WorkflowManager) Read

Read returns a snapshot of one run.

func (*WorkflowManager) SetOnDone

func (m *WorkflowManager) SetOnDone(fn func(WorkflowNotification))

SetOnDone registers the completion hook (next-turn notification). nil disables it.

func (*WorkflowManager) SetOnEvent

func (m *WorkflowManager) SetOnEvent(fn func(WorkflowEvent))

SetOnEvent registers the live-progress sink (web panel). nil disables it.

func (*WorkflowManager) Start

Start launches req in the background and returns its handle id (e.g. "wf_1"). The run executes under a detached context so it survives the turn.

type WorkflowNotification

type WorkflowNotification struct {
	RunID        string
	Description  string
	Status       string // "done" | "error"
	Result       string // final output, or the error message
	JournalRunID string // the resume_from handle, when journaling was available
}

WorkflowNotification is delivered to the onDone hook when a background run finishes, so the transport can nudge the model on its next turn.

type WorkflowRunRequest

type WorkflowRunRequest struct {
	Description   string
	Script        string
	Args          string // the run's input value as a JSON string ("" = none)
	Agent         workflow.AgentFunc
	MaxConcurrent int
	ResumeFrom    string
}

WorkflowRunRequest starts one background run. The Agent func (spawner-backed) and limits are supplied by the caller so the manager stays decoupled from the tools.Spawner concrete type.

type WorkflowRunSnapshot

type WorkflowRunSnapshot struct {
	ID           string
	Description  string
	Status       string // "running" | "done" | "error"
	Output       string
	ErrMsg       string
	Logs         []string
	JournalRunID string
	Start        time.Time
	End          time.Time
	// LastActivity is when the run last emitted progress (a log line or an
	// agent start/finish). A running run whose LastActivity is far in the past
	// is likely stuck — the gap, not the total elapsed, is the liveness signal.
	LastActivity time.Time
}

WorkflowRunSnapshot is a point-in-time view of a background run for listing and status reads.

type WorkflowSaveTool

type WorkflowSaveTool struct{}

WorkflowSaveTool persists a Ruby workflow script to the registry so it can be re-run by name with the workflow tool. Like the workflow tool, it is advertised only when a Spawner is configured.

func (WorkflowSaveTool) Definition

func (WorkflowSaveTool) Definition() agent.ToolDefinition

func (WorkflowSaveTool) Execute

func (WorkflowSaveTool) Execute(_ context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type WorkflowStatusTool

type WorkflowStatusTool struct{}

WorkflowStatusTool reports on background workflow runs: a list with no argument, or one run's full status + result when given a run id.

func (WorkflowStatusTool) Definition

func (WorkflowStatusTool) Execute

func (WorkflowStatusTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

type WorkflowTool

type WorkflowTool struct{}

WorkflowTool runs a Ruby (mruby) orchestration script in an embedded wasm interpreter. The script drives sub-agents through the agent() / parallel() / pipeline() primitives; each agent() call delegates to the same Spawner that backs sub_agent. The tool is advertised only when a Spawner is registered.

Like sub_agent, it refuses to run inside a sub-agent — workflow agents are themselves marked as sub-agents, so a child can't recursively launch another workflow.

func (WorkflowTool) Definition

func (WorkflowTool) Definition() agent.ToolDefinition

func (WorkflowTool) Execute

func (WorkflowTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

Execute starts the workflow in the background and returns its run handle.

type WriteFileTool

type WriteFileTool struct{}

WriteFileTool writes (or overwrites) a file with the given content. Parent directories are created with mkdir -p semantics. File permissions default to 0644.

func (WriteFileTool) Definition

func (WriteFileTool) Definition() agent.ToolDefinition

func (WriteFileTool) Execute

func (WriteFileTool) Execute(ctx context.Context, _ string, input map[string]any) (agent.ToolResult, error)

Directories

Path Synopsis
Package rgembed provides a bundled ripgrep (rg) binary for the current platform.
Package rgembed provides a bundled ripgrep (rg) binary for the current platform.

Jump to

Keyboard shortcuts

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