How can this simple coroutine implementation be improved?
Things you did well on:


Overall, this looks like a pretty decent little bit of code.  It looks like some research went into this
I like the comments.  Everywhere I was confused on something, there was a comment to clarify.  You also didn't go overboard with them!




Things you could improve on:

Preprocessor:


Group all of your #defines at the top of your code, just after your #imports.  Otherwise you will have to jump around your code to find them.


Initialization:


typedef your structs.


struct {
  jmp_buf jmp;
  void *stack;
  long stack_sz;
} coroutines[MAX_COROS];



The typedef means you no longer have to write struct all over the place. That not only saves keystrokes, it also can make the code cleaner since it provides a smidgen more abstraction.
You can initialize some variables right away.


int stack_sz; 
stack_sz = coroutines[coro_pid].stack_sz;



int stack_sz = coroutines[coro_pid].stack_sz;



Syntax:


You use print() where it is unneeded.


printf("starting the scheduler...\n");



You can use puts() instead.

puts("Starting the scheduler...");



Memory:


You allocated memory to stack, but never free it.


void *stack = malloc(stack_sz);



Failure to deallocate memory using free leads to buildup of non-reusable memory, which is no longer used by the program. This wastes memory resources and can lead to allocation failures when these resources are exhausted.

free(stack);



Comments:


You could use the /* ... */ comment format instead of the // ... format when commenting over multiple lines.


// value == 0 means just starting
// value == -1 means spawning new coroutine
// value positive means hop back to a specific pid



You could also condense your comments down a bit.

/* 
 * If 'value' is 0, it is just starting; if -1 it is spawning.
 * If 'value' is positive, we "hop" to a specific PID.
 */





Resources:


Coroutine implementations for C
Coroutines in C
Building Coroutines
Libtask: a Coroutine Library for C and Unix