Help us shape the future of Go! Take the 2026 Go Developer Survey

agentic

package
v0.2.11 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Overview

Package agentic implements the agentic coding assistant actors for use in both the CLI and server contexts.

Package agentic implements the agentic coding assistant actors. The LLMPromptExecutionActor manages conversation history and spawns OrchestratorActors for each user request. The OrchestratorActor runs the autonomous loop: call LLM, execute tools, request approval, repeat until done.

Index

Constants

View Source
const (
	GroundingOff      = "off"
	GroundingPrompt   = "prompt"
	GroundingEnforced = "enforced"
)

Grounding: make the agent read the codebase the way a human does before it acts — locate the relevant code with search tools (glob / grep / symbol_search), read it, and iterate until the request maps to concrete files and symbols. If the codebase and documents don't yield the target, the agent asks the user WHERE the relevant code lives instead of guessing; if the prompt alone already makes clear that information is missing, it may ask immediately without ritually exhausting search first.

Three modes (per LLM-execution actor, threaded into each orchestrator):

GroundingOff      — no protocol; legacy behavior.
GroundingPrompt   — ADVISORY: the grounding protocol is appended to the
                    system prompt, but nothing is enforced. Default for
                    sub-agents (pre-scoped by their parent) and
                    agents/humanoids (chat users don't want a grep
                    ritual forced on quick questions).
GroundingEnforced — the GATE: the run starts in PhaseGrounding where only
                    read-only exploration tools are allowed; mutating
                    tools stay locked until the model calls
                    grounding_report{understood: true}. Default for panes.

grounding_report is an intercepted pseudo-tool (the sub_agent pattern): the registered executor is never run; the orchestrator handles the call.

understood=true            → exit PhaseGrounding, unlock all tools.
understood=false + question → surface the question to the user and PAUSE
                             the run (StoppedReasonAwaitingInfo); the
                             user's reply arrives as the next prompt and
                             resumes from the checkpoint.
View Source
const DefaultContextTokenLimit = 160000

DefaultContextTokenLimit is the input-token budget used when the host does not specify one. Claude models accept ~200k input tokens; the limit leaves headroom for the response and tool output.

View Source
const DefaultGroundingMaxIterations = 6

DefaultGroundingMaxIterations caps how many loop iterations a run may stay in PhaseGrounding before the gate opens anyway (fail-open: exploration budget exhausted must never brick the task).

View Source
const DefaultSubAgentMaxIterations = 25

DefaultSubAgentMaxIterations bounds the child orchestrator's agentic loop independently of the parent's budget. Sub-tasks are intended to be focused and short; a tighter cap protects against runaway children.

View Source
const DefaultSubAgentTimeout = 15 * time.Minute

DefaultSubAgentTimeout is the wall-clock upper bound a parent will wait for a child orchestrator to report MsgOrchestratorDone. The parent's own cancellation propagates through the shared o.ctx as well.

View Source
const GroundingToolName = "grounding_report"

GroundingToolName is the pseudo-tool the model calls to declare its understanding (or lack of it). Intercepted in executeTool before normal dispatch — the registered executor is a defensive no-op.

View Source
const MaxSubAgentDepth = 2

MaxSubAgentDepth caps recursive sub-agent spawning. The top-level orchestrator runs at depth 0; the first sub-agent runs at depth 1; a child of that sub-agent would run at depth 2 and so on. Attempts to spawn beyond MaxSubAgentDepth return a structured error so the parent can recover.

View Source
const SubAgentCallsMetaKey = "sub_agent_tool_calls"

SubAgentCallsMetaKey is the sub_agent tool output Metadata key carrying the child's tool calls as JSON ([]provider.SubAgentCall). buildToolResultTurn moves it onto the result turn's SubAgentCalls; it never enters Content.

View Source
const SubAgentToolName = "sub_agent"

SubAgentToolName is the tool name the model invokes to delegate a focused task to a child orchestrator. The orchestrator intercepts this name in executeTool and routes it through handleSubAgent rather than the registered ToolExecutor (whose Execute is a defensive no-op).

Variables

View Source
var (
	// MaxToolOutputBytes is the byte threshold at which truncation kicks in.
	MaxToolOutputBytes = 50000

	// MaxToolOutputLines is the line threshold at which truncation kicks in.
	// Whichever threshold (bytes or lines) fires first triggers truncation.
	MaxToolOutputLines = 600

	// ToolOutputHeadLines is the number of lines preserved verbatim at the
	// HEAD of a truncated output. Tail uses the same count by default.
	ToolOutputHeadLines = 200

	// ToolOutputTailLines is the number of lines preserved verbatim at the
	// TAIL of a truncated output.
	ToolOutputTailLines = 200
)

