Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions internal/agent/learn.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,21 @@ func (a *Agent) lessonsBlock(ctx context.Context) string {
return ""
}
var lessons []string
seen := make(map[string]struct{})
for _, m := range items {
if m.Source == lessonSource {
lessons = append(lessons, m.Content)
lesson := strings.TrimSpace(m.Content)
if !usableLesson(lesson) {
continue
}
key := strings.ToLower(lesson)
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
lessons = append(lessons, lesson)
}
if len(lessons) >= 20 {
if len(lessons) >= 12 {
break
}
}
Expand All @@ -123,3 +133,20 @@ func (a *Agent) lessonsBlock(ctx context.Context) string {
}
return b.String()
}

// usableLesson keeps malformed auxiliary-model output out of the system
// prompt. Historical rows include fragments such as "NONE", "Wait", and
// duplicated partial sentences; presenting them as instructions makes tool
// behavior less predictable and wastes context.
func usableLesson(s string) bool {
if len(s) < 32 || len(s) > 400 {
return false
}
if strings.EqualFold(s, "none") || strings.HasSuffix(strings.ToLower(s), " none") {
return false
}
if strings.EqualFold(s, "wait") || strings.EqualFold(s, ".") {
return false
}
return true
}
20 changes: 20 additions & 0 deletions internal/agent/learn_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package agent

import "testing"

func TestUsableLessonRejectsAuxiliaryFragments(t *testing.T) {
for _, lesson := range []string{"NONE", "Wait", ".", "When `edit_file"} {
if usableLesson(lesson) {
t.Errorf("malformed lesson accepted: %q", lesson)
}
}
if !usableLesson("When edit_file fails with old_string not found, read the current file and copy exact whitespace before retrying.") {
t.Fatal("valid lesson rejected")
}
}

func TestUsableLessonRejectsTrailingNone(t *testing.T) {
if usableLesson("When a tool fails, inspect the concrete error and retry with corrected arguments. NONE") {
t.Fatal("auxiliary NONE suffix should not enter the prompt")
}
}
1 change: 1 addition & 0 deletions internal/agent/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ help them now — do not block them.
// paste line numbers into old_string or expand tabs to spaces and
// the exact match fails repeatedly.
b.WriteString("- read_file returns lines as `NUMBER|CONTENT`. The `|` is metadata only. When calling edit_file, copy **only** the content after `|` into old_string/new_string — never the line number. Preserve tabs and spaces exactly (do not expand tabs to spaces). Line endings are matched automatically.\n")
b.WriteString("- Before every edit_file call, re-read the region you are editing (read_file with offset/limit on large files). edit_file requires an exact, unique old_string from that fresh read. After any successful edit or write, re-read before making another edit; do not reuse an older block or invent identifiers. If it reports multiple occurrences, include unique neighbouring lines or use replace_all only when every occurrence should change.\n")
}
if hasTool(active, "vps_upload") || hasTool(active, "vps_download") || hasTool(active, "vps_run") {
// Without this, models fall back to terminal rsync/scp and never use
Expand Down
11 changes: 10 additions & 1 deletion internal/tools/background_process_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ import (
)

func configureProcessGroup(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
if cmd == nil {
return
}
// Sandboxed commands may already carry Cloneflags, uid mappings, or a
// parent-death signal. Preserve those settings while adding the process
// group needed to terminate a command tree on timeout.
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
cmd.SysProcAttr.Setpgid = true
}

func terminateProcessGroup(cmd *exec.Cmd) {
Expand Down
Loading
Loading