agent

package
v0.2.4-alpha.2 Latest Latest
Warning

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

Go to latest
Published: May 22, 2026 License: MIT Imports: 44 Imported by: 0

Documentation

Overview

Package profiles supplies preset agent.Profile constructors.

A profile picks two tool-name lists — ActiveTools (eager) and DeferredTools (lazy via TOOL_SEARCH) — an LLM target, and a system prompt. Each profile builds its own system prompt internally via the sysprompt package; callers never pass a sysprompt string in. The invariant: a distinct system prompt always lives behind a distinct profile constructor — never as an ad-hoc input — so two agents on the same Profile behave identically.

Adding a new profile = one function composing name lists from the family Names() helpers plus a buildSysPrompt call.

Index

Constants

This section is empty.

Variables

View Source
var ErrIterLimit = errors.New("agent: iteration limit reached")

ErrIterLimit is returned by Run / Continue when the loop hits maxIters without the model producing a terminal text response. The agent is paused, not failed — call Continue(ctx) to resume from the same session.

View Source
var ErrRunInProgress = errors.New("agent: run already in progress")

ErrRunInProgress is returned by Run / Continue when another goroutine is already executing the loop for this agent. Concurrent runs would race on session.Messages and corrupt the assistant-toolcall → tool-result invariant the LLM providers require, so the second caller fails fast instead.

Functions

This section is empty.

Types

type Agent

type Agent struct {
	Parent *Agent

	ID   string
	Name string
	// contains filtered or unexported fields
}

Agent runs a chat loop against an llm.Client, configured by a Profile.

Tool lifecycle (three phases for the model's view of a tool):

  1. ACTIVE — built eagerly in New() and sent (name + description + schema) to the LLM on every Complete call. The model can call them with no preamble.

  2. DEFERRED — listed in the profile's allowlist but NOT built at startup. The model sees them by name only (typically referenced in the system prompt). It must call TOOL_SEARCH to fetch a deferred tool's full schema; TOOL_SEARCH uses toolset.Describe, which reads metadata without building. Construction is intentionally postponed.

  3. RESOLVED — the first time the model actually invokes a deferred tool, the dispatcher calls ResolveTool(name): the tool is built, cached in the active map, executed, and remains available (with its schema sent to the LLM) on every subsequent turn.

toolState holds the shared state container toolset.Build threads into stateful tool constructors. The TUI and session-persist layer read state through it (e.g. agent.ToolState().TodoStore().List()).

sink is the event consumer (nil => Discard). ParentID is empty for the root agent and the root's AgentID for subagents — see Option AsSubagent.

func New

func New(parent *Agent, profile Profile, opts ...Option) (*Agent, error)

New constructs an agent with a fresh ID, a per-agent logger, and the given profile applied. ActiveTools are built immediately; DeferredTools are recorded as an allowlist and only built on the first ResolveTool call.

Options run after the agent struct is populated from the profile and before the LLM client is constructed, so they can influence either layer.

func (*Agent) AgentID

func (a *Agent) AgentID() string

func (*Agent) BeginWorktreeSession

func (a *Agent) BeginWorktreeSession(s mode.WorktreeSession)

BeginWorktreeSession records that the agent has entered a worktree. Called by the EnterWorktree tool after a successful SwitchWorkdir.

func (*Agent) Broker

func (a *Agent) Broker() permission.Broker

Broker is an alias for PermissionBroker that satisfies mode.PlanModeController. Kept short so the controller interface stays terse — EnterPlanMode / ExitPlanMode only know the agent through this interface, not the full *Agent.

func (*Agent) Compact

func (a *Agent) Compact(ctx context.Context, kind string) error

Compact is the manual entry point invoked by the TUI's /compact chooser. Unlike the auto path it bypasses the threshold check and the micro→full escalation — the user explicitly picked a kind.

Refuses with ErrRunInProgress when a Run is currently driving the loop, same guard SwitchLLM uses; the caller (TUI) surfaces that as a hint rather than queueing.

kind is "micro" or "full"; any other value is an error.

func (*Agent) Continue

func (a *Agent) Continue(ctx context.Context) (string, error)

Continue resumes a paused agent without appending a new user message. Used after ErrIterLimit (the "press enter to keep going" path) and after /resume reloads a session snapshot.

func (*Agent) CyclePermissionMode

func (a *Agent) CyclePermissionMode() string

CyclePermissionMode advances the mode in Shift+Tab order and returns the new mode name. Implements ui.Controller.

Unlike SetPermissionMode, this method does NOT emit a KindModeChanged event. It is called from the TUI's Update goroutine (via the Shift+Tab handler), and emitting would call tea.Program.Send() back into the same event loop — a guaranteed deadlock since bubbletea's p.msgs channel is unbuffered. The TUI already updates its status bar directly, so the event is redundant on this path.

func (*Agent) DeferredNames

func (a *Agent) DeferredNames() []tools.ToolName

DeferredNames returns the canonical list of tool names the profile allows to be lazy-loaded. TOOL_SEARCH uses this to know which names it may describe (and the system-prompt builder uses it to advertise them).

Part of the meta.DeferredLookup interface; the agent installs itself as the lookup target via toolState.SetDeferredLookup in New().

func (*Agent) Describe

func (a *Agent) Describe(name tools.ToolName) (tools.Descriptor, error)

Describe returns the metadata for a deferred tool by name. Delegates to toolset.Describe, which constructs a throwaway instance to read its static fields — no agent state is mutated and no tool is "loaded".

Part of the meta.DeferredLookup interface, used by TOOL_SEARCH.

func (*Agent) Effort

func (a *Agent) Effort() string

Effort returns the current effort level name.

func (*Agent) EndWorktreeSession

func (a *Agent) EndWorktreeSession()

EndWorktreeSession clears the active worktree session. Called by the ExitWorktree tool after a successful SwitchWorkdir back to the original workdir (whether the worktree was kept or removed).

func (*Agent) IsAsync

func (a *Agent) IsAsync() bool

func (*Agent) IsSubagent

func (a *Agent) IsSubagent() bool

IsSubagent reports whether this agent was constructed with AsSubagent. The AGENT tool checks this to enforce the "subagents cannot spawn subagents" invariant.

func (*Agent) ListMainProfiles

func (a *Agent) ListMainProfiles() []ui.ProfileChoice

ListMainProfiles enumerates the personas the /profile picker can switch to. Pulls from the registry's ListMain(); subagents return nil (they don't drive the picker).

