Skip to content

Commit 808092a

Browse files
committed
perf: update task reminders
1 parent 3a16a84 commit 808092a

5 files changed

Lines changed: 83 additions & 126 deletions

File tree

internal/agent/runtime_policy.go

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ func (p *sessionRuntimePolicy) trackToolEnd(ev agentcore.Event) {
114114
p.session.mu.Unlock()
115115

116116
p.detectRepeatedCalls(record, recent)
117-
p.detectTaskManagementGap()
118117
}
119118

120119
func (p *sessionRuntimePolicy) detectRepeatedCalls(current toolCallFingerprint, recent []toolCallFingerprint) {
@@ -140,26 +139,6 @@ func (p *sessionRuntimePolicy) detectRepeatedCalls(current toolCallFingerprint,
140139
}
141140
}
142141

143-
func (p *sessionRuntimePolicy) detectTaskManagementGap() {
144-
s := p.session
145-
if s.taskStore == nil {
146-
return
147-
}
148-
149-
s.mu.Lock()
150-
turn := s.currentTurn
151-
s.mu.Unlock()
152-
153-
snap := s.taskStore.Snapshot()
154-
if key, reminder, ok := taskManagementReminderForTurn(turn, snap); ok {
155-
p.session.deliverRuntimeReminder(
156-
key,
157-
ReminderTaskManagement,
158-
reminder,
159-
)
160-
}
161-
}
162-
163142
func (p *sessionRuntimePolicy) handleMessageEnd(msg agentcore.Message) {
164143
if msg.Role != agentcore.RoleAssistant {
165144
return

internal/agent/session_runtime.go

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -485,27 +485,6 @@ func (s *Session) SetModel(prov, model string) error {
485485

486486
s.reclampThinking()
487487

488-
s.mu.Lock()
489-
thinkLvl := s.settings.ThinkingLevel
490-
curSmall := s.settings.SmallModel
491-
cwd := s.cwd
492-
s.mu.Unlock()
493-
494-
patch := config.Settings{
495-
Provider: &prov,
496-
Model: &model,
497-
SmallModel: &curSmall,
498-
ThinkingLevel: &thinkLvl,
499-
}
500-
if err := config.PatchGlobalSettings(patch); err != nil {
501-
fmt.Fprintf(os.Stderr, "warning: persist global setting: %v\n", err)
502-
}
503-
if config.ProjectConfigExists(cwd) {
504-
if err := config.PatchProjectSettings(cwd, patch); err != nil {
505-
fmt.Fprintf(os.Stderr, "warning: persist project setting: %v\n", err)
506-
}
507-
}
508-
509488
return nil
510489
}
511490

internal/agent/session_test.go

Lines changed: 78 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"os"
9+
"path/filepath"
810
"strings"
911
"sync"
1012
"testing"
@@ -471,6 +473,82 @@ func TestSetModelKeepsStateWhenPersistFails(t *testing.T) {
471473
}
472474
}
473475

476+
func TestSetModelDoesNotRewriteGlobalSettings(t *testing.T) {
477+
home := t.TempDir()
478+
t.Setenv("HOME", home)
479+
480+
configDir := filepath.Join(home, ".codebot")
481+
if err := os.MkdirAll(configDir, 0o755); err != nil {
482+
t.Fatalf("mkdir config dir: %v", err)
483+
}
484+
initial := `{
485+
"provider": "openai",
486+
"model": "gpt-5.4",
487+
"small_model": "gpt-5.4",
488+
"providers": {
489+
"openai": {"api_key": "openai-key"},
490+
"anthropic": {"api_key": "anthropic-key"}
491+
}
492+
}`
493+
settingsPath := filepath.Join(configDir, "settings.json")
494+
if err := os.WriteFile(settingsPath, []byte(initial), 0o600); err != nil {
495+
t.Fatalf("write settings: %v", err)
496+
}
497+
498+
dir := t.TempDir()
499+
mgr := storage.NewManager(dir)
500+
store, err := mgr.Create(dir)
501+
if err != nil {
502+
t.Fatalf("create session: %v", err)
503+
}
504+
505+
s := NewSession(SessionConfig{
506+
Agent: agentcore.NewAgent(agentcore.WithModel(&stubChatModel{})),
507+
Store: store,
508+
Manager: mgr,
509+
Settings: config.Resolved{
510+
Provider: "openai",
511+
Model: "gpt-5.4",
512+
Providers: map[string]config.ProviderConfig{
513+
"openai": {APIKey: "openai-key"},
514+
"anthropic": {APIKey: "anthropic-key"},
515+
},
516+
ContextWindow: 128000,
517+
AutoCompaction: false,
518+
MaxTurns: 30,
519+
},
520+
Cwd: dir,
521+
CreateModel: func(_ string, _ string, _ string, _ string) (agentcore.ChatModel, error) {
522+
return &stubChatModel{}, nil
523+
},
524+
})
525+
t.Cleanup(s.Close)
526+
527+
if err := s.SetModel("anthropic", "claude-sonnet-4-5"); err != nil {
528+
t.Fatalf("set model: %v", err)
529+
}
530+
531+
got, err := config.LoadSettingsStrict(dir)
532+
if err != nil {
533+
t.Fatalf("load settings: %v", err)
534+
}
535+
if got.Provider != "openai" {
536+
t.Fatalf("provider rewritten: got %q want %q", got.Provider, "openai")
537+
}
538+
if got.Model != "gpt-5.4" {
539+
t.Fatalf("model rewritten: got %q want %q", got.Model, "gpt-5.4")
540+
}
541+
if got.SmallModel != "gpt-5.4" {
542+
t.Fatalf("small model rewritten: got %q want %q", got.SmallModel, "gpt-5.4")
543+
}
544+
if s.Provider() != "anthropic" {
545+
t.Fatalf("session provider not switched: got %q want %q", s.Provider(), "anthropic")
546+
}
547+
if s.ModelName() != "claude-sonnet-4-5" {
548+
t.Fatalf("session model not switched: got %q want %q", s.ModelName(), "claude-sonnet-4-5")
549+
}
550+
}
551+
474552
func TestResolveAndSetModelSupportsExplicitProviderSyntax(t *testing.T) {
475553
t.Parallel()
476554

@@ -927,68 +1005,6 @@ func TestPromptDoesNotQueueTaskManagementReminderFromUserText(t *testing.T) {
9271005
}
9281006
}
9291007

930-
func TestTaskManagementReminderQueuedForUntrackedOrBroadWork(t *testing.T) {
931-
t.Parallel()
932-
933-
cases := []struct {
934-
name string
935-
store *localtools.TaskStore
936-
run func(s *Session)
937-
}{
938-
{
939-
name: "missing task list",
940-
store: localtools.NewTaskStore(),
941-
run: func(s *Session) {
942-
args := json.RawMessage(`{"path":"main.go"}`)
943-
for i := 0; i < 3; i++ {
944-
toolID := fmt.Sprintf("read-%d", i)
945-
s.handleAgentEvent(agentcore.Event{Type: agentcore.EventToolExecStart, ToolID: toolID, Tool: "read", Args: args})
946-
s.handleAgentEvent(agentcore.Event{Type: agentcore.EventToolExecEnd, ToolID: toolID, Tool: "read"})
947-
}
948-
},
949-
},
950-
{
951-
name: "single broad task",
952-
store: func() *localtools.TaskStore {
953-
store := localtools.NewTaskStore()
954-
store.Create("Implement the entire project", "An overly broad task", "Implementing the entire project", nil)
955-
return store
956-
}(),
957-
run: func(s *Session) {
958-
s.handleAgentEvent(agentcore.Event{Type: agentcore.EventToolExecStart, ToolID: "task-1", Tool: "task_create"})
959-
s.handleAgentEvent(agentcore.Event{Type: agentcore.EventToolExecEnd, ToolID: "task-1", Tool: "task_create"})
960-
editArgs := json.RawMessage(`{"file":"main.go"}`)
961-
s.handleAgentEvent(agentcore.Event{Type: agentcore.EventToolExecStart, ToolID: "edit-1", Tool: "edit", Args: editArgs})
962-
s.handleAgentEvent(agentcore.Event{Type: agentcore.EventToolExecEnd, ToolID: "edit-1", Tool: "edit"})
963-
},
964-
},
965-
}
966-
967-
for _, tc := range cases {
968-
t.Run(tc.name, func(t *testing.T) {
969-
ag := agentcore.NewAgent(agentcore.WithModel(&stubChatModel{}))
970-
s := NewSession(SessionConfig{
971-
Agent: ag,
972-
Settings: config.Resolved{MaxTurns: 30},
973-
Cwd: t.TempDir(),
974-
TaskStore: tc.store,
975-
})
976-
t.Cleanup(s.Close)
977-
978-
s.beginTurn()
979-
tc.run(s)
980-
981-
msg := s.buildUserMessage(agentcore.TextBlock("continue"))
982-
if len(msg.Content) != 2 {
983-
t.Fatalf("expected one injected reminder plus user block, got %#v", msg.Content)
984-
}
985-
if !strings.Contains(msg.Content[0].Text, "<system-reminder>") {
986-
t.Fatalf("expected injected system reminder, got %#v", msg.Content)
987-
}
988-
})
989-
}
990-
}
991-
9921008
func TestTaskManagementReminderSteersBeforeStopWithOpenInProgressTask(t *testing.T) {
9931009
t.Parallel()
9941010

internal/agent/task_reminders.go

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,12 @@ import (
99
)
1010

1111
const (
12-
taskManagementMissingReminder = "<system-reminder>\nYou are doing multi-step work without maintaining a task list. Create concrete tasks now instead of continuing without structure.\n</system-reminder>"
13-
taskManagementExpandSingleReminder = "<system-reminder>\nYour task list is too broad for the current scope. Split the single broad task into multiple more specific tasks and keep their statuses up to date.\n</system-reminder>"
14-
taskManagementStaleReminderTag = "<task-management-stale-reminder>"
15-
taskManagementStaleReminderLead = "The task tools haven't been used recently."
16-
taskReminderTurnsSinceWrite = 10
17-
taskReminderTurnsBetweenReminders = 10
12+
taskManagementStaleReminderTag = "<task-management-stale-reminder>"
13+
taskManagementStaleReminderLead = "The task tools haven't been used recently."
14+
taskReminderTurnsSinceWrite = 10
15+
taskReminderTurnsBetweenReminders = 10
1816
)
1917

20-
func taskManagementReminderForTurn(turn TurnOutcomeSnapshot, snap localtools.TaskSnapshot) (key, reminder string, ok bool) {
21-
if turn.ReadOnlyToolCalls < 3 && turn.CodeEditToolCalls == 0 {
22-
return "", "", false
23-
}
24-
25-
switch {
26-
case turn.TaskMutations == 0:
27-
return "task_management:missing", taskManagementMissingReminder, true
28-
case snap.Total == 1 && (turn.ReadOnlyToolCalls >= 3 || turn.CodeEditToolCalls > 0):
29-
return "task_management:expand_single", taskManagementExpandSingleReminder, true
30-
default:
31-
return "", "", false
32-
}
33-
}
34-
3518
func taskManagementReminderForNextPrompt(msgs []agentcore.AgentMessage, snap localtools.TaskSnapshot) (key, reminder string, ok bool) {
3619
if snap.Total == 0 {
3720
return "", "", false

settings.example.jsonc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
// Current selection — updated automatically when switching via /model
2+
// 默认 provider/model;运行时切换模型只影响当前会话,不会自动回写这里
33
"provider": "anthropic",
44
"model": "claude-sonnet-4-6",
55
"small_model": "claude-haiku-4-5", // auto-filled from provider config or main model

0 commit comments

Comments
 (0)