agent

package
v1.3.0-beta.1 Latest Latest
Warning

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

Go to latest
Published: May 29, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrIterLimit = agent_impl.ErrIterLimit

ErrIterLimit is returned by Run / Continue when the agent reaches its loop-iteration cap before the model stops calling tools. Callers pause and call Continue to resume. Re-exported from the internal agent so a pkg-only host can errors.Is against it.

Functions

This section is empty.

Types

type Agent

type Agent interface {
	// Run drives the agent for a single user turn.
	Run(ctx context.Context, prompt string) (string, error)

	// Continue resumes an iter-limit-paused run without appending a new
	// user message.
	Continue(ctx context.Context) (string, error)

	// Session returns a snapshot of the conversation state.
	Session() SessionInfo

	// Logger exposes the agent's structured logger.
	Logger() *slog.Logger

	// Model returns the model id the agent is currently bound to.
	Model() string

	// AgentID returns the agent's unique identifier.
	AgentID() string

	// MaxIterations / SetMaxIterations exposes the loop cap.
	MaxIterations() int
	SetMaxIterations(int)

	// SwitchLLM rebuilds the agent's LLM client with a new (provider, model)
	// pair and clears the conversation history.
	SwitchLLM(provider constant.LLMProvider, model constant.Model) error

	// SwitchProfile reconstructs the agent under a new persona.
	SwitchProfile(name string) error

	// ProfileName returns the active persona's wire identity.
	ProfileName() string

	// ListMainProfiles enumerates the personas available for switching.
	ListMainProfiles() []ProfileChoice

	// Effort returns the current effort level name ("low"|"medium"|"high"|"ultra").
	Effort() string

	// SetEffort updates the effort level at runtime.
	SetEffort(level string) error

	// Skills returns the merged catalog of user-installed skills.
	Skills() []Skill

	// Compact forces an immediate compaction of the current session.
	Compact(ctx context.Context, kind string) error

	// PermissionModeName returns the agent's current permission stance
	// as a string ("default", "accept_edits", "plan", "bypass", "auto").
	PermissionModeName() string

	// CyclePermissionMode advances the mode in Shift+Tab order and
	// returns the new mode name.
	CyclePermissionMode() string

	// RespondPermission delivers the user's approval/denial back to
	// the blocked tool goroutine.
	RespondPermission(id string, decision PermissionDecision) error

	// RespondQuestion delivers the user's answers back to the blocked
	// AskUserQuestion tool goroutine.
	RespondQuestion(id string, resp QuestionResponse) error

	// ListSessions enumerates persisted sessions for the agent's workdir,
	// sorted by mtime descending. The second return is a slice of
	// non-fatal warnings collected while scanning (corrupt files, etc.).
	ListSessions() ([]ResumableSession, []string)

	// ResumeSession reloads a session by id, swapping the live agent's
	// transcript + profile + LLM with the persisted state. Returns an
	// error when the file is missing, unreadable, or a Run is currently
	// in flight.
	ResumeSession(id string) error

	// Controller returns the agent as a ui.Controller — the narrow seam a
	// UI implementation (e.g. pkg/ui/bubbletea) drives the agent through.
	// The public Agent interface and ui.Controller share method names with
	// different payload types (DTO vs ui.* views), so they cannot live on
	// one concrete type; this accessor hands a host the ui-typed view to
	// pass to UI.Attach.
	Controller() ui.Controller

	// Shutdown cancels the agent's root context, tearing down the signal
	// pump and every background worker (bash tasks, monitors, subagents).
	// Safe to call once at host exit (typically via defer).
	Shutdown()
}

Agent is the public API for creating and driving an evva agent programmatically. It is implemented by a wrapper around the internal agent.

func New

func New(cfg Config, opts ...Option) (Agent, error)

New is the one-call constructor: it absorbs the whole bootstrap a host used to hand-wire — persona resolution (with an evva fallback), EVVA.md / USER_PROFILE.md memory + skill auto-load, the permission store + mode, and the approval / question brokers — driven entirely by Config plus Options.