Tool-output shaping (Phase 2 item K). Large raw outputs from bash / grep / file_read inflate the parent context and degrade subsequent model turns. shapeToolOutput applies a head+tail truncation with an explicit marker so the model knows content was dropped, while preserving the most informative edges of the output. It also annotates the output's Metadata with line and byte counts so the model can reason about size without re-counting.

Configurable thresholds: kept as package-level vars (not consts) so a host can tune them per environment or via tests. The defaults are tuned for Claude's context window: 50 KB / ~12k tokens is comfortable; 600 lines is enough to capture most useful tool output without overwhelming the parent.

View Source
var DefaultCompactionSummarizePrompt = `` /* 302-byte string literal not displayed */

DefaultCompactionSummarizePrompt is the prompt body used by the compaction path when summarising dropped conversation turns. The token `{{transcript}}` is substituted with the rendered transcript at call time. As with the sub- agent prompt above this is a VARIABLE so rysh-cli can replace it from the embedded rysh-cli-agent-prompts/system_compaction_summarize.md file.

View Source
var DefaultGroundingPromptAdvisory = `` /* 1002-byte string literal not displayed */

DefaultGroundingPromptAdvisory is the grounding protocol appended to the system prompt in GroundingPrompt and GroundingEnforced modes. A VARIABLE (not const) so rysh-cli can replace it from an embedded prompt file, like DefaultSubAgentSystemPrompt.

View Source
var DefaultGroundingPromptEnforced = DefaultGroundingPromptAdvisory + `

The grounding gate is ACTIVE for this task: only read-only exploration tools
are available until you call grounding_report. When the request maps to
concrete files (or plainly needs no codebase context), call
grounding_report{understood: true, relevant_files: [...], evidence: "..."} to
unlock all tools and proceed. If you cannot locate what the request refers
to, call grounding_report{understood: false, question: "..."} — the question
is shown to the user and the session pauses until they reply.`

DefaultGroundingPromptEnforced extends the advisory protocol with the gate contract. Also a VARIABLE for host override.

View Source
var DefaultSubAgentSystemPrompt = `` /* 456-byte string literal not displayed */

DefaultSubAgentSystemPrompt is used when a sub_agent tool call doesn't supply its own `system_prompt`. It is a VARIABLE (not const) so that the hosting binary — rysh-cli — can replace its content at startup from the embedded rysh-cli-agent-prompts/system_sub_agent.md file. The fallback below keeps the package self-contained for tests and library callers.

View Source
var GroundingAllowedTools = map[string]bool{
	"glob":            true,
	"grep":            true,
	"symbol_search":   true,
	"file_read":       true,
	"bash":            true,
	"list_tools":      true,
	"env_read":        true,
	"web_search":      true,
	"web_fetch":       true,
	"pane_inspect":    true,
	"session_history": true,
	"agents_list":     true,
	"ask_user":        true,
	"context":         true,
	"todo":            true,
	GroundingToolName: true,
}

GroundingAllowedTools is the read-only exploration set permitted while the grounding gate is closed. Everything else returns a permission error telling the model to ground first. bash is included because its own approval allowlist already gates mutating commands behind explicit user consent; ask_user is included so the model can ask mid-grounding. The set is a package variable so hosts can extend it (e.g. read-only MCP tools).

View Source
var GroundingReadOnlyBrowserActions = map[string]bool{
	"get_text":     true,
	"get_html":     true,
	"get_elements": true,
	"get_value":    true,
	"get_tabs":     true,
	"screenshot":   true,
	"wait":         true,
}

GroundingReadOnlyBrowserActions is the subset of browser_action actions permitted while the grounding gate is closed: a web-mode pane must be able to OBSERVE the page (read text/elements, screenshot, inspect tabs) to ground itself, while mutating actions (click, type, navigate, execute_js…) stay locked until grounding_report. Package variable so hosts can extend.

View Source
var NewSubAgentOrchID = func() string { return uuid.New().String() }

NewSubAgentOrchID returns a UUID for a child orchestrator. Centralised so tests can swap it out if needed.

Functions

func ApprovalRequestSubject

func ApprovalRequestSubject(paneID string) string

ApprovalRequestSubject returns the NATS subject for approval requests.

func ApprovalResponseSubject

func ApprovalResponseSubject(paneID string) string

ApprovalResponseSubject returns the NATS subject for approval responses.

func BuildSubAgentRegistry

func BuildSubAgentRegistry(parent *tools.ToolRegistry, allowedTools []string) *tools.ToolRegistry

BuildSubAgentRegistry returns a new tools.ToolRegistry containing only the tools the sub-agent is allowed to use. If allowedTools is empty, the full parent registry is cloned (so the child inherits everything except the hard-coded depth cap on nested sub_agent calls).

Unknown tool names in allowedTools are silently skipped — surfacing them as errors would force the model to know the exact registry contents, which it doesn't.

func BuildSubAgentSeedConversation

