Documentation
¶
Index ¶
- func ApproxTextTokens(value string) int
- func BuildSystemPromptPreview(options Options) string
- func Compact(messages []zeroruntime.Message, opts CompactionOptions) ([]zeroruntime.Message, error)
- func IsNoProgressStop(content string) bool
- func NewFileDiagnostics(manager *lsp.Manager, workspaceRoot string) func(context.Context, string) string
- func NewLSPDiagnosticsChecker(manager *lsp.Manager) diagnosticsChecker
- func NewProjectVerifier(workspaceRoot string) projectVerifier
- func ToolAdvertised(tool tools.Tool, permissionMode PermissionMode) bool
- func ToolAllowedByFilters(name string, enabledTools []string, disabledTools []string) bool
- func ToolVisible(tool tools.Tool, permissionMode PermissionMode, enabledTools []string, ...) bool
- type AskUserQuestion
- type AskUserRequest
- type AskUserResponse
- type CompactionOptions
- type CompactionResult
- type ContextBreakdown
- type CorrectionReport
- type DenialCategory
- type Message
- type Options
- type Outcome
- type PermissionAction
- type PermissionDecision
- type PermissionDecisionAction
- type PermissionEvent
- type PermissionMode
- type PermissionRequest
- type Provider
- type Result
- type SelfCorrectConfig
- type SelfCorrector
- type SkillInfo
- type SpecialistInfo
- type StopReason
- type ToolCall
- type ToolResult
- type Usage
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ApproxTextTokens ¶
ApproxTextTokens estimates the token count of text WITHOUT a real tokenizer. The BPE tokenizers zero targets fold a run's leading space into the following token rather than emitting whitespace as its own token, so naive len/4 overcounts real text by ~15-20% (measured: a 24.8k-char prompt counted 5.2k real tokens where len/4 said 6.2k). Counting NON-whitespace bytes / 4 tracks the provider's actual count closely (validated against live usage) while staying allocation- and dependency-free. zero still receives the exact count back as usage on every request; this estimate is only for the pre-request context budget preview and the compaction threshold.
func BuildSystemPromptPreview ¶
BuildSystemPromptPreview returns the same system prompt a run would seed for the supplied options. It is intended for non-runtime reporting surfaces that need to estimate prompt footprint without starting a provider call.
func Compact ¶
func Compact(messages []zeroruntime.Message, opts CompactionOptions) ([]zeroruntime.Message, error)
Compact summarizes the oldest middle of a conversation, keeping the leading system message(s) and the most recent turns verbatim. The result is:
[system..., summaryAsUser, preservedSuffix...]
Rules:
- Leading system messages stay at the head untouched.
- The preserved suffix is widened backward so it never begins with a tool/tool_result message (provider APIs reject a dangling tool result with no preceding assistant tool call).
- The summary is injected as a single user-role message labeled with summaryLabel.
- If there is nothing to summarize (too few messages once system and the preserved suffix are removed), the input is returned unchanged.
Compact is pure: it performs no provider I/O. A Summarize error is returned to the caller, which decides how to recover.
func IsNoProgressStop ¶
IsNoProgressStop reports whether content IS the no-output guardrail stop answer (a run that produced no visible text and no tool calls). It matches the EXACT structure noOutputStopAnswer emits — prefix + "<int> turns " + marker + " " + suffix, where only the integer turn count varies — rather than just looking for the three parts in order. A loose check (prefix && contains-marker && suffix) would misclassify a genuine assistant/tool message that merely quotes the marker amid other prose, which would wrongly hide a real session from /resume and skip its title generation.
func NewFileDiagnostics ¶ added in v0.2.0
func NewFileDiagnostics(manager *lsp.Manager, workspaceRoot string) func(context.Context, string) string
NewFileDiagnostics adapts an *lsp.Manager to the per-edit inline diagnostics callback (tools.RunOptions.Diagnostics): it reads the just-written file, checks it against the file's language server, and formats error-severity diagnostics for the model. Warnings and hints are excluded — nagging about style on every edit is noise, while a type error the edit just introduced is exactly what the model should see before its next step. Diagnostics are rendered with workspace-relative paths: the absolute path would put the local username/home directory into the model prompt and session transcript on every edit. Returns nil when manager is nil, disabling inline diagnostics entirely.
func NewLSPDiagnosticsChecker ¶
NewLSPDiagnosticsChecker wraps an *lsp.Manager for use as a SelfCorrector checker. Returns nil when manager is nil so self-correct cleanly skips LSP.
func NewProjectVerifier ¶
func NewProjectVerifier(workspaceRoot string) projectVerifier
NewProjectVerifier builds the default verifier rooted at workspaceRoot.
func ToolAdvertised ¶
func ToolAdvertised(tool tools.Tool, permissionMode PermissionMode) bool
func ToolAllowedByFilters ¶
func ToolVisible ¶
Types ¶
type AskUserQuestion ¶
type AskUserQuestion struct {
Question string `json:"question"`
Header string `json:"header,omitempty"`
Options []string `json:"options,omitempty"`
OptionDescriptions []string `json:"optionDescriptions,omitempty"`
Recommended string `json:"recommended,omitempty"`
MultiSelect bool `json:"multiSelect,omitempty"`
}
AskUserQuestion is one clarifying question the agent wants answered. Options are optional suggested answers an interactive front-end can render as a picker; Recommended (when set) is the suggested default — it should match one of Options. Header is an optional short tab title for a multi-question prompt (falls back to the question text). OptionDescriptions, when present, holds a one-line description per option aligned by index to Options (empty string = no description).
type AskUserRequest ¶
type AskUserRequest struct {
ToolCallID string `json:"toolCallId"`
Header string `json:"header,omitempty"`
Questions []AskUserQuestion `json:"questions"`
}
AskUserRequest is handed to OnAskUser when the model invokes the ask_user tool.
type AskUserResponse ¶
type AskUserResponse struct {
Answers []string `json:"answers"`
}
AskUserResponse carries the user's answers back to the loop, one per question.
type CompactionOptions ¶
type CompactionOptions struct {
// PreserveLast is the number of trailing messages to keep verbatim. The
// preserved suffix is widened (never shrunk) so it begins at a safe
// user/assistant boundary. <= 0 falls back to defaultCompactionPreserveLast.
PreserveLast int
// Summarize turns the to-be-elided middle into a single dense summary. It is
// injected so Compact stays pure and testable; the agent loop wires it to a
// real provider call.
Summarize func(toSummarize []zeroruntime.Message) (string, error)
}
CompactionOptions configure a single Compact call.
type CompactionResult ¶
type CompactionResult struct {
// Messages is the original conversation or the compacted replacement.
Messages []zeroruntime.Message
// RemovedCount is the number of original messages summarized away.
RemovedCount int
// PreservedCount is the number of original messages kept verbatim, including
// leading system messages and the preserved suffix.
PreservedCount int
// SummaryText is the trimmed text returned by the summarizer. The injected
// summary message also includes summaryLabel and any preserved structured
// state needed by later compactions.
SummaryText string
// Compacted reports whether Messages contains an injected summary.
Compacted bool
}
CompactionResult is the metadata-bearing result returned by CompactMessages.
func CompactMessages ¶
func CompactMessages(messages []zeroruntime.Message, opts CompactionOptions) (CompactionResult, error)
CompactMessages summarizes the oldest middle of a conversation and returns both the replacement messages and UI/session-friendly metadata about what changed. It uses the same compaction rules as Compact.
type ContextBreakdown ¶
type ContextBreakdown struct {
SystemTokens int // leading system messages (prompt + injected context)
ToolTokens int // advertised tool definitions
MessageTokens int // non-system conversation messages
TotalTokens int // SystemTokens + ToolTokens + MessageTokens
ContextWindow int // model context window; 0 when unknown
UsedFraction float64 // TotalTokens / ContextWindow; 0 when window unknown
}
ContextBreakdown is a per-category estimate of a request's token footprint.
func MeasureContext ¶
func MeasureContext(messages []zeroruntime.Message, tools []zeroruntime.ToolDefinition, contextWindow int) ContextBreakdown
MeasureContext estimates the per-category token footprint of a request: the leading system messages, the advertised tool definitions, and the remaining conversation messages.
type CorrectionReport ¶
type CorrectionReport struct {
LSPDiagnostics map[string][]lsp.Diagnostic // path -> error diagnostics
VerifyReport verify.Report
// InspectErrors holds non-degradable verification failures (an LSP
// startup/sync error, an unreadable changed file, or plan detection failing).
// These are distinct from diagnostics and must fail the pass rather than be
// silently swallowed into a false "passed".
InspectErrors []string
Failed bool
}
CorrectionReport is the unified result of a post-edit verification pass.
type DenialCategory ¶
type DenialCategory string
DenialCategory classifies why a tool call was blocked before it executed.
const ( DenialNone DenialCategory = "" DenialFiltered DenialCategory = "filtered" // tool not enabled for this run DenialPermissionDenied DenialCategory = "permission_denied" // approval declined DenialApprovalCanceled DenialCategory = "approval_canceled" // approval canceled and run aborted DenialSandboxBlock DenialCategory = "sandbox_block" // blocked by the sandbox DenialHookBlocked DenialCategory = "hook_blocked" // vetoed by a beforeTool hook )
type Message ¶
type Message = zeroruntime.Message
type Options ¶
type Options struct {
MaxTurns int
// DeferThreshold activates deferred MCP-tool loading: when the number of
// deferred-eligible visible tools is >= this value (and it is > 0), their
// full schemas are withheld and advertised as compact lines via tool_search.
// 0 (or below the eligible count) keeps every tool eager — byte-identical to
// the pre-deferral behavior.
DeferThreshold int
// Specialists lists the sub-agents the orchestrator may delegate to via the
// Task tool; when non-empty the system prompt gains a delegation section that
// names them and nudges the model to offload read-heavy work (search,
// exploration) so verbose tool output stays out of the main context. It is
// populated only where the Task tool is actually registered, so an empty slice
// (the default) reproduces the previous prompt byte-for-byte.
Specialists []SpecialistInfo
// Skills lists the reusable skills installed for this run (the default skills
// dir merged with any plugin skill roots). When non-empty the system prompt
// gains an <available_skills> block naming them so the model loads the right one
// via the skill tool on the first try. Empty (the default) reproduces the
// previous prompt byte-for-byte.
Skills []SkillInfo
// Specialist/sub-agent metadata is carried through exec now and consumed by
// the specialist runtime in later slices.
SessionID string
CallingSessionID string
CallingToolUseID string
Tag string
Depth int
SessionTitle string
ProviderName string
Model string
ReasoningEffort string
Cwd string
SystemPrompt string
// ResponseStyle is the operator-selected reply style from the TUI /style
// command (e.g. "concise", "explanatory", "review"). It is rendered into the
// system prompt as a short directive. Empty or "balanced" adds nothing — the
// prompt is then byte-identical to the pre-style behavior.
ResponseStyle string
// Images are optional image attachments to seed onto the initial user turn.
// nil for text-only runs (the seeded message then carries no images, exactly
// as before).
Images []zeroruntime.ImageBlock
// ContextWindow is the model's maximum input token budget. When > 0 the agent
// loop compacts long conversations once the estimated size crosses a fraction
// of this window. 0 DISABLES compaction entirely (every existing caller/test
// behaves identically).
ContextWindow int
// CompactionPreserveLast is how many trailing messages compaction keeps
// verbatim. <= 0 falls back to defaultCompactionPreserveLast.
CompactionPreserveLast int
Registry *tools.Registry
PermissionMode PermissionMode
Autonomy string
Sandbox *sandbox.Engine
// FileTracker records per-session file read/write versions so the write tools
// can detect a file changed on disk outside Zero since it was last read. nil
// disables the check. Created once per session and threaded into every tool run.
FileTracker *tools.FileTracker
// Hooks, when set, runs configured beforeTool (blocking) and afterTool
// (advisory) commands around each tool call. nil disables hooks entirely; a
// dispatcher built from an empty config is also a safe no-op.
Hooks *hooks.Dispatcher
EnabledTools []string
DisabledTools []string
OnText func(string)
OnReasoning func(string)
OnToolCall func(ToolCall)
// OnToolCallStart / OnToolCallDelta stream a tool call's arguments LIVE as the
// model generates them — OnToolCallStart on open (id, tool name), then
// OnToolCallDelta for each argument fragment. A surface can render the
// in-progress call (e.g. a file being written) instead of waiting for
// OnToolCall, which only fires once the whole call has accumulated. nil no-ops.
OnToolCallStart func(id, name string)
OnToolCallDelta func(id, fragment string)
OnPermissionRequest func(context.Context, PermissionRequest) (PermissionDecision, error)
OnPermission func(PermissionEvent)
OnAskUser func(context.Context, AskUserRequest) (AskUserResponse, error)
OnToolResult func(ToolResult)
OnUsage func(Usage)
// OnToolProgress, when set, is called with each stream-json event a
// specialist child process emits while running. The toolCallID identifies
// which Task tool call the progress belongs to. nil is a no-op.
OnToolProgress func(toolCallID string, event streamjson.Event)
// OnContext, when set, is called once per turn with the per-category context
// budget of the request about to be sent, so a surface (TUI/CLI) can show
// context utilization. Opt-in like the other callbacks; nil is a no-op.
OnContext func(ContextBreakdown)
// ModelSwitcher, when set, lets a tool escalate the run to a stronger model
// mid-run: the loop calls it with the requested model id and, on success,
// swaps the active provider and updates Options.Model for the rest of the
// run. nil DISABLES escalation entirely (the loop ignores any switch
// request), so every existing caller is unaffected. A returned error is
// non-fatal: the run continues on the current model.
ModelSwitcher func(ctx context.Context, modelID string) (Provider, error)
// SelfCorrect, when set, runs a post-edit verify-and-correct cycle after a
// mutating tool call: it verifies the changed files (LSP diagnostics + project
// tests) and feeds failures back to the model to fix, bounded by an attempt
// ceiling and the autonomy gate. nil disables it entirely (the loop is
// byte-identical to before). One instance per run — it holds attempt state.
SelfCorrect *SelfCorrector
// FileDiagnostics, when set, checks files changed by mutating tools for
// error-severity language diagnostics IN THE BACKGROUND and appends any
// errors as a nudge before the model's next request — the model still sees
// an error it introduced at its next decision point, but no tool call ever
// blocks on the language server (the old inline path stalled every edit on
// a ≥300ms debounce, 10s cap). Build one with NewFileDiagnostics. nil
// disables post-edit diagnostics.
FileDiagnostics func(ctx context.Context, absPath string) string
// RequireCompletionSignal gates run completion for HEADLESS exec. Without it,
// any assistant turn that produces text but no tool call is accepted as the
// final answer. With it, a no-tool-call turn is NOT treated as "done" while
// work clearly remains — pending update_plan items, or a message that ends on a
// continuation cue ("…Let me check the config:"). The loop then nudges the
// model to continue instead, bounded by maxContinueNudges (and still by
// MaxTurns and the run deadline); if the model keeps stalling, the run
// finalizes as INCOMPLETE (Result.Incomplete) rather than success. Default
// false leaves the loop byte-identical, so the interactive TUI is unaffected.
RequireCompletionSignal bool
// contains filtered or unexported fields
}
type Outcome ¶
type Outcome string
Outcome reports what a self-correct round decided, for observability/tests.
const ( OutcomeDisabled Outcome = "disabled" // self-correct off, or nothing to check OutcomePassed Outcome = "passed" // verification passed; nothing to do OutcomeCorrecting Outcome = "correcting" // failed; feedback issued for the model to fix OutcomeReported Outcome = "reported" // failed; low autonomy, reported but no auto-fix OutcomeAborted Outcome = "aborted" // failed but the correction budget is exhausted )
type PermissionAction ¶
type PermissionAction string
const ( PermissionActionAllow PermissionAction = "allow" PermissionActionPrompt PermissionAction = "prompt" PermissionActionDeny PermissionAction = "deny" PermissionActionCancel PermissionAction = "cancel" )
type PermissionDecision ¶
type PermissionDecision struct {
Action PermissionDecisionAction `json:"action"`
Reason string `json:"reason,omitempty"`
}
type PermissionDecisionAction ¶
type PermissionDecisionAction string
const ( PermissionDecisionAllow PermissionDecisionAction = "allow" PermissionDecisionAllowStrict PermissionDecisionAction = "allow_with_strict_auto_review" PermissionDecisionAllowForSession PermissionDecisionAction = "allow_for_session" PermissionDecisionAllowPrefix PermissionDecisionAction = "allow_prefix_for_session" PermissionDecisionAlwaysAllowPrefix PermissionDecisionAction = "always_allow_prefix" PermissionDecisionDeny PermissionDecisionAction = "deny" PermissionDecisionAlwaysAllow PermissionDecisionAction = "always_allow" PermissionDecisionCancel PermissionDecisionAction = "cancel" )
type PermissionEvent ¶
type PermissionEvent struct {
ToolCallID string `json:"toolCallId"`
ToolName string `json:"name"`
Action PermissionAction `json:"action"`
DecisionAction PermissionDecisionAction `json:"decisionAction,omitempty"`
Permission string `json:"permission"`
PermissionGranted bool `json:"permissionGranted,omitempty"`
PermissionMode PermissionMode `json:"permissionMode"`
Autonomy string `json:"autonomy,omitempty"`
SideEffect string `json:"sideEffect"`
Reason string `json:"reason,omitempty"`
Scope string `json:"scope,omitempty"`
DecisionReason string `json:"decisionReason,omitempty"`
Risk sandbox.Risk `json:"risk"`
Block *sandbox.Block `json:"block,omitempty"`
GrantMatched bool `json:"grantMatched,omitempty"`
Grant *sandbox.Grant `json:"grant,omitempty"`
CommandPrefix []string `json:"commandPrefix,omitempty"`
}
type PermissionMode ¶
type PermissionMode string
const ( PermissionModeAuto PermissionMode = "auto" PermissionModeAsk PermissionMode = "ask" PermissionModeUnsafe PermissionMode = "unsafe" PermissionModeSpecDraft PermissionMode = "spec-draft" // PermissionModeMemberAuto is a headless mode for swarm/specialist MEMBERS: it // advertises the in-workspace mutators a member needs to build (write/edit + // shell) on top of the Auto set, while the sandbox engine still gates them at // call time — in-workspace writes and sandbox-backed shell auto-allow, but // out-of-workspace writes, network, and destructive commands still prompt (and // a headless member has no approver, so they are denied). It normalizes to Auto // everywhere except ToolAdvertised, so authority is never widened beyond what an // interactive auto agent already has inside the sandbox. PermissionModeMemberAuto PermissionMode = "member-auto" )
type PermissionRequest ¶
type PermissionRequest struct {
ToolCallID string `json:"toolCallId"`
ToolName string `json:"name"`
Action PermissionAction `json:"action"`
Permission string `json:"permission"`
PermissionMode PermissionMode `json:"permissionMode"`
Autonomy string `json:"autonomy,omitempty"`
SideEffect string `json:"sideEffect"`
Reason string `json:"reason,omitempty"`
Scope string `json:"scope,omitempty"`
Risk sandbox.Risk `json:"risk"`
Args map[string]any `json:"args,omitempty"`
Block *sandbox.Block `json:"block,omitempty"`
GrantMatched bool `json:"grantMatched,omitempty"`
Grant *sandbox.Grant `json:"grant,omitempty"`
CommandPrefix []string `json:"commandPrefix,omitempty"`
AvailableDecisions []PermissionDecisionAction `json:"availableDecisions,omitempty"`
}
type Provider ¶
type Provider = zeroruntime.Provider
type Result ¶
type Result struct {
FinalAnswer string
Turns int
Messages []Message
StopReason StopReason
// FinishReason is the provider's normalized terminal stop reason for the turn
// that produced FinalAnswer: zeroruntime.FinishReasonLength when the output
// hit the token cap, FinishReasonContentFilter when it was filtered. Empty for
// a normal completion.
FinishReason string
// Incomplete reports that a headless run (RequireCompletionSignal) stopped with
// work clearly unfinished: the model ended a turn with no tool call while plan
// items were pending or the message ended mid-step, the model admitted it
// guessed / could not meet the objective, and/or it failed a task-grounded
// acceptance check. Callers map it to a non-success terminal status / exit
// code. False for every normal completion.
Incomplete bool
// IncompleteReason is a short, model-derived explanation of why the run was
// marked Incomplete (e.g. "pending plan items remain"). Empty when Incomplete
// is false. Surfaced in logs / run_end so an abandoned run is debuggable.
IncompleteReason string
}
func (Result) Truncated ¶
Truncated reports whether the final response ended abnormally (cut off at the output token cap or withheld by a content filter) rather than completing naturally. Callers can use it to warn the user that FinalAnswer is incomplete.
func (Result) TruncationNotice ¶
TruncationNotice returns a user-facing warning when the final response was truncated, or "" for a normal completion. Shared by the CLI and TUI so the wording stays consistent.
type SelfCorrectConfig ¶
type SelfCorrectConfig struct {
Enabled bool
MaxAttempts int
IncludeTests bool
IncludeLSP bool
Autonomy string // low | medium | high
}
SelfCorrectConfig controls the post-edit verify-and-correct cycle.
type SelfCorrector ¶
type SelfCorrector struct {
// contains filtered or unexported fields
}
SelfCorrector runs verification after a mutating edit and, when it fails, synthesizes corrective feedback for the model — bounded by a per-run attempt budget and gated by the autonomy ceiling. One instance per run (it holds the attempt counter). A nil *SelfCorrector is a no-op.
func NewSelfCorrector ¶
func NewSelfCorrector(workspaceRoot string, checker diagnosticsChecker, verifier projectVerifier, cfg SelfCorrectConfig) *SelfCorrector
NewSelfCorrector builds a self-corrector. checker/verifier may be nil to disable that half of verification (e.g. no LSP server, or tests off).
func (*SelfCorrector) AfterEdit ¶
AfterEdit runs a verification pass over the files a mutating tool changed and returns corrective feedback (empty when nothing actionable) plus the outcome. The loop appends any non-empty feedback to the conversation so the model can fix the problem on its next turn.
type SkillInfo ¶ added in v0.2.0
SkillInfo is a one-line summary of a reusable, on-demand skill (its name and frontmatter description) surfaced to the system prompt so the model can invoke the right skill with the skill tool on the first try instead of guessing a name and reading the failure. Like SpecialistInfo it is plain data, so the agent package needs no dependency on internal/skills.
type SpecialistInfo ¶
SpecialistInfo is a one-line summary of a delegatable sub-agent (its name and when-to-use description) surfaced to the orchestrator's system prompt so it can route work to the right specialist. It is plain data so the agent package needs no dependency on internal/specialist.
type StopReason ¶
type StopReason string
const (
StopReasonSpecReviewRequired StopReason = "spec_review_required"
)
type ToolCall ¶
type ToolCall = zeroruntime.ToolCall
type ToolResult ¶
type ToolResult struct {
ToolCallID string
Name string
Status tools.Status
Output string
Meta map[string]string
Redacted bool
ChangedFiles []string
Display tools.Display
// DenialReason categorizes why a tool call was blocked (empty when it ran).
// It lets a surface distinguish the cause precisely instead of parsing Output.
DenialReason DenialCategory
// LoadedTools carries the deferred-tool names a tool_search call asked the
// loop to expose next turn (lifted from Meta["load_tools"]). nil for every
// ordinary tool result; only tool_search populates it.
LoadedTools []string
// RequestedModel is the model id a tool asked the loop to switch to for the
// rest of the run (lifted from the tool's Meta["escalate_to_model"]). Empty
// for every normal tool result; the Run loop performs the switch when it is
// set and Options.ModelSwitcher is wired.
RequestedModel string
}
type Usage ¶
type Usage = zeroruntime.Usage