With no Options it builds a headless agent (the default brokers auto-deny any approval the model requests, so a request never parks a goroutine). Pass WithSink to surface approval + question events to an interactive UI (resolve them via Agent.RespondPermission / RespondQuestion) and WithRootContext so Ctrl-C reaches every background worker — that combination is all a ~40-line host needs for a full TUI with personas, permissions, /resume, and /compact.

Options are applied after the Config-derived wiring, so a host Option always wins on conflict (e.g. WithName, WithMaxIterations).

func NewWithProfile

func NewWithProfile(profile Profile, opts ...Option) (Agent, error)

NewWithProfile is the flexible public constructor a downstream app uses to build an agent against its own profile and option set. Unlike New (which loads the bundled "evva" persona, memdir, and skill catalog) this constructor wires only what the caller supplies — no skill catalog, no memory snapshot, no agent registry by default.

Approval / question handling follows the agent's defaults. Pass agent.WithSink to surface approval + question events to an interactive UI (resolve them via Agent.RespondPermission / RespondQuestion), or agent.WithPermissionBroker to plug in a custom allow/deny policy. With neither, the agent auto-denies so an async request never parks the caller forever.

Example:

prof, _ := agent.NewProfile("custom", "you are helpful",
    []tools.ToolName{tools.READ_FILE, tools.BASH},
    "anthropic", constant.CLAUDE_SONNET_4_6,
    agent.ProfileOptions{})

ag, _ := agent.NewWithProfile(prof,
    agent.WithConfig(cfg),
    agent.WithSink(mySink),
    agent.WithMaxIterations(20),
)
resp, _ := ag.Run(ctx, "...")
Example

ExampleNewWithProfile builds a complete agent and runs one turn. The flow: register a provider, build a Config with credentials, build a Profile, construct the agent with options, then call Run.

WithHeadlessBypass is the Phase 19c convenience — required for downstream apps that don't render an approval UI, otherwise every tool call needing approval would be auto-denied.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"os"
	"path/filepath"

	"github.com/johnny1110/evva/pkg/agent"
	"github.com/johnny1110/evva/pkg/config"
	"github.com/johnny1110/evva/pkg/constant"
	"github.com/johnny1110/evva/pkg/event"
	"github.com/johnny1110/evva/pkg/llm"
	"github.com/johnny1110/evva/pkg/tools"
)

// exampleConfig loads a throwaway Config under /tmp so the example
// works without touching the user's real ~/.evva home.
func exampleConfig() *config.Config {
	tmp := filepath.Join(os.TempDir(), "evva-example-agent")
	cfg, _ := config.Load(config.LoadOptions{
		AppName: "example",
		AppHome: tmp,
		WorkDir: tmp,
	})
	return cfg
}

// echoLLM is a minimal stand-in for examples. Production callers
// import `_ "github.com/johnny1110/evva/pkg/llm/builtins"` to get
// Anthropic / DeepSeek / Ollama pre-registered instead.
type echoLLM struct{ model string }

func (e *echoLLM) Name() string             { return "echo" }
func (e *echoLLM) Model() string            { return e.model }
func (*echoLLM) SupportsDeferLoading() bool { return false }
func (e *echoLLM) Complete(_ context.Context, msgs []llm.Message, _ []tools.Tool) (llm.Response, error) {
	return llm.Response{Content: "echo: ok"}, nil
}
func (e *echoLLM) Stream(ctx context.Context, m []llm.Message, t []tools.Tool, _ llm.ChunkSink) (llm.Response, error) {
	return e.Complete(ctx, m, t)
}
func (*echoLLM) Apply(...llm.Option) {}

// examplePingTool answers any input with "pong". Demonstrates the public
// pkg/tools.Tool interface a downstream consumer satisfies.
type examplePingTool struct{}

