How do I save my undo history as well to my blender file?
as you mentioned there is a limitation.
this test shows you how many steps you can undo. In my pc I can undo only 32 steps:
import bpy

for i in range(0,100):
    bpy.ops.mesh.primitive_cube_add(enter_editmode=False, align='WORLD', location=(i, 0, 0))
    bpy.ops.ed.undo_push(message=&quot;Add Cube&quot;)


for i in range(0,100):
    bpy.ops.ed.undo()

I decompiled undo script
void ED_undo_push(bContext *C, const char *str)
{
  CLOG_INFO(&amp;LOG, 1, &quot;name='%s'&quot;, str);
  WM_file_tag_modified();

  wmWindowManager *wm = CTX_wm_manager(C);
  int steps = U.undosteps;

  /* Ensure steps that have been initialized are always pushed,
   * even when undo steps are zero.
   *
   * Note that some modes (paint, sculpt) initialize an undo step before an action runs,
   * then accumulate changes there, or restore data from it in the case of 2D painting.
   *
   * For this reason we need to handle the undo step even when undo steps is set to zero.
   */
  if ((steps &lt;= 0) &amp;&amp; wm-&gt;undo_stack-&gt;step_init != NULL) {
    steps = 1;
  }
  if (steps &lt;= 0) {
    return;
  }

  /* Only apply limit if this is the last undo step. */
  if (wm-&gt;undo_stack-&gt;step_active &amp;&amp; (wm-&gt;undo_stack-&gt;step_active-&gt;next == NULL)) {
    BKE_undosys_stack_limit_steps_and_memory(wm-&gt;undo_stack, steps - 1, 0);
  }

  BKE_undosys_step_push(wm-&gt;undo_stack, C, str);

  if (U.undomemory != 0) {
    const size_t memory_limit = (size_t)U.undomemory * 1024 * 1024;
    BKE_undosys_stack_limit_steps_and_memory(wm-&gt;undo_stack, -1, memory_limit);
  }

  if (CLOG_CHECK(&amp;LOG, 1)) {
    BKE_undosys_print(wm-&gt;undo_stack);
  }
}

you can see how limitation calculated:
const size_t memory_limit = (size_t)U.undomemory * 1024 * 1024;

as  @Jachym Michal said in comments increasing this limitation is not recommended
I suggest you use Git.you can easily undo (checkout) by git commands

Git is a distributed version control system for tracking changes in source code during software development.

but if you like the automatic way you can commit after each change:
import subprocess
from subprocess import Popen, PIPE, STDOUT

import re

# undo step
step = 0
# repository path
repoDir = 'C:/Users/Kamali/Desktop/Test'

def gitCommit(message):
    global step
    step += 1
    cmd = 'git add . &amp;&amp; git commit -am '+message
    p = subprocess.Popen(cmd, cwd=repoDir)
    p.wait()

def undo():
  global step
  step -= 1
  checkoutTo(step)


# check out to specific step
def checkoutTo(Index):
    cmd = 'git checkout ' + getStepRevision()
    p = subprocess.Popen(cmd, cwd=repoDir)
    p.wait()


# get specific revision from git log history 546bece2c66acb454878500689e3017b92c43d28
def getStepRevision():
    cmd = 'powershell.exe git show HEAD~'+str(step)+' --pretty=oneline --no-patch'
    p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True, cwd=repoDir)
    output = p.stdout.read()
    p.wait()
    
    revision = re.search('(.*?)\s',output.decode('utf-8')).group(1)
    print(revision)


Update
if you don't know git commands
you can use git extension
after you undo (check out) you have to close and reopen blender to refresh
but these lines of code help you to reload it easy
import bpy

bpy.ops.wm.open_mainfile(filepath=bpy.data.filepath)

Tutorial
run this code and see what happened!!!
import subprocess
from subprocess import Popen, PIPE, STDOUT

import bpy

repoDir = 'C:/Users/Kamali/Desktop/New folder'

for i in range(0,100):
    bpy.ops.mesh.primitive_cube_add(enter_editmode=False, align='WORLD', location=(i, 0, 0))
    bpy.ops.ed.undo_push(message=&quot;Add Cube&quot;)


def gitCommit(message):
    bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath)
    add = 'git add .'
    p = subprocess.Popen(add, cwd=repoDir)
    p.wait()
    commit = 'git commit -m ' + message
    p = subprocess.Popen(commit, cwd=repoDir)
    p.wait()

I could commit 100 steps with code