Donβt worry, you are pretty much on the right track. As an assembly, you can do what you want, so you have the freedom to decide how you want to manage your registers and data. I would recommend developing some standards for myself, and using C-like standards might not be a bad idea. I would also recommend using a different assembler language for the first such project (for example, ARM running on qemu), x86 is somewhat terrible as command sets go. but this is a separate issue ...
Assemblers usually allow you to declare variables, if you wish, a memory with names:
bob: .word 0x1234
Then from assembler (using ARM asm here)
ldr r0,bob add r0,#1 str r0,bob
Registers are used temporarily, real data is stored in memory. Such a model can help track everything, since real data is stored in memory with user-created names, like a high-level language. x86 makes this even easier, since you can perform memory operations and do not have to go through all the registers. Similarly, you can control this using the stack frame for local variables, subtract a certain amount from the stack to cover your stack stack for this function, and inside this function know / remember that the joe variable is the +4 stack pointer, and ted is the pointer stack +8, etc. Probably use comments in your code to remember where these things are. Remembering to restore the pointer / frame of the stack to its entry point before returning. This method is a bit more complicated since you do not use variable names other than numerical offsets. But it provides local variables and recursion and / or some memory savings.
Performing this work as a person with eyes and hands (keyboard and mouse), you probably want to save the data in the register no more than what can fit on the screen in a text editor at a time, at a glance to see the variable goes into the register, and then returns to the variable in memory at a glance. A program / compiler, of course, can track as much memory as there is in a system, much more than a person. That is why compilers, on average, generate better assembler than people (in specific cases, people can always tweak or fix the problem).
Error handling, you need to be careful using flags, for some reason this doesn't seem right to me. This can be very good, interrupts save flags, your code will need to save or set flags, etc. Hmm, a problem with flags - you need to check / use this return value right after the function returns, before you have an instruction that changes flags. where, if you use a register, you can not change this return register for many instructions before you need to try or use this return value.
I think the bottom line is here, look at the C-convention rules that compilers use for this set of commands, and perhaps other sets of instructions, you will see strong similarities for a good reason. They are manageable. With so many registers, you can understand why calling conventions sometimes go straight to the stack for all arguments, and sometimes return values. I was told that bios Amiga has a user calling convention for every bios function that was created for a tough and fast runtime system, but the serum tries to recreate the bios in C using compilers and attaching functions to them using the assembler shell is at best more difficult. I am sure that without good documentation for each function, this is unmanageable. On the way, you can decide if you want this portable machine and you might want you to choose a generally accepted calling agreement. You still want to comment on your code to say that parameter 1 is this, and parameter 2 is this, etc. On the other hand, if you currently or earlier had a programmed x86 assembler that calls DOS and BIOS calls, you would be quite comfortable looking at each function in the link and putting the data in the appropriate registers for each function. Since there were good reference materials, it was convenient to have every functional function.