Documentation
¶
Overview ¶
Package sysprompt builds the system prompt for each kind of agent evva runs. The package exposes one AgentDefinition per built-in agent (see agent_def.go) whose BuildSystemPrompt function turns a PromptContext into the full prompt string. The Main agent's prompt is composed from shared fragments (identity, environment, memory, skills, dev) plus per-agent harness/tools/planning blocks; subagents (Explore, General) own a single hand-written string mirroring ref/src/tools/AgentTool/built-in/*Agent.ts.
The package is dependency-light on purpose: it imports only stdlib. The caller (cmd/evva/main.go via agent.Main) reads memory through the memdir package and threads it into PromptContext.WorkdirMemory / .MemoryIndex. The skill registry is similarly flattened by the caller into []SkillRef so sysprompt does not depend on pkg/skill.
Tool names interpolated into prompts live in toolnames.go and are drift-checked against internal/tools/name.go by toolnames_link_test.go.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( MainAgent = AgentDefinition{ Name: "evva", WhenToUse: "", OmitMemory: false, AdvertiseSkills: true, BuildSystemPrompt: buildMainPrompt, As: []string{"main"}, } ExploreAgent = AgentDefinition{ Name: subagentExplore, WhenToUse: exploreWhenToUse, OmitMemory: true, AdvertiseSkills: false, BuildSystemPrompt: buildExplorePrompt, As: []string{"subagent"}, } GeneralAgent = AgentDefinition{ Name: subagentGeneral, WhenToUse: generalWhenToUse, OmitMemory: true, AdvertiseSkills: false, BuildSystemPrompt: buildGeneralPrompt, As: []string{"subagent"}, } PlanAgent = AgentDefinition{ Name: subagentPlan, WhenToUse: planWhenToUse, OmitMemory: true, AdvertiseSkills: false, BuildSystemPrompt: buildPlanPrompt, As: []string{"subagent"}, } )
Built-in agent registry. Phase 11 adds PlanAgent for design-phase work inside plan mode; Phase 6 may add more main-tier personas (nono, noen) as siblings.
Functions ¶
func ComposeDiskMainPrompt ¶
func ComposeDiskMainPrompt(body string, ctx PromptContext, def AgentDefinition) string
ComposeDiskMainPrompt assembles a system prompt for a disk-loaded main-tier persona. body is the verbatim system_prompt.md the loader captured; def carries the OmitMemory / AdvertiseSkills flags from meta.yml. The result is identity + environment + (optional) memory + tools guide + body + (optional) skills + (optional) deferred catalog + (optional) dev feedback, joined the same way the built-in evva prompt is composed.
Lives here (in the sysprompt package) so the section builders stay package-private and disk-persona composition has a single seam.
Disk personas DELIBERATELY skip the ref-ported conduct sections (doing-tasks, actions, tone, output-efficiency, system, session-specific-guidance). Those would conflict with the persona's own definition; the persona supplies its own conduct rules in body. Harness mechanics are a different matter (RP-19): how tool calling works does not vary by persona and no operator should have to hand-write it, so the per-tool-gated diskToolsGuideSection renders BEFORE body (harness facts first, personality second) and the deferred-tool catalog renders near the bottom, mirroring the main agent's section order.
Types ¶
type AgentDefinition ¶
type AgentDefinition struct {
Name string
WhenToUse string
OmitMemory bool
AdvertiseSkills bool
BuildSystemPrompt func(ctx PromptContext) string
// As controls where this agent appears. Values:
// "main" — selectable via /profile (Phase 6 picker).
// "subagent" — invokable via the Agent tool's subagent_type.
// Both can be set; for built-ins the slice is fixed (Main is main-only;
// Explore/General are subagent-only). Disk agents declare this via
// meta.yml's `as:` field.
As []string
// ActiveTools / DeferredTools name the tools this agent's profile loads.
// Empty means "use the built-in constructor's default" (Main, Explore,
// General supply their own lists in agent.Main/Explore/General). For
// disk-loaded agents these come from tools.yml.
ActiveTools []tools.ToolName
DeferredTools []tools.ToolName
// Model is the optional model override declared in meta.yml. Empty
// means "inherit from parent" (existing built-in behavior).
Model string
// OutputStyle is the optional output style declared in meta.yml. When
// set it wins over the user's configured output_style while this
// persona is active (PRD output-styles §5.5). Empty for built-ins.
OutputStyle string
// PromptBody is the raw persona prompt body before composition, set when
// the definition came from disk (system_prompt.md) or a public AgentSpec.
// Empty for built-ins whose prompt is assembled from internal fragments —
// their prompt isn't recoverable as a single string. Lets a definition
// round-trip back to a public AgentDefinition for inspection.
PromptBody string
// LongRunning marks a persona that stays live for a very long time — a swarm
// member runs for weeks. The prompt builder then drops fragments that would
// drift across rebuilds and bust the prompt-cache prefix: currently the
// "- Today:" date in the environment section (gated via PromptContext.OmitDate
// in mainProfileFromDiskAgent). Built-in evva leaves it false; internal/swarm
// sets it at member registration (RP-5).
LongRunning bool
// PromptSuffix, when non-empty, is appended verbatim (after one blank
// line) to the END of the fully composed system prompt — built-in and
// disk-composed paths alike. It is the seam a swarm uses to attach its
// team protocol to a persona whose prompt is assembled internally and so
// cannot be pre-concatenated into a body string (RP-29). Living on the
// definition (not an agent option) means every prompt re-render —
// ReloadSkills, MCP discovery, SwitchProfile — re-reads it from the
// registry and keeps it.
PromptSuffix string
}
AgentDefinition is the Go-side seam for a built-in agent. Phase 2's internal/agent/loader/ will define an AgentRegistry interface that this struct satisfies; for Phase 0 it is a concrete struct since the only agents are built-ins. Disk-authored agents (Phase 2) construct an AgentDefinition where BuildSystemPrompt is a closure that returns the on-disk system_prompt.md body.
Field semantics:
- Name wire identifier ("evva", "explore", "general-purpose"). Same string the Agent tool's subagent_type enum will accept (Phase 2 unifies these). Lowercase, hyphenated.
- WhenToUse description shown in the Agent tool's catalog so a parent agent knows what to delegate. Empty for Main (Main is not delegated to).
- OmitMemory subagents skip <workdir>/EVVA.md and <evvaHome>/USER_PROFILE.md. Matches ref TS omitClaudeMd: true on Explore/Plan.
- AdvertiseSkills only the Main agent surfaces the skill catalog. Subagents don't (it would bloat their context).
- BuildSystemPrompt assembles the prompt from ctx. Each built-in closure-captures its own per-agent text fragments.
func (AgentDefinition) IsMain ¶
func (d AgentDefinition) IsMain() bool
IsMain reports whether this agent appears in the /profile picker (Phase 6).
func (AgentDefinition) IsSubagent ¶
func (d AgentDefinition) IsSubagent() bool
IsSubagent reports whether this agent is invokable via the Agent tool's subagent_type parameter.
type DeferredToolSpec ¶
type DeferredToolSpec struct {
Name string
}
DeferredToolSpec is the prompt-side view of one deferred tool. Only the Name is included — full schemas are fetched on demand via tool_search, matching ref/ Claude Code's name-only advertisement.
type PromptContext ¶
type PromptContext struct {
// Identity
AgentName string // e.g. "evva" — the name the main agent introduces itself as.
Today time.Time // anchors the model in absolute time; zero = use time.Now() at render.
// OmitDate drops the "- Today:" line from the environment section. Long-
// running personas (swarm members) set it so the system-prompt prefix stays
// bit-stable across rebuilds — a drifting date would bust the prompt cache for
// a swarm that runs for weeks. The time they need arrives in wake/run prompts,
// never in the static system prompt (RP-5). Driven by AgentDefinition.LongRunning.
OmitDate bool
// OmitSkillAuthoring drops the "How to create a skill" guidance from the
// main prompt's skills section. Long-running swarm-resident personas set
// it (via AgentDefinition.LongRunning) for the same reason disk swarm
// members do (RP-10-3): a member with write/bash should not be invited to
// author SKILL.md mid-run.
OmitSkillAuthoring bool
// Environment
OS string // runtime.GOOS — "darwin", "linux", "windows".
Shell string // basename of $SHELL — "zsh", "bash", ...
WorkDir string // absolute path of the current working directory.
EvvaHome string // ~/.evva — where skills, memory, and config live.
Env string // "dev" | "prod" — dev gates the feedback section.
Model string // canonical model id ("claude-opus-4-8", "deepseek-chat", ...). Empty = skip the model line in env block.
// Catalogs
Skills []SkillRef // advertised skill list; empty = skip the section.
DeferredTools []DeferredToolSpec // deferred-tool catalog; rendered as a <functions> block in the main prompt. Empty = skip the section.
// Memory (loaded by internal/memdir)
WorkdirMemory string // contents of <workdir>/EVVA.md (user-authored); "" = skip.
MemoryIndex string // <appHome>/memory/MEMORY.md body (truncated, inject-ready); "" = skip.
// EnableAutoMemory gates the typed-memory guidance + the MEMORY.md index
// sections in the main prompt. false → both sections are suppressed so the
// model isn't told about a memory system it can't use this session.
EnableAutoMemory bool
// EnableDynamicWorkflow swaps the multi-step-work protocol: true renders
// the dynamic-workflow board guidance (wf_task_* + engine dispatch) in
// place of the todo_write protocol, matching the tool swap the Main
// profile performs under the same flag. Solo main agent only.
EnableDynamicWorkflow bool
// RepoMap is the already-rendered, token-bounded repo-map body (built by
// internal/repomap from the LSP layer, or its glob fallback). "" → the
// section is skipped entirely. The string arrives pre-rendered so this
// package keeps its stdlib-only, no-IO discipline — the same one-way arrow
// as memory. Main agent only; subagents never set it.
RepoMap string
// Output style (resolved by the caller via internal/outputstyle — same
// one-way arrow as memory and skills). Empty OutputStylePrompt = the
// default style: no overlay section, prompt byte-identical to a build
// that never knew the feature. When set, the identity line defers to the
// style ("according to your Output Style below…", ref's
// getSimpleIntroSection) and an "# Output Style: <name>" section renders.
// OutputStyleKeepCoding false additionally drops the "Doing tasks"
// coding-doctrine section (ref's keepCodingInstructions === true check).
// Main-tier agents only; subagents and swarm-resident (LongRunning)
// personas never set these.
OutputStyleName string
OutputStylePrompt string
OutputStyleKeepCoding bool
}
PromptContext is the input bundle for every AgentDefinition.BuildSystemPrompt. Build once per agent at construction time and pass by value — builders read-only. Zero values render cleanly: empty environment fields become "(unknown)" / "(unset)"; empty Skills / ProjectMemory / UserProfile suppress their respective sections.
func DetectContext ¶
func DetectContext(agentName, evvaHome, env string) PromptContext
DetectContext returns a PromptContext with the runtime-detectable fields (Today, OS, Shell, WorkDir) populated. Caller fills AgentName, EvvaHome, Env, Skills, and the two memory fields — those live in configs.AppConfig and on the memdir.Snapshot, which the sysprompt module deliberately does not import.