agent: layered preamble scaffolding

BuildRuntime now accepts optional baseLayers (variadic strings) that
are prepended to the agent-resolved preamble. This enables the server
to compose: system_prompt + operational_model + environment + agent_prompts.

- config.Config: add SystemPrompt field (path override)
- AgentCoreConfig: add OperationalModel and SystemPrompt fields
- BuildRuntime: prepend baseLayers before agent prompt content
- Remove debug fprintf statements from prompt resolution
This commit is contained in:
ollie 2026-07-21 12:53:51 +02:00
parent c2688a1d3d
commit f5298306b1
3 changed files with 26 additions and 6 deletions

View File

@ -38,7 +38,7 @@ type toolClassifier func(name string) bool
// env provides additional environment variables injected into prompt resolution
// subprocesses (e.g. OLLIE_SESSION_ID=xxx).
// The caller is responsible for registering all servers on d before calling this.
func BuildRuntime(cfg *config.Config, d tools.Dispatcher, cwd string, env []string) *Runtime {
func BuildRuntime(cfg *config.Config, d tools.Dispatcher, cwd string, env []string, baseLayers ...string) *Runtime {
var messages []string
var allToolInfos []tools.ToolInfo
@ -65,11 +65,9 @@ func BuildRuntime(cfg *config.Config, d tools.Dispatcher, cwd string, env []stri
for k, v := range cfg.Hooks {
hooks[k] = []string(v)
}
fmt.Fprintf(os.Stderr, "[DEBUG BuildRuntime] cfg.Prompt.IsExec=%v len(Value)=%d\n", cfg.Prompt.IsExec, len(cfg.Prompt.Value))
if resolved, err := resolvePrompt(cfg.Prompt, cwd, env); err != nil {
fmt.Fprintf(os.Stderr, "resolve prompt: %v\n", err)
} else {
fmt.Fprintf(os.Stderr, "[DEBUG BuildRuntime] preamble len=%d\n", len(resolved))
preamble = resolved
}
genParams = backend.GenerationParams{
@ -147,6 +145,21 @@ func BuildRuntime(cfg *config.Config, d tools.Dispatcher, cwd string, env []stri
compactionModel = cfg.CompactionModel
}
// Prepend base layers (system prompt, operational model, environment)
// before the agent-specific preamble.
if len(baseLayers) > 0 {
var prefix strings.Builder
for _, layer := range baseLayers {
if layer != "" {
prefix.WriteString(layer)
prefix.WriteByte('\n')
}
}
if prefix.Len() > 0 {
preamble = prefix.String() + preamble
}
}
return &Runtime{
Dispatcher: d,
Tools: allTools,
@ -251,6 +264,13 @@ type AgentCoreConfig struct {
PromptEnvExtra []string
// Remote is the SSH target for remote execution (empty = local).
Remote string
// OperationalModel is server-injected text describing the 9P/D-Bus
// interface available to the agent. Not user-configurable.
OperationalModel string
// SystemPrompt is the base system prompt text (embedded default or
// override from agent config / session creation). If empty, BuildRuntime
// uses only the agent config's prompt commands.
SystemPrompt string
}
// agent is the Core implementation. It owns all agent and session state

View File

@ -71,16 +71,13 @@ func resolveExecPrompt(cmds []string, cwd string, env []string) (string, error)
}
var parts []string
fmt.Fprintf(os.Stderr, "[DEBUG resolveExecPrompt] %d entries\n", len(cmds))
for _, entry := range cmds {
entry = strings.TrimSpace(entry)
if entry == "" {
continue
}
fmt.Fprintf(os.Stderr, "[DEBUG resolveExecPrompt] entry=%q\n", entry)
// Try resolving as a prompt file first.
if content, ok := resolvePromptFile(entry, envMap, expand); ok {
fmt.Fprintf(os.Stderr, "[DEBUG resolveExecPrompt] -> file ok, len=%d\n", len(content))
if content != "" {
parts = append(parts, content)
}

View File

@ -85,6 +85,9 @@ type Config struct {
Stop []string `json:"stop,omitempty"`
Verbosity string `json:"verbosity,omitempty"`
CompactionModel string `json:"compactionModel,omitempty"`
// SystemPrompt overrides the embedded system prompt with a file path.
// If set and the file exists, its content replaces the compiled-in default.
SystemPrompt string `json:"systemPrompt,omitempty"`
}
// Load parses a Config from r.