Documentation
¶
Index ¶
- Variables
- type Agent
- type AgentDefinition
- type AgentRegistry
- type Config
- type Option
- func WithConfig(cfg *config.Config) Option
- func WithCustomTool(name tools.ToolName, factory pubtoolset.ToolFactory) Option
- func WithHeadlessBypass() Option
- func WithHookRegistry(r *hooks.Registry) Option
- func WithMaxIterations(n int) Option
- func WithName(name string) Option
- func WithPermissionBroker(b permission.Broker) Option
- func WithPermissionMode(m PermissionMode) Option
- func WithPermissionStore(s *permission.Store) Option
- func WithPersona(name string) Option
- func WithPersonaRegistry(r *AgentRegistry) Option
- func WithRootContext(ctx context.Context) Option
- func WithSink(s event.Sink) Option
- func WithSkillRegistry(r *skill.Registry) Option
- func WithStream(stream bool) Option
- type PermissionDecision
- type PermissionMode
- type PermissionRuleSeed
- type Profile
- type ProfileChoice
- type ProfileOptions
- type QuestionAnnotation
- type QuestionResponse
- type ResumableSession
- type SessionInfo
- type Skill
Examples ¶
Constants ¶
This section is empty.
Variables ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
WithMaxIterations overrides the agent's loop cap. Pass 0 to keep the cfg-derived default. Values in (0, 2) are clamped to 2.
func WithName ¶
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 ¶
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 ¶
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 ¶
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 ¶
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:
- 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.
- Mixed catalogs: start from skill.LoadRegistry(home, workdir) and Add programmatic extras alongside.
- Suppression: pass skill.NewRegistry() to disable disk auto-load when the host wants the SKILL tool to surface no skills at all.
func WithStream ¶
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 ¶
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 ¶
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 ¶
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.