func BuildSubAgentSeedConversation(p *SubAgentParams) []provider.ConversationTurn

BuildSubAgentSeedConversation constructs the single-user-turn conversation the child orchestrator starts with. Task and optional context are joined with a labelled separator so the child can distinguish them.

func CloseDanglingToolCalls

func CloseDanglingToolCalls(turns []provider.ConversationTurn, note string) []provider.ConversationTurn

CloseDanglingToolCalls heals the TAIL of a conversation: when the last assistant turn carries tool_use blocks that have no matching tool_result turns (the run was interrupted mid-tool), synthetic error results are appended so the Messages API accepts the transcript on resume. The synthetic results are categorized "tool" with the interrupted note, so the model knows the calls never ran and can re-issue them.

func FormatSubAgentSummary

func FormatSubAgentSummary(done *msg.MsgOrchestratorDone) string

FormatSubAgentSummary renders a child orchestrator's MsgOrchestratorDone as the human-readable Content of a tools.ToolOutput. Only the final summary (plus terse counts of files-changed and errors) is included — by design, the child's tool transcript is not surfaced to the parent.

func LastGroundingReport

func LastGroundingReport(turns []provider.ConversationTurn) *msg.GroundingReportInfo

LastGroundingReport scans the conversation backwards for the most recent grounding_report tool CALL (on an assistant turn) and returns its parsed projection for status display. The call's params — not the tool-result ack — carry the substance (files, evidence, question). Returns nil when the session has no grounded run yet.

func SetApprovalPolicy

func SetApprovalPolicy(f ApprovalPolicyFunc)

SetApprovalPolicy installs the approval policy (nil clears it). Called by rysh-cli's WorkspaceActor on policy load and on every ##policy reload.

func ShouldAutoRetry

func ShouldAutoRetry(kind string) bool

ShouldAutoRetry returns true when a tool error of the given kind is worth a one-shot transparent retry by the orchestrator. Today only `transient` qualifies — adding kinds requires a behavioural argument, not just a categorisation one. Follow-up item 4.

func SubAgentCallsFromConversation added in v0.2.11

func SubAgentCallsFromConversation(conv []provider.ConversationTurn) []provider.SubAgentCall

SubAgentCallsFromConversation lists the tool calls recorded in a child orchestrator's conversation, in call order: every assistant ToolCalls entry, matched to its result turn by ToolCallID. A call with no result turn (the child stopped mid-tool) has Ended false. A nested sub_agent call carries the SubAgentCalls of its own result turn, so the list is recursive.

func ValidGroundingMode

func ValidGroundingMode(s string) bool

ValidGroundingMode reports whether s is a recognised grounding mode.

Types

type AgentConfig

type AgentConfig struct {
	// APIKey is the Anthropic API key.
	APIKey string

	// APIURL is the base URL for the Anthropic Messages API.
	// Defaults to "https://api.anthropic.com" if empty.
	APIURL string

	// DefaultModel is the model name to use.
	// Empty means the provider's own default (provider.DefaultClaudeModel);
	// this comment used to name a dated model id that the API has since
	// retired, which is exactly why the value is not repeated here.
	DefaultModel string

	// MaxTokens is the maximum number of tokens per response.
	// Defaults to 4096 if zero.
	MaxTokens int

	// SystemPrompt is the system prompt text (or file path) for the agent.
	SystemPrompt string

	// BraveAPIKey is the API key for the Brave Search tool.
	BraveAPIKey string

	// MaxIterations is the maximum number of orchestrator loop iterations.
	// Defaults to 20 if zero.
	MaxIterations int
}

AgentConfig holds the configuration needed to run an LLMPromptExecutionActor. Both the CLI (rysh-cli) and the server (rysh-server) populate this struct from their respective configuration systems before passing it to NewLLMPromptExecutionActor. This keeps the shared package free of any host-specific configuration dependency.

type ApprovalManager

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

ApprovalManager handles sending approval requests and receiving responses. It is used by the TUI to publish user decisions back to the orchestrator.

The TUI calls PublishApproval when the user makes a decision on a pending approval request (displayed as a diff or confirmation prompt).

func NewApprovalManager

func NewApprovalManager(nc *nats.Conn, pub *msg.NATSPublisher, paneID string) *ApprovalManager

NewApprovalManager creates an ApprovalManager for a specific pane.

func (*ApprovalManager) PublishApproval

func (am *ApprovalManager) PublishApproval(requestID string, decision msg.ApprovalDecision, reason string)

PublishApproval publishes an approval response to the orchestrator.

func (*ApprovalManager) SubscribeApprovalRequests

func (am *ApprovalManager) SubscribeApprovalRequests(callback func(*msg.MsgApprovalRequest)) (*nats.Subscription, error)

SubscribeApprovalRequests subscribes to approval requests for this pane. The callback is invoked for each request, allowing the TUI to render the approval prompt.

