executeSubtask performs the Plan Phase + Main Loop for a single file.
(ctx context.Context, d model.Diff)
| 520 | |
| 521 | // executeSubtask performs the Plan Phase + Main Loop for a single file. |
| 522 | func (a *Agent) executeSubtask(ctx context.Context, d model.Diff) (bool, string, error) { |
| 523 | ctx, span := telemetry.StartSpan(ctx, "subtask.execute."+d.NewPath) |
| 524 | defer span.End() |
| 525 | telemetry.SetAttr(span, "file.path", d.NewPath) |
| 526 | telemetry.SetAttr(span, "lines.changed", d.Insertions+d.Deletions) |
| 527 | telemetry.SetAttr(span, "lines.inserted", d.Insertions) |
| 528 | telemetry.SetAttr(span, "lines.deleted", d.Deletions) |
| 529 | |
| 530 | if ctx.Err() != nil { |
| 531 | return false, "", ctx.Err() |
| 532 | } |
| 533 | |
| 534 | newPath := d.NewPath |
| 535 | |
| 536 | // Build change-files list excluding current file |
| 537 | changeFilesExcludingCurrent := a.buildChangeFilesExcept(newPath) |
| 538 | |
| 539 | rule := a.resolveSystemRule(strings.ToLower(newPath)) |
| 540 | |
| 541 | threshold := a.args.Template.PlanModeLineThreshold |
| 542 | changeLines := d.Insertions + d.Deletions |
| 543 | |
| 544 | // Phase 1: Plan (skip when changes are below threshold) |
| 545 | var planResult string |
| 546 | if a.args.Template.PlanTask != nil && len(a.args.Template.PlanTask.Messages) > 0 && threshold > 0 && changeLines < int64(threshold) { |
| 547 | fmt.Fprintf(stdout.Writer(), "[ocr] Skipping plan phase for %s (%d lines < threshold %d)\n", newPath, changeLines, threshold) |
| 548 | telemetry.Event(ctx, "plan.skipped", |
| 549 | telemetry.AnyToAttr("file.path", newPath), |
| 550 | telemetry.AnyToAttr("lines.changed", changeLines), |
| 551 | telemetry.AnyToAttr("threshold", threshold)) |
| 552 | } else if a.args.Template.PlanTask != nil && len(a.args.Template.PlanTask.Messages) > 0 { |
| 553 | var err error |
| 554 | planResult, err = a.executePlanPhase(ctx, newPath, d.Diff, changeFilesExcludingCurrent, rule) |
| 555 | if err != nil { |
| 556 | fmt.Fprintf(stdout.Writer(), "[ocr] Plan phase failed for %s: %v (continuing without plan)\n", newPath, err) |
| 557 | telemetry.Eventf(ctx, "plan.failed", err.Error(), |
| 558 | telemetry.AnyToAttr("file.path", newPath)) |
| 559 | planResult = "" |
| 560 | } |
| 561 | } |
| 562 | |
| 563 | // Phase 2: Main task loop |
| 564 | if len(a.args.Template.MainTask.Messages) == 0 { |
| 565 | return false, "", fmt.Errorf("main_task.messages is empty in template") |
| 566 | } |
| 567 | |
| 568 | rawMsgs := a.args.Template.MainTask.Messages |
| 569 | messages := make([]llm.Message, 0, len(rawMsgs)) |
| 570 | for _, m := range rawMsgs { |
| 571 | content := m.Content |
| 572 | content = strings.ReplaceAll(content, "{{current_system_date_time}}", a.currentDate) |
| 573 | content = strings.ReplaceAll(content, "{{current_file_path}}", newPath) |
| 574 | content = strings.ReplaceAll(content, "{{system_rule}}", rule) |
| 575 | content = strings.ReplaceAll(content, "{{change_files}}", changeFilesExcludingCurrent) |
| 576 | content = strings.ReplaceAll(content, "{{diff}}", d.Diff) |
| 577 | content = strings.ReplaceAll(content, "{{requirement_background}}", a.args.Background) |
| 578 | // Always substitute the {{plan_guidance}} token so the literal placeholder |
| 579 | // never leaks into the rendered prompt. When the plan phase produced no |