agent

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: May 13, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Path-validation helpers for the mutating filesystem tools (write_file, edit_file, mkdir, copy_file, move_file, delete_file). Three layers of defense:

  1. Cwd confinement — paths must resolve under the session's cwd, or under one of the user-provided AllowedPaths roots.
  2. Symlink rejection — symlinked write targets are refused unless the user explicitly opts in. Stops the "symlink to /etc/passwd" trick.
  3. Deny list — yottacode's own state directories (sessions, auto/, permissions.json, permissions.local.json) and git-internal paths are off-limits regardless of approval. Self-grants and memory injection don't go through the tool surface.

Reads run through a narrower validator (ValidateReadPath) gated by a targeted deny list of credential-bearing paths (DefaultDenyReadPaths). The agent legitimately reads dotfiles, USER.md, /etc/os-release, etc., so the read deny list is targeted — well-known credential locations only — instead of a blanket cwd-confinement. Reading credentials directly into the model's context (and from there into the upstream provider's logs) is a silent exfiltration vector via prompt injection; run_bash is the escape hatch for the rare case the user really wants the contents, because run_bash always prompts.

Index

Constants

View Source
const AgentToolName = "Agent"

AgentToolName is the schema-visible name of the subagent dispatch tool. Capital-A mirrors Claude Code's surface ("Agent"/"Task"). The name is referenced in several places (recursion guard, plan-mode gate exemption checks, TUI tool-card suppression) so it lives as a const here. Exported so consumers in internal/tui can compare against it without hardcoding the string.

View Source
const DefaultSystemPrompt = `` /* 7955-byte string literal not displayed */

DefaultSystemPrompt is the agent identity prompt sent to the model at the start of every session. It declares yottacode's tool surface and the action discipline the model should follow.

Both the TUI (`internal/tui/run.go`) and the non-interactive runner (`internal/oneshot/oneshot.go`) consume this single constant. Historically each kept its own copy of the string and the two drifted — the TUI gained guidance about choosing between edit_file / apply_diff / write_file (and a longer rule on read_many_files) that oneshot never got. One source of truth retires that drift class; a regression test in TestDefaultSystemPrompt_NamesEveryRegisteredTool keeps the tool list honest.

Memory injection (USER.md / YOTTACODE.md / memory tools) wraps this prompt downstream — see internal/memory/memory.go SystemPrompt for the "background — do not narrate" framing that gets layered on.

View Source
const ExitPlanModeRefusalMessage = "User chose to keep planning. " +
	"END THIS TURN NOW with a brief one-sentence question asking what they'd like to change about the plan. " +
	"Do NOT call exit_plan_mode again in this turn. " +
	"Do NOT edit the plan file in this turn. " +
	"Do NOT call any other tools in this turn. " +
	"Wait for the user's next message before doing anything else. " +
	"After they respond with feedback, you may revise the plan file and call exit_plan_mode again on the following turn."

ExitPlanModeRefusalMessage is what the loop returns to the model when the user picks [K] / Keep planning at the plan-approval card. Phrased firmly so the model STOPS THE TURN and waits for user feedback. Without the firmness, models read "call exit_plan_mode again when ready" and immediately re-call without changing the plan — looping the approval card forever. The user pressed [K] because they have something to say; the model must yield the turn so they can say it.

View Source
const ExitPlanModeSavedForLaterMessage = "user approved the plan but is saving it for implementation later. " +
	"The plan file is preserved on disk; the user will resume via /plan list when ready. " +
	"END THIS TURN NOW. Do NOT call any more tools. Do NOT implement any part of the plan. " +
	"Do NOT suggest next steps. Reply with a one-sentence acknowledgement and stop."

ExitPlanModeSavedForLaterMessage is what the loop returns to the model when the user picks [L] / approve and implement later. The plan is good but the user isn't starting work now — they'll resume via /plan list or --plan-resume in a future session. The message is phrased firmly to make the model END THE TURN: no more tool calls, no implementation, no "next steps" prose. The user will re-initiate when they're ready.

View Source
const MaxBackgroundSubagents = 8