type ApprovalMemory added in v0.2.6

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

ApprovalMemory is the session's "approve all like this" registry: the set of tool+context keys a human answered `yes_always` to (buildApprovalKey).

It is a shared, guarded object rather than a plain map because of WHO touches it and WHEN. The registry belongs to the pane's long-lived LLMPromptExecutionActor, but the answers arrive inside an OrchestratorActor — a fresh one per prompt, running on its own goroutine.

It used to be a `map[string]bool` COPIED into each orchestrator at construction. Every `yes_always` was therefore written to a copy that was discarded when the turn ended, and the next turn started from the actor's map, which nothing ever wrote to (`SetAutoApproval` had no callers). The registry the code described as session-scoped lasted exactly one prompt: answer "Always", and the same dialog is back on the next turn. In the desktop app, with an agent touching a dozen files, that is a dozen dialogs that never stop coming.

Sharing the object fixes the lifetime; the mutex is what makes sharing it legal, since the orchestrator's goroutine is not the actor's.

Every method is nil-safe: a nil *ApprovalMemory reads as empty and swallows writes, so an orchestrator constructed without one (tests, a headless caller) behaves exactly as before.

func ApprovalMemoryFrom added in v0.2.6

func ApprovalMemoryFrom(seed map[string]bool) *ApprovalMemory

ApprovalMemoryFrom builds a registry seeded from a plain map, for callers that still hold one.

func NewApprovalMemory added in v0.2.6

func NewApprovalMemory() *ApprovalMemory

NewApprovalMemory returns an empty registry.

func (*ApprovalMemory) Approve added in v0.2.6

func (m *ApprovalMemory) Approve(key string)

Approve records a `yes_always` answer. It applies from the next tool call on, for the life of the pane's execution actor — not just the current turn.

func (*ApprovalMemory) Approved added in v0.2.6

func (m *ApprovalMemory) Approved(key string) bool

Approved reports whether key was answered `yes_always` in this session.

func (*ApprovalMemory) Len added in v0.2.6

func (m *ApprovalMemory) Len() int

Len returns how many keys are remembered. Diagnostics and tests.

type ApprovalPolicyFunc

type ApprovalPolicyFunc func(toolName string, input json.RawMessage) (PolicyDecision, string)

ApprovalPolicyFunc evaluates one tool call (name + raw JSON input) against policy-as-code and returns a decision plus the matched rule ID (e.g. "bash.deny[1]") for traceability. Returns (PolicyDefault, "") when no rule matches.

type GroundingReportParams

type GroundingReportParams struct {
	// Understood: true when the request now maps to concrete files/symbols
	// (or needs no codebase context at all).
	Understood bool `json:"understood"`
	// RelevantFiles lists the files the understanding is grounded on.
	RelevantFiles []string `json:"relevant_files,omitempty"`
	// Evidence is a one-paragraph summary of what was found and where.
	Evidence string `json:"evidence,omitempty"`
	// MissingInfo describes what could not be located (understood=false).
	MissingInfo string `json:"missing_info,omitempty"`
	// Question is the question to surface to the user (understood=false) —
	// typically "where does X live?".
	Question string `json:"question,omitempty"`
}

GroundingReportParams mirrors the JSON schema of the grounding_report tool.

func ParseGroundingReport

func ParseGroundingReport(raw json.RawMessage) (*GroundingReportParams, error)

ParseGroundingReport decodes a grounding_report tool-call payload.

type LLMPromptExecutionActor

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

LLMPromptExecutionActor is the top-level agent manager for a pane. It receives user prompts, maintains conversation history, spawns orchestrators, and relays output to the pane's data-plane subjects.

func NewLLMPromptExecutionActor

func NewLLMPromptExecutionActor(
	paneID string,
	maxIterations int,
	pub *msg.NATSPublisher,
	nc *nats.Conn,
	prov provider.AgenticProvider,
	toolRegistry *tools.ToolRegistry,
	systemPrompt string,
	pipelineOutputSubject string,
	chatOutputPaneID string,
	kvStore nats.KeyValue,
) *LLMPromptExecutionActor

NewLLMPromptExecutionActor creates a new LLMPromptExecutionActor for the given pane.

func (*LLMPromptExecutionActor) Conversation

Conversation returns the current conversation history (for persistence).

func (*LLMPromptExecutionActor) InboxSubject

func (a *LLMPromptExecutionActor) InboxSubject() string

InboxSubject returns the NATS subject this actor listens on.

func (*LLMPromptExecutionActor) Receive

func (a *LLMPromptExecutionActor) Receive(ctx actor.Context)

Receive handles messages for the LLMPromptExecutionActor.

func (*LLMPromptExecutionActor) Session

func (a *LLMPromptExecutionActor) Session() *SessionMemory

Session exposes the session memory (turns + pause checkpoint). Hosts use it for diagnostics; mutation goes through actor messages only.