func (*Agent) ListSessions

func (a *Agent) ListSessions() ([]ui.SessionInfo, []string)

ListSessions enumerates persisted sessions for this agent's workdir, sorted by file mtime descending. Implements ui.Controller. Subagents never persist, so this returns an empty slice for them — the /resume command is only meaningful for the root.

func (*Agent) Logger

func (a *Agent) Logger() *slog.Logger

Logger exposes the agent's logger so callers can emit records that share the agent's structured context.

func (*Agent) MaxIterations

func (a *Agent) MaxIterations() int

MaxIterations returns the current loop cap. Safe to call from any goroutine — backed by an atomic load.

func (*Agent) Model

func (a *Agent) Model() string

Model returns the model id the agent's LLM client is bound to. Wraps llm.Client.Model() so the ui.Controller interface stays independent of the llm package. Empty when no client is attached.

func (*Agent) ParentID

func (a *Agent) ParentID() string

func (*Agent) PermissionBroker

func (a *Agent) PermissionBroker() permission.Broker

PermissionBroker exposes the shared approval back-channel.

func (*Agent) PermissionMode

func (a *Agent) PermissionMode() permission.Mode

PermissionMode returns the agent's current permission stance. Safe to call from any goroutine.

func (*Agent) PermissionModeName

func (a *Agent) PermissionModeName() string

PermissionModeName returns the mode as a plain string (ui.Controller uses a string-typed interface to avoid importing internal/permission).

func (*Agent) PermissionStore

func (a *Agent) PermissionStore() *permission.Store

PermissionStore exposes the shared rule store. Returns nil if the caller didn't install one (tests, headless CLI runs).

func (*Agent) PlanModeState

