claudetool: add simplified patch support

For weaker models.

Also, improve fallback parsing introduced earlier.
diff --git a/claudetool/patch.go b/claudetool/patch.go
index bd01fa8..f398ac6 100644
--- a/claudetool/patch.go
+++ b/claudetool/patch.go
@@ -31,7 +31,11 @@
 	Callback PatchCallback // may be nil
 	// Pwd is the working directory for resolving relative paths
 	Pwd string
+	// Simplified indicates whether to use the simplified input schema.
+	// Helpful for weaker models.
+	Simplified bool
 	// ClipboardEnabled controls whether clipboard functionality is enabled.
+	// Ignored if Simplified is true.
 	// NB: The actual implementation of the patch tool is unchanged,
 	// this flag merely extends the description and input schema to include the clipboard operations.
 	ClipboardEnabled bool
@@ -43,7 +47,10 @@
 func (p *PatchTool) Tool() *llm.Tool {
 	description := PatchBaseDescription + PatchUsageNotes
 	schema := PatchStandardInputSchema
-	if p.ClipboardEnabled {
+	switch {
+	case p.Simplified:
+		schema = PatchStandardSimplifiedSchema
+	case p.ClipboardEnabled:
 		description = PatchBaseDescription + PatchClipboardDescription + PatchUsageNotes
 		schema = PatchClipboardInputSchema
 	}
@@ -130,6 +137,36 @@
 }
 `
 
+	PatchStandardSimplifiedSchema = `{
+  "type": "object",
+  "required": ["path", "patch"],
+  "properties": {
+    "path": {
+      "type": "string",
+      "description": "Path to the file to patch"
+    },
+    "patch": {
+      "type": "object",
+      "required": ["operation", "newText"],
+      "properties": {
+        "operation": {
+          "type": "string",
+          "enum": ["replace", "append_eof", "prepend_bof", "overwrite"],
+          "description": "Type of operation to perform"
+        },
+        "oldText": {
+          "type": "string",
+          "description": "Text to locate for the operation (must be unique in file, required for replace)"
+        },
+        "newText": {
+          "type": "string",
+          "description": "The new text to use (empty for deletions)"
+        }
+      }
+    }
+  }
+}`
+
 	PatchClipboardInputSchema = `
 {
   "type": "object",
@@ -199,8 +236,14 @@
 
 // PatchInputOne is a simplified version of PatchInput for single patch operations.
 type PatchInputOne struct {
-	Path    string       `json:"path"`
-	Patches PatchRequest `json:"patches"`
+	Path    string        `json:"path"`
+	Patches *PatchRequest `json:"patches"`
+}
+
+// PatchInputOneSingular is PatchInputOne with a better name for the singular case.
+type PatchInputOneSingular struct {
+	Path  string        `json:"path"`
+	Patch *PatchRequest `json:"patch"`
 }
 
 type PatchInputOneString struct {
@@ -253,17 +296,21 @@
 func (p *PatchTool) patchParse(m json.RawMessage) (PatchInput, error) {
 	var input PatchInput
 	originalErr := json.Unmarshal(m, &input)
-	if originalErr == nil {
+	if originalErr == nil && len(input.Patches) > 0 {
 		return input, nil
 	}
 	var inputOne PatchInputOne
-	if err := json.Unmarshal(m, &inputOne); err == nil {
-		return PatchInput{Path: inputOne.Path, Patches: []PatchRequest{inputOne.Patches}}, nil
+	if err := json.Unmarshal(m, &inputOne); err == nil && inputOne.Patches != nil {
+		return PatchInput{Path: inputOne.Path, Patches: []PatchRequest{*inputOne.Patches}}, nil
+	}
+	var inputOneSingular PatchInputOneSingular
+	if err := json.Unmarshal(m, &inputOneSingular); err == nil && inputOneSingular.Patch != nil {
+		return PatchInput{Path: inputOneSingular.Path, Patches: []PatchRequest{*inputOneSingular.Patch}}, nil
 	}
 	var inputOneString PatchInputOneString
 	if err := json.Unmarshal(m, &inputOneString); err == nil {
 		var onePatch PatchRequest
-		if err := json.Unmarshal([]byte(inputOneString.Patches), &onePatch); err == nil {
+		if err := json.Unmarshal([]byte(inputOneString.Patches), &onePatch); err == nil && onePatch.Operation != "" {
 			return PatchInput{Path: inputOneString.Path, Patches: []PatchRequest{onePatch}}, nil
 		}
 		var patches []PatchRequest
diff --git a/claudetool/patch_test.go b/claudetool/patch_test.go
index 93bbe1c..6a8d090 100644
--- a/claudetool/patch_test.go
+++ b/claudetool/patch_test.go
@@ -320,7 +320,7 @@
 	// Test single patch format (PatchInputOne)
 	inputOne := PatchInputOne{
 		Path: testFile,
-		Patches: PatchRequest{
+		Patches: &PatchRequest{
 			Operation: "overwrite",
 			NewText:   "Single patch format\n",
 		},
diff --git a/llm/ant/ant.go b/llm/ant/ant.go
index 046456f..7be2911 100644
--- a/llm/ant/ant.go
+++ b/llm/ant/ant.go
@@ -565,3 +565,8 @@
 		}
 	}
 }
+
+// For debugging only, Claude can definitely handle the full patch tool.
+// func (s *Service) UseSimplifiedPatch() bool {
+// 	return true
+// }
diff --git a/llm/llm.go b/llm/llm.go
index ffaad3e..19a1d8d 100644
--- a/llm/llm.go
+++ b/llm/llm.go
@@ -21,6 +21,18 @@
 	TokenContextWindow() int
 }
 
+type SimplifiedPatcher interface {
+	// UseSimplifiedPatch reports whether the service should use the simplified patch input schema.
+	UseSimplifiedPatch() bool
+}
+
+func UseSimplifiedPatch(svc Service) bool {
+	if sp, ok := svc.(SimplifiedPatcher); ok {
+		return sp.UseSimplifiedPatch()
+	}
+	return false
+}
+
 // MustSchema validates that schema is a valid JSON schema and returns it as a json.RawMessage.
 // It panics if the schema is invalid.
 // The schema must have at least type="object" and a properties key.
diff --git a/llm/oai/oai.go b/llm/oai/oai.go
index c561095..6a32a74 100644
--- a/llm/oai/oai.go
+++ b/llm/oai/oai.go
@@ -37,11 +37,12 @@
 )
 
 type Model struct {
-	UserName         string // provided by the user to identify this model (e.g. "gpt4.1")
-	ModelName        string // provided to the service provide to specify which model to use (e.g. "gpt-4.1-2025-04-14")
-	URL              string
-	APIKeyEnv        string // environment variable name for the API key
-	IsReasoningModel bool   // whether this model is a reasoning model (e.g. O3, O4-mini)
+	UserName           string // provided by the user to identify this model (e.g. "gpt4.1")
+	ModelName          string // provided to the service provide to specify which model to use (e.g. "gpt-4.1-2025-04-14")
+	URL                string
+	APIKeyEnv          string // environment variable name for the API key
+	IsReasoningModel   bool   // whether this model is a reasoning model (e.g. O3, O4-mini)
+	UseSimplifiedPatch bool   // whether to use the simplified patch input schema; defaults to false
 }
 
 var (
@@ -210,17 +211,19 @@
 	}
 
 	Qwen3CoderFireworks = Model{
-		UserName:  "qwen3-coder-fireworks",
-		ModelName: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
-		URL:       FireworksURL,
-		APIKeyEnv: FireworksAPIKeyEnv,
+		UserName:           "qwen3-coder-fireworks",
+		ModelName:          "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
+		URL:                FireworksURL,
+		APIKeyEnv:          FireworksAPIKeyEnv,
+		UseSimplifiedPatch: true,
 	}
 
 	// Qwen is a skaband-specific model name for Qwen3-Coder
 	// Provider details (URL and APIKeyEnv) are handled by skaband
 	Qwen = Model{
-		UserName:  "qwen",
-		ModelName: "qwen", // skaband will map this to the actual provider model
+		UserName:           "qwen",
+		ModelName:          "qwen", // skaband will map this to the actual provider model
+		UseSimplifiedPatch: true,
 	}
 )
 
@@ -783,3 +786,7 @@
 		}
 	}
 }
+
+func (s *Service) UseSimplifiedPatch() bool {
+	return s.Model.UseSimplifiedPatch
+}
diff --git a/loop/agent.go b/loop/agent.go
index 310564d..85a4afe 100644
--- a/loop/agent.go
+++ b/loop/agent.go
@@ -1394,6 +1394,7 @@
 	patchTool := &claudetool.PatchTool{
 		Callback:         a.patchCallback,
 		Pwd:              a.workingDir,
+		Simplified:       llm.UseSimplifiedPatch(a.config.Service),
 		ClipboardEnabled: experiment.Enabled("clipboard"),
 	}