wire memory and task servers; drop filePlanBackend

Register memory.Decl() and task.Decl(planDir) in the dispatcher.
Remove filePlanBackend (plan logic now lives in core/pkg/tools/task).
Drop WithFallbackPlanBackend now that BuildAgentEnv takes no options.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-04-14 08:14:57 +02:00
parent 3cd2afc24c
commit 6168dc9313
2 changed files with 5 additions and 118 deletions

View File

@ -1,116 +0,0 @@
package p9
import (
"context"
"crypto/rand"
"fmt"
"os"
"regexp"
"strings"
"time"
"ollie/pkg/tools"
)
// filePlanBackend implements tools.PlanBackend by writing a markdown checklist
// to the planning directory. The file persists across crashes so another
// session can pick up where the previous one left off.
//
// Filename: YYYYMMDDThhmmss_{uid8}--{goal-slugified}__todo.md
type filePlanBackend struct {
dir string // planning directory path
}
var slugRe = regexp.MustCompile(`[^a-z0-9]+`)
func slugify(s string) string {
s = strings.ToLower(s)
s = slugRe.ReplaceAllString(s, "-")
s = strings.Trim(s, "-")
if len(s) > 60 {
s = s[:60]
if i := strings.LastIndex(s, "-"); i > 20 {
s = s[:i]
}
}
return s
}
// CreatePlan writes a markdown checklist to disk and returns step IDs.
func (b *filePlanBackend) CreatePlan(_ context.Context, goal string, steps []tools.PlanStep) ([]string, string, error) {
order, err := topoSort(steps)
if err != nil {
return nil, "", fmt.Errorf("file plan: %w", err)
}
slug := slugify(goal)
uid := make([]byte, 4)
rand.Read(uid) //nolint:errcheck
ts := time.Now().Format("20060102T150405")
filename := ts + "_" + fmt.Sprintf("%08x", uid) + "--" + slug + "__todo.md"
var md strings.Builder
fmt.Fprintf(&md, "# %s\n\n", goal)
ids := make([]string, len(steps))
for _, idx := range order {
step := steps[idx]
ids[idx] = fmt.Sprintf("s%d", idx+1)
fmt.Fprintf(&md, "- [ ] %s\n", step.Title)
if step.Body != "" {
for _, line := range strings.Split(step.Body, "\n") {
fmt.Fprintf(&md, " %s\n", line)
}
}
}
if err := os.MkdirAll(b.dir, 0755); err != nil {
return nil, "", fmt.Errorf("file plan: %w", err)
}
path := b.dir + "/" + filename
if err := os.WriteFile(path, []byte(md.String()), 0644); err != nil {
return nil, "", fmt.Errorf("file plan: %w", err)
}
msg := fmt.Sprintf(
"Plan saved to pl/%s (%d steps). Rename __todo → __wip when you start, mark items [x] as you complete them (e.g. sed -i 's/- \\[ \\] Step title/- [x] Step title/' planfile.md), then rename __wip → __done when the goal is realized.",
filename, len(steps),
)
return ids, msg, nil
}
// topoSort returns the indices of steps in topological order (blockers first).
func topoSort(steps []tools.PlanStep) ([]int, error) {
n := len(steps)
visited := make([]bool, n)
result := make([]int, 0, n)
var visit func(i int, path []bool) error
visit = func(i int, path []bool) error {
if path[i] {
return fmt.Errorf("dependency cycle at step %d", i)
}
if visited[i] {
return nil
}
path[i] = true
for _, dep := range steps[i].After {
if dep < 0 || dep >= n {
continue
}
if err := visit(dep, path); err != nil {
return err
}
}
path[i] = false
visited[i] = true
result = append(result, i)
return nil
}
for i := range steps {
if err := visit(i, make([]bool, n)); err != nil {
return nil, err
}
}
return result, nil
}

View File

@ -49,7 +49,9 @@ import (
"ollie/pkg/tools"
"ollie/pkg/tools/execute"
"ollie/pkg/tools/file"
"ollie/pkg/tools/memory"
"ollie/pkg/tools/reasoning"
"ollie/pkg/tools/task"
)
const (
@ -1186,7 +1188,9 @@ func (s *Server) createSession(args []string) error {
newDisp := tools.NewDispatcherFunc(map[string]func() tools.Server{
"execute": execute.Decl(cwd),
"file": file.Decl(cwd),
"memory": memory.Decl(),
"reasoning": reasoning.Decl(),
"task": task.Decl(s.planDir),
})
sessID := name
@ -1201,8 +1205,7 @@ func (s *Server) createSession(args []string) error {
return fmt.Errorf("session already exists: %s", sessID)
}
fallback := &filePlanBackend{dir: s.planDir}
env := agent.BuildAgentEnv(cfg, newDisp(), cwd, agent.WithFallbackPlanBackend(fallback))
env := agent.BuildAgentEnv(cfg, newDisp(), cwd)
// Inject per-session env vars into the execute server.
if srv, ok := env.Dispatcher().GetServer("execute"); ok {