func (examplePingTool) Name() string            { return "ping" }
func (examplePingTool) Description() string     { return "respond with pong" }
func (examplePingTool) Schema() json.RawMessage { return json.RawMessage(`{"type":"object"}`) }
func (examplePingTool) Execute(_ context.Context, _ *slog.Logger, _ json.RawMessage) (tools.Result, error) {
	return tools.Result{Content: "pong"}, nil
}

func main() {
	// 1. Register an echo provider for the example.
	if !llm.DefaultRegistry().Has("echo-example") {
		_ = llm.DefaultRegistry().Register("echo-example",
			func(_ llm.APIConfig, model string, _ ...llm.Option) (llm.Client, error) {
				return &echoLLM{model: model}, nil
			})
	}

	// 2. Build a config with credentials. Real apps use config.Load
	//    with their own AppName / AppHome; this example uses an inline
	//    Config so the example doesn't pollute the filesystem.
	cfg := exampleConfig()
	_ = cfg.SetProviderCredentials("echo-example", "http://example", "n/a")

	// 3. Build the Profile + Agent.
	prof, _ := agent.NewProfile("example", "you echo",
		nil, "echo-example", constant.Model("v0"), agent.ProfileOptions{})

	collected := []string{}
	sink := event.SinkFunc(func(e event.Event) {
		if e.Kind == event.KindText && e.Text != nil {
			collected = append(collected, e.Text.Text)
		}
	})

	ag, err := agent.NewWithProfile(prof,
		agent.WithConfig(cfg),
		agent.WithSink(sink),
		agent.WithMaxIterations(3),
		agent.WithHeadlessBypass(),
		agent.WithCustomTool("ping", func(tools.State) (tools.Tool, error) { return examplePingTool{}, nil }),
	)
	if err != nil {
		fmt.Println("construct error:", err)
		return
	}

	resp, _ := ag.Run(context.Background(), "hi")
	fmt.Println("final:", resp)
}
Output:
final: echo: ok

type AgentDefinition

type AgentDefinition struct {
	// Name is the wire identifier ("nono", "noen", ...). Lowercase,
	// hyphenated. Matches the /profile label and the Agent tool's
	// subagent_type value.
	Name string

	// WhenToUse tells a parent agent what to delegate to this persona; shown
	// in the Agent tool's catalog. Leave empty for a main-only persona.
	WhenToUse string

	// As controls visibility: "main" (in the /profile picker), "subagent"
	// (invokable via the Agent tool), or both.
	As []string

	// InjectMemory opts the persona into EVVA.md / USER_PROFILE.md injection.
	// Main personas usually want this true; subagents usually false.
	InjectMemory bool

	// AdvertiseSkills surfaces the installed skill catalog in the prompt.
	// Usually only main personas set this.
	AdvertiseSkills bool

	// ActiveTools are eagerly exposed to the model; DeferredTools are
	// advertised by name and fetched on demand via tool_search.
	ActiveTools   []tools.ToolName
	DeferredTools []tools.ToolName

	// Model optionally overrides the provider/model the persona runs under.
	// Empty inherits the parent agent's model.
	Model string

	// SystemPrompt is the persona's prompt body (the equivalent of an on-disk
	// system_prompt.md). Empty on definitions read back from the registry for
	// built-in personas, whose prompts are assembled internally.
	SystemPrompt string
}