func (a *Agent) PlanModeState() *permission.PlanModeState

PlanModeState exposes the unified plan-mode state holder so the attachment computer (internal/agent/attachments) can read the reminder-cycle counters without going through the agent's narrow permission-mode interface.

func (*Agent) PrePlanMode

func (a *Agent) PrePlanMode() permission.Mode

PrePlanMode returns the mode that was active immediately before plan mode became active. Empty until the first plan-mode entry; ExitPlanMode falls back to ModeDefault when empty.

Reads through the unified plan-mode state holder so the TUI's Shift+Tab path and the EnterPlanMode tool path agree on what was stashed.

func (*Agent) Profile

func (a *Agent) Profile() Profile

Profile returns the profile this agent was constructed with.

func (*Agent) ProfileName

func (a *Agent) ProfileName() string

ProfileName returns the active persona's wire name ("evva", "nono", ...). Used by the TUI status bar and the /profile picker's current-row marker. Subagents return the persona kind they were constructed under.

func (*Agent) ResolveTool

func (a *Agent) ResolveTool(name tools.ToolName) (tools.Tool, error)

ResolveTool returns the runnable instance for a tool name, building it on the fly if it's a still-unmaterialized deferred tool. This is the path the tool-call dispatcher takes whenever the LLM invokes a tool by name:

  • If the name is already in the active map (either built at New() or resolved on a previous turn), the cached instance is returned.
  • Otherwise, if the name is in the deferred allowlist, the tool is built via toolset.Build, cached in active, and returned. Its schema will be advertised to the LLM from the next turn forward.
  • Otherwise, the name is rejected — the agent never silently expands beyond the profile's declared authority.

Note: TOOL_SEARCH should NOT call this — it only fetches descriptors via toolset.Describe. The build is triggered by the first actual invocation.

func (*Agent) RespondPermission

func (a *Agent) RespondPermission(id string, dec ui.PermissionDecision) error

RespondPermission forwards the user's approval choice from the TUI to the broker. The id ties back to a single blocked Broker.Request call. Returns ui.ErrUnknownPermission if the id is no longer pending (already answered or cancelled). Implements ui.Controller.

func (*Agent) RespondQuestion

func (a *Agent) RespondQuestion(id string, resp ui.QuestionResponse) error

RespondQuestion forwards the user's answers from the TUI to the question broker. id ties back to a single blocked question.Broker.Request call. Implements ui.Controller.

func (*Agent) ResumeSession

func (a *Agent) ResumeSession(id string) error

ResumeSession loads the snapshot with `id` off disk and swaps the live agent into it. Implements ui.Controller. The actual state-swap logic lives in ResumeSnapshot — this wrapper handles the disk read and the workdir-slug resolution.

func (*Agent) ResumeSnapshot

func (a *Agent) ResumeSnapshot(snap *session.Snapshot) error

ResumeSnapshot swaps the live agent into a previously-persisted session loaded from disk. Structurally mirrors SwitchProfile: enforce the running guard, rebuild profile/tools/LLM under the snapshot's persona+provider+model, then overwrite the session.

The snapshot's session-id replaces the live agent's ID so subsequent persistSession writes target the same file (continuing the same resume-list entry rather than orphaning the original).

