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:
parent
63c4cd6173
commit
3b83b9d373
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue