Documentation
¶
Index ¶
- type Agent
- type Config
- type Option
- func WithConfig(cfg *config.Config) Option
- func WithCustomTool(name tools.ToolName, factory pubtoolset.ToolFactory) Option
- func WithHeadlessBypass() Option
- func WithMaxIterations(n int) Option
- func WithName(name string) Option
- func WithPermissionMode(m PermissionMode) Option
- func WithSink(s event.Sink) 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 ¶
This section is empty.
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
}
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 constructs an Agent ready to call Run. When cfg.AppConfig is nil it loads the process-wide default (config.Get(), which initializes via LoadDefault on first use); pass an explicit *config.Config to target a custom AppHome.
Set API keys via the <app>-config.yml file under cfg.AppHome (or env vars consumed by your own loader for downstream apps).
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 Main persona, memdir, and a non-interactive permission stub) this constructor wires only what the caller supplies — no skill catalog, no memory snapshot, no agent registry by default.
A non-interactive permission broker + question broker are installed internally so async approval / question requests don't park the caller forever; pass agent_impl.WithPermissionBroker / WithQuestionBroker via the variadic opts list to override.
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 (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 Config ¶
type Config struct {
// Provider is the LLM provider name: "anthropic", "openai", "deepseek", "ollama".
Provider string
// Model is the model id, e.g. "claude-sonnet-4-6". When empty, the
// provider's cheapest model is used.
Model string
// MaxIters overrides the default loop iteration cap. Zero means use the
// default (from <app>-config.yml or 10).
MaxIters int
// PermissionMode sets the initial permission stance: "default", "bypass",
// "accept_edits", "plan". Empty means "bypass" (all tool calls auto-approved).
PermissionMode string
// AppConfig is the runtime configuration the agent reads from. Nil falls
// back to config.LoadDefault() / config.Get() — the historical 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 public constructor input for creating an agent. Provider and Model are the only required fields.
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 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 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 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 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
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.