func (*LLMPromptExecutionActor) SetAgentName

func (a *LLMPromptExecutionActor) SetAgentName(name string)

SetAgentName marks this actor as serving a named autonomous agent or humanoid, so its usage records are attributed to that agent (design 003 "by agent"). Regular panes never call this, so their records carry an empty AgentName.

func (*LLMPromptExecutionActor) SetAutoApproval

func (a *LLMPromptExecutionActor) SetAutoApproval(key string)

SetAutoApproval grants automatic approval for a tool+context combination.

func (*LLMPromptExecutionActor) SetAutoApproveAll

func (a *LLMPromptExecutionActor) SetAutoApproveAll(b bool)

SetAutoApproveAll enables headless auto-approval for every orchestrator this actor spawns: all tool calls run without an approval prompt. Used for humanoids/agents that have no human approver.

func (*LLMPromptExecutionActor) SetContextTokenLimit

func (a *LLMPromptExecutionActor) SetContextTokenLimit(limit int)

SetContextTokenLimit overrides the input-token budget used for context compaction. A value <= 0 resets it to DefaultContextTokenLimit.

func (*LLMPromptExecutionActor) SetCwdResolver

func (a *LLMPromptExecutionActor) SetCwdResolver(fn func() string)

SetCwdResolver installs a working-directory resolver threaded into every orchestrator this actor spawns. The resolver is evaluated at the start of each run; a non-empty result re-points all working-directory-aware tools at that path. Pass nil to disable (tools keep their default workDir).

func (*LLMPromptExecutionActor) SetEnvBlockProvider

func (a *LLMPromptExecutionActor) SetEnvBlockProvider(fn func() string)

SetEnvBlockProvider installs a per-turn environment-block provider appended to the effective system prompt before each prompt. Pass nil to disable.

func (*LLMPromptExecutionActor) SetGroundingMode

func (a *LLMPromptExecutionActor) SetGroundingMode(mode string)

SetGroundingMode selects the grounding protocol (GroundingOff / GroundingPrompt / GroundingEnforced) threaded into every orchestrator this actor spawns. See grounding.go.

func (*LLMPromptExecutionActor) SetMetricsSink

func (a *LLMPromptExecutionActor) SetMetricsSink(s MetricsSink)

SetMetricsSink installs a metrics sink that's threaded into every orchestrator this actor spawns. Pass nil to disable recording on future runs. Follow-up 3b.

func (*LLMPromptExecutionActor) SetScopeResolver

func (a *LLMPromptExecutionActor) SetScopeResolver(fn func(scopeHint string) *tools.ToolRegistry)

