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()