AgentDefinition is the public, closure-free description of a persona — a main-tier agent (selectable via /profile) and/or a subagent kind (invokable via the Agent tool's subagent_type). Register one on an AgentRegistry to teach the runtime a persona authored in your own Go code; on-disk personas under <AppHome>/agents/{name}/ produce the same shape via LoadDiskAgents.

For a main persona, SystemPrompt is composed with evva's identity + environment + memory + skills sections around your body (the persona brings its own conduct rules). For a subagent it is used as the body directly. This mirrors how on-disk agents behave.

func LoadDiskAgents

func LoadDiskAgents(evvaHome string) ([]AgentDefinition, []string)

LoadDiskAgents reads the on-disk personas under <evvaHome>/agents/ (no built-ins), returning each as a public AgentDefinition plus any load warnings. Useful for inspecting an agents directory before building a full registry.

func (AgentDefinition) IsMain

func (d AgentDefinition) IsMain() bool

IsMain reports whether the persona appears in the /profile picker.

func (AgentDefinition) IsSubagent

func (d AgentDefinition) IsSubagent() bool

IsSubagent reports whether the persona is invokable via the Agent tool.

type AgentRegistry

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

AgentRegistry is the public catalog of personas the runtime knows about — Go-defined built-ins (evva, explore, general-purpose, plan) merged with any in-code or on-disk personas you add. Pass it to an agent via WithPersonaRegistry; the /profile picker (Agent.ListMainProfiles) and the Agent tool's subagent catalog resolve through it.

func BuildAgentRegistry

func BuildAgentRegistry(evvaHome string) (*AgentRegistry, []string)

BuildAgentRegistry assembles the runtime catalog: built-ins first, then on-disk personas under <evvaHome>/agents/. A disk persona that collides with a built-in name is skipped (built-ins win); the returned warnings describe any that failed to load. Never returns an error — a missing or malformed catalog degrades gracefully.

func NewAgentRegistry

func NewAgentRegistry() *AgentRegistry

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

func (*AgentRegistry) Get

func (r *AgentRegistry) Get(name string) (AgentDefinition, bool)

Get returns the persona registered under name (case-insensitive). The returned definition's SystemPrompt is empty for built-in personas, whose prompts are assembled internally.

func (*AgentRegistry) ListMain

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

ListMain returns every persona selectable via /profile, sorted by name.

func (*AgentRegistry) ListSubagent

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

ListSubagent returns every persona invokable via the Agent tool, sorted by name.

func (*AgentRegistry) Register

func (r *AgentRegistry) Register(def AgentDefinition)

Register adds (or overwrites) a persona. A definition whose Name matches an existing entry replaces it.

type Config

type Config struct {
	// Provider optionally overrides the config's default LLM provider for this
	// agent ("anthropic", "openai", "deepseek", "ollama"). Empty uses
	// AppConfig.DefaultProvider.
	Provider string
	// Model optionally overrides the config's default model id (e.g.
	// "claude-sonnet-4-6"). Empty uses AppConfig.DefaultModel, or — when
	// Provider is set — that provider's first model.
	Model string
	// MaxIters overrides the loop iteration cap. Zero uses the config default
	// (from <app>-config.yml, else 10).
	MaxIters int
	// PermissionMode sets the initial permission stance: "default",
	// "accept_edits", "plan", "bypass". Empty falls back to
	// AppConfig.PermissionMode, then "default". Headless hosts with no approval
	// surface should set "bypass" so tool calls don't auto-deny.
	PermissionMode string
	// Persona names the main-tier persona to start as ("evva", a disk persona,
	// or one Register'd on Personas). Empty falls back to
	// AppConfig.DefaultProfile, then "evva". An unknown name degrades to "evva"
	// rather than failing the boot.
	Persona string
	// Personas is the catalog the agent resolves the active persona, the
	// /profile picker, and the subagent kinds through. Nil builds the default
	// from disk (BuildAgentRegistry over AppConfig.AppHome); supply one to add
	// in-code personas (the evva→nono pattern).
	Personas *AgentRegistry
	// PermissionStore is the rule store the permission gate consults. Nil loads
	// project + user rules from disk (permission.Load over AppConfig.WorkDir /
	// AppHome).
	PermissionStore *permission.Store
	// LLMOptions are sampling overrides (temperature, max tokens, ...) baked
	// into the resolved persona's profile. Empty leaves provider defaults.
	LLMOptions []llm.Option
	// AppConfig is the runtime configuration the agent reads from. Nil falls
	// back to config.Get() — the process-wide singleton. Downstream apps that
	// want a non-default AppHome (e.g. ~/.myapp/) pass a *config.Config they
	// built via config.Load(...).
	AppConfig *config.Config
}

Config is the declarative input for the one-call constructor New. Every field is optional: an empty Config builds the bundled "evva" persona against the process-wide config (config.Get()). Imperative extras — an event sink, a root context, a custom permission broker — are layered on as Options.

type Option

type Option = agent_impl.Option

Option mutates an Agent during construction. Downstream callers build options via the public With* functions in this package; the internal Option type is aliased here so options compose with the bundled cmd/evva wiring without duplicate type definitions.

func WithConfig

func WithConfig(cfg *config.Config) Option

WithConfig installs the runtime configuration the agent reads from. Subagents inherit the parent's config; downstream apps that run multiple agents with different AppHome 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.

func WithHeadlessBypass

func WithHeadlessBypass() Option

WithHeadlessBypass is the convenience option for downstream apps that run an agent without an interactive approval surface (no TUI overlay, no CLI prompt). It bundles WithPermissionMode(PermissionBypass) behind a more discoverable name + a strong docstring.

SECURITY: with bypass mode every tool call auto-succeeds. The agent will run any bash command the model emits, edit any file under its workdir, and fetch any URL it decides to. Use only in trusted environments (CI runners, sandboxed containers, ephemeral VMs).

For an interactive host that wants real prompts, omit this option — the default permission mode asks before each mutating call. Wire your UI's approval flow through agent.RespondPermission.

Example

ExampleWithHeadlessBypass shows the named option for the bypass permission mode. Equivalent to WithPermissionModeTyped(PermissionBypass) but more discoverable — non-interactive hosts should always use this.

package main

import (
	"fmt"

	"github.com/johnny1110/evva/pkg/agent"
)

func main() {
	_ = agent.WithHeadlessBypass()
	fmt.Println("see godoc for security notes")
}
Output:
see godoc for security notes

func WithHookRegistry

func WithHookRegistry(r *hooks.Registry) Option

WithHookRegistry installs the lifecycle-hook registry. Populate it with hooks.Load(workdir, appHome) to read from .evva/settings.json (project) and <appHome>/settings.json (user). nil is safe — the dispatcher noops when no registry is present. Shared across the root agent and every subagent so one settings.json load drives the whole agent tree.

func WithMaxIterations

func WithMaxIterations(n int) Option

WithMaxIterations overrides the agent's loop cap. Pass 0 to keep the cfg-derived default. Values in (0, 2) are clamped to 2.

func WithMcpManager

func WithMcpManager(m *mcp.Manager) Option

WithMcpManager installs a pre-built MCP connection manager, suppressing the one-call agent.New's auto-load. Build it with mcp.Load + mcp.Open and call mgr.RegisterFactories(toolset.DefaultRegistry()) before passing it here when you want a custom logger, OAuth prompt, or a manager shared across agents. Omit it to let New load + open the manager from settings.json (and wire the bundled ask_user_question OAuth flow) automatically. nil is safe — MCP tools and resources just have nothing to surface. Subagents inherit the parent's manager.

func WithName

func WithName(name string) Option

WithName sets a human-readable label on the agent. Surfaced in logs and in subagent panels.

func WithPermissionBroker

func WithPermissionBroker(b permission.Broker) Option

WithPermissionBroker installs a custom approval back-channel — the seam for a downstream allow/deny policy. Build one with permission.NewBroker() and register a callback via permission.SetOnRequest that inspects each permission.ApprovalRequest and calls Broker.Respond with a Decision.

Omit it and the agent installs a default broker: when a sink is present (WithSink) it emits an approval event for an interactive UI to resolve via Agent.RespondPermission; with no sink it auto-denies. One Broker is shared by the root agent and every subagent.

func WithPermissionMode

func WithPermissionMode(m PermissionMode) Option

WithPermissionMode sets the agent's initial permission stance. Pass one of the typed PermissionMode constants — passing an unknown PermissionMode (e.g. PermissionMode("by-pass") with a typo) results in a no-op option, but that path is hard to reach: the constants are the only typed values exposed.

agent.WithPermissionMode(agent.PermissionBypass)

If your config layer hands you a string (YAML / CLI flag), convert at the boundary with PermissionMode(s).

func WithPermissionStore

func WithPermissionStore(s *permission.Store) Option

WithPermissionStore installs the rule store the permission gate consults. Build one with permission.NewStore() (empty) or permission.Load(workdir, home) to read project + user rules from disk. One Store is shared by the root agent and every subagent it spawns.

Omit it to run with no pre-seeded rules — the active permission mode's safelist still governs which calls auto-allow, ask, or deny.

func WithPersona

func WithPersona(name string) Option

WithPersona records the active persona's wire name (e.g. "nono") so Agent.ProfileName and the UI render the right label. Pair it with an initial Profile from ResolveMainProfile for that same name.

func WithPersonaRegistry

func WithPersonaRegistry(r *AgentRegistry) Option

WithPersonaRegistry installs the persona catalog the agent resolves through: the /profile picker (Agent.ListMainProfiles / SwitchProfile) and the Agent tool's subagent kinds. Build one with BuildAgentRegistry (built-ins + disk) and Register your own personas before passing it here. A nil registry leaves the agent with only the built-in "evva".

func WithRootContext

func WithRootContext(ctx context.Context) Option

WithRootContext installs the agent-lifetime context. The signal pump goroutine, background bash tasks, and Monitor goroutines all bind to this ctx, so cancelling it (or calling Agent.Shutdown) cleans up every detached worker the agent ever spawned.

Pass the host's session-level cancellable ctx (e.g. signal.NotifyContext) so Ctrl-C reaches every long-lived goroutine.

func WithSink

func WithSink(s event.Sink) Option

WithSink installs the event consumer the agent emits into. nil sinks become event.Discard at emit time; pass event.Multi{...} to fan out to several consumers (e.g. a TUI plus a JSON-over-stdout bridge).

func WithSkillRegistry

func WithSkillRegistry(r *skill.Registry) Option

WithSkillRegistry installs a pre-built skill catalog on the agent's ToolState. The SKILL tool reads through it at Execute time, and the agent's system prompt advertises every registered skill on the available-skills list.

When omitted, agent.New auto-loads from cfg.AppHomeSkillsDir and cfg.WorkDirSkillsDir — the same disk path cmd/evva uses. Pass an override for either of:

  1. Programmatic-only catalogs: build with skill.NewRegistry() + Add(...) to ship skills inside the binary via go:embed, fetch them at boot, or generate them on the fly.
  2. Mixed catalogs: start from skill.LoadRegistry(home, workdir) and Add programmatic extras alongside.
  3. Suppression: pass skill.NewRegistry() to disable disk auto-load when the host wants the SKILL tool to surface no skills at all.

func WithStream

func WithStream(stream bool) Option

WithStream toggles streaming completions. Overrides the Profile.Stream field; useful for tests and downstream apps that want to force the buffered or chunked path without rebuilding the profile.

type PermissionDecision

type PermissionDecision struct {
	Behavior string // "allow" | "deny"
	Reason   string
	AddRule  *PermissionRuleSeed
}

PermissionDecision is the payload returned through Agent.RespondPermission.

type PermissionMode

type PermissionMode string

PermissionMode is the typed form of the agent's permission stance. Use the typed constants (PermissionDefault / PermissionAcceptEdits / PermissionPlan / PermissionBypass) together with WithPermissionMode so a typo becomes a compile error rather than a silent fall-through.

const (
	// PermissionDefault asks before every mutating tool call. Read-only
	// tools (read, grep, glob, web_*, agent self-coordination) auto-allow.
	// Bash auto-allows when the classifier reports a read-only command.
	// Best for beginners and sensitive work.
	PermissionDefault PermissionMode = "default"

	// PermissionAcceptEdits extends PermissionDefault by auto-allowing
	// edit/write/notebook_edit plus common filesystem bash commands
	// (mkdir, touch, mv, cp, ln, chmod, chown, rmdir). Best for
	// iterating on code under review.
	PermissionAcceptEdits PermissionMode = "accept_edits"

	// PermissionPlan denies every non-safelist tool outright (no
	// prompt). The model can only read and explore. Best for codebase
	// orientation before committing to an approach.
	PermissionPlan PermissionMode = "plan"

	// PermissionBypass auto-allows every tool call with no prompting.
	// Dangerous-command classification still happens and is logged but
	// never blocks. Use only inside isolated containers, VMs, or
	// downstream apps that have no approval UI surface — see
	// WithHeadlessBypass.
	PermissionBypass PermissionMode = "bypass"
)

func (PermissionMode) String

func (m PermissionMode) String() string

String returns the wire form of the mode — equal to the string form of the PermissionMode constant.

func (PermissionMode) Valid

func (m PermissionMode) Valid() bool

Valid reports whether m is one of the four known modes.

type PermissionRuleSeed

type PermissionRuleSeed struct {
	ToolName string
	Content  string // empty means tool-wide
}

PermissionRuleSeed is the minimum info needed to construct a session-scope allow rule.

type Profile

type Profile = agent_impl.Profile

Profile is the public alias for the internal agent profile. Downstream apps construct one via NewProfile (or via an evva-bundled persona constructor like Main when running inside the evva binary).

The struct's fields are accessible so downstream tests and tools can inspect a profile, but the constructor remains the recommended entry point — it normalizes optional fields and handles the AgentType classification (which is internal evva concept downstream apps don't need to think about).