MaxBackgroundSubagents caps how many background subagents may be running concurrently per session. Hit the cap → the tool rejects the call with a recoverable error message the model can adapt around. Foreground subagents are unbounded (they serialize on the parent's call stack anyway). 8 is a round number that matches what most users informally do — enough for genuine parallelism, low enough to keep API spend bounded if a model gets enthusiastic.

View Source
const PlanModeAddendum = `` /* 5572-byte string literal not displayed */

PlanModeAddendum is the per-iteration system message appended on top of DefaultSystemPrompt when LoopConfig.PlanMode is active. The single `%s` is filled with the current plan-file path. Mirrors Claude Code's plan-mode framing so the model recognizes the surface regardless of which agent it's running under.

Lives in the prompt module (not plan_mode.go) so the schema-vs-prompt regression test in prompt_test.go can assert plan-mode directives are reachable from the same package as the rest of the prompt copy.

Variables

This section is empty.

Functions

func CheckpointFromContext added in v0.2.0

func CheckpointFromContext(ctx context.Context) (sessionID, cpID string)

CheckpointFromContext recovers the (sessionID, checkpointID) pair stored by WithCheckpoint. Returns empty strings when no checkpoint is bound — callers should treat that as "checkpointing disabled."

func DefaultDenyPaths

func DefaultDenyPaths(cwd string) []string

func DefaultDenyReadPaths

func DefaultDenyReadPaths(cwd string) []string

DefaultDenyReadPaths returns the hardcoded list of paths the agent's auto-execute read tools (read_file, read_many_files, grep) refuse to touch. Targeted at well-known credential stores; intentionally narrow so the model can still read dotfiles, /etc/os-release, USER.md, and other benign system files.

  • ~/.yottacode/.env — the agent's own provider keys. Reading this into context exfiltrates the active session's API key to the upstream provider on the next turn.
  • ~/.yottacode/auth/ — OAuth bearer + refresh tokens for the openai-auth provider. Same exfiltration risk as .env, plus the refresh token grants long-lived access. Whole directory denied so future per-provider auth files inherit the protection.
  • ~/.ssh/, ~/.gnupg/ — private key material.
  • ~/.aws/{credentials,config}, ~/.config/gcloud/ — cloud provider access.
  • ~/.netrc — HTTP basic-auth credentials.
  • ~/.config/gh/hosts.yml, ~/.config/hub — GitHub tokens.
  • ~/.docker/config.json — registry tokens.
  • ~/.kube/config — cluster credentials.
  • <cwd>/.env, <cwd>/.env.local — project secrets, the most common accidental-exfiltration target.

Power users who need the model to read one of these can bypass at the OS layer (cat the contents into a non-denied file first) or via run_bash, which prompts. Listing more paths is cheap; the cost is false-positive blocks on benign reads. Keep the list to files universally understood as secrets.

func IsAutoModeSafetyFloor added in v0.2.0

func IsAutoModeSafetyFloor(toolName string) bool

IsAutoModeSafetyFloor returns true for tools whose approval prompt must NOT be skipped by auto mode. These are the calls that run arbitrary code (run_bash) or write permanent / hard-to-reverse history (git_commit, git_checkpoint, rollback). The user opted into auto mode to skip edit-by-edit approval friction — not to silently hand over shell access or amend git history.

To get true blanket auto-approval (including run_bash and commits), launch with --dangerously-skip-permissions; that's the user-explicit "yolo" path and is intentionally session-wide so it can't be toggled away in the middle of a run (mirroring Claude Code).

func IsPlanFileWrite added in v0.2.0

func IsPlanFileWrite(name, argsJSON, planFile string) bool

IsPlanFileWrite reports whether this tool call is one of the mutating tools (write_file / edit_file / apply_diff) targeting the resolved plan file. The loop uses this to auto-approve those writes in plan mode — they're the model's only legitimate mutation surface while planning, so prompting on every edit is friction with no value (the gate already established the target is the plan file). False when planFile is empty, when the tool isn't a write tool, or when the target path differs from planFile.

func ParentDecisions added in v0.2.0

func ParentDecisions(ctx context.Context) <-chan Decision

ParentDecisions recovers the channel attached by WithParentDecisions, or nil when no parent loop is on the stack.

func ParentEvents added in v0.2.0

func ParentEvents(ctx context.Context) chan<- Event

ParentEvents recovers the channel attached by WithParentEvents, or nil when no parent loop is on the stack (tests, oneshot paths that haven't wired it in). Always check for nil before sending.

func PlanFilePath added in v0.2.0

func PlanFilePath(slug string) (string, error)

PlanFilePath resolves a slug to its absolute plan-file path. The returned path is what the gate compares writes against and what the system-prompt addendum tells the model to write to.

func PlanModeGate added in v0.2.0

func PlanModeGate(tool Tool, argsJSON, planFile string) (string, bool)

PlanModeGate is the read/write classifier the loop consults before every tool call when plan mode is active. Returns ("", false) when the call may proceed, or (errorString, true) when the call must be refused. The errorString is what the model sees as the tool result, so it's phrased as actionable guidance — the model can switch to a read-only or plan-file alternative on the next iteration.

Allowlist:

  • exit_plan_mode: the only way out of plan mode.
  • todo_write: progress tracking, no side effects.
  • write_file/edit_file/apply_diff: only when the target path equals planFile. Any other write target is blocked.
  • any tool whose RequiresApproval returns false: the implicit "read-only" classification (read_file, grep, glob, list_*, git_log_file, fetch_url, …). New read-only tools auto-classify.

The gate runs BEFORE Permissions.Evaluate so explicit deny rules still win in plan mode (Deny > plan-mode-allow > permissions > tool-policy).

func PlansDir added in v0.2.0

func PlansDir() (string, error)

PlansDir returns the directory plan files live under: $YOTTACODE_HOME/plans (when set) or ~/.yottacode/plans otherwise. Does not create the directory — write_file's MkdirAll handles that lazily on first write.

func SlugFromPrompt added in v0.2.0

func SlugFromPrompt(prompt, salt string) string

SlugFromPrompt converts a free-form prompt into a stable, filesystem- safe slug suitable for a plan filename. Format:

<kebab>-<16-hex-of-sha256(salt|prompt)>

The kebab portion caps at 60 characters so the suffixed filename stays well under common filesystem limits. Empty / all-punctuation input falls back to "untitled".

The hash suffix gives every plan a unique filename even when two prompts share the same opening words (e.g. "fix bug in foo" vs "fix bug in bar" both truncated to "fix-bug-in") and avoids the slug-collision class entirely. Salt is typically the session ID, so the same prompt typed in a different session lands on a different file.

func ToolPathsToSnapshot added in v0.2.0

func ToolPathsToSnapshot(t Tool, cwd, argsJSON string) []string

ToolPathsToSnapshot exposes the Mutator capability to callers in other packages (e.g. the agent loop's checkpoint hook) without forcing them to import nothing-vs-something interface assertions. Returns nil when the tool isn't a Mutator.

func Turn

func Turn(
	ctx context.Context,
	cfg LoopConfig,
	history *[]adapter.Message,
	events chan<- Event,
	decisions <-chan Decision,
) error

Turn drives one user-initiated round: it streams an assistant response (emitting Reasoning/Content tokens), dispatches any tool calls (with approval flow if required), feeds the results back, and loops until the assistant produces a tool-free reply or hits MaxIterations.

events is producer-only; Turn never closes it (caller owns lifecycle). decisions is consumer-only; Turn reads from it only after emitting an ApprovalNeeded event. Cancel ctx to abort cleanly.

history is mutated in place: user message is assumed already appended by the caller; Turn appends each assistant reply and tool result.

func ValidateReadPath

func ValidateReadPath(path string, deny []string) error

DefaultDenyPaths returns the hardcoded list of paths the agent's mutating filesystem tools must refuse to write to. Includes:

  • User-scope yottacode state under ~/.yottacode/ (sessions, memory/, projects/, auth/, index.sqlite, USER.md). The agent has supported pathways for the memory dirs (memory_save / memory_forget); the generic write_file / edit_file surface must not be a back door. USER.md is global preferences — the agent's project-scope view doesn't have enough signal to curate cross-project preferences.
  • Project-scope yottacode state under <cwd>/.yottacode/ (permissions.json, permissions.local.json). The permissions files are the user's policy surface — letting the model edit them via tools would let it self-grant approval. The /permissions slash command and the user's editor are the only legitimate write paths.
  • Git internals: .git/HEAD, .git/config, .git/index, .git/refs/, .git/packed-refs, .git/objects/. These define repo state; writes here should go through `git` commands, not direct filesystem manipulation. .git/hooks/ is deliberately NOT in the list — model authoring of hooks is a legitimate task.

YOTTACODE.md is deliberately NOT in the deny list. It's the project-scope context file the agent reads on every turn, and keeping it fresh requires writes — same role CLAUDE.md plays for Claude Code. The approval modal still gates every write, so the user sees each change before it lands.

Bypass is not possible via flags. Power users can edit these files themselves with their editor; the model just can't via tools. ValidateReadPath returns nil if the read of path is permitted under the given deny list, or a descriptive error. Targeted at silent exfiltration of credential-bearing files via read_file / read_many_files / grep — tools whose RequiresApproval is false. The user can still read these files via run_bash, which always prompts.

func ValidateWritePath

func ValidateWritePath(path string, opts WritePathOptions) error

ValidateWritePath returns nil if a write to path is permitted under the given options, or a descriptive error. Validation order matters: deny list checked first (so even a path inside cwd can be refused), then symlink check, then containment check against cwd / allowed roots.

func WithCheckpoint added in v0.2.0

func WithCheckpoint(ctx context.Context, sessionID, cpID string) context.Context

WithCheckpoint binds a checkpoint id + session id to ctx. The TUI calls this immediately after checkpoints.Begin returns, before passing ctx into Turn. Returns ctx unchanged when either id is empty so callers don't need to special-case nil-checkpoint paths.

func WithParentDecisions added in v0.2.0

func WithParentDecisions(ctx context.Context, decisions <-chan Decision) context.Context

WithParentDecisions attaches the parent loop's decisions channel to ctx. Tools that don't need to forward approvals should ignore this seam — the channel is receive-only and may be nil.

func WithParentEvents added in v0.2.0

func WithParentEvents(ctx context.Context, events chan<- Event) context.Context

WithParentEvents attaches the parent loop's events channel to ctx so downstream Tool.Execute calls can pull it via ParentEvents(ctx). The channel is send-only; tools may push Subagent* / progress events onto it without coordinating with the loop.

Tools that don't need this seam should ignore the helper — the normal tool-result return value is still the primary output path.

Types

type AgentTool added in v0.2.0

type AgentTool struct {
	// Configs is the resolved set of agent definitions (builtin +
	// global + project). The Execute method looks up subagent_type
	// against this slice; it should remain stable across the session.
	Configs []subagents.AgentConfig

	// Tasks is the session-scoped task registry. Foreground runs add
	// + MarkDone within a single Execute; background runs add now and
	// MarkDone later from a detached goroutine.
	Tasks *subagents.Registry

	// Adapter is the streamer the child Turn calls into. Shared with
	// the parent — adapter calls are stateless per-request and
	// concurrency-safe by construction.
	Adapter Streamer

	// ParentRegistry is the live tool set the parent session is using.
	// We clone it for the child, dropping the Agent tool itself plus
	// exit_plan_mode, and intersecting with the agent's `tools:`
	// allowlist when one is configured. The clone is a single-pass
	// snapshot — runtime changes to the parent registry don't
	// propagate into in-flight children, which keeps semantics easy
	// to reason about.
	ParentRegistry *Registry

	// Permissions is the parent's permission ruleset; children
	// inherit it unchanged. Per-config narrowing is a v2 extension.
	Permissions *permissions.Permissions

	// YoloMode is the process-wide yolo overlay. The pointer is
	// shared so a yolo session also applies to its subagents — the
	// user explicitly opted into unattended mutation, child runs
	// included.
	YoloMode *YoloModeState

	// PlanMode is the parent's plan-mode state. Pointer-shared so a
	// child run under a plan-mode parent inherits the restriction
	// transitively (no writes outside the plan file). When the
	// parent flips out of plan mode mid-conversation the next
	// subagent run automatically sees the new state. nil is safe —
	// runChild allocates a fresh inactive state in that case.
	PlanMode *PlanModeState

	// AutoMode is the parent's auto-mode state. Pointer-shared so a
	// child inherits parent's auto-mode (mutating tools auto-allow
	// except the safety floor, iteration cap multiplied 4×). nil is
	// safe — runChild allocates a fresh inactive state in that case.
	AutoMode *AutoModeState

	// Cwd is the working directory child tools resolve relative
	// paths against. Same as the parent's cwd.
	Cwd string

	// TranscriptDir is the directory background-task transcripts get
	// persisted under. Must exist at Execute time; the caller (TUI
	// or oneshot wiring) creates it via subagents.EnsureTranscriptDir
	// at startup.
	TranscriptDir string

	// AllowBackground controls whether `run_in_background: true` is
	// honored. The TUI sets this true; oneshot leaves it false so the
	// non-interactive entry point returns a sensible error string the
	// model can recover from rather than silently detaching work that
	// nobody will see.
	AllowBackground bool

	// SystemPromptSuffix is appended to the agent definition's body
	// when building the child's system prompt. Used to inject runtime
	// context the static config can't know (currently empty; reserved
	// for cwd / repo metadata if we decide to inject it).
	SystemPromptSuffix string
	// contains filtered or unexported fields
}

AgentTool dispatches typed-subagent invocations. One instance is registered per session — Execute spawns a child agent.Turn against a filtered registry and either blocks until the child completes (foreground) or detaches the child to a goroutine that updates the task registry on completion (background).

func (*AgentTool) AgentConfigs added in v0.2.0

func (t *AgentTool) AgentConfigs() []subagents.AgentConfig

Configs returns the resolved set of agent definitions (for the TUI's /subagents help / status rendering and for tests). The returned slice references the same memory; callers must not mutate.

func (*AgentTool) Description added in v0.2.0

func (t *AgentTool) Description() string

func (*AgentTool) Execute added in v0.2.0

func (t *AgentTool) Execute(ctx context.Context, argsJSON string) (string, error)

Execute is the parent-loop entry point. Parses args, validates the subagent_type, builds the child config + history, and either:

  • foreground: spawns the child Turn, drains its events through the translator inline, and returns the captured final reply as the tool result string the model sees;
  • background: launches the whole flow in a goroutine, returns a task-id handle immediately, and posts completion via the session-level callback when the child finishes.

func (*AgentTool) Name added in v0.2.0

func (t *AgentTool) Name() string

func (*AgentTool) ParallelSafe added in v0.2.0

func (t *AgentTool) ParallelSafe(string) bool

ParallelSafe returns false so two Agent calls from the same assistant message serialize. Two simultaneous subagent spawns can rate-limit the same provider key and burn the iteration budget on both — better to do them in sequence. (Background subagents are the parallelism story; foreground spawns are a one-at-a-time affair.)

func (*AgentTool) PreviewCall added in v0.2.0

func (t *AgentTool) PreviewCall(argsJSON string) string

func (*AgentTool) RequiresApproval added in v0.2.0

func (t *AgentTool) RequiresApproval(string) bool

RequiresApproval is always false for the Agent tool itself — delegation is just compute. The child's own mutating tool calls still go through their normal approval flow (and v1 auto-denies any ApprovalNeeded inside the child since the child has no UI attached). The user retains control via the parent's permission rules.

func (*AgentTool) Schema added in v0.2.0

func (t *AgentTool) Schema() map[string]any

func (*AgentTool) SetBackgroundDoneCallback added in v0.2.0

func (t *AgentTool) SetBackgroundDoneCallback(fn func(SubagentBackgroundDone))

SetBackgroundDoneCallback installs the session-level handler that receives a SubagentBackgroundDone event when a detached child finishes. Safe to call after registration; safe to call with nil to clear.

type ApplyDiffTool

type ApplyDiffTool struct {
	Cwd       string
	WriteOpts WritePathOptions
}

func (*ApplyDiffTool) Description

func (t *ApplyDiffTool) Description() string

func (*ApplyDiffTool) Execute

func (t *ApplyDiffTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*ApplyDiffTool) Name

func (t *ApplyDiffTool) Name() string

func (*ApplyDiffTool) PathsToSnapshot added in v0.2.0

func (t *ApplyDiffTool) PathsToSnapshot(cwd, argsJSON string) []string

PathsToSnapshot reports every file the diff touches so /checkpoints can restore each pre-image. Uses the same ParseDiffPaths the tool already runs during validation, so the snapshot set matches the validation set by construction.

func (*ApplyDiffTool) PreviewCall

func (t *ApplyDiffTool) PreviewCall(argsJSON string) string

func (*ApplyDiffTool) RequiresApproval

func (t *ApplyDiffTool) RequiresApproval(string) bool

func (*ApplyDiffTool) Schema

func (t *ApplyDiffTool) Schema() map[string]any

type ApprovalAuto

type ApprovalAuto struct {
	ToolName string
	Preview  string
	Source   string
}

ApprovalAuto is logged when the loop auto-approves (or auto-denies) a tool call without asking the user. Source identifies which gate fired: "permissions" (matched an allow rule), "deny-rule" (matched a deny rule, no execution), or "bypass-permissions" (--dangerously-skip-permissions flag is set and no rule matched).

type ApprovalNeeded

type ApprovalNeeded struct {
	ToolName string
	Preview  string
	ArgsJSON string
}

ApprovalNeeded is the request half of an approval round-trip. The loop blocks on the decisions channel until the consumer replies. ArgsJSON is the raw tool args so the consumer can render richer previews — e.g. the TUI shows a colored diff for edit_file by parsing old_string/new_string.

type AssistantMessage

type AssistantMessage struct{ Message adapter.Message }

AssistantMessage fires once a streamed assistant response is finalized, just before its tool_calls (if any) are dispatched. Includes the same Message that gets appended to history; useful for consumers that want to react to a complete reply (e.g., TUI message-list rendering).

type AutoModeState added in v0.2.0

type AutoModeState struct {
	Active atomic.Bool
}

AutoModeState is the per-session, runtime-mutable auto-mode flag the loop reads on every tool dispatch. When active, mutating tools that would normally hit an approval modal auto-allow with Source=auto-mode — EXCEPT for the safety floor (run_bash, git_commit, git_checkpoint, rollback), which always prompt regardless of mode.

Mutually exclusive with plan mode at the TUI layer: entering one turns the other off. The loop-level gates don't enforce this on their own — they just observe whichever flag is set.

The pointer is shared between LoopConfig and the TUI Model so a flip from /auto, Shift+Tab, or the plan-card [Y] hotkey takes effect on the next iteration with no reconstruction. atomic.Bool keeps that benign race detector-clean.

func (*AutoModeState) IsActive added in v0.2.0

func (a *AutoModeState) IsActive() bool

IsActive is a nil-safe check used by the loop.

type CheckpointInfo added in v0.2.0

type CheckpointInfo struct{ Message string }

CheckpointInfo carries a non-fatal status from the checkpoint subsystem — typically a snapshot failure for one file (permission denied, race with deletion) that should NOT abort the user's tool call. The TUI renders these dimly in the scrollback so the user knows the file won't be restorable from this checkpoint without confusing them about whether the tool itself failed.

type CheckpointWriter added in v0.2.0

type CheckpointWriter interface {
	SnapshotPath(sessionID, checkpointID, absPath string) error
}

CheckpointWriter is the slice of the checkpoint store the agent loop depends on. Defining it here keeps internal/checkpoint out of the agent's import surface and lets tests substitute a recording fake.

type CommandSegment

type CommandSegment struct {
	Text      string // the segment, trimmed
	Separator string // "" for first segment; "&&", "||", ";", "|" thereafter
	Risk      Risk
	Reason    string // human-readable why this is flagged, "" when RiskNone
}

CommandSegment is one piece of a (possibly) compound shell command, separated from the next segment by a logical operator or pipe. The separator that *precedes* this segment is recorded so the modal can label "and then" vs "or" vs "piped to" relationships.

func SplitCommand

func SplitCommand(cmd string) []CommandSegment

SplitCommand parses a shell command into segments separated by &&, ||, ;, and pipes. Quoted metacharacters (`"foo && bar"`) and escaped ones (`\&\&`) are ignored. Command substitutions ($(...) and `...`) are NOT recursively split; the whole substitution stays as part of its enclosing segment with a "contains substitution" caution flag if the substitution is non-trivial.

The output is suitable for display in the approval modal — not for execution semantics. We're trying to surface what a tired human might miss, not reproduce a real shell parser.

type ContentToken

type ContentToken struct{ Text string }

ContentToken carries one chunk of the assistant's actual reply. Render in normal style.

type CopyFileTool

type CopyFileTool struct {
	Cwd       string
	WriteOpts WritePathOptions
}

func (*CopyFileTool) Description

func (t *CopyFileTool) Description() string

func (*CopyFileTool) Execute

func (t *CopyFileTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*CopyFileTool) Name

func (t *CopyFileTool) Name() string

func (*CopyFileTool) PathsToSnapshot added in v0.2.0

func (t *CopyFileTool) PathsToSnapshot(cwd, argsJSON string) []string

PathsToSnapshot reports only the destination — src is read-only here.

func (*CopyFileTool) PreviewCall

func (t *CopyFileTool) PreviewCall(argsJSON string) string

func (*CopyFileTool) RequiresApproval

func (t *CopyFileTool) RequiresApproval(string) bool

func (*CopyFileTool) Schema

func (t *CopyFileTool) Schema() map[string]any

type Decision

type Decision int

Decision is the verdict the consumer sends back when the loop emits an ApprovalNeeded event. AllowAlways writes a derived rule to the project-local .yottacode/permissions.local.json via the permissions.Permissions value passed in LoopConfig.

const (
	// Deny refuses this single call and reports "denied by user" to
	// the model so it can recover.
	Deny Decision = iota
	// AllowOnce permits this single call. No persistence.
	AllowOnce
	// AllowAlways permits this call and asks the loop to derive a
	// pattern from it (via permissions.DeriveAllowRule) and append it
	// to permissions.local.json so future matching calls are silent.
	// The TUI suppresses this option for cases where derivation isn't
	// safe (compound shell commands, dangerous verbs).
	AllowAlways
	// SaveForLater is the plan-mode-specific "[L] approve and
	// implement later" decision. The loop refuses the tool call (so
	// Execute never runs) but returns a firm "end this turn" message
	// to the model instead of the generic denial / refinement hint.
	// Only meaningful for exit_plan_mode; other tools should never
	// receive this value, and the loop falls back to generic-denial
	// semantics if they do.
	SaveForLater
)

type DeleteFileTool

type DeleteFileTool struct {
	Cwd       string
	WriteOpts WritePathOptions
}

func (*DeleteFileTool) Description

func (t *DeleteFileTool) Description() string

func (*DeleteFileTool) Execute

func (t *DeleteFileTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*DeleteFileTool) Name

func (t *DeleteFileTool) Name() string

func (*DeleteFileTool) PathsToSnapshot added in v0.2.0

func (t *DeleteFileTool) PathsToSnapshot(cwd, argsJSON string) []string

PathsToSnapshot reports the target so /checkpoints can recreate the deleted file on rewind.

func (*DeleteFileTool) PreviewCall

func (t *DeleteFileTool) PreviewCall(argsJSON string) string

func (*DeleteFileTool) RequiresApproval

func (t *DeleteFileTool) RequiresApproval(string) bool

func (*DeleteFileTool) Schema

func (t *DeleteFileTool) Schema() map[string]any

type EditFileTool

type EditFileTool struct {
	Cwd       string
	WriteOpts WritePathOptions
}

EditFileTool performs a surgical replacement inside an existing file. Strictly better than write_file for code edits: it preserves the rest of the file and refuses to apply a non-unique match unless replace_all is set, which catches stale assumptions before they corrupt code.

func (*EditFileTool) Description

func (t *EditFileTool) Description() string

func (*EditFileTool) Execute

func (t *EditFileTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*EditFileTool) Name

func (t *EditFileTool) Name() string

func (*EditFileTool) PathsToSnapshot added in v0.2.0

func (t *EditFileTool) PathsToSnapshot(cwd, argsJSON string) []string

PathsToSnapshot reports the target file so /checkpoints can restore the pre-edit contents on rewind.

func (*EditFileTool) PreviewCall

func (t *EditFileTool) PreviewCall(argsJSON string) string

func (*EditFileTool) RequiresApproval

func (t *EditFileTool) RequiresApproval(string) bool

func (*EditFileTool) Schema

func (t *EditFileTool) Schema() map[string]any

type ErrorEvent

type ErrorEvent struct{ Err error }

ErrorEvent fires when the turn terminates because of an error (adapter failure, ctx cancel, etc.). The error is also returned by Turn.

type Event

type Event interface {
	// contains filtered or unexported methods
}

Event is the union of things the agent loop emits while a turn runs. Consumers (REPL today, TUI next, `yottacode run` after that) type-switch on the concrete value.

Channel ownership: the *caller* owns the events channel and is responsible for closing it; Turn never closes it. Use a buffered channel (~64) so the loop doesn't block when the consumer is briefly busy.

Approval flow: when a tool requires approval and policy doesn't pre-approve (a matching allow rule in permissions.json, or --dangerously-skip-permissions), the loop emits ApprovalNeeded and blocks on a receive from the decisions channel. The consumer must reply with a Decision or cancel ctx.

type ExitPlanModeTool added in v0.2.0

type ExitPlanModeTool struct{}

ExitPlanModeTool is the model's signal that planning is finished and the plan file is ready for user approval. Mirrors Claude Code's `ExitPlanMode` exactly: the tool takes no `plan` argument — the content is read from the plan file the model has been writing to all along. Single source of truth (the file on disk), lower token usage, and no ambiguity about whether the approval card shows the same thing as the file the model intends to execute.

The tool itself is intentionally minimal: RequiresApproval=true routes the call through the standard approval flow, the TUI reads the plan file from disk and renders a plan-specific approval card ([A]/[K] hotkeys) for `exit_plan_mode` rather than the generic preview, and on approve the TUI flips the shared PlanModeState.Active flag off before forwarding the decision. The loop's `deniedResultFor` is special-cased for this tool name so a [K] (Keep planning) returns refinement guidance to the model instead of the generic "denied by user".

Execute therefore only runs on the approve path and unconditionally returns the "approved" message. The TUI is responsible for the "file is missing/empty" guard — it auto-denies before showing the approval card.

func (*ExitPlanModeTool) Description added in v0.2.0

func (t *ExitPlanModeTool) Description() string

func (*ExitPlanModeTool) Execute added in v0.2.0

func (t *ExitPlanModeTool) Execute(_ context.Context, _ string) (string, error)

Execute is only reached on the approve path — the loop short-circuits on Deny in promptForApproval and never calls the tool. Before showing the approval card the TUI inspects the plan file and auto-denies if it's missing or empty, so by the time we're here the file existed and the user said yes. Return the "approved" string and the model continues.

func (*ExitPlanModeTool) Name added in v0.2.0

func (t *ExitPlanModeTool) Name() string

func (*ExitPlanModeTool) PreviewCall added in v0.2.0

func (t *ExitPlanModeTool) PreviewCall(string) string

func (*ExitPlanModeTool) RequiresApproval added in v0.2.0

func (t *ExitPlanModeTool) RequiresApproval(string) bool

RequiresApproval is always true: every exit_plan_mode call goes through the approval card so the user can see the proposed plan before yottacode regains write access.

func (*ExitPlanModeTool) Schema added in v0.2.0

func (t *ExitPlanModeTool) Schema() map[string]any

Schema is an empty object — exit_plan_mode takes no arguments. The model writes the plan to disk via write_file/edit_file first, then calls this tool to surface it for approval. Matches Claude Code's ExitPlanMode shape.

type Fallback

type Fallback struct {
	From   string
	To     string
	Reason string
	Policy string
}

Fallback fires when the multi-provider router falls through from one candidate to another after an early failure (an error before any tokens streamed). Carries enough metadata for the TUI to render a loud "↻ fallback: A → B (reason)" line — silent fallback is the failure mode the router design is built to avoid.

type FetchURLTool

type FetchURLTool struct{}

FetchURLTool retrieves a single URL over HTTP(S) and returns capped textual content. This is the local-network fallback for models that do not have provider-native web search.

func (*FetchURLTool) Description

func (t *FetchURLTool) Description() string

func (*FetchURLTool) Execute

func (t *FetchURLTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*FetchURLTool) Name

func (t *FetchURLTool) Name() string

func (*FetchURLTool) ParallelSafe

func (t *FetchURLTool) ParallelSafe(string) bool

func (*FetchURLTool) PreviewCall

func (t *FetchURLTool) PreviewCall(argsJSON string) string

func (*FetchURLTool) RequiresApproval

func (t *FetchURLTool) RequiresApproval(string) bool

func (*FetchURLTool) Schema

func (t *FetchURLTool) Schema() map[string]any

type GetSubagentResultTool added in v0.2.0

type GetSubagentResultTool struct {
	// Tasks is the session-scoped subagent task registry. The same
	// pointer the AgentTool uses to record spawns; pointer-shared
	// so we observe live-updated state.
	Tasks *subagents.Registry
}

GetSubagentResultTool retrieves a previously-dispatched subagent's state and final reply from the session task registry. The intended flow:

  1. Parent calls Agent(...) with run_in_background:true → tool returns a task id handle immediately.
  2. Parent's turn ends; user keeps working; child runs to completion in a goroutine.
  3. Some turns later, when the user asks about the subagent's findings, the parent calls get_subagent_result(task_id=<id>) and the final reply lands as a normal tool result that flows back into the parent's adapter context.

Without this tool, background subagents are fire-and-forget: the transcript lives on disk and in the registry, but the parent's model has no programmatic way to pull a completed result back into the conversation. The pairing with run_in_background is what makes background runs actually useful.

Read-only and ParallelSafe — safe to call repeatedly, safe to call alongside other read-only tools in the same model turn.

func (*GetSubagentResultTool) Description added in v0.2.0

func (t *GetSubagentResultTool) Description() string

func (*GetSubagentResultTool) Execute added in v0.2.0

func (t *GetSubagentResultTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*GetSubagentResultTool) Name added in v0.2.0

func (t *GetSubagentResultTool) Name() string

func (*GetSubagentResultTool) ParallelSafe added in v0.2.0

func (t *GetSubagentResultTool) ParallelSafe(string) bool

ParallelSafe lets the model fetch several subagent results in one turn — useful for "summarize all the background investigations I started" workflows.

func (*GetSubagentResultTool) PreviewCall added in v0.2.0

func (t *GetSubagentResultTool) PreviewCall(argsJSON string) string

func (*GetSubagentResultTool) RequiresApproval added in v0.2.0

func (t *GetSubagentResultTool) RequiresApproval(string) bool

RequiresApproval is false: the tool only reads from the in-memory task registry and produces a string — no disk mutation, no network calls, no shell. Safe to auto-execute on every call.

func (*GetSubagentResultTool) Schema added in v0.2.0

func (t *GetSubagentResultTool) Schema() map[string]any

type GitBlameLinesTool

type GitBlameLinesTool struct{ Cwd string }

func (*GitBlameLinesTool) Description

func (t *GitBlameLinesTool) Description() string

func (*GitBlameLinesTool) Execute

func (t *GitBlameLinesTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*GitBlameLinesTool) Name

func (t *GitBlameLinesTool) Name() string

func (*GitBlameLinesTool) ParallelSafe

func (t *GitBlameLinesTool) ParallelSafe(string) bool

func (*GitBlameLinesTool) PreviewCall

func (t *GitBlameLinesTool) PreviewCall(argsJSON string) string

func (*GitBlameLinesTool) RequiresApproval

func (t *GitBlameLinesTool) RequiresApproval(string) bool

func (*GitBlameLinesTool) Schema

func (t *GitBlameLinesTool) Schema() map[string]any

type GitBranchStatusTool

type GitBranchStatusTool struct{ Cwd string }

func (*GitBranchStatusTool) Description

func (t *GitBranchStatusTool) Description() string

func (*GitBranchStatusTool) Execute

func (t *GitBranchStatusTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*GitBranchStatusTool) Name

func (t *GitBranchStatusTool) Name() string

func (*GitBranchStatusTool) ParallelSafe

func (t *GitBranchStatusTool) ParallelSafe(string) bool

func (*GitBranchStatusTool) PreviewCall

func (t *GitBranchStatusTool) PreviewCall(string) string

func (*GitBranchStatusTool) RequiresApproval

func (t *GitBranchStatusTool) RequiresApproval(string) bool

func (*GitBranchStatusTool) Schema

func (t *GitBranchStatusTool) Schema() map[string]any

type GitCheckpointTool

type GitCheckpointTool struct{ Cwd string }

func (*GitCheckpointTool) Description

func (t *GitCheckpointTool) Description() string

func (*GitCheckpointTool) Execute

func (t *GitCheckpointTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*GitCheckpointTool) Name

func (t *GitCheckpointTool) Name() string

func (*GitCheckpointTool) PreviewCall

func (t *GitCheckpointTool) PreviewCall(argsJSON string) string

func (*GitCheckpointTool) RequiresApproval

func (t *GitCheckpointTool) RequiresApproval(string) bool

func (*GitCheckpointTool) Schema

func (t *GitCheckpointTool) Schema() map[string]any

type GitCommitTool

type GitCommitTool struct{ Cwd string }

func (*GitCommitTool) Description

func (t *GitCommitTool) Description() string

func (*GitCommitTool) Execute

func (t *GitCommitTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*GitCommitTool) Name

func (t *GitCommitTool) Name() string

func (*GitCommitTool) PreviewCall

func (t *GitCommitTool) PreviewCall(argsJSON string) string

func (*GitCommitTool) RequiresApproval

func (t *GitCommitTool) RequiresApproval(string) bool

func (*GitCommitTool) Schema

func (t *GitCommitTool) Schema() map[string]any

type GitDiffFilesTool

type GitDiffFilesTool struct{ Cwd string }

func (*GitDiffFilesTool) Description

func (t *GitDiffFilesTool) Description() string

func (*GitDiffFilesTool) Execute

func (t *GitDiffFilesTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*GitDiffFilesTool) Name

func (t *GitDiffFilesTool) Name() string

func (*GitDiffFilesTool) ParallelSafe

func (t *GitDiffFilesTool) ParallelSafe(string) bool

func (*GitDiffFilesTool) PreviewCall

func (t *GitDiffFilesTool) PreviewCall(argsJSON string) string

func (*GitDiffFilesTool) RequiresApproval

func (t *GitDiffFilesTool) RequiresApproval(string) bool

func (*GitDiffFilesTool) Schema

func (t *GitDiffFilesTool) Schema() map[string]any

type GitLogFileTool

type GitLogFileTool struct{ Cwd string }

func (*GitLogFileTool) Description

func (t *GitLogFileTool) Description() string

func (*GitLogFileTool) Execute

func (t *GitLogFileTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*GitLogFileTool) Name

func (t *GitLogFileTool) Name() string

func (*GitLogFileTool) ParallelSafe

func (t *GitLogFileTool) ParallelSafe(string) bool

func (*GitLogFileTool) PreviewCall

func (t *GitLogFileTool) PreviewCall(argsJSON string) string

func (*GitLogFileTool) RequiresApproval

func (t *GitLogFileTool) RequiresApproval(string) bool

func (*GitLogFileTool) Schema

func (t *GitLogFileTool) Schema() map[string]any

type GitMergeBaseTool

type GitMergeBaseTool struct{ Cwd string }

func (*GitMergeBaseTool) Description

func (t *GitMergeBaseTool) Description() string

func (*GitMergeBaseTool) Execute

func (t *GitMergeBaseTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*GitMergeBaseTool) Name

func (t *GitMergeBaseTool) Name() string

func (*GitMergeBaseTool) ParallelSafe

func (t *GitMergeBaseTool) ParallelSafe(string) bool

func (*GitMergeBaseTool) PreviewCall

func (t *GitMergeBaseTool) PreviewCall(argsJSON string) string

func (*GitMergeBaseTool) RequiresApproval

func (t *GitMergeBaseTool) RequiresApproval(string) bool

func (*GitMergeBaseTool) Schema

func (t *GitMergeBaseTool) Schema() map[string]any

type GitShowFileAtRevTool

type GitShowFileAtRevTool struct{ Cwd string }

func (*GitShowFileAtRevTool) Description

func (t *GitShowFileAtRevTool) Description() string

func (*GitShowFileAtRevTool) Execute

func (t *GitShowFileAtRevTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*GitShowFileAtRevTool) Name

func (t *GitShowFileAtRevTool) Name() string

func (*GitShowFileAtRevTool) ParallelSafe

func (t *GitShowFileAtRevTool) ParallelSafe(string) bool

func (*GitShowFileAtRevTool) PreviewCall

func (t *GitShowFileAtRevTool) PreviewCall(argsJSON string) string

func (*GitShowFileAtRevTool) RequiresApproval

func (t *GitShowFileAtRevTool) RequiresApproval(string) bool

func (*GitShowFileAtRevTool) Schema

func (t *GitShowFileAtRevTool) Schema() map[string]any

type GitStageFilesTool

type GitStageFilesTool struct{ Cwd string }

func (*GitStageFilesTool) Description

func (t *GitStageFilesTool) Description() string

func (*GitStageFilesTool) Execute

func (t *GitStageFilesTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*GitStageFilesTool) Name

func (t *GitStageFilesTool) Name() string

func (*GitStageFilesTool) PreviewCall

func (t *GitStageFilesTool) PreviewCall(argsJSON string) string

func (*GitStageFilesTool) RequiresApproval

func (t *GitStageFilesTool) RequiresApproval(string) bool

func (*GitStageFilesTool) Schema

func (t *GitStageFilesTool) Schema() map[string]any

type GitTool

type GitTool struct {
	Cwd string
}

GitTool is the unified entrypoint for every git command. The model passes argv-style tokens (no shell), and approval policy is decided by inspecting the first arg.

func (*GitTool) Description

func (t *GitTool) Description() string

func (*GitTool) Execute

func (t *GitTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*GitTool) Name

func (t *GitTool) Name() string

func (*GitTool) PreviewCall

func (t *GitTool) PreviewCall(argsJSON string) string

func (*GitTool) RequiresApproval

func (t *GitTool) RequiresApproval(argsJSON string) bool

func (*GitTool) Schema

func (t *GitTool) Schema() map[string]any

type GitUnstageFilesTool

type GitUnstageFilesTool struct{ Cwd string }

func (*GitUnstageFilesTool) Description

func (t *GitUnstageFilesTool) Description() string

func (*GitUnstageFilesTool) Execute

func (t *GitUnstageFilesTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*GitUnstageFilesTool) Name

func (t *GitUnstageFilesTool) Name() string

func (*GitUnstageFilesTool) PreviewCall

func (t *GitUnstageFilesTool) PreviewCall(argsJSON string) string

func (*GitUnstageFilesTool) RequiresApproval

func (t *GitUnstageFilesTool) RequiresApproval(string) bool

func (*GitUnstageFilesTool) Schema

func (t *GitUnstageFilesTool) Schema() map[string]any

type GlobTool

type GlobTool struct {
	Cwd string
}

GlobTool finds files matching a doublestar pattern (e.g., "**/*.go").

func (*GlobTool) Description

func (t *GlobTool) Description() string

func (*GlobTool) Execute

func (t *GlobTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*GlobTool) Name

func (t *GlobTool) Name() string

func (*GlobTool) ParallelSafe

func (t *GlobTool) ParallelSafe(string) bool

func (*GlobTool) PreviewCall

func (t *GlobTool) PreviewCall(argsJSON string) string

func (*GlobTool) RequiresApproval

func (t *GlobTool) RequiresApproval(string) bool

func (*GlobTool) Schema

func (t *GlobTool) Schema() map[string]any

type GrepTool

type GrepTool struct {
	Cwd           string
	DenyReadPaths []string
}

GrepTool searches files for a pattern. Uses ripgrep if available, otherwise falls back to GNU grep. Args are passed via argv (no /bin/sh) so the model can't inject shell metacharacters. When the user supplies an explicit path, it's validated against DenyReadPaths so a targeted grep can't extract secrets line-by-line.

func (*GrepTool) Description

func (t *GrepTool) Description() string

func (*GrepTool) Execute

func (t *GrepTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*GrepTool) Name

func (t *GrepTool) Name() string

func (*GrepTool) ParallelSafe

func (t *GrepTool) ParallelSafe(string) bool

func (*GrepTool) PreviewCall

func (t *GrepTool) PreviewCall(argsJSON string) string

func (*GrepTool) RequiresApproval

func (t *GrepTool) RequiresApproval(string) bool

func (*GrepTool) Schema

func (t *GrepTool) Schema() map[string]any

type IterCap

type IterCap struct{ Max int }

IterCap fires when the loop hits MaxIterations without a final assistant reply. The turn ends after this event.

type IterationContinue

type IterationContinue struct {
	Number    int
	Reason    string
	ToolCalls int
}

IterationContinue explains why the loop is going around again after an iteration completes.

type IterationStart

type IterationStart struct {
	Number int
	Max    int
}

IterationStart fires when a new model->tools->model loop iteration begins. Number is 1-based.

type ListDirTool

type ListDirTool struct {
	Cwd string
}

ListDirTool returns the immediate children of a directory.

func (*ListDirTool) Description

func (t *ListDirTool) Description() string

func (*ListDirTool) Execute

func (t *ListDirTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*ListDirTool) Name

func (t *ListDirTool) Name() string

func (*ListDirTool) ParallelSafe

func (t *ListDirTool) ParallelSafe(string) bool

func (*ListDirTool) PreviewCall

func (t *ListDirTool) PreviewCall(argsJSON string) string

func (*ListDirTool) RequiresApproval

func (t *ListDirTool) RequiresApproval(string) bool

func (*ListDirTool) Schema

func (t *ListDirTool) Schema() map[string]any

type ListGitChangedFilesTool

type ListGitChangedFilesTool struct{ Cwd string }

func (*ListGitChangedFilesTool) Description

func (t *ListGitChangedFilesTool) Description() string

func (*ListGitChangedFilesTool) Execute

func (t *ListGitChangedFilesTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*ListGitChangedFilesTool) Name

func (t *ListGitChangedFilesTool) Name() string

func (*ListGitChangedFilesTool) ParallelSafe

func (t *ListGitChangedFilesTool) ParallelSafe(string) bool

func (*ListGitChangedFilesTool) PreviewCall

func (t *ListGitChangedFilesTool) PreviewCall(argsJSON string) string

func (*ListGitChangedFilesTool) RequiresApproval

func (t *ListGitChangedFilesTool) RequiresApproval(string) bool

func (*ListGitChangedFilesTool) Schema

func (t *ListGitChangedFilesTool) Schema() map[string]any

type ListProjectStructureTool

type ListProjectStructureTool struct {
	Cwd string
}

ListProjectStructureTool returns a bounded tree view of files and directories with sizes and last-modified timestamps. Designed as the "survey first" tool: the model can scan structure once and choose what to read with read_many_files instead of reading exploratorily.

func (*ListProjectStructureTool) Description

func (t *ListProjectStructureTool) Description() string

func (*ListProjectStructureTool) Execute

func (t *ListProjectStructureTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*ListProjectStructureTool) Name

func (t *ListProjectStructureTool) Name() string

func (*ListProjectStructureTool) ParallelSafe

func (t *ListProjectStructureTool) ParallelSafe(string) bool

func (*ListProjectStructureTool) PreviewCall

func (t *ListProjectStructureTool) PreviewCall(argsJSON string) string

func (*ListProjectStructureTool) RequiresApproval

func (t *ListProjectStructureTool) RequiresApproval(string) bool

func (*ListProjectStructureTool) Schema

func (t *ListProjectStructureTool) Schema() map[string]any

type LoopConfig

type LoopConfig struct {
	Adapter  Streamer
	Registry *Registry
	// Permissions gates every tool call against project-local rules
	// loaded from .yottacode/permissions.json (committable) and
	// .yottacode/permissions.local.json (gitignored). Optional: nil
	// disables rule matching and falls through to the tool's own
	// RequiresApproval policy.
	Permissions *permissions.Permissions
	// BypassPermissions is the internal name for the user-facing
	// --dangerously-skip-permissions flag (mirroring Claude Code).
	// Skip every approval prompt, run silently. DANGEROUS —
	// model-emitted commands execute without a human in the loop.
	// Explicit `deny` rules in permissions.json still refuse the call
	// (bypass is "skip prompts," not "ignore my policy"). Use only
	// in trusted CI / scripted contexts.
	BypassPermissions bool
	Cwd               string
	MaxIterations     int
	// PlanMode is the shared plan-mode flag the TUI flips via /plan or
	// Shift+Tab. nil disables plan mode entirely (oneshot; tests). When
	// set and Active, the loop prepends a plan-mode addendum to the
	// system prompt on every request and gates mutating tools through
	// PlanModeGate before approval evaluation. Pointer-shared so a TUI
	// flip takes effect on the next iteration with no reconfiguration.
	PlanMode *PlanModeState

	// AutoMode is the shared auto-mode flag the TUI flips via /auto,
	// Shift+Tab, or the plan-card [Y] hotkey. When active, the loop
	// auto-approves non-safety-floor tool calls (no modal) so the
	// model can implement a multi-step plan without per-edit friction.
	// run_bash and git mutations remain in the safety floor — see
	// IsAutoModeSafetyFloor.
	AutoMode *AutoModeState

	// YoloMode is the unrestricted toggle — auto-approves ALL tool
	// calls including the safety floor, and removes the iteration
	// cap entirely. Explicit Deny rules in permissions.json still
	// win. Intended for unattended long-running implementations
	// where the user has decided no further oversight is needed.
	// Mutually exclusive with AutoMode and PlanMode at the TUI layer.
	YoloMode *YoloModeState

	// Checkpoints, when non-nil, receives pre-image snapshot
	// requests for every Mutator tool call. nil disables checkpoint
	// capture entirely — oneshot and tests pass nil. The TUI builds
	// a *checkpoint.Store and attaches it; see internal/tui/run.go.
	// Implementations must be safe under concurrent SnapshotPath
	// calls within a turn.
	Checkpoints CheckpointWriter
}

LoopConfig is the value-typed configuration for one or more agent turns. Channels are passed separately to Turn so the same config can drive a streaming session across many turns without rewiring.

type MemoryForgetTool

type MemoryForgetTool struct {
	Cwd string
}

MemoryForgetTool deletes a memory file and regenerates the scope's MEMORY.md index. Errors cleanly when the named memory does not exist — the agent can use that signal to learn the right names.

func (*MemoryForgetTool) Description

func (t *MemoryForgetTool) Description() string

func (*MemoryForgetTool) Execute

func (t *MemoryForgetTool) Execute(_ context.Context, argsJSON string) (string, error)

func (*MemoryForgetTool) Name

func (t *MemoryForgetTool) Name() string

func (*MemoryForgetTool) ParallelSafe

func (t *MemoryForgetTool) ParallelSafe(string) bool

func (*MemoryForgetTool) PreviewCall

func (t *MemoryForgetTool) PreviewCall(argsJSON string) string

func (*MemoryForgetTool) RequiresApproval

func (t *MemoryForgetTool) RequiresApproval(string) bool

func (*MemoryForgetTool) Schema

func (t *MemoryForgetTool) Schema() map[string]any

type MemorySaveTool

type MemorySaveTool struct {
	Cwd string
}

MemorySaveTool persists a typed memory file under either the user-scope (~/.yottacode/memory/) or project-scope (~/.yottacode/projects/<slug>/memory/) directory and refreshes the MEMORY.md index for that scope. Replaces the post-turn extractor — the agent now decides in-band when something is worth remembering.

func (*MemorySaveTool) Description

func (t *MemorySaveTool) Description() string

func (*MemorySaveTool) Execute

func (t *MemorySaveTool) Execute(_ context.Context, argsJSON string) (string, error)

func (*MemorySaveTool) Name

func (t *MemorySaveTool) Name() string

func (*MemorySaveTool) ParallelSafe

func (t *MemorySaveTool) ParallelSafe(string) bool

func (*MemorySaveTool) PreviewCall

func (t *MemorySaveTool) PreviewCall(argsJSON string) string

func (*MemorySaveTool) RequiresApproval

func (t *MemorySaveTool) RequiresApproval(string) bool

func (*MemorySaveTool) Schema

func (t *MemorySaveTool) Schema() map[string]any

type MkdirTool

type MkdirTool struct {
	Cwd       string
	WriteOpts WritePathOptions
}

func (*MkdirTool) Description

func (t *MkdirTool) Description() string

func (*MkdirTool) Execute

func (t *MkdirTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*MkdirTool) Name

func (t *MkdirTool) Name() string

func (*MkdirTool) PreviewCall

func (t *MkdirTool) PreviewCall(argsJSON string) string

func (*MkdirTool) RequiresApproval

func (t *MkdirTool) RequiresApproval(string) bool

func (*MkdirTool) Schema

func (t *MkdirTool) Schema() map[string]any

type MoveFileTool

type MoveFileTool struct {
	Cwd       string
	WriteOpts WritePathOptions
}

func (*MoveFileTool) Description

func (t *MoveFileTool) Description() string

func (*MoveFileTool) Execute

func (t *MoveFileTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*MoveFileTool) Name

func (t *MoveFileTool) Name() string

func (*MoveFileTool) PathsToSnapshot added in v0.2.0

func (t *MoveFileTool) PathsToSnapshot(cwd, argsJSON string) []string

PathsToSnapshot reports both src (so we can recreate it on rewind) and dst (so we can remove the moved-to file on rewind).

func (*MoveFileTool) PreviewCall

func (t *MoveFileTool) PreviewCall(argsJSON string) string

func (*MoveFileTool) RequiresApproval

func (t *MoveFileTool) RequiresApproval(string) bool

func (*MoveFileTool) Schema

func (t *MoveFileTool) Schema() map[string]any

type Mutator added in v0.2.0

type Mutator interface {
	PathsToSnapshot(cwd, argsJSON string) []string
}

Mutator is the optional capability marker for tools that modify files on disk. The checkpoint subsystem queries this before tool.Execute to capture pre-images so /checkpoints can restore. PathsToSnapshot returns absolute paths the tool intends to touch, derived from argsJSON. Returning extra paths is harmless (snapshots are content-addressed and dedup); returning too few breaks restore.

Tools that mutate files via opaque side effects (e.g. run_bash) do NOT implement this — those mutations are intentionally untracked, mirroring Claude Code /rewind. Surface the limitation in user-facing docs / picker footer.

type ParallelSafeTool

type ParallelSafeTool interface {
	ParallelSafe(argsJSON string) bool
}

ParallelSafeTool is an optional capability marker for tools that can run concurrently with other read-only tool calls from the same assistant message. Keep this narrow and explicit: a false negative only costs some latency, but a false positive can create hard-to-debug races.

type PlanEntry added in v0.2.0

type PlanEntry struct {
	Slug     string
	Path     string
	Modified time.Time
	Size     int64
}

PlanEntry describes one plan file on disk for the picker + CLI resume flow. Slug is the basename without the `.md` suffix (matches the format SlugFromPrompt produces).

func ListPlans added in v0.2.0

func ListPlans() ([]PlanEntry, error)

ListPlans enumerates plan files under PlansDir() and returns them sorted by modified-time descending (newest first). Missing directory returns (nil, nil) — a fresh install has no plans yet and that's not an error.

func MatchPlan added in v0.2.0

func MatchPlan(plans []PlanEntry, query string) *PlanEntry

MatchPlan returns the first plan whose slug contains the query (case-insensitive substring match). Plans must already be sorted newest-first — typical usage is ListPlans → MatchPlan, so the most recent match wins on ties. Returns nil when nothing matches; the caller renders the list to help the user pick a real slug.

type PlanModeState added in v0.2.0

type PlanModeState struct {
	Active   atomic.Bool
	PlanFile string
}

PlanModeState is the per-session, runtime-mutable plan-mode flag the loop reads on every tool dispatch and prompt assembly. The TUI flips `Active` from the main goroutine via /plan or Shift+Tab while the agent goroutine reads it inside executeToolCall and streamIteration — atomic.Bool keeps that benign race detector-clean.

The pointer is shared between LoopConfig and the TUI Model, so a flip in cmd_plan.go takes effect on the very next iteration with no reconstruction or message rewriting. PlanFile is set when entering plan mode (from `/plan <topic>` arg or the first user message) and kept stable for the lifetime of the active plan. Until it's set, writes to the plan file are blocked — the gate compares against PlanFile and an empty string never matches a real path.

func (*PlanModeState) IsActive added in v0.2.0

func (p *PlanModeState) IsActive() bool

IsActive is a nil-safe check used by the loop.

type PlanStore added in v0.2.0

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

PlanStore is the session-scoped owner of the current todo list. The TodoWriteTool replaces the list wholesale on each call; the agent loop reads the snapshot to emit a TodoUpdate event; the TUI renders from that event; session save/load copies the slice in/out for cross-session persistence.

func NewPlanStore added in v0.2.0

func NewPlanStore() *PlanStore

func (*PlanStore) Replace added in v0.2.0

func (p *PlanStore) Replace(items []Todo)

Replace overwrites the list with the given items. The caller is responsible for validation (TodoWriteTool.Execute does it before reaching here).

func (*PlanStore) Snapshot added in v0.2.0

func (p *PlanStore) Snapshot() []Todo

Snapshot returns a copy of the current todo list. Safe for the caller to retain — the returned slice does not alias the store's internal state.

type ProviderToolCall

type ProviderToolCall struct {
	ToolName string
	Phase    string
	Detail   string
}

ProviderToolCall carries a provider-native tool lifecycle update emitted by the adapter stream itself, e.g. OpenAI/xAI web search or code interpreter.

type ReadFileTool

type ReadFileTool struct {
	Cwd           string
	DenyReadPaths []string
}

ReadFileTool lets the model fetch local file contents. Read-only, no approval. DenyReadPaths blocks a small set of credential-bearing locations (see DefaultDenyReadPaths) so prompt injection can't silently exfiltrate keys; everything else is fair game.

func (*ReadFileTool) Description

func (t *ReadFileTool) Description() string

func (*ReadFileTool) Execute

func (t *ReadFileTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*ReadFileTool) Name

func (t *ReadFileTool) Name() string

func (*ReadFileTool) ParallelSafe

func (t *ReadFileTool) ParallelSafe(string) bool

func (*ReadFileTool) PreviewCall

func (t *ReadFileTool) PreviewCall(argsJSON string) string

func (*ReadFileTool) RequiresApproval

func (t *ReadFileTool) RequiresApproval(string) bool

func (*ReadFileTool) Schema

func (t *ReadFileTool) Schema() map[string]any

type ReadManyFilesTool

type ReadManyFilesTool struct {
	Cwd           string
	DenyReadPaths []string
}

func (*ReadManyFilesTool) Description

func (t *ReadManyFilesTool) Description() string

func (*ReadManyFilesTool) Execute

func (t *ReadManyFilesTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*ReadManyFilesTool) Name

func (t *ReadManyFilesTool) Name() string

func (*ReadManyFilesTool) ParallelSafe

func (t *ReadManyFilesTool) ParallelSafe(string) bool

func (*ReadManyFilesTool) PreviewCall

func (t *ReadManyFilesTool) PreviewCall(argsJSON string) string

func (*ReadManyFilesTool) RequiresApproval

func (t *ReadManyFilesTool) RequiresApproval(string) bool

func (*ReadManyFilesTool) Schema

func (t *ReadManyFilesTool) Schema() map[string]any

type ReasoningToken

type ReasoningToken struct{ Text string }

ReasoningToken carries one chunk of "thinking" output from a reasoning model (Qwen 3, DeepSeek R1). Render dimmed.

type Registry

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

Registry owns the set of tools exposed to a given agent run.

func NewRegistry

func NewRegistry() *Registry

func (*Registry) AsAdapterTools

func (r *Registry) AsAdapterTools() []adapter.Tool

AsAdapterTools converts the registry into the schema shape the adapter advertises to the model. Equivalent to AsAdapterToolsFiltered(nil) — every registered tool is exposed.

func (*Registry) AsAdapterToolsFiltered added in v0.2.0

func (r *Registry) AsAdapterToolsFiltered(filter func(name string) bool) []adapter.Tool

AsAdapterToolsFiltered is the gated variant: when filter is non-nil and returns false for a tool name, that tool is omitted from the advertised schema. Used by the loop to hide `exit_plan_mode` outside of plan mode — without the filter the model could synthesize the call out of context and confuse the user with an approval card for a plan that doesn't exist. Pure read-side filter; the registry's own map is unchanged so Get() still resolves the tool when (legitimately) called.

func (*Registry) Get

func (r *Registry) Get(name string) (Tool, bool)

func (*Registry) Names added in v0.2.0

func (r *Registry) Names() map[string]bool

Names returns the set of registered tool names. Useful for callers that need to validate references (e.g. subagent allowlists) without caring about the Tool values themselves.

func (*Registry) Register

func (r *Registry) Register(t Tool)

func (*Registry) Tools added in v0.2.0

func (r *Registry) Tools() []Tool

Tools returns every registered Tool. The order is non-deterministic (map iteration). Subagent registry construction uses this to clone the parent's toolset while applying an allowlist filter — see internal/agent/agent_tool.go. Read-only on the registry: callers must not mutate the returned tools.

type Risk

type Risk int

Risk classifies how dangerous a command segment looks at a glance. Used by the approval modal to color-code parts of a compound command so users can see destructive segments without parsing the whole line.

const (
	RiskNone Risk = iota
	RiskCaution
	RiskDestructive
)

func AssessRisk

func AssessRisk(segment string) (Risk, string)

AssessRisk classifies a single segment. Returns RiskNone for boring commands; RiskCaution for things worth a glance; RiskDestructive for patterns that almost always end in tears if mistakenly approved. The reason string is human-readable for display next to the segment.

func (Risk) String

func (r Risk) String() string

type RollbackTool

type RollbackTool struct{ Cwd string }

func (*RollbackTool) Description

func (t *RollbackTool) Description() string

func (*RollbackTool) Execute

func (t *RollbackTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*RollbackTool) Name

func (t *RollbackTool) Name() string

func (*RollbackTool) PreviewCall

func (t *RollbackTool) PreviewCall(argsJSON string) string

func (*RollbackTool) RequiresApproval

func (t *RollbackTool) RequiresApproval(string) bool

func (*RollbackTool) Schema

func (t *RollbackTool) Schema() map[string]any

type RunBashTool

type RunBashTool struct {
	Cwd string
}

RunBashTool runs a shell command in cwd via /bin/sh -c. Always requires approval. There is no sandbox today; for real isolation, run yottacode itself inside a container.

func (*RunBashTool) Description

func (t *RunBashTool) Description() string

func (*RunBashTool) Execute

func (t *RunBashTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*RunBashTool) Name

func (t *RunBashTool) Name() string

func (*RunBashTool) PreviewCall

func (t *RunBashTool) PreviewCall(argsJSON string) string

func (*RunBashTool) RequiresApproval

func (t *RunBashTool) RequiresApproval(string) bool

func (*RunBashTool) Schema

func (t *RunBashTool) Schema() map[string]any

type RunTestsTool

type RunTestsTool struct{ Cwd string }

func (*RunTestsTool) Description

func (t *RunTestsTool) Description() string

func (*RunTestsTool) Execute

func (t *RunTestsTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*RunTestsTool) Name

func (t *RunTestsTool) Name() string

func (*RunTestsTool) PreviewCall

func (t *RunTestsTool) PreviewCall(argsJSON string) string

func (*RunTestsTool) RequiresApproval

func (t *RunTestsTool) RequiresApproval(string) bool

func (*RunTestsTool) Schema

func (t *RunTestsTool) Schema() map[string]any

type StreamProgress

type StreamProgress struct{}

StreamProgress is a heartbeat for adapter-side stream activity that has no visible text — currently emitted by the OpenAI Responses adapter on each `function_call_arguments.delta`. The TUI uses this to keep the live "tok/s" indicator moving on turns that produce only a tool call (no reasoning summary, no body text) so the row doesn't sit at "0.0 tok/s" the whole time.

type Streamer

type Streamer interface {
	ChatStream(ctx context.Context, messages []adapter.Message, tools []adapter.Tool) <-chan adapter.StreamEvent
}

Streamer is the slice of the adapter the loop actually depends on. Defining it here (instead of pulling in the concrete *adapter.Adapter) lets tests substitute a scripted implementation without standing up an HTTP server.

type SubagentBackgroundDone added in v0.2.0

type SubagentBackgroundDone struct {
	TaskID     string
	AgentType  string
	Result     string
	Errored    bool
	Duration   time.Duration
	TokensUsed int
	ToolCalls  int // child's tool-call count, for inline stats rendering
}

SubagentBackgroundDone fires asynchronously when a background subagent completes after the parent turn has already ended. The TUI surfaces this as a card on the next idle redraw / via /subagents list. Oneshot rejects background invocations entirely so this event is unreachable from non-interactive contexts.

type SubagentDone added in v0.2.0

type SubagentDone struct {
	TaskID     string
	AgentType  string
	Result     string
	Errored    bool
	Duration   time.Duration
	TokensUsed int
	ToolCalls  int // child's tool-call count, for inline stats rendering
}

SubagentDone fires when a foreground subagent completes. The Result is the child's final assistant message — that same string is also returned synchronously from the Agent tool's Execute, so the parent's model receives it as a normal tool result. SubagentDone is for the UI side of the picture: it lets the TUI close the "subagent running" card and oneshot print a final status line.

type SubagentProgress added in v0.2.0

type SubagentProgress struct {
	TaskID    string
	AgentType string
	Activity  string
}

SubagentProgress is the parent-visible activity stream for a running subagent — typically "Explore: read_file internal/foo.go" or "Plan: grep TODO". The child's raw ContentToken/ReasoningToken events are deliberately NOT forwarded; only high-level tool-level activity reaches the parent, which keeps the parent's UI uncluttered AND keeps the child's reasoning out of the parent's adapter context.

type SubagentStart added in v0.2.0

type SubagentStart struct {
	TaskID         string
	AgentType      string
	Prompt         string
	Background     bool
	TranscriptPath string
}

SubagentStart fires when the Agent tool begins a child Turn. The parent's TUI/oneshot renders this as a short header so the user can see that delegation is happening; the child's transcript file at TranscriptPath captures everything that happens inside the child.

type Todo added in v0.2.0

type Todo struct {
	Content string     `json:"content"`
	Status  TodoStatus `json:"status"`
}

Todo is one item in the agent's working plan. Content is the human-readable description; Status is one of the three lifecycle values above. There is intentionally no stable ID — the model passes the full list every call, so identity is positional and renames are indistinguishable from delete+add (matching Claude's shape).

type TodoStatus added in v0.2.0

type TodoStatus string

TodoStatus is the lifecycle state of a single todo item. The three values mirror Claude Code's TodoWrite contract so the model has a familiar schema target.

const (
	TodoPending    TodoStatus = "pending"
	TodoInProgress TodoStatus = "in_progress"
	TodoCompleted  TodoStatus = "completed"
)

type TodoUpdate added in v0.2.0

type TodoUpdate struct{ Todos []Todo }

TodoUpdate fires after a tool implementing the planAware interface (TodoWriteTool today) finishes, carrying the new full snapshot of the working plan. The TUI renders this as a scrollback card showing the current list with status markers; oneshot prints a one-liner on stderr. The slice is a copy — consumers may retain it.

type TodoWriteTool added in v0.2.0

type TodoWriteTool struct {
	Store *PlanStore
}

TodoWriteTool is the yottacode analogue of Claude Code's TodoWrite: a model-callable tool that replaces the working plan on every call, with one item allowed to be `in_progress` at a time. Rendering and persistence happen one layer out — this tool just owns the validated write into the PlanStore. The loop notices the write via the planAware interface (see loop.go) and emits a TodoUpdate event for the TUI.

func (*TodoWriteTool) Description added in v0.2.0

func (t *TodoWriteTool) Description() string

func (*TodoWriteTool) Execute added in v0.2.0

func (t *TodoWriteTool) Execute(_ context.Context, argsJSON string) (string, error)

func (*TodoWriteTool) Name added in v0.2.0

func (t *TodoWriteTool) Name() string

func (*TodoWriteTool) PlanStore added in v0.2.0

func (t *TodoWriteTool) PlanStore() *PlanStore

PlanStore exposes the underlying store so the agent loop can snapshot the list after this tool runs and emit a TodoUpdate event. Satisfies the unexported planAware interface in loop.go.

func (*TodoWriteTool) PreviewCall added in v0.2.0

func (t *TodoWriteTool) PreviewCall(argsJSON string) string

func (*TodoWriteTool) RequiresApproval added in v0.2.0

func (t *TodoWriteTool) RequiresApproval(string) bool

RequiresApproval is always false: this tool has no filesystem, network, or external side effects — it's purely a visibility primitive that updates a per-session in-memory list. Matches Claude Code's TodoWrite posture exactly: real safety comes from the per-mutation prompts on edit_file, write_file, run_bash, etc., not from gating the plan itself.

func (*TodoWriteTool) Schema added in v0.2.0

func (t *TodoWriteTool) Schema() map[string]any

type Tool

type Tool interface {
	Name() string
	Description() string
	Schema() map[string]any
	RequiresApproval(argsJSON string) bool
	PreviewCall(argsJSON string) string
	Execute(ctx context.Context, argsJSON string) (string, error)
}

Tool is one capability the agent can invoke. Execute receives the raw JSON arguments the model emitted; tools parse them internally so the registry stays schema-agnostic.

RequiresApproval takes the argsJSON because some tools (e.g. the unified git tool) decide policy based on the specific subcommand: `git status` auto-executes, `git push --force` prompts. Tools that don't care about args may ignore the parameter.

type ToolResult

type ToolResult struct {
	ToolName string
	Output   string
	Errored  bool
}

ToolResult fires after the tool finishes. Output is the string the model will see; Errored signals whether it was a tool-level error vs. success.

type ToolStart

type ToolStart struct {
	ToolName string
	Preview  string
	ArgsJSON string
}

ToolStart fires immediately before a tool's Execute is called (after any approval flow has resolved). The consumer can render this as a status line. ArgsJSON carries the raw tool-call arguments so consumers can do structured rendering (e.g., the TUI's edit_file diff card) without parsing the human-friendly Preview string.

type TurnDone

type TurnDone struct{}

TurnDone fires when the turn completes cleanly (no more tool calls, no error, not at iter cap). The consumer can re-enable input.

type TurnInterrupted added in v0.2.0

type TurnInterrupted struct {
	// PartialContent is the assistant text that streamed before the
	// cancel, already appended to history. Carried in the event so the
	// TUI can render a one-line snippet without re-walking history.
	PartialContent string
	// OrphanedCalls counts tool_use entries in the just-cancelled batch
	// that received synthetic results (i.e. were never actually run or
	// did not produce real output). Zero when the cancel landed mid-
	// stream before any tool call started.
	OrphanedCalls int
}

TurnInterrupted fires when the turn ended via user-initiated context cancellation (Enter or Esc/Ctrl+C mid-turn) rather than an error or a clean finish. By the time this event lands, the loop has already preserved history correctness: any tokens that streamed before the cancel are appended as a content-only assistant message, and any in-flight or pending tool_calls in the current batch get synthetic "interrupted by user" tool_result entries so no tool_use is left orphaned for the next request. Consumers should render this as a calm marker, not an error — the turn was cut on purpose.

type WriteFileTool

type WriteFileTool struct {
	Cwd       string
	WriteOpts WritePathOptions
}

WriteFileTool creates or overwrites a file. Always needs approval; the WriteOpts validator pre-rejects out-of-cwd, symlinked, or deny-listed paths *before* the approval modal opens, so the model can't trick a distracted user into approving a misleading path.

func (*WriteFileTool) Description

func (t *WriteFileTool) Description() string

func (*WriteFileTool) Execute

func (t *WriteFileTool) Execute(ctx context.Context, argsJSON string) (string, error)

func (*WriteFileTool) Name

func (t *WriteFileTool) Name() string

func (*WriteFileTool) PathsToSnapshot added in v0.2.0

func (t *WriteFileTool) PathsToSnapshot(cwd, argsJSON string) []string

PathsToSnapshot reports the destination path so /checkpoints can restore the pre-write contents (or remove the file if it didn't exist before this turn).

func (*WriteFileTool) PreviewCall

func (t *WriteFileTool) PreviewCall(argsJSON string) string

func (*WriteFileTool) RequiresApproval

func (t *WriteFileTool) RequiresApproval(string) bool

func (*WriteFileTool) Schema

func (t *WriteFileTool) Schema() map[string]any

type WritePathOptions

type WritePathOptions struct {
	// Cwd is the primary allowed root. Required.
	Cwd string

	// AllowedPaths is the list of additional roots a user has opted into
	// via --allow-paths or YOTTACODE_ALLOW_PATHS. Each entry is treated
	// as an absolute root the model is allowed to write under.
	AllowedPaths []string

	// DenyExact is a list of absolute paths (or path prefixes) the model
	// must never write to. Populated from DefaultDenyPaths(cwd) at
	// registration time. Always wins, even if a path otherwise matches
	// Cwd or AllowedPaths.
	DenyExact []string

	// AllowSymlinks lets the validator follow symlinks on write paths.
	// Default false — symlinks are a known exfil vector.
	AllowSymlinks bool

	// PlanModeAllowedFile is the absolute path of the single plan file
	// the agent is permitted to write to while plan mode is active.
	// When non-empty, ValidateWritePath short-circuits to nil for an
	// exact match (after symlink rejection still applies). Empty when
	// plan mode is off — the regular Cwd / AllowedPaths / DenyExact
	// stack is the only authority. The TUI mutates this field on the
	// registered *WriteFileTool / *EditFileTool / *ApplyDiffTool when
	// /plan toggles, and zeroes it on exit.
	PlanModeAllowedFile string
}

WritePathOptions configures the validator for a single tool. Build it once per session in run.go and share across every mutating tool.

type YoloModeState added in v0.2.0

type YoloModeState struct {
	Active atomic.Bool
}

YoloModeState is the per-session "no questions asked" flag. When active, the loop auto-approves every tool call WITHOUT the safety floor that auto mode keeps (run_bash, git_commit, git_checkpoint, rollback all auto-allow silently), AND removes the iteration cap. Explicit Deny rules in permissions.json still win — yolo is "skip prompts," not "ignore my policy."

Mutually exclusive with AutoMode and PlanMode at the TUI layer: entering yolo turns the other two off. The loop-level gate doesn't enforce this on its own; the TUI does.

Intentionally NOT in the Shift+Tab mode cycle — you have to type /yolo to enter so it can't be reached by accident.

func (*YoloModeState) IsActive added in v0.2.0

func (y *YoloModeState) IsActive() bool

IsActive is a nil-safe check used by the loop.

Jump to

Keyboard shortcuts

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