SetScopeResolver installs a per-prompt scope resolver. Given a prompt's ScopeHint it returns the parent registry this actor's tool registry should chain to for that run (so an agent inherits the invoking pane's scope). Pass nil to disable. Used for agents/humanoids; panes leave it nil.

func (*LLMPromptExecutionActor) SetSecretNAT

func (a *LLMPromptExecutionActor) SetSecretNAT(s secretnat.SessionHandle)

SetSecretNAT installs this conversation's SecretNAT session, threaded into every leg provider and every spawned orchestrator. Pass nil to disable.

type MetricsSink

type MetricsSink interface {
	// RecordTool fires after a tool's Execute returns (success or error).
	// dur is wall-clock elapsed; out is the result (may be nil on a Go
	// error before the tool produced a ToolOutput); err is the Go-level
	// error if any.
	RecordTool(name string, dur time.Duration, out *tools.ToolOutput, err error)

	// RecordLLMCall fires after each provider.CompleteWithTools[Stream]
	// returns. usage carries token accounting; cache hit ratio is
	// derivable from CacheReadInputTokens / TotalInputTokens.
	RecordLLMCall(model string, dur time.Duration, usage provider.Usage)

	// RecordCompaction fires after maybeCompact summarises and drops old
	// turns. droppedTurns is the number of original turns replaced.
	RecordCompaction(droppedTurns int, dur time.Duration)

	// RecordApproval fires for every approval decision (yes / yes_always
	// / no / no_with_reason / timeout).
	RecordApproval(toolName, decision string, dur time.Duration)
}

Follow-up item 3 — observability hooks for the agentic system.

MetricsSink is the small interface the orchestrator and Setup call into when significant events happen: a tool finishes, an LLM call returns, compaction runs, an approval decision lands. Implementations are expected to be cheap (return quickly; do real work in the background).

rysh-cli ships a Prometheus implementation under rysh-cli/internal/metrics/; every other binary / test uses the NoopMetricsSink default so nothing breaks when metrics aren't wired up.

type NoopMetricsSink

type NoopMetricsSink struct{}

NoopMetricsSink is the default; safe to use everywhere. Production rysh-cli replaces it with a Prometheus-backed implementation at startup.

func (NoopMetricsSink) RecordApproval

func (NoopMetricsSink) RecordApproval(string, string, time.Duration)

func (NoopMetricsSink) RecordCompaction

func (NoopMetricsSink) RecordCompaction(int, time.Duration)

func (NoopMetricsSink) RecordLLMCall

func (NoopMetricsSink) RecordLLMCall(string, time.Duration, provider.Usage)

func (NoopMetricsSink) RecordTool

type OrchestratorActor

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

OrchestratorActor runs the agentic loop for a single user request. It calls the LLM, dispatches tool calls, handles approvals, and loops until the task is complete or limits are reached.

func NewOrchestratorActor

func NewOrchestratorActor(
	id string,
	paneID string,
	pub *msg.NATSPublisher,
	nc *nats.Conn,
	prov provider.AgenticProvider,
	toolRegistry *tools.ToolRegistry,
	conversation []provider.ConversationTurn,
	systemPrompt string,
	autoApproved *ApprovalMemory,
	ctx context.Context,
	maxIterations int,
	pipelineOutputSubject string,
	chatOutputPaneID string,
	approvalPaneGroups []string,
	paneName string,
	contextTokenLimit int,
	subAgentDepth int,
	agentName string,
) *OrchestratorActor

NewOrchestratorActor creates an orchestrator for a single task.

func (*OrchestratorActor) Receive

func (o *OrchestratorActor) Receive(ctx actor.Context)

Receive handles messages for the orchestrator.

func (*OrchestratorActor) SetAutoApproveAll

func (o *OrchestratorActor) SetAutoApproveAll(b bool)

SetAutoApproveAll enables headless auto-approval: every tool call is treated as pre-approved, so no approval request is published and no approval pane is spawned. Used for humanoids/agents that have no human approver.

func (*OrchestratorActor) SetCwdResolver

func (o *OrchestratorActor) SetCwdResolver(fn func() string)

SetCwdResolver installs the working-directory resolver. When set, runLoop evaluates it once at the start of the run and re-points working-directory aware tools at the returned path (the pane's live shell cwd).

func (*OrchestratorActor) SetGroundingMode

func (o *OrchestratorActor) SetGroundingMode(mode string)

SetGroundingMode selects the grounding protocol for this run: GroundingOff, GroundingPrompt (advisory system-prompt section), or GroundingEnforced (read-only tool gate until grounding_report{understood:true}). See grounding.go.

func (*OrchestratorActor) SetMaxContextTokens

func (o *OrchestratorActor) SetMaxContextTokens(n int)

SetMaxContextTokens installs a cumulative token cap: the loop pauses with StoppedReasonMaxTokens once this leg's total token usage (input+output) reaches n. The parent passes the run's remaining budget. 0 disables the cap.

func (*OrchestratorActor) SetMaxDuration

func (o *OrchestratorActor) SetMaxDuration(d time.Duration)

SetMaxDuration installs a wall-clock cap on the agentic loop. 0 disables the cap (iteration limit remains the only fail-safe). Phase 3 smaller-wins.

func (*OrchestratorActor) SetMetricsSink

func (o *OrchestratorActor) SetMetricsSink(s MetricsSink)

SetMetricsSink installs a metrics sink on this orchestrator. Pass nil to fall back to NoopMetricsSink. Follow-up item 3.

func (*OrchestratorActor) SetPermissionPolicy

func (o *OrchestratorActor) SetPermissionPolicy(p *PermissionPolicy)

SetPermissionPolicy installs a fine-grained tool-permission policy. Pass nil to remove it. Phase 3 N.

func (*OrchestratorActor) SetSecretNAT

func (o *OrchestratorActor) SetSecretNAT(s secretnat.SessionHandle)

SetSecretNAT installs the SecretNAT session for this run. Pass nil to disable. The provider decorator (secretnat.Wrap) handles the HTTP boundary; these hooks handle the tool seam and KV hygiene.

type OrchestratorPhase

type OrchestratorPhase string

OrchestratorPhase represents the current phase of the orchestrator loop.

const (
	PhasePlanning        OrchestratorPhase = "planning"
	PhaseExecuting       OrchestratorPhase = "executing"
	PhaseWaitingApproval OrchestratorPhase = "waiting_approval"
	PhaseEvaluating      OrchestratorPhase = "evaluating"
	PhaseDone            OrchestratorPhase = "done"
	PhaseError           OrchestratorPhase = "error"
	PhaseCompacting      OrchestratorPhase = "compacting"
	// PhasePaused: the run stopped without finishing (user interrupt via
	// double-Ctrl+C, iteration cap, or wall-clock cap) but its state is fully
	// preserved in session memory — "continue" resumes from this checkpoint.
	PhasePaused OrchestratorPhase = "paused"
	// PhaseGrounding: the run is reading the codebase like a human before
	// acting — only read-only exploration tools are allowed until the model
	// calls grounding_report{understood: true}. See grounding.go.
	PhaseGrounding OrchestratorPhase = "grounding"
)

type PatchKind

type PatchKind string

PatchKind classifies a target file in a unified diff.

const (
	PatchModify PatchKind = "modify"
	PatchCreate PatchKind = "create"
	PatchDelete PatchKind = "delete"
)

type PatchTarget

type PatchTarget struct {
	Path string
	Kind PatchKind
}

PatchTarget is one file touched by a unified diff.

func ParsePatchTargets

func ParsePatchTargets(patch string) ([]PatchTarget, error)

ParsePatchTargets scans a unified diff and returns the set of files it touches with their classification. Both standard `a/`/`b/` prefixes and `--no-prefix` form are accepted. Lines that don't match a header pair are ignored — this is a *best-effort* extractor; the executor remains responsible for validating that the patch actually applies.

Behavior:

  • "--- a/x" + "+++ b/x" → modify(x)
  • "--- /dev/null" + "+++ b/x" → create(x)
  • "--- a/x" + "+++ /dev/null" → delete(x)
  • any other pairing → skipped

Returns an empty slice (not nil) when the patch is empty or has no recognised headers. Errors are reserved for genuine parse failures (e.g. a `+++` with no preceding `---`).

type PermDecision

type PermDecision int

PermDecision is one of the three policy outcomes.

const (
	PermAsk   PermDecision = iota // default: fall through to the existing approval flow
	PermAllow                     // bypass the approval prompt, run the tool
	PermDeny                      // refuse the tool call with a structured error
)

type PermissionPolicy

type PermissionPolicy struct {
	Rules []PermissionRule
}

PermissionPolicy is an ordered list of rules. First match wins; if no rule matches, the default decision is PermAsk.

func NewPermissionPolicy

func NewPermissionPolicy(rules []PermissionRule) *PermissionPolicy

NewPermissionPolicy constructs a policy from a slice of rules. The caller's slice is COPIED so later mutations of the source don't surprise the orchestrator.

func (*PermissionPolicy) Decide

func (p *PermissionPolicy) Decide(toolName string, params json.RawMessage) PermDecision

Decide returns the decision for a tool call. params is the raw JSON the model emitted; the policy extracts a primary string field (see permissionPath) to glob against. Empty / nil policy returns PermAsk.

type PermissionRule

type PermissionRule struct {
	Tool     string       // tool name (exact); "" matches any tool
	Match    string       // glob over the primary path/command; "" matches any
	Decision PermDecision // allow / deny / ask
}

PermissionRule is a single match-and-decide entry. Tool must match exactly; Match is a doublestar-friendly glob over the tool's primary path / command argument (interpreted via PathMatch below). An empty Match means "any argument" so a tool-only rule applies broadly.

type PolicyDecision

type PolicyDecision int

PolicyDecision is the outcome of consulting the approval policy for one call.

const (
	// PolicyDefault — no rule matched; fall back to the tool's own classifier.
	PolicyDefault PolicyDecision = iota
	// PolicyAutoApprove — a permissive rule matched; skip approval.
	PolicyAutoApprove
	// PolicyGate — a restrictive rule matched; force approval. This OVERRIDES
	// the per-session "approve all like this" registry and headless
	// auto-approve, so an unattended humanoid still gates a policy-gated call.
	PolicyGate
)

type ReadTracker

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

ReadTracker remembers which files were read during an orchestrator run. Goroutine-safe: ALL access goes through ReadTracker methods, and the orchestrator's runLoop is single-threaded so no external locking is required (consistent with the rest of the orchestrator state).

func NewReadTracker

func NewReadTracker() *ReadTracker

NewReadTracker returns an empty tracker. Each orchestrator creates one at construction time.

func (*ReadTracker) Forget

func (t *ReadTracker) Forget(path string)

Forget removes a path from the tracker (e.g. after a write to a brand-new location). Useful when a tool re-creates a file from scratch.

func (*ReadTracker) Record

func (t *ReadTracker) Record(path string)

Record stamps the current state of path into the tracker. Called after a successful file_read. Errors are silently ignored — the tracker is best- effort; a record we couldn't capture just means no stale-edit check will fire on that path until the next successful read.

func (*ReadTracker) Refresh

func (t *ReadTracker) Refresh(path string)

Refresh re-stamps a path. Called after a successful edit so the next edit on the same file doesn't immediately re-fire the stale check.

type SessionMemory

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

SessionMemory is the single owner of a user session's conversational state for one LLM execution actor (pane, agent, or humanoid). It consolidates what used to be scattered across the actor: the categorized conversation turns, the pause/resume checkpoint, and throttled KV persistence.

Responsibilities:

  • Turns: append / replace / bound-and-heal the categorized conversation.
  • Checkpoint: a paused run (double-Ctrl+C, iteration cap, wall-clock cap) records Paused + PausedReason; "continue" resumes from the same turns.
  • Persistence: the full state (turns + checkpoint) is written to JetStream KV under "<owner>.llm_session" (wrapper format, versioned) and the bare turns array is mirrored to the legacy "<owner>.llm_conversation" key so pre-existing readers (PaneActor restore) keep working.

SessionMemory is NOT goroutine-safe by design: it is owned by a single actor and only touched from that actor's Receive (proto.actor guarantees sequential processing).

func NewSessionMemory

func NewSessionMemory(ownerID string, kv nats.KeyValue) *SessionMemory

NewSessionMemory creates a SessionMemory for the given owner (pane / agent / humanoid id). kv may be nil (no persistence — e.g. tests).

func (*SessionMemory) Append

Append appends an arbitrary turn (already categorized by the producer).

func (*SessionMemory) AppendUserTurn

func (s *SessionMemory) AppendUserTurn(content string, blocks []provider.ContentBlock)

AppendUserTurn appends a categorized user turn (Category "user") stamped with the current time.

func (*SessionMemory) Bound

func (s *SessionMemory) Bound()

Bound trims the window to the retained turn budget and heals both ends: the head never starts with an orphaned tool_result and the tail never ends with an unanswered tool_use (synthetic interrupted results are appended for those — required for a paused run to resume cleanly).

func (*SessionMemory) ClearPause

func (s *SessionMemory) ClearPause()

ClearPause marks the session as no longer paused (a new prompt or an explicit continue arrived).

func (*SessionMemory) GroundingOverride

func (s *SessionMemory) GroundingOverride() string

GroundingOverride returns the persisted ##grounding override ("" = none).

func (*SessionMemory) Len

func (s *SessionMemory) Len() int

Len returns the number of retained turns.

func (*SessionMemory) Pause

func (s *SessionMemory) Pause(reason string)

Pause records the resumable checkpoint: the reason the run stopped. The turns already in memory ARE the checkpoint — nothing else is needed to resume, because the orchestrator loop is stateless between LLM calls.

func (*SessionMemory) Paused

func (s *SessionMemory) Paused() (bool, string)

Paused reports whether the session holds a resumable checkpoint, and why.

func (*SessionMemory) Persist

func (s *SessionMemory) Persist(force bool)

Persist writes the state to KV. Writes are throttled to at most one per minWriteInterval unless force is true (shutdown / pause — moments where losing state would break resume).

func (*SessionMemory) Replace

func (s *SessionMemory) Replace(turns []provider.ConversationTurn)

Replace swaps the full conversation (used when an orchestrator returns its merged transcript). Turns missing a category are back-filled from their role so pre-categorisation transcripts stay well-formed.

func (*SessionMemory) Restore

func (s *SessionMemory) Restore() bool

Restore loads state from KV, preferring the wrapper format and falling back to the legacy bare-array key. Returns true when anything was restored.

The wrapper's settings (grounding override) are restored even when it holds no turns yet — an override set on a fresh pane must survive a restart. The legacy bare-array key is only consulted for turns the wrapper didn't have.

func (*SessionMemory) SetGroundingOverride

func (s *SessionMemory) SetGroundingOverride(mode string)

SetGroundingOverride records (or clears, with "") the user's explicit ##grounding mode override for this pane. Persisted with the session so it survives daemon restarts.

func (*SessionMemory) Turns

Turns returns the current conversation window. Callers must not mutate the returned slice's backing array; use Append/Replace instead.

type SessionMemoryState

type SessionMemoryState struct {
	Version      int                         `json:"version"`
	Turns        []provider.ConversationTurn `json:"turns"`
	Paused       bool                        `json:"paused,omitempty"`
	PausedReason string                      `json:"paused_reason,omitempty"`
	// GroundingOverride is the user's explicit ##grounding mode override
	// ("" = none). Additive field: wrappers persisted before it existed
	// simply restore with no override.
	GroundingOverride string `json:"grounding_override,omitempty"`
	UpdatedMs         int64  `json:"updated_ms"`
}

SessionMemoryState is the persisted JSON wrapper.

type SubAgentParams

type SubAgentParams struct {
	Task         string   `json:"task"`
	Context      string   `json:"context,omitempty"`
	SystemPrompt string   `json:"system_prompt,omitempty"`
	AllowedTools []string `json:"allowed_tools,omitempty"`
}

SubAgentParams mirrors the JSON schema exposed by the sub_agent tool. It is declared in the shared package so the orchestrator can parse the model's tool input directly without a runtime dependency on rysh-cli.

func ParseSubAgentParams

func ParseSubAgentParams(raw json.RawMessage) (*SubAgentParams, error)

ParseSubAgentParams decodes a sub_agent tool-call payload and validates minimum required fields.

Jump to

Keyboard shortcuts

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