func NewProfile

func NewProfile(name, systemPrompt string, activeTools []tools.ToolName, providerName string, model constant.Model, opts ProfileOptions) (Profile, error)

NewProfile builds a Profile a downstream app can pass into NewWithProfile. The system prompt is wrapped into an llm.WithSystem option automatically.

providerName must match a name registered on pkg/llm.DefaultRegistry ("anthropic", "deepseek", "ollama", or a downstream-registered name). model is the typed constant.Model id sent to the provider; for downstream-registered providers whose model strings aren't in pkg/constant, write `constant.Model("custom-model-name")`. Empty is allowed but downstream factories typically expect a concrete id.

The profile is classified as GENERAL_PURPOSE — evva's internal type system reserves MAIN/EXPLORE/PLAN for the bundled personas. Downstream apps that want richer behavior (memory injection, skills, plan-mode) should ship an on-disk agent definition under <AppHome>/agents/ instead of constructing a Profile in code.

providerName does not need to be in pkg/constant.GetAllProviders() — downstream apps register their own provider via pkg/llm.DefaultRegistry().Register and pass the same name here. When the name isn't in the bundled constants, an LLMProvider stub is synthesised on the fly so the rest of the agent loop has a value to log + introspect.

Example

ExampleNewProfile shows the basic Profile construction. Tool names come from pkg/tools; the model arg takes a typed constant.Model so typos become compile errors. Tool kit, persona, prompt, provider, model — that's everything the Profile carries.