Fallbacks (the snapshot may have been written under a persona or model that's since been removed):

  • Missing persona → "evva".
  • Unknown provider → current cfg.DefaultProvider.
  • Unknown model → provider's first listed model.

MUST be called while no Run is in flight. Subagents cannot resume — only the root. The string-keyed ResumeSession wrapper below is what ui.Controller exposes; this method is the testable seam.

func (*Agent) Run

func (a *Agent) Run(ctx context.Context, prompt string) (string, error)

Run drives the agent to completion for a single user turn.

It appends a RoleUser{prompt} message to the session and then loops: LLM completion → if tool_use, dispatch all tool calls in parallel, append the collected results as a single RoleTool message, repeat. The loop exits when the model emits no tool calls (normal terminal), the context is cancelled, the iteration cap is hit (ErrIterLimit), or a Go-level error aborts.

Events flow to the agent's Sink. The returned llm.Response is the final assistant turn — or the zero value when the loop ended without one.

func (*Agent) Session

func (a *Agent) Session() *session.Session

Session exposes the conversation history for inspection or TUI rendering.

func (*Agent) SetEffort

func (a *Agent) SetEffort(level string) error

SetEffort updates the effort level at runtime. Validates the name, applies it to the LLM client, and persists to config.

func (*Agent) SetMaxIterations

func (a *Agent) SetMaxIterations(n int)

SetMaxIterations updates the loop cap. Takes effect at the next iteration boundary (loop.go:74 reads a.maxIters via atomic.Load). Values <= 0 are clamped to 1.

func (*Agent) SetPermissionMode

func (a *Agent) SetPermissionMode(m permission.Mode)

SetPermissionMode updates the agent's permission stance at runtime. Every entry path (Shift+Tab cycle, EnterPlanMode / ExitPlanMode tools, future SDK control messages) routes through here so the plan-mode transition hub runs exactly once per mode change and the TUI receives a single KindModeChanged event per change.

Validates the mode; ignores unknown values to keep the system in a known-good state. Idempotent on no-op transitions: same-mode calls neither run side effects nor emit the change event.

Mode changes don't propagate to already-spawned subagents — they captured the mode at spawn time. New spawns see the updated mode.

func (*Agent) SetPrePlanMode

func (a *Agent) SetPrePlanMode(m permission.Mode)

SetPrePlanMode is retained on the PlanModeController interface for the EnterPlanMode tool, but new code should rely on SetPermissionMode (which runs the transition hub and stashes the prior mode automatically).

func (*Agent) Sink

func (a *Agent) Sink() event.Sink

Sink returns the agent's event sink. Used by the AGENT tool to wrap with BubbleUp when spawning a subagent. Returns event.Discard if no sink was installed.

func (*Agent) Skills

func (a *Agent) Skills() []ui.Skill

Skills returns the user-installed skill catalog as the UI sees it — name + description per entry, sorted by name. Implements ui.Controller so the TUI's slash-suggestion panel can list skills alongside the built-in commands without reaching into ToolState directly.

Returns nil when no registry was installed (e.g. tests / headless callers that didn't pass WithSkillRegistry).

func (*Agent) Spawn

func (a *Agent) Spawn(ctx context.Context, req meta.SpawnRequest) (string, error)

Spawn implements meta.SubagentSpawner. The AGENT tool's lookup resolves to *Agent (the root agent registers itself via SetSubagentSpawner in New).

Spawn:

  1. Rejects calls from a subagent (the "main only" invariant).
  2. Picks a Profile via subagentProfile, inheriting the ParentID's provider, options, and any baseline preferences.
  3. Overrides the model based on req.Level via LLMProvider.ModelForLevel — 1 is the normal tier, 2 is the big tier.
  4. Constructs a child agent with event.BubbleUp routing its events back to the ParentID's Sink, tagged with the ParentID's AgentID.
  5. Registers the child in the ParentID's SpawnGroup panel — every mutation is observable through the unified ToolState change stream.
  6. Runs the child: - Sync mode: blocks until child.Run completes, removes from panel on return, and propagates the child's text through the tool result. - Async mode: spawns a goroutine that runs the child and marks the panel entry Report / Crushed on exit. Returns immediately with an ack message; the ParentID loop will pick up the eventual result via AgentGroup.DrainCompleted between turns.

func (*Agent) Status

func (a *Agent) Status() constant.AgentStatus

func (*Agent) SubagentTypes

func (a *Agent) SubagentTypes() []string

SubagentTypes returns the agent names that the AGENT tool's subagent_type enum should accept. Pulls from the registry's ListSubagent (so disk subagents become wire-callable as soon as the registry sees them). Falls back to the built-in pair when no registry is installed.

func (*Agent) SwitchLLM

func (a *Agent) SwitchLLM(provider constant.LLMProvider, model constant.Model) error

SwitchLLM rebuilds a.llm with a new (provider, model) pair, updates a.profile so subagents inherit the new provider, and clears the session — provider-specific in-flight state (Anthropic ThinkingSignature, DeepSeek reasoning_content) is provider-locked, so keeping history across a swap would 400 on the next request.

MUST be called while no Run is in flight. Returns ErrRunInProgress when the running guard is set, so the caller can refuse the swap instead of racing a.llm reads on the agent loop's goroutine.

func (*Agent) SwitchProfile

func (a *Agent) SwitchProfile(name string) error

SwitchProfile rebuilds the agent for a new persona — different system prompt, different active/deferred tool lists, fresh session. Mirrors SwitchLLM's running-guard discipline.

The toolState is preserved so observable subscriptions (the TUI panels) keep working across the swap; only the TodoStore is cleared since its entries belong to a single session. The LLM client is rebuilt because the new profile carries its own WithSystem option.

MUST be called while no Run is in flight. Returns ErrRunInProgress when the running guard is set. Persists the new persona name to evva-config.yml so the next launch boots into it.

func (*Agent) SwitchWorkdir

func (a *Agent) SwitchWorkdir(path string) error

SwitchWorkdir mutates the agent's workdir and rebuilds every workdir-sensitive piece of session state in lockstep:

  1. updates a.workdir and a.cfg.WorkDir so toolset accessors see the new path immediately;
  2. reloads the EVVA.md + USER_PROFILE.md snapshot from the new workdir;
  3. rebuilds the active-tools map so fs Read/Write/Edit/Glob and Bash (which captured the OLD workdir at construction) point at the new path;
  4. re-renders the system prompt against the new memory snapshot and applies it to the live LLM client.

The session transcript is preserved — a worktree switch is a workdir move, not a persona change. Returns ErrRunInProgress if the agent is mid-Run (a tool changing workdir while the loop reads a.active would race); callers from inside tool Execute are already serialised with the loop, so this only blocks reentrant API misuse.

Subagents reject SwitchWorkdir — the AgentTool isolation path sets the child's workdir at construction; mid-life changes are reserved for the root agent.

func (*Agent) Tool

func (a *Agent) Tool(name string) (tools.Tool, bool)

Tool returns the runnable instance for an already-built tool. Returns ok=false for deferred names that have not been resolved yet — call ResolveTool when you intend to execute.

func (*Agent) ToolState

func (a *Agent) ToolState() *toolset.ToolState

ToolState exposes the shared state container so the TUI / session-persist layer can read tool state through typed accessors (e.g. TaskStore.List()).

func (*Agent) Workdir

func (a *Agent) Workdir() string

Workdir returns the agent's current logical working directory. Used by the permission gate's plan-file carve-out, the EnterPlanMode tool's plan-file path, and the workdir-bound tool factories (Bash, fs). May change at runtime when EnterWorktree / ExitWorktree fires — read through this accessor each time you need it, don't cache.

func (*Agent) WorktreeSession

func (a *Agent) WorktreeSession() *mode.WorktreeSession

WorktreeSession returns the active worktree session, or nil if the agent isn't currently in one. Satisfies mode.WorktreeController.

type AgentRegistry

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

AgentRegistry holds every agent definition known to the runtime — Go-defined built-ins (sysprompt.MainAgent, ExploreAgent, GeneralAgent) merged with disk-loaded definitions from <EVVA_HOME>/agents/{name}/.

Phase 6 will consume this registry from two sides:

  • The /profile slash command picker lists agents with ListMain().
  • The Agent tool's subagent_type schema enum becomes the union of every agent in ListSubagent().

Phase 2 plants the registry and routes subagent resolution through it, but the Agent tool's schema enum stays hardcoded — disk-loaded subagents will load into the registry but won't be wire-callable until Phase 6 flips the schema.

func BuildAgentRegistry

func BuildAgentRegistry(evvaHome string) (*AgentRegistry, []loader.Warning)

BuildAgentRegistry assembles the runtime registry: built-ins first, then disk agents from <evvaHome>/agents/. Disk agents that collide with a built-in name are skipped with a warning — built-ins always win so a typo in a disk agent's directory name can't silently replace `explore`.

Returns the registry and any warnings the host should log. Never returns an error: a missing or malformed disk catalog degrades gracefully.

func NewAgentRegistry

func NewAgentRegistry() *AgentRegistry

NewAgentRegistry returns an empty registry. Most callers want BuildAgentRegistry, which pre-populates built-ins + disk agents.

func (*AgentRegistry) All

All returns every definition sorted by name. Useful for diagnostics and the Phase 6 picker enumeration paths.

func (*AgentRegistry) Get

Get returns the definition for name. Lookup is case-insensitive to match the AGENT tool's subagent_type behavior — "Explore", "explore", "EXPLORE" all resolve to the same agent.

func (*AgentRegistry) ListMain

func (r *AgentRegistry) ListMain() []sysprompt.AgentDefinition

ListMain returns every definition whose `as:` includes "main" — the candidates the Phase 6 /profile picker shows.

func (*AgentRegistry) ListSubagent

func (r *AgentRegistry) ListSubagent() []sysprompt.AgentDefinition

ListSubagent returns every definition whose `as:` includes "subagent" — the candidates Phase 6 will surface in the Agent tool's dynamic enum.

func (*AgentRegistry) Register

Register adds (or overwrites) a definition. Returns the previous value and a bool indicating whether a definition with this name already existed — callers (the loader merge step) can warn on duplicates so a disk agent silently shadowing a built-in is visible at startup.

type AgentType

type AgentType int

AgentType enumerates the kinds of agent we know how to bootstrap. Profiles in agent/profiles are keyed off these values; the value also appears in logs to identify which kind of agent emitted a record.

const (
	MAIN AgentType = iota
	EXPLORE
	GENERAL_PURPOSE
	PLAN
)

func (AgentType) String

func (t AgentType) String() string

String returns a short human label suitable for logs and the system prompt.

type Option

type Option func(*Agent)

Option mutates an Agent during construction. Options are applied after the profile is materialized but before the LLM client is initialized so any option can influence either side without ordering surprises.

func WithAgentRegistry

func WithAgentRegistry(r *AgentRegistry) Option

WithAgentRegistry installs the merged built-in + disk agent registry on the agent. Subagent spawn resolves through this registry: kinds the AGENT tool's schema enum accepts ("explore", "general-purpose") plus any disk-loaded subagent the registry surfaces once Phase 6 opens the schema.

Subagents inherit the same pointer when the spawner forwards it explicitly — see spawn.go where the parent's registry is threaded into child.New so a delegated query (Phase 6's evva → nono pattern) can still look up the disk catalog without rebuilding it.

nil clears the registry; in practice the root agent always installs one at startup and subagents inherit it, so nil only appears in tests.

func WithAsync

func WithAsync(async bool) Option

func WithConfig

func WithConfig(cfg *config.Config) Option

WithConfig installs the runtime configuration the agent reads from. Subagents inherit the parent's config via spawn; downstream apps that run multiple agents with different home dirs pass distinct *config.Config pointers per agent.

Omitting WithConfig boots the agent against config.Get() — the historical singleton — so cmd/evva and existing callers don't need to change.

func WithCustomTool

func WithCustomTool(name tools.ToolName, factory pubtoolset.ToolFactory) Option

WithCustomTool registers a downstream-authored tool on the pkg/toolset.DefaultRegistry and adds it to the agent's active list. The factory receives the agent's pkg/tools.State at build time so the tool can read Config() and Workdir().

Registration is idempotent across agents — calling WithCustomTool with the same name in two New calls registers the factory once and reuses it. Use this for tool factories that are private to the downstream app; tools authored as part of a shared library should be registered directly on pkg/toolset.DefaultRegistry at process startup instead.

The tool's name MUST be unique across all registered tools (built-ins and prior customs alike). Re-registering an existing name during agent construction does NOT error — the original factory keeps serving.

func WithMaxIterations

func WithMaxIterations(n int) Option

WithMaxIterations overrides the agent's loop cap. Pass 0 to keep the cfg-derived default (applied after options run in agent.New). Values in (0, 2) are clamped to 2 (single-turn agents would never observe a tool result).

func WithMemorySnapshot

func WithMemorySnapshot(snap memdir.Snapshot) Option

WithMemorySnapshot stashes the EVVA.md + USER_PROFILE.md snapshot loaded at startup. Reused by SwitchProfile when constructing a new persona's system prompt — the on-disk files are read once at boot.

func WithName

func WithName(name string) Option

func WithPermissionBroker

func WithPermissionBroker(b permission.Broker) Option

WithPermissionBroker installs the approval back-channel. Same pattern as WithPermissionStore: one Broker per process, shared by all agents. The TUI registers its onRequest callback on this Broker at startup.

func WithPermissionMode

func WithPermissionMode(m permission.Mode) Option

WithPermissionMode sets the agent's initial permission stance. Subagents inherit the parent's mode at spawn time; the runtime cycle (Shift+Tab) uses Agent.SetPermissionMode.

func WithPermissionStore

func WithPermissionStore(s *permission.Store) Option

WithPermissionStore installs the rule store. One process-wide Store is built in cmd/evva/main.go and threaded into the root agent and every subagent so session rules added in one place are visible everywhere.

func WithPersona

func WithPersona(name string) Option

WithPersona records the active persona's wire name on the agent. Callers set this from profile-resolution so ProfileName() and the TUI status bar render the right label. Empty leaves the field as-is (the bootstrap caller is expected to set it explicitly).

func WithQuestionBroker

func WithQuestionBroker(b question.Broker) Option

WithQuestionBroker installs the question back-channel. Same pattern as WithPermissionBroker: one Broker per process, shared by all agents. The TUI registers its OnRequest callback on this Broker at startup.

func WithSink

func WithSink(s event.Sink) Option

WithSink installs the event consumer. nil sinks become event.Discard at emit-time; pass event.Multi{...} to fan out to several consumers.

func WithSkillRefs

func WithSkillRefs(refs []sysprompt.SkillRef) Option

WithSkillRefs stashes the skill snapshot the agent was bootstrapped with so SwitchProfile can rebuild the system prompt with the same skill catalog. The snapshot is a flat slice the sysprompt builder consumes; the agent does not call into the skill package directly.

func WithSkillRegistry

func WithSkillRegistry(r *skill.Registry) Option

WithSkillRegistry installs the merged skill catalog on the agent's ToolState before the first turn. The SKILL tool reads through this registry at Execute time; passing nil leaves the SKILL tool with no skills available.

The same pointer is shared with subagents when the spawner forwards it explicitly — today subagent profiles omit SKILL, so this is primarily a root-agent concern.

func WithStream

func WithStream(stream bool) Option

WithStream toggles streaming completions for this agent. Overrides the Profile's Stream field; useful for tests and one-off callers that want to force the buffered or chunked path without editing the profile.

type Profile

type Profile struct {
	Type         AgentType
	SystemPrompt string

	// Tool policy
	ActiveTools   []tools.ToolName
	DeferredTools []tools.ToolName

	// LLM core
	LLMProvider constant.LLMProvider
	LLMModel    constant.Model
	LLMOptions  []llm.Option

	// Stream selects the streaming completion path. When true the agent
	// calls llm.Client.Stream and forwards each delta to the event sink
	// as KindTextChunk / KindThinkingChunk; when false it calls Complete
	// and emits a single KindText / KindThinking after the turn assembles.
	Stream bool
}

Profile is the configuration an Agent runs under: which kind of agent it is, what system prompt it presents, and which tool *names* are exposed to the model.

Tool policy is split into two lists — this split is purely an agent-level scheduling decision; the tool packages themselves know nothing about it:

  • ActiveTools are constructed at agent.New() and exposed to the LLM in every Complete call. The model can invoke them with no preamble.

  • DeferredTools are advertised to the model by name only. They are materialized on demand via agent.LoadDeferred (driven by TOOL_SEARCH). Listing a name here is the agent's allowlist for what may be lazily loaded; a profile that omits a name forbids it entirely.

Two agents with the same Profile behave identically — the loop, dispatch, and lifecycle are shared in the Agent type; only configuration varies.

func Explore

func Explore(cfg *config.Config, provider constant.LLMProvider, model constant.Model, options []llm.Option) Profile

Explore returns a read-only profile: just READ_FILE / GREP / TREE, plus WEB_SEARCH for docs lookup. Useful for sub-agents whose job is to inspect without risk of modification.

The Explore prompt is self-contained (mirrors ref TS Explore agent) and does not include EVVA.md / USER_PROFILE.md — sysprompt.ExploreAgent declares OmitMemory: true.

func General

func General(cfg *config.Config, provider constant.LLMProvider, model constant.Model, options []llm.Option, toolset ...tools.ToolName) Profile

General returns a minimal profile carrying only the tool names the caller supplies as active. No deferred tools. Useful for narrow-purpose sub-agents.

Like Explore, the General prompt does not include EVVA.md / USER_PROFILE.md.

func Main

func Main(cfg *config.Config, provider constant.LLMProvider, model constant.Model, skills []sysprompt.SkillRef, mem memdir.Snapshot, options []llm.Option) Profile

Main returns the full-kit profile: fs/shell/meta/skill are active; the rest are deferred (loaded on demand via TOOL_SEARCH).

The system prompt is built via sysprompt.MainAgent. Skills are advertised (Main is the only agent that surfaces them), and the EVVA.md / USER_PROFILE.md memory snapshot is threaded into the prompt under labeled headings. Callers pass an empty memdir.Snapshot{} when memory injection is not desired.

Streaming is on by default — the user-facing UX win is large and the chunk adapter falls back cleanly for providers without native streaming. Callers who want the old buffered behavior can pass WithStream(false) at agent construction.

func Plan

func Plan(cfg *config.Config, provider constant.LLMProvider, model constant.Model, options []llm.Option) Profile

Plan returns a read-only profile for design-phase planning work — same tool kit as Explore (read, web_search, glob, tree, grep, json_query) plus an architect-flavored system prompt that asks for a step-by-step plan and a critical-files list. Used by the main agent during plan-mode Phase 2 (Design) to delegate per-perspective design takes.

Plan deliberately does not get edit/write/enter_plan_mode/exit_plan_mode — its job is to explore and recommend, not modify state. Like Explore, the prompt does not include EVVA.md / USER_PROFILE.md (sysprompt.PlanAgent declares OmitMemory: true).

func ResolveMainProfile

func ResolveMainProfile(cfg *config.Config, reg *AgentRegistry, name string, skills []sysprompt.SkillRef, mem memdir.Snapshot, options []llm.Option) (Profile, error)

ResolveMainProfile is the single entry point for picking a main-tier Profile by persona name. Used by both bootstrap (cmd/evva/main.go) and the runtime /profile switch (Agent.SwitchProfile).

Built-in "evva" routes through Main(...) verbatim — the same full-kit active/deferred tool lists, the same memdir + skills wiring. Disk-loaded main personas route through mainProfileFromDiskAgent which uses the def's own tool lists and BuildSystemPrompt body, gated by the def's OmitMemory / AdvertiseSkills flags from meta.yml.

Empty name defaults to "evva". Unknown or non-main names return an error so callers (bootstrap fallback, the /profile picker) can surface the failure.

Directories

Path Synopsis
Package attachments computes per-turn <system-reminder>-wrapped messages the agent loop prepends to the user's incoming prompts.
Package attachments computes per-turn <system-reminder>-wrapped messages the agent loop prepends to the user's incoming prompts.
Package loader reads user-authored agent definitions from <EVVA_HOME>/agents/{name}/ at startup and turns them into sysprompt.AgentDefinition values the agent.Registry can merge with Go-defined built-ins (sysprompt.MainAgent, ExploreAgent, GeneralAgent).
Package loader reads user-authored agent definitions from <EVVA_HOME>/agents/{name}/ at startup and turns them into sysprompt.AgentDefinition values the agent.Registry can merge with Go-defined built-ins (sysprompt.MainAgent, ExploreAgent, GeneralAgent).
Package sysprompt builds the system prompt for each kind of agent evva runs.
Package sysprompt builds the system prompt for each kind of agent evva runs.

Jump to

Keyboard shortcuts

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