all: delete dead interfaces, kill globalRootState

- agent: delete 'state' interface (one implementation: *History). run()
  now takes *History directly.
- agent: delete 'backendNamer' interface. resolveCompactionModel takes
  backend.Backend directly.
- fs: delete Shutdown/InterruptAll/Lookup wrappers that took *Tree they
  never used. Callers (olliesrv) now call session.* directly.
- fs: delete globalRootState. sessionBindings uses ctx.Root.Data.(*RootState)
  via the HandlerCtx that was always available.
This commit is contained in:
Ollie Agent 2026-08-08 14:58:47 +02:00
parent 63c4cd6173
commit 3b83b9d373
8 changed files with 29 additions and 74 deletions

View File

@ -1,16 +1,15 @@
package agent
import "os"
import (
"os"
// backendNamer is the subset of backend.Backend needed for model resolution.
type backendNamer interface {
Name() string
}
"ollie/backend"
)
// resolveCompactionModel returns the model to use for compaction.
// Priority: agent config > OLLIE_COMPACTION_MODEL env > per-backend default from models.yaml > session's current model.
// Returns "" if no override is configured (use the session's current model).
func resolveCompactionModel(cfgModel string, b backendNamer) string {
func resolveCompactionModel(cfgModel string, b backend.Backend) string {
if cfgModel != "" {
return cfgModel
}

View File

@ -132,7 +132,7 @@ type TurnCtx struct {
ResultCache *sync.Map
}
func run(rt *Runtime, ctx TurnCtx, state state) error {
func run(rt *Runtime, ctx TurnCtx, h *History) error {
var step int
var consecutiveErrors int // rounds where every tool call returned an error
var roundsWithoutPlan int // consecutive tool rounds without a PLAN: block
@ -152,15 +152,15 @@ func run(rt *Runtime, ctx TurnCtx, state state) error {
for {
emit(ctx, Event{Role: "state", Content: "thinking"})
// Proactive context gate: strip cold material before calling the backend.
if budget := contextBudget(ctx, rt.Backend); budget > 0 && state.estimateTokens() > budget {
state.stripCold(ctx, rt.Backend)
if budget := contextBudget(ctx, rt.Backend); budget > 0 && h.estimateTokens() > budget {
h.stripCold(ctx, rt.Backend)
}
history := state.history()
history := h.history()
if rt.Preamble != "" {
history = append([]backend.Message{{Role: "system", Content: rt.Preamble}}, history...)
}
if ts := state.taskState(); ts != nil {
if ts := h.taskState(); ts != nil {
if msg := ts.render(); msg != "" && len(history) > 0 {
// Insert after system prompt, before conversation.
history = append([]backend.Message{history[0], {Role: "user", Content: msg}}, history[1:]...)
@ -269,7 +269,7 @@ func run(rt *Runtime, ctx TurnCtx, state state) error {
IsError: true,
})
}
state.update(msg, results)
h.update(msg, results)
ctx.Save()
return ctx.Err()
}
@ -537,13 +537,13 @@ func run(rt *Runtime, ctx TurnCtx, state state) error {
i = j
}
state.update(msg, results)
h.update(msg, results)
ctx.Save()
// Auto-update TaskState from what actually happened this round.
if ts := state.taskState(); ts != nil {
if ts := h.taskState(); ts != nil {
inferTaskStateUpdate(ts, msg, results, ctx.ReadPlanStep)
state.updateTaskState(*ts)
h.updateTaskState(*ts)
}
if interrupted {
@ -583,7 +583,7 @@ func run(rt *Runtime, ctx TurnCtx, state state) error {
}
if consecutiveErrors == consecutiveErrorSoftLimit {
// Nudge the model to try a different approach and keep the plan current.
state.update(backend.Message{
h.update(backend.Message{
Role: "user",
Content: "<system-consecutive-errors>\nYour last several tool calls all failed. Try a different approach, or ask the user for help. Keep your plan current.\n</system-consecutive-errors>",
}, nil)
@ -609,7 +609,7 @@ func run(rt *Runtime, ctx TurnCtx, state state) error {
lastErrorSig = sig
}
if repeatErrorCount == 3 {
state.update(backend.Message{
h.update(backend.Message{
Role: "user",
Content: "<system-repeated-error>\nYou have made the same malformed tool call 3 times in a row. Read the error message carefully and fix the arguments. The error was:\n" + strings.TrimSpace(results[0].Content) + "\n</system-repeated-error>",
}, nil)
@ -627,7 +627,7 @@ func run(rt *Runtime, ctx TurnCtx, state state) error {
roundsWithoutPlan++
}
if roundsWithoutPlan >= replanGate {
state.update(backend.Message{
h.update(backend.Message{
Role: "user",
Content: fmt.Sprintf("<system-replan-required>\nYou have executed %d tool rounds without replanning. Stop and write a PLAN: block showing your current checklist before calling any more tools. Update session/$OLLIE_SESSION_ID/agent/$OLLIE_UNAME/plan.\n</system-replan-required>", replanGate),
}, nil)
@ -636,7 +636,7 @@ func run(rt *Runtime, ctx TurnCtx, state state) error {
// Stall detection: if LastAction hasn't changed across rounds, the
// model is likely stuck in a loop.
if ts := state.taskState(); ts != nil {
if ts := h.taskState(); ts != nil {
if ts.LastAction == lastAction && lastAction != "" {
stallRounds++
} else {
@ -644,7 +644,7 @@ func run(rt *Runtime, ctx TurnCtx, state state) error {
}
lastAction = ts.LastAction
if stallRounds >= stallThreshold {
state.update(backend.Message{
h.update(backend.Message{
Role: "user",
Content: fmt.Sprintf("<system-stall-detected>\nYou have repeated the same action for %d rounds. Step back, reassess your approach, and try something different. Update your plan.\n</system-stall-detected>", stallRounds),
}, nil)
@ -671,13 +671,13 @@ func run(rt *Runtime, ctx TurnCtx, state state) error {
if rt.MaxSteps > 0 {
halfBudget := rt.MaxSteps / 2
if step == halfBudget {
state.update(backend.Message{
h.update(backend.Message{
Role: "user",
Content: fmt.Sprintf("<system-step-budget-warning>\nYou have used %d/%d research steps without taking action. Consider making progress — write code, edit files, or run commands. Action tools reset this counter.\n</system-step-budget-warning>", step, rt.MaxSteps),
}, nil)
} else if step >= rt.MaxSteps-1 {
emit(ctx, Event{Role: "maxsteps", Content: fmt.Sprintf("%d", step+1)})
state.update(backend.Message{
h.update(backend.Message{
Role: "user",
Content: fmt.Sprintf("<system-step-budget-stop>\nStep budget exhausted (%d/%d steps used). Stop calling tools. Summarize what you have done and what remains, then stop.\n</system-step-budget-stop>", step+1, rt.MaxSteps),
}, nil)
@ -688,9 +688,9 @@ func run(rt *Runtime, ctx TurnCtx, state state) error {
// Periodic task state re-injection: every N tool rounds, surface the
// structured task state back into the conversation to keep the model on track.
if step > 0 && step%planReinjectInterval == 0 {
if ts := state.taskState(); ts != nil {
if ts := h.taskState(); ts != nil {
if msg := ts.render(); msg != "" {
state.update(backend.Message{
h.update(backend.Message{
Role: "user",
Content: "<system-task-state-review>\nReview your current task state and continue. Update it if your approach has changed.\n</system-task-state-review>\n\n" + msg,
}, nil)

View File

@ -1,8 +1,6 @@
package agent
import (
"context"
"ollie/backend"
)
@ -24,27 +22,3 @@ type toolResult struct {
IsError bool
Tier ResultTier // default TierHot; tools may self-classify
}
// state is the interface both ephemeral and bead-backed state must satisfy.
// The loop reads from it on Observe, writes to it on Update.
type state interface {
// History returns the full conversation history for the current session.
// The first entry is always the initial user message containing the goal.
history() []backend.Message
// taskState returns the current structured task state, or nil if not set.
taskState() *TaskState
// updateTaskState replaces the current task state.
updateTaskState(ts TaskState)
// Update appends the assistant's reply and any tool results to the history.
// Called once per loop iteration after Act completes.
update(assistant backend.Message, results []toolResult)
// estimateTokens returns a rough token count of the current history.
estimateTokens() int
// stripCold summarizes large tool-result messages outside the hot tail to reduce context size.
stripCold(ctx context.Context, b backend.Backend)
}

View File

@ -20,6 +20,7 @@ import (
"ollie/fs"
olog "ollie/log"
"ollie/paths"
"ollie/session"
"ollie/skills"
"github.com/godbus/dbus/v5"
@ -127,7 +128,7 @@ func runServer(sockPath string) {
Notify: notifyFn,
Credential: credentialPrompt,
Logf: func(f string, a ...any) { fmt.Printf(f+"\n", a...) },
SessionValid: func(id string) bool { return fs.Lookup(rootTree, id) != nil },
SessionValid: func(id string) bool { return session.Lookup(id) != nil },
})
if err != nil {
fmt.Fprintf(os.Stderr, "warning: %v\n", err)

View File

@ -16,6 +16,7 @@ import (
"ollie/fs"
olog "ollie/log"
"ollie/session"
"9fans.net/go/plan9"
)
@ -678,7 +679,7 @@ func (s *Server) Kill() {
conn.Close()
}
s.connMu.Unlock()
fs.Shutdown(s.rootTree)
session.Shutdown()
done := make(chan struct{})
go func() {
s.serveWG.Wait()

View File

@ -238,8 +238,8 @@ func requestSessionNew(_ HandlerCtx, data []byte) ([]byte, error) {
// sessionBindings returns bindings for all sessions.
// The Blueprint pattern clones the structure and binds ctx.Session.
func sessionBindings(_ HandlerCtx) ([]Binding, error) {
rs := globalRootState
func sessionBindings(ctx HandlerCtx) ([]Binding, error) {
rs := ctx.Root.Data.(*RootState)
sessions := session.Sessions()
out := make([]Binding, 0, len(sessions))

View File

@ -6,10 +6,6 @@ import (
"ollie/session"
)
// globalRootState is set by NewRoot so handler functions can access
// root state without going through ctx.Root.
var globalRootState *RootState
// RootState holds the root-level state for the 9P filesystem.
type RootState struct {
Cfg Config
@ -56,18 +52,3 @@ func (rs *RootState) RenameNode(oldName, newName string) {
rs.SessionNodes[newName] = node
}
}
// Shutdown interrupts all sessions, waits for idle, persists, and closes.
func Shutdown(root *Tree) {
session.Shutdown()
}
// InterruptAll interrupts every active session.
func InterruptAll(root *Tree) {
session.InterruptAll()
}
// Lookup returns the session for the given ID, or nil.
func Lookup(root *Tree, id string) *session.Session {
return session.Lookup(id)
}

View File

@ -75,7 +75,6 @@ func NewRoot(cfg Config) *Tree {
SessionNodes: make(map[string]*SessionNode),
}
tree.Data = rs
globalRootState = rs
// Restore persisted sessions from disk.
for _, r := range session.RestoreAllSessions() {