package main

import (
	"fmt"

	"github.com/johnny1110/evva/pkg/agent"
	"github.com/johnny1110/evva/pkg/constant"
	"github.com/johnny1110/evva/pkg/tools"
)

func main() {
	prof, _ := agent.NewProfile(
		"my-agent",
		"You are a brisk assistant.",
		[]tools.ToolName{tools.READ_FILE, tools.BASH},
		"deepseek",
		constant.DEEPSEEK_V4_PRO,
		agent.ProfileOptions{},
	)
	fmt.Println("prompt:", prof.SystemPrompt)
	fmt.Println("provider:", prof.LLMProvider.Name)
	fmt.Println("model:", string(prof.LLMModel))
}
Output:
prompt: You are a brisk assistant.
provider: deepseek
model: deepseek-v4-pro

func ResolveMainProfile

func ResolveMainProfile(cfg *config.Config, reg *AgentRegistry, name string, opts ...llm.Option) (Profile, []string, error)

ResolveMainProfile builds the initial Profile for a main-tier persona by name, ready to pass to NewWithProfile. It auto-loads the skill catalog and the EVVA.md / USER_PROFILE.md memory snapshot from cfg, so a host never touches the internal memory or skills packages.

reg is the persona catalog (from BuildAgentRegistry, with any Register'd personas); a nil registry resolves only the built-in "evva". An empty name defaults to "evva". The returned warnings are non-fatal memory-load notes (oversize / unreadable files); an unknown or non-main persona name returns an error.

Typical use:

reg, _ := agent.BuildAgentRegistry(cfg.AppHome)
reg.Register(myPersona)
prof, _, err := agent.ResolveMainProfile(cfg, reg, "nono")
ag, _ := agent.NewWithProfile(prof,
    agent.WithConfig(cfg),
    agent.WithPersonaRegistry(reg),
    agent.WithPersona("nono"),
    agent.WithSink(myUI),
)

type ProfileChoice

type ProfileChoice struct {
	Name      string
	WhenToUse string
}

ProfileChoice is one row in the /profile picker.

type ProfileOptions

type ProfileOptions struct {
	// DeferredTools is the names the LLM sees by description-only; the
	// model must invoke TOOL_SEARCH to fetch their full schemas. Empty
	// means every active tool is eagerly available.
	DeferredTools []tools.ToolName

	// Stream selects the streaming completion path. Defaults to false
	// — most downstream apps want the buffered Complete path until they
	// build a streaming UI.
	Stream bool

	// LLMOptions are forwarded to the LLM client. The system prompt is
	// already supplied via the SystemPrompt argument to NewProfile and
	// does not need to be repeated here.
	LLMOptions []llm.Option
}

ProfileOptions tunes NewProfile. Zero-value fields fall back to sensible defaults: streaming off, no deferred tools, no extra llm options beyond the system prompt.

type QuestionAnnotation

type QuestionAnnotation struct {
	Notes   string
	Preview string
}

QuestionAnnotation captures the preview content (if any) of the option the user selected, plus any free-text notes they added.

type QuestionResponse

type QuestionResponse struct {
	Answers     map[string]string
	Annotations map[string]QuestionAnnotation
}

QuestionResponse is the payload returned through Agent.RespondQuestion.

type ResumableSession

type ResumableSession struct {
	ID              string
	FirstUserPrompt string
	UpdatedAt       int64 // unix nano of last save
	CreatedAt       int64 // unix nano of first save
	Profile         string
	Provider        string
	Model           string
	MessageCount    int
}

ResumableSession is one row in /resume — a persisted session the host can present to the user and rehydrate via Agent.ResumeSession.

type SessionInfo

type SessionInfo struct {
	// MessageCount is the number of messages on the session transcript —
	// counts user prompts, assistant turns, and tool results.
	MessageCount int
	// InputTokens is the cumulative input-token total across every LLM
	// call in this session.
	InputTokens int
	// OutputTokens is the cumulative output-token total across every
	// LLM call in this session.
	OutputTokens int
	// LastInputTokens is the input-token count of the most recent LLM
	// call. Useful for surfacing per-turn cost in a status bar.
	LastInputTokens int
}

SessionInfo is a snapshot of the agent's conversation state (message count and cumulative token usage). External callers get a point-in-time copy.

type Skill

type Skill struct {
	Name        string
	Description string
}

Skill is the UI-facing view of a user-installed skill.

Jump to

Keyboard shortcuts

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