From 5c81eefe0a64e98eb7076b21e9e0e20fc1b35960 Mon Sep 17 00:00:00 2001 From: Ollie Agent Date: Sun, 9 Aug 2026 01:03:19 +0200 Subject: [PATCH] remove dead code: TaskState subsystem, Verbosity, dead session fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove entire TaskState machinery: - TaskState struct, render(), taskState()/updateTaskState() on History - inferTaskStateUpdate() — heuristic plan tracking - Replan gate (replanGate const, roundsWithoutPlan counter) - Stall detection (stallThreshold, stallRounds, lastAction) - Plan reinject (planReinjectInterval, periodic re-injection) - ReadPlanStep plumbing (AgentCfg → Agent → TurnCtx → loop) None of this worked in practice — the model ignores the nudges, ReadPlanStep was never wired to a non-nil value, and TaskState fields were only populated by inferTaskStateUpdate (which depends on ReadPlanStep being set). Compaction now uses plain summary text in the cold zone instead of serialized TaskState JSON. Also remove: - Verbosity field (declared, threaded, never read by any backend) - DisallowTools (config field never wired to session) - Session.listHandlers, plan, prevPrompt (never read) - Config.Output, Config.ListHandlers, Config.ReadPlanStep (never set) -246 net lines --- agent/agent.go | 11 ---- agent/agent_config.go | 4 -- agent/history.go | 97 +++----------------------------- agent/loop.go | 126 ------------------------------------------ agent/turn.go | 7 +-- backend/backend.go | 1 - session/persist.go | 4 +- session/session.go | 46 +++++---------- 8 files changed, 25 insertions(+), 271 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 49ea24a..676984e 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -51,7 +51,6 @@ type Agent struct { log *olog.Logger sessionID string // the owning session's ID startupMessages []string - readPlanStep func() string save func() // trigger debounced persistence flush func() // immediately flush persistence onStateChange func(agentID, state string) // optional callback for state changes @@ -76,14 +75,6 @@ func (ag *Agent) Messages() []backend.Message { return ag.history.history() } -// TaskState returns the current task state, if any. -func (ag *Agent) TaskState() *TaskState { - if ag.history == nil { - return nil - } - return ag.history.TaskState -} - // UsageStats holds token usage and cost information. type UsageStats struct { TotalInputTokens int @@ -570,7 +561,6 @@ type AgentCfg struct { Output EventHandler Log *olog.Logger StartupMsgs []string - ReadPlanStep func() string Save func() Flush func() } @@ -594,7 +584,6 @@ func NewAgent(cfg AgentCfg) *Agent { log: cfg.Log, sessionID: cfg.SessionID, startupMessages: cfg.StartupMsgs, - readPlanStep: cfg.ReadPlanStep, save: cfg.Save, flush: cfg.Flush, state: "idle", diff --git a/agent/agent_config.go b/agent/agent_config.go index a2bfa21..09d96a4 100644 --- a/agent/agent_config.go +++ b/agent/agent_config.go @@ -36,7 +36,6 @@ type AgentConfig struct { 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. @@ -54,10 +53,8 @@ type AgentConfig struct { IncludeReasoning *bool `json:"includeReasoning,omitempty"` ResponseFormat string `json:"responseFormat,omitempty"` 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"` } @@ -94,6 +91,5 @@ func (c *AgentConfig) GenParams() backend.GenerationParams { IncludeReasoning: c.IncludeReasoning, ResponseFormat: c.ResponseFormat, Stop: c.Stop, - Verbosity: c.Verbosity, } } diff --git a/agent/history.go b/agent/history.go index 411b402..9e6f736 100644 --- a/agent/history.go +++ b/agent/history.go @@ -36,45 +36,6 @@ Be concise. Capture what another LLM needs to seamlessly continue.` warmIndexSize = 10 ) -// TaskState is a compact structured overlay that summarizes the agent's -// current position in a task. Injected at the top of every turn so the -// model doesn't rely on full history recall. -type TaskState struct { - Objective string `json:"objective"` - PlanStep string `json:"plan_step"` - Constraints []string `json:"constraints,omitempty"` - LastAction string `json:"last_action"` - OpenQuestions []string `json:"open_questions,omitempty"` - NextDecision string `json:"next_decision"` -} - -// render produces a compact text representation for injection into the message history. -func (ts *TaskState) render() string { - if ts.Objective == "" { - return "" - } - var sb strings.Builder - sb.WriteString("[task state]\n") - sb.WriteString("objective: " + ts.Objective + "\n") - if ts.PlanStep != "" { - sb.WriteString("plan_step: " + ts.PlanStep + "\n") - } - if len(ts.Constraints) > 0 { - sb.WriteString("constraints: " + strings.Join(ts.Constraints, "; ") + "\n") - } - if ts.LastAction != "" { - sb.WriteString("last_action: " + ts.LastAction + "\n") - } - if len(ts.OpenQuestions) > 0 { - sb.WriteString("open_questions: " + strings.Join(ts.OpenQuestions, "; ") + "\n") - } - if ts.NextDecision != "" { - sb.WriteString("next_decision: " + ts.NextDecision + "\n") - } - sb.WriteString("[/task state]") - return sb.String() -} - // RestoreHistoryFromMessages reconstructs a History from just the message list. // Used for multi-agent session restore where each agent has separate messages. func RestoreHistoryFromMessages(messages []backend.Message) *History { @@ -97,9 +58,8 @@ func RestoreHistoryFromMessages(messages []backend.Message) *History { // History is an ephemeral in-memory state backend. type History struct { - goal string - messages []backend.Message - TaskState *TaskState + goal string + messages []backend.Message // Cumulative usage tracking. TotalInputTokens int TotalCachedInputTokens int @@ -128,14 +88,6 @@ func (s *History) history() []backend.Message { return s.messages } -func (s *History) taskState() *TaskState { - return s.TaskState -} - -func (s *History) updateTaskState(ts TaskState) { - s.TaskState = &ts -} - func (s *History) addUsage(u backend.Usage, estimated bool) { s.TotalInputTokens += u.InputTokens s.TotalCachedInputTokens += u.CachedInputTokens @@ -294,25 +246,16 @@ func (s *History) compact(ctx context.Context, b backend.Backend) (int, string, return 0, "", fmt.Errorf("compact: empty summary") } - // Parse structured task state from the model's JSON response. - var ts TaskState - raw := extractJSON(summaryText) - if err := json.Unmarshal([]byte(raw), &ts); err != nil { - // Fallback: treat as unstructured summary if JSON parse fails. - ts = TaskState{Objective: summaryText} - } - s.TaskState = &ts - beforeCount := len(s.messages) - s.messages = buildCompactedHistory(ts, s.messages) + s.messages = buildCompactedHistory(summaryText, s.messages) return beforeCount - len(s.messages), summaryText, nil } // buildCompactedHistory constructs a three-zone post-compaction message list: -// - Cold: structured task state summary (everything older than warm+hot) +// - Cold: compaction summary (everything older than warm+hot) // - Warm: brief index of recent decisions/actions (warmIndexSize messages) // - Hot: last hotTailSize messages verbatim -func buildCompactedHistory(ts TaskState, allMessages []backend.Message) []backend.Message { +func buildCompactedHistory(summary string, allMessages []backend.Message) []backend.Message { total := len(allMessages) // Determine zone boundaries. @@ -335,11 +278,10 @@ func buildCompactedHistory(ts TaskState, allMessages []backend.Message) []backen warmStart = 0 } - // Cold zone: structured task state. - stateJSON, _ := json.Marshal(ts) + // Cold zone: compaction summary. cold := backend.Message{ Role: "user", - Content: "[compacted context — cold zone]\n```json\n" + string(stateJSON) + "\n```", + Content: "[compacted context]\n" + summary, } var out []backend.Message @@ -399,31 +341,6 @@ func summarizeMessage(m backend.Message) string { } // extractJSON finds the first JSON object in s (handling markdown fences). -func extractJSON(s string) string { - // Strip markdown code fence if present. - if i := strings.Index(s, "```json"); i >= 0 { - s = s[i+7:] - if j := strings.Index(s, "```"); j >= 0 { - s = s[:j] - } - } else if i := strings.Index(s, "```"); i >= 0 { - s = s[i+3:] - if j := strings.Index(s, "```"); j >= 0 { - s = s[:j] - } - } - // Find first { ... } - start := strings.Index(s, "{") - if start < 0 { - return s - } - end := strings.LastIndex(s, "}") - if end < start { - return s - } - return s[start : end+1] -} - // flattenToolMessages converts tool call/result sequences into plain text // so the compaction request doesn't include tool-specific structures that // the API may reject when no tools are defined. diff --git a/agent/loop.go b/agent/loop.go index 1b173a1..c0ee43c 100644 --- a/agent/loop.go +++ b/agent/loop.go @@ -30,18 +30,6 @@ const ( consecutiveErrorHardLimit = 10 ) -// replanGate is the max consecutive tool rounds without a PLAN: block before -// a mandatory replan nudge is injected. -const replanGate = 8 - -// stallThreshold is the number of consecutive rounds where LastAction doesn't -// change before injecting a stall nudge. -const stallThreshold = 5 - -// planReinjectInterval is the number of tool-call rounds between periodic -// task state re-injection into the conversation. -const planReinjectInterval = 10 - type toolExecutor func(ctx context.Context, name string, args json.RawMessage) (string, []backend.ContentBlock, error) // cachedResult stores a tool result with file metadata for staleness detection. @@ -127,7 +115,6 @@ type TurnCtx struct { 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 - ReadPlanStep func() string // next unchecked step from plan file, or "" IncrToolCallCount func() int64 ResultCache *sync.Map } @@ -135,9 +122,6 @@ type TurnCtx struct { 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 - var lastAction string // previous round's LastAction for stall detection - var stallRounds int // consecutive rounds with unchanged LastAction var lastErrorSig string // signature of previous round's error(s) for repeat detection var repeatErrorCount int // consecutive rounds with identical error signature // resultCache stores outputs of read-safe tool calls keyed by name+args. @@ -160,12 +144,6 @@ func run(rt *Runtime, ctx TurnCtx, h *History) error { if preamble := rt.PreambleString(); preamble != "" { history = append([]backend.Message{{Role: "system", Content: preamble}}, history...) } - 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:]...) - } - } // Stream the assistant's response, retrying on rate limits, transient // backend errors (5xx, network), and mid-stream drops. One stable ID is @@ -540,12 +518,6 @@ func run(rt *Runtime, ctx TurnCtx, h *History) error { h.update(msg, results) ctx.Save() - // Auto-update TaskState from what actually happened this round. - if ts := h.taskState(); ts != nil { - inferTaskStateUpdate(ts, msg, results, ctx.ReadPlanStep) - h.updateTaskState(*ts) - } - if interrupted { return ctx.Err() } @@ -619,43 +591,7 @@ func run(rt *Runtime, ctx TurnCtx, h *History) error { lastErrorSig = "" } - // Replan gate: if the model has been calling tools without emitting a - // PLAN: block, force it to replan before continuing. - if strings.Contains(msg.Content, "PLAN:") { - roundsWithoutPlan = 0 - } else { - roundsWithoutPlan++ - } - if roundsWithoutPlan >= replanGate { - h.update(backend.Message{ - Role: "user", - Content: fmt.Sprintf("\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", replanGate), - }, nil) - roundsWithoutPlan = 0 - } - - // Stall detection: if LastAction hasn't changed across rounds, the - // model is likely stuck in a loop. - if ts := h.taskState(); ts != nil { - if ts.LastAction == lastAction && lastAction != "" { - stallRounds++ - } else { - stallRounds = 0 - } - lastAction = ts.LastAction - if stallRounds >= stallThreshold { - h.update(backend.Message{ - Role: "user", - Content: fmt.Sprintf("\nYou have repeated the same action for %d rounds. Step back, reassess your approach, and try something different. Update your plan.\n", stallRounds), - }, nil) - stallRounds = 0 - } - } - // Reset step counter when an action-oriented tool is called successfully. - // This allows the agent to continue working without hitting the soft - // step-budget guardrail, as long as it's making active progress (writing - // files, renaming symbols, etc.) rather than looping on research. if rt.ToolMeta != nil { for _, r := range results { if !r.IsError && rt.ToolMeta[r.Name].ResetsCounter { @@ -666,8 +602,6 @@ func run(rt *Runtime, ctx TurnCtx, h *History) error { } // Step budget warnings: nudge the agent at 50% and when exhausted. - // These only fire for research-heavy loops — action tools (file_write, - // file_edit, shell) reset the counter, so active progress never triggers. if rt.MaxSteps > 0 { halfBudget := rt.MaxSteps / 2 if step == halfBudget { @@ -685,19 +619,6 @@ func run(rt *Runtime, ctx TurnCtx, h *History) 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 := h.taskState(); ts != nil { - if msg := ts.render(); msg != "" { - h.update(backend.Message{ - Role: "user", - Content: "\nReview your current task state and continue. Update it if your approach has changed.\n\n\n" + msg, - }, nil) - } - } - } - if ctx.AutoCompact != nil { ctx.AutoCompact(ctx) } @@ -727,53 +648,6 @@ func contextBudget(ctx context.Context, b backend.Backend) int { return ctxLen / 2 } -// inferTaskStateUpdate updates TaskState fields from the actual tool round -// without requiring model cooperation. Keeps the state current even if the -// model ignores re-injection nudges. -func inferTaskStateUpdate(ts *TaskState, msg backend.Message, results []toolResult, readPlanStep func() string) { - // LastAction: summarize what tools ran and whether they succeeded. - if len(msg.ToolCalls) > 0 { - names := make([]string, 0, len(msg.ToolCalls)) - for _, tc := range msg.ToolCalls { - names = append(names, tc.Name) - } - errCount := 0 - for _, r := range results { - if r.IsError { - errCount++ - } - } - action := strings.Join(names, ", ") - if errCount > 0 { - action += fmt.Sprintf(" (%d/%d failed)", errCount, len(results)) - } - ts.LastAction = action - } - - // PlanStep: prefer the plan file (ground truth), fall back to text heuristic. - if readPlanStep != nil { - if step := readPlanStep(); step != "" { - ts.PlanStep = step - return - } - } - // Fallback: extract from PLAN: block if present in assistant text. - if idx := strings.Index(msg.Content, "PLAN:"); idx >= 0 { - lines := strings.Split(msg.Content[idx:], "\n") - for _, line := range lines[1:] { - trimmed := strings.TrimSpace(line) - if strings.HasPrefix(trimmed, "- [ ]") { - ts.PlanStep = strings.TrimSpace(trimmed[5:]) - break - } - if trimmed == "" || (!strings.HasPrefix(trimmed, "- [") && trimmed != "") { - break - } - } - } -} - - // defaultToolResultMaxBytes caps tool result content sent back to the model. const defaultToolResultMaxBytes = 131072 diff --git a/agent/turn.go b/agent/turn.go index 3fb8595..2f490d6 100644 --- a/agent/turn.go +++ b/agent/turn.go @@ -120,10 +120,9 @@ func (ag *Agent) executeTurn(ctx context.Context, input string) string { } turnCtx := TurnCtx{ - Output: ag.output, - ReadPlanStep: ag.readPlanStep, - Save: ag.save, - ResultCache: &ag.resultCache, + Output: ag.output, + Save: ag.save, + ResultCache: &ag.resultCache, } var replyBuf strings.Builder diff --git a/backend/backend.go b/backend/backend.go index 48dd5f1..cc29cfb 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -138,7 +138,6 @@ type GenerationParams struct { IncludeReasoning *bool // include reasoning in response (OpenRouter) ResponseFormat string // "json_object", "text", etc. Stop []string // stop sequences - Verbosity string // internal: controls output detail level } // Backend is the interface all LLM providers must implement. diff --git a/session/persist.go b/session/persist.go index 1a51d61..543e19f 100644 --- a/session/persist.go +++ b/session/persist.go @@ -34,7 +34,6 @@ type PersistedAgent struct { 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"` @@ -77,8 +76,7 @@ func PersistSession(name string) error { Backend: ag.BackendName(), Model: ag.ModelName(), CWD: ag.Cwd(), - Messages: ag.Messages(), - TaskState: ag.TaskState(), + Messages: ag.Messages(), } if usage := ag.Usage(); usage != nil { pa.TotalInputTokens = usage.TotalInputTokens diff --git a/session/session.go b/session/session.go index 83e77bd..ba1d15d 100644 --- a/session/session.go +++ b/session/session.go @@ -35,13 +35,10 @@ type Config struct { NewBackend func(string) (backend.Backend, error) Log *olog.Logger MaxSteps int - ReadPlanStep func() string - ListHandlers map[string]func() []string PromptEnvExtra []string Remote string SystemPrompt string EnvBlock string - Output agent.EventHandler } // Session owns all session-level state: identity, agents, tool server lifecycle. @@ -60,26 +57,20 @@ type Session struct { Remote string // Agent state - agents []*agent.Agent - log *olog.Logger - sessionsDir string - listHandlers map[string]func() []string + agents []*agent.Agent + log *olog.Logger + sessionsDir string // Environment - envMu sync.RWMutex - env map[string]string - plan []byte - prevPrompt string + envMu sync.RWMutex + env map[string]string // Mutable state protected by mu mu sync.RWMutex - name string // friendly name - toolsConn *toolsrv.Conn // multiplexed RPC connection + name string // friendly name + toolsConn *toolsrv.Conn // multiplexed RPC connection paused bool - // Tool restrictions - disallowTools map[string]struct{} - // Autosave saveMu sync.Mutex saveDirty bool @@ -181,14 +172,13 @@ func New(cfg Config) *Session { } s := &Session{ - ID: cfg.SessionID, - name: name, - Remote: cfg.Remote, - env: make(map[string]string), - agents: make([]*agent.Agent, 0, 1), - log: log, - sessionsDir: cfg.SessionsDir, - listHandlers: cfg.ListHandlers, + ID: cfg.SessionID, + name: name, + Remote: cfg.Remote, + env: make(map[string]string), + agents: make([]*agent.Agent, 0, 1), + log: log, + sessionsDir: cfg.SessionsDir, } ag := agent.NewAgent(agent.AgentCfg{ @@ -203,11 +193,9 @@ func New(cfg Config) *Session { PromptEnvExtra: cfg.PromptEnvExtra, NewToolServer: cfg.NewToolServer, NewBackend: cfg.NewBackend, - Output: cfg.Output, Log: log, SessionID: cfg.SessionID, StartupMsgs: rt.Messages, - ReadPlanStep: cfg.ReadPlanStep, Save: s.saveSession, Flush: s.flushSave, }) @@ -405,12 +393,6 @@ func (s *Session) LoadTool(name string, ag *agent.Agent) error { if name == "" { return nil } - s.mu.RLock() - _, blocked := s.disallowTools[name] - s.mu.RUnlock() - if blocked { - return fmt.Errorf("tool %q is disallowed for this session", name) - } var conn *toolsrv.Conn if s.ToolsConn() != nil { conn = s.ToolsConn()