Chapter 4: Planning, Execution, and Diff Sandbox

April 13, 2026 ยท View on GitHub

Welcome to Chapter 4: Planning, Execution, and Diff Sandbox. In this part of Plandex Tutorial: Large-Task AI Coding Agent Workflows, you will build an intuitive mental model first, then move into concrete implementation details and practical production tradeoffs.

Plandex keeps generated changes separate until they are reviewed, reducing risk in complex tasks.

Safety Pattern

  • execute in staged plan increments
  • inspect cumulative diff output
  • iterate before touching project files
  • apply only after verification

Summary

You now know how to use Plandex's review sandbox for safer high-impact changes.

Next: Chapter 5: Model Packs and Provider Strategy

Source Code Walkthrough

app/shared/plan_result_pending_summary.go

The pendingChangesSummary function in app/shared/plan_result_pending_summary.go handles a key part of this chapter's functionality:


func (state *CurrentPlanState) PendingChangesSummaryForBuild() string {
	return state.pendingChangesSummary(false, "")
}

func (state *CurrentPlanState) PendingChangesSummaryForApply(commitSummary string) string {
	return state.pendingChangesSummary(true, commitSummary)
}

func (state *CurrentPlanState) pendingChangesSummary(forApply bool, commitSummary string) string {
	var msgs []string

	descByConvoMessageId := make(map[string]*ConvoMessageDescription)

	for _, desc := range state.ConvoMessageDescriptions {
		if desc.ConvoMessageId == "" {
			log.Println("Warning: ConvoMessageId is empty for description:", desc)
			continue
		}

		descByConvoMessageId[desc.ConvoMessageId] = desc
	}

	type changeset struct {
		descsSet map[string]bool
		descs    []*ConvoMessageDescription
		results  []*PlanFileResult
	}
	byDescs := map[string]*changeset{}

	for _, result := range state.PlanResult.Results {
		// log.Println("result:")

This function is important because it defines how Plandex Tutorial: Large-Task AI Coding Agent Workflows implements the patterns covered in this chapter.

app/cli/upgrade.go

The checkForUpgrade function in app/cli/upgrade.go handles a key part of this chapter's functionality:

)

func checkForUpgrade() {
	if os.Getenv("PLANDEX_SKIP_UPGRADE") != "" {
		return
	}

	if version.Version == "development" {
		return
	}

	term.StartSpinner("")
	defer term.StopSpinner()
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	latestVersionURL := "https://plandex.ai/v2/cli-version.txt"
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, latestVersionURL, nil)
	if err != nil {
		log.Println("Error creating request:", err)
		return
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Println("Error checking latest version:", err)
		return
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Println("Error reading response body:", err)
		return

This function is important because it defines how Plandex Tutorial: Large-Task AI Coding Agent Workflows implements the patterns covered in this chapter.

app/cli/upgrade.go

The doUpgrade function in app/cli/upgrade.go handles a key part of this chapter's functionality:

		if confirmed {
			term.ResumeSpinner()
			err := doUpgrade(latestVersion.String())
			if err != nil {
				term.OutputErrorAndExit("Failed to upgrade: %v", err)
				return
			}
			term.StopSpinner()
			restartPlandex()
		} else {
			fmt.Println("Note: set PLANDEX_SKIP_UPGRADE=1 to stop upgrade prompts")
		}
	}
}

func doUpgrade(version string) error {
	tag := fmt.Sprintf("cli/v%s", version)
	escapedTag := url.QueryEscape(tag)

	downloadURL := fmt.Sprintf("https://github.com/plandex-ai/plandex/releases/download/%s/plandex_%s_%s_%s.tar.gz", escapedTag, version, runtime.GOOS, runtime.GOARCH)
	resp, err := http.Get(downloadURL)
	if err != nil {
		return fmt.Errorf("failed to download the update: %w", err)
	}
	defer resp.Body.Close()

	// Create a temporary file to save the downloaded archive
	tempFile, err := os.CreateTemp("", "*.tar.gz")
	if err != nil {
		return fmt.Errorf("failed to create temporary file: %w", err)
	}
	defer os.Remove(tempFile.Name()) // Clean up file afterwards

This function is important because it defines how Plandex Tutorial: Large-Task AI Coding Agent Workflows implements the patterns covered in this chapter.

app/cli/upgrade.go

The restartPlandex function in app/cli/upgrade.go handles a key part of this chapter's functionality:

			}
			term.StopSpinner()
			restartPlandex()
		} else {
			fmt.Println("Note: set PLANDEX_SKIP_UPGRADE=1 to stop upgrade prompts")
		}
	}
}

func doUpgrade(version string) error {
	tag := fmt.Sprintf("cli/v%s", version)
	escapedTag := url.QueryEscape(tag)

	downloadURL := fmt.Sprintf("https://github.com/plandex-ai/plandex/releases/download/%s/plandex_%s_%s_%s.tar.gz", escapedTag, version, runtime.GOOS, runtime.GOARCH)
	resp, err := http.Get(downloadURL)
	if err != nil {
		return fmt.Errorf("failed to download the update: %w", err)
	}
	defer resp.Body.Close()

	// Create a temporary file to save the downloaded archive
	tempFile, err := os.CreateTemp("", "*.tar.gz")
	if err != nil {
		return fmt.Errorf("failed to create temporary file: %w", err)
	}
	defer os.Remove(tempFile.Name()) // Clean up file afterwards

	// Copy the response body to the temporary file
	_, err = io.Copy(tempFile, resp.Body)
	if err != nil {
		return fmt.Errorf("failed to save the downloaded archive: %w", err)
	}

This function is important because it defines how Plandex Tutorial: Large-Task AI Coding Agent Workflows implements the patterns covered in this chapter.

How These Components Connect

flowchart TD
    A[pendingChangesSummary]
    B[checkForUpgrade]
    C[doUpgrade]
    D[restartPlandex]
    E[init]
    A --> B
    B --> C
    C --> D
    D --> E