simplify prompt loading, some renames
This commit is contained in:
parent
605212e1fd
commit
ba740701e2
|
|
@ -399,31 +399,31 @@ func (ag *Agent) RenamePreamble(old, new string) {
|
|||
}
|
||||
|
||||
// refreshToolListing replaces the "# Available Tools" section in the preamble
|
||||
// with an updated listing. Called when tool_list detects directory changes.
|
||||
// with an updated listing. Called when directory contents change.
|
||||
// Note: This only updates the tool listing, not the full tool documentation.
|
||||
// Tool documentation requires a full BuildPreamble call (on agent reload).
|
||||
func (ag *Agent) refreshToolListing(listing string) {
|
||||
if ag.runtime == nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Find the "# Available Tools" section
|
||||
const availableToolsMarker = "\n# Available Tools\n\n"
|
||||
availableToolsIdx := strings.Index(ag.runtime.Preamble, availableToolsMarker)
|
||||
|
||||
|
||||
if availableToolsIdx < 0 {
|
||||
// No tool listing exists yet
|
||||
if listing != "" {
|
||||
// Find where to insert - before "# Tool Documentation" or "## Tool discovery"
|
||||
const toolDocsMarker = "\n# Tool Documentation\n\n"
|
||||
const discoveryMarker = "\n## Tool discovery\n"
|
||||
|
||||
|
||||
toolDocsIdx := strings.Index(ag.runtime.Preamble, toolDocsMarker)
|
||||
discoveryIdx := strings.Index(ag.runtime.Preamble, discoveryMarker)
|
||||
|
||||
|
||||
var insertIdx int
|
||||
var insertBefore string
|
||||
|
||||
|
||||
if toolDocsIdx >= 0 {
|
||||
insertIdx = toolDocsIdx
|
||||
insertBefore = toolDocsMarker
|
||||
|
|
@ -435,25 +435,25 @@ func (ag *Agent) refreshToolListing(listing string) {
|
|||
ag.runtime.Preamble += availableToolsMarker + listing
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Insert before the marker
|
||||
ag.runtime.Preamble = ag.runtime.Preamble[:insertIdx] + availableToolsMarker + listing + insertBefore + ag.runtime.Preamble[insertIdx+len(insertBefore):]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Tool listing exists - find where it ends
|
||||
// Look for next section marker after the listing
|
||||
afterMarker := ag.runtime.Preamble[availableToolsIdx+len(availableToolsMarker):]
|
||||
|
||||
|
||||
// Find next section marker
|
||||
const toolDocsMarker = "\n# Tool Documentation\n\n"
|
||||
const discoveryMarker = "\n## Tool discovery\n"
|
||||
|
||||
|
||||
var endIdx int
|
||||
toolDocsIdx := strings.Index(afterMarker, toolDocsMarker)
|
||||
discoveryIdx := strings.Index(afterMarker, discoveryMarker)
|
||||
|
||||
|
||||
if toolDocsIdx >= 0 {
|
||||
endIdx = toolDocsIdx
|
||||
} else if discoveryIdx >= 0 {
|
||||
|
|
@ -462,11 +462,11 @@ func (ag *Agent) refreshToolListing(listing string) {
|
|||
// No markers found - listing goes to end
|
||||
endIdx = len(afterMarker)
|
||||
}
|
||||
|
||||
|
||||
// Rebuild preamble with new listing
|
||||
beforeListing := ag.runtime.Preamble[:availableToolsIdx+len(availableToolsMarker)]
|
||||
afterListing := afterMarker[endIdx:]
|
||||
|
||||
|
||||
if listing == "" {
|
||||
// Remove the entire "# Available Tools" section
|
||||
// Need to also remove the preceding "\n" if it exists
|
||||
|
|
|
|||
|
|
@ -41,15 +41,15 @@ func (p Prompt) MarshalJSON() ([]byte, error) {
|
|||
}
|
||||
|
||||
type AgentConfig struct {
|
||||
Prompt Prompt `json:"prompt,omitempty"`
|
||||
Backend string `json:"backend,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
Tools *bool `json:"tools,omitempty"`
|
||||
AllowTools []string `json:"allowTools,omitempty"`
|
||||
AutoLoad []string `json:"autoLoad,omitempty"`
|
||||
DisallowTools []string `json:"disallowTools,omitempty"`
|
||||
MaxTokens int `json:"maxTokens,omitempty"`
|
||||
MaxCompletionTokens int `json:"maxCompletionTokens,omitempty"`
|
||||
Prompt Prompt `json:"prompt,omitempty"`
|
||||
Backend string `json:"backend,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
Tools *bool `json:"tools,omitempty"`
|
||||
AllowTools []string `json:"allowTools,omitempty"`
|
||||
AutoLoad []string `json:"autoLoad,omitempty"`
|
||||
DisallowTools []string `json:"disallowTools,omitempty"`
|
||||
MaxTokens int `json:"maxTokens,omitempty"`
|
||||
MaxCompletionTokens int `json:"maxCompletionTokens,omitempty"`
|
||||
// MaxSteps caps the number of tool-call rounds per turn. 0 means unlimited.
|
||||
MaxSteps int `json:"maxSteps,omitempty"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ func (ag *Agent) cmdAgent(_ context.Context, args []string) {
|
|||
disp := ag.newToolServer()
|
||||
env := []string{"OLLIE_SESSION_ID=" + ag.sessionID, "OLLIE_UNAME=" + ag.id}
|
||||
env = append(env, ag.promptEnvExtra...)
|
||||
rt := BuildRuntime(cfg, disp, ag.cwd, env, ag.baseLayers...)
|
||||
rt := BuildRuntime(cfg, disp, ag.cwd, env, ag.systemPrompt, ag.envBlock)
|
||||
if rt.CfgBackend != "" {
|
||||
newBe, err := ag.newBackend(rt.CfgBackend)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -287,8 +287,6 @@ func (s *History) addUsage(u backend.Usage, estimated bool) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (s *History) resetTurnAccumulators() {
|
||||
s.turnInputTokens = 0
|
||||
s.turnCachedTokens = 0
|
||||
|
|
|
|||
|
|
@ -116,9 +116,9 @@ type agentConfig struct {
|
|||
Output EventHandler
|
||||
preamble string // compiled system+agent prompt sent as the system role
|
||||
GenerationParams backend.GenerationParams
|
||||
PopInject func() string // returns and clears pending inject, or ""
|
||||
AutoCompact func(ctx context.Context) // called after each tool round; may compact in-place
|
||||
Save func() // called after each state.update(); persists mid-turn progress
|
||||
PopInject func() string // returns and clears pending inject, or ""
|
||||
AutoCompact func(ctx context.Context) // called after each tool round; may compact in-place
|
||||
Save func() // called after each state.update(); persists mid-turn progress
|
||||
// MaxSteps is the maximum number of tool-call rounds per turn.
|
||||
// When reached, a soft nudge is injected and the loop exits cleanly.
|
||||
// 0 means unlimited.
|
||||
|
|
|
|||
22
agent/new.go
22
agent/new.go
|
|
@ -10,20 +10,21 @@ import (
|
|||
|
||||
// AgentCfg is the configuration for constructing a new Agent.
|
||||
type AgentCfg struct {
|
||||
ID string // unique agent identity (uname)
|
||||
SessionID string // id of session the agent belongs to
|
||||
Profile string // config profile name (e.g. "default" → agents/default.json)
|
||||
History *History
|
||||
Runtime *Runtime
|
||||
Profile string // config profile name (e.g. "default" → agents/default.json)
|
||||
AgentsDir string
|
||||
AgentID string // unique agent identity (uname)
|
||||
CWD string // working directory for tool execution
|
||||
BaseLayers []string
|
||||
Cwd string // working directory for tool execution
|
||||
SystemPrompt string
|
||||
EnvBlock string
|
||||
PromptEnvExtra []string
|
||||
NewToolServer func() toolsrv.Runner
|
||||
NewBackend func(string) (backend.Backend, error)
|
||||
Output EventHandler
|
||||
Log *olog.Logger
|
||||
AuditLog *olog.Logger
|
||||
SessionID string
|
||||
StartupMsgs []string
|
||||
ReadPlanStep func() string
|
||||
Save func()
|
||||
|
|
@ -34,7 +35,7 @@ type AgentCfg struct {
|
|||
func NewAgent(cfg AgentCfg) *Agent {
|
||||
auditLog := cfg.AuditLog
|
||||
if auditLog == nil && cfg.Log != nil {
|
||||
cfg.Log.Warn("AuditLog not provided for agent %s, deriving from Log", cfg.AgentID)
|
||||
cfg.Log.Warn("AuditLog not provided for agent %s, deriving from Log", cfg.ID)
|
||||
auditLog = cfg.Log.Sub("audit")
|
||||
}
|
||||
|
||||
|
|
@ -42,11 +43,12 @@ func NewAgent(cfg AgentCfg) *Agent {
|
|||
history: cfg.History,
|
||||
runtime: cfg.Runtime,
|
||||
profile: cfg.Profile,
|
||||
name: cfg.AgentID, // display name defaults to uname
|
||||
name: cfg.ID, // display name defaults to uname
|
||||
agentsDir: cfg.AgentsDir,
|
||||
id: cfg.AgentID,
|
||||
cwd: cfg.CWD,
|
||||
baseLayers: cfg.BaseLayers,
|
||||
id: cfg.ID,
|
||||
cwd: cfg.Cwd,
|
||||
systemPrompt: cfg.SystemPrompt,
|
||||
envBlock: cfg.EnvBlock,
|
||||
promptEnvExtra: cfg.PromptEnvExtra,
|
||||
newToolServer: cfg.NewToolServer,
|
||||
newBackend: cfg.NewBackend,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
)
|
||||
|
||||
// PromptEnv returns the standard PRIME_* environment variables for prompt
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ func (ag *Agent) executeTurn(ctx context.Context, input string) string {
|
|||
}
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, ErrInterrupted) {
|
||||
ag.auditLog.Debug("turn: interrupted session=%s", ag.sessionID)
|
||||
ag.save()
|
||||
ag.save()
|
||||
return ""
|
||||
}
|
||||
ag.emit(Event{Role: "error", Content: err.Error()})
|
||||
|
|
@ -316,11 +316,11 @@ func (ag *Agent) executeTurn(ctx context.Context, input string) string {
|
|||
auditTruncate(ag.Reply()), ag.history.LastTurnCostUSD, ag.history.SessionCostUSD, ag.sessionID)
|
||||
ag.notifyChange()
|
||||
}
|
||||
ag.save()
|
||||
ag.save()
|
||||
|
||||
// Inject that was pending but never consumed (text-only response with no
|
||||
// Inject that was pending but never consumed (text-only response with no
|
||||
// tool calls) — treat it as the next user message.
|
||||
if p := ag.pendingInject.Swap(nil); p != nil {
|
||||
if p := ag.pendingInject.Swap(nil); p != nil {
|
||||
return *p
|
||||
}
|
||||
|
||||
|
|
@ -378,17 +378,17 @@ func (ag *Agent) runCompact(ctx context.Context, trigger string) (int, error) {
|
|||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n > 0 {
|
||||
ag.auditLog.Debug("compact: removed %d messages trigger=%s session=%s", n, trigger, ag.sessionID)
|
||||
ag.warnedContext = false
|
||||
if sc := ag.spawnContext(ctx); sc != "" {
|
||||
ag.history.appendUserMessage(sc)
|
||||
}
|
||||
if n > 0 {
|
||||
ag.auditLog.Debug("compact: removed %d messages trigger=%s session=%s", n, trigger, ag.sessionID)
|
||||
ag.warnedContext = false
|
||||
if sc := ag.spawnContext(ctx); sc != "" {
|
||||
ag.history.appendUserMessage(sc)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// ErrInterrupted is returned when the user cancels an agent turn (Ctrl-C).
|
||||
// ErrInterrupted is returned when the user cancels an agent turn (Ctrl-C).
|
||||
var ErrInterrupted = errors.New("interrupted")
|
||||
|
||||
// defaultContextLength is used when the backend cannot report the model's
|
||||
|
|
|
|||
2
justfile
2
justfile
|
|
@ -73,8 +73,6 @@ kde-kf5:
|
|||
@echo '=== Building ollie-kde (KF5) ==='
|
||||
just -f kde/justfile kde-kf5
|
||||
|
||||
# === Install targets ===
|
||||
|
||||
install: test install-data install-el
|
||||
just -f kde/justfile install-kde
|
||||
@echo === Install complete ===
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
## Tool discovery
|
||||
|
||||
Use the 9P filesystem to discover and load tools dynamically:
|
||||
- **List available tools** — `ollie-9p read /tools`
|
||||
- **Load a tool** — `echo "toolname" | ollie-9p write session/{sname}/agent/{aname}/tools`
|
||||
- **See loaded tools** — `ollie-9p read session/{sname}/agent/{aname}/tools`
|
||||
|
||||
For manual 9P operations:
|
||||
```bash
|
||||
# List all available tools
|
||||
ollie-9p read /tools
|
||||
|
||||
# Load a tool
|
||||
echo "toolname" | ollie-9p write session/{sname}/agent/{aname}/tools
|
||||
|
||||
# See which tools are loaded
|
||||
ollie-9p read session/{sname}/agent/{aname}/tools
|
||||
```
|
||||
|
|
@ -18,24 +18,24 @@ import (
|
|||
|
||||
// PersistedSession is the on-disk format for a saved session with multiple agents.
|
||||
type PersistedSession struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CWD string `json:"cwd"`
|
||||
Remote string `json:"remote,omitempty"`
|
||||
Paused bool `json:"paused,omitempty"`
|
||||
Agents []PersistedAgent `json:"agents"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CWD string `json:"cwd"`
|
||||
Remote string `json:"remote,omitempty"`
|
||||
Paused bool `json:"paused,omitempty"`
|
||||
Agents []PersistedAgent `json:"agents"`
|
||||
}
|
||||
|
||||
// PersistedAgent is per-agent state within a session.
|
||||
type PersistedAgent struct {
|
||||
ID string `json:"id"` // uname
|
||||
Name string `json:"name"` // display name
|
||||
Profile string `json:"profile"`
|
||||
Backend string `json:"backend"`
|
||||
Model string `json:"model"`
|
||||
CWD string `json:"cwd"`
|
||||
Messages []backend.Message `json:"messages"`
|
||||
TaskState *agent.TaskState `json:"taskState,omitempty"`
|
||||
ID string `json:"id"` // uname
|
||||
Name string `json:"name"` // display name
|
||||
Profile string `json:"profile"`
|
||||
Backend string `json:"backend"`
|
||||
Model string `json:"model"`
|
||||
CWD string `json:"cwd"`
|
||||
Messages []backend.Message `json:"messages"`
|
||||
TaskState *agent.TaskState `json:"taskState,omitempty"`
|
||||
// Usage tracking
|
||||
TotalInputTokens int `json:"totalInputTokens,omitempty"`
|
||||
TotalCachedInputTokens int `json:"totalCachedInputTokens,omitempty"`
|
||||
|
|
@ -295,9 +295,10 @@ func restoreMultiAgentSession(ps *PersistedSession) (*RestoredSession, error) {
|
|||
Runtime: rt,
|
||||
Profile: pa.Profile,
|
||||
AgentsDir: pkgAgentsDir,
|
||||
AgentID: pa.ID,
|
||||
CWD: cwd,
|
||||
BaseLayers: layers.BaseLayers(),
|
||||
ID: pa.ID,
|
||||
Cwd: cwd,
|
||||
SystemPrompt: layers.SystemPrompt,
|
||||
EnvBlock: layers.EnvBlock,
|
||||
PromptEnvExtra: promptEnv,
|
||||
NewToolServer: newToolServer,
|
||||
NewBackend: backend.NewWithName,
|
||||
|
|
@ -340,5 +341,3 @@ func restoreMultiAgentSession(ps *PersistedSession) (*RestoredSession, error) {
|
|||
ToolCount: toolCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -419,9 +419,10 @@ func CreateAgent(sessName string, args []string) (*agent.Agent, error) {
|
|||
Runtime: rt,
|
||||
Profile: profile,
|
||||
AgentsDir: pkgAgentsDir,
|
||||
AgentID: uname,
|
||||
CWD: cwd,
|
||||
BaseLayers: layers.BaseLayers(),
|
||||
ID: uname,
|
||||
Cwd: cwd,
|
||||
SystemPrompt: layers.SystemPrompt,
|
||||
EnvBlock: layers.EnvBlock,
|
||||
PromptEnvExtra: infra.PromptEnv,
|
||||
NewToolServer: infra.NewToolServer,
|
||||
NewBackend: backend.NewWithName,
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ type Config struct {
|
|||
ListHandlers map[string]func() []string
|
||||
PromptEnvExtra []string
|
||||
Remote string
|
||||
BaseLayers []string
|
||||
SystemPrompt string
|
||||
EnvBlock string
|
||||
Output agent.EventHandler
|
||||
}
|
||||
|
||||
|
|
@ -171,9 +172,10 @@ func New(cfg Config) *Session {
|
|||
Runtime: rt,
|
||||
Profile: cfg.Profile,
|
||||
AgentsDir: cfg.AgentsDir,
|
||||
AgentID: cfg.AgentID,
|
||||
CWD: paths.ExpandHome(cfg.CWD),
|
||||
BaseLayers: cfg.BaseLayers,
|
||||
ID: cfg.AgentID,
|
||||
Cwd: paths.ExpandHome(cfg.CWD),
|
||||
SystemPrompt: cfg.SystemPrompt,
|
||||
EnvBlock: cfg.EnvBlock,
|
||||
PromptEnvExtra: cfg.PromptEnvExtra,
|
||||
NewToolServer: cfg.NewToolServer,
|
||||
NewBackend: cfg.NewBackend,
|
||||
|
|
@ -310,8 +312,6 @@ func (a *Session) SetSessionID(newID string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (a *Session) activeSessionPath(id, suffix string) string {
|
||||
return filepath.Join(a.sessionsDir, "active", id+suffix)
|
||||
}
|
||||
|
|
@ -532,7 +532,7 @@ func (a *Session) Resume() error {
|
|||
if cwd == "" {
|
||||
cwd, _ = os.Getwd()
|
||||
}
|
||||
|
||||
|
||||
// First agent spawns the tool server, others reuse
|
||||
var reuseFrom *InfraConfig
|
||||
if a.proc != nil {
|
||||
|
|
@ -542,7 +542,7 @@ func (a *Session) Resume() error {
|
|||
ToolsConn: a.toolsConn,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
infra, err := SetupToolServer(ToolServerConfig{
|
||||
Ctx: ctx,
|
||||
CWD: cwd,
|
||||
|
|
@ -553,14 +553,14 @@ func (a *Session) Resume() error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("resume setup for agent %s failed: %w", ag.ID(), err)
|
||||
}
|
||||
|
||||
|
||||
// First agent sets session infra
|
||||
if i == 0 {
|
||||
a.proc = infra.Proc
|
||||
a.keeper = infra.Keeper
|
||||
a.toolsConn = infra.ToolsConn
|
||||
}
|
||||
|
||||
|
||||
ag.SetToolServer(infra.NewToolServer, infra.ToolsConn)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,11 +175,6 @@ func BuildPromptLayers(cfg *agent.AgentConfig, cwd, sessID, uname string, prompt
|
|||
}
|
||||
}
|
||||
|
||||
// BaseLayers returns the prompt layers as a slice for agent configuration.
|
||||
func (p PromptLayers) BaseLayers() []string {
|
||||
return []string{p.SystemPrompt, p.EnvBlock}
|
||||
}
|
||||
|
||||
// LoadAutoLoadTools loads all tools from the agent config's autoLoad list.
|
||||
// It first sends the session ID and uname to the tool server so that
|
||||
// ollie-remote can attach its tool registry before processing tool_load RPCs.
|
||||
|
|
|
|||
Loading…
Reference in New Issue