The mechanics behind FRAME! and ACTION! have been mixed up and reshaped over time, to where they are very much tied together.
At times I've wondered if we need both types. If ACTION!s become inert unless they are isotopic, then that would seem to blur the distinction even more... since FRAME!s have kind of been "inert actions".
So I thought I'd try writing up an explanation of the current state of things.
First: What's a FRAME! ?
You can think of a FRAME! as an object which has keys and values for the arguments and locals of a function. There's a flag on each cell which indicates whether the object member has been "specialized out". If the flag is set, the slot is presumed to contain the value it will have when the function runs. Otherwise the object member is presumed to contain the type information for acquiring that parameter.
So when you make an ACTION! like:
foo: func [x [integer!] y [text!] <local> z] [...]
It's internals are actually a FRAME! (called an "exemplar") that looks something like:
make frame! [ x: [integer!] ; unspecialized y: [text!] ; unspecialized z: ~ ; specialized ]
What's The Difference Between ACTION! and FRAME!, Then?
Looking at the above: you wouldn't want to try and DO a FRAME! which has type information in the argument slots. When the code for the function runs, it's expected that X be an integer instance...not type information saying an integer is expected!
So when you MAKE FRAME! from an ACTION!, it goes through all the slots and wipes them out to be unset:
>> make frame! :foo == make frame! [ x: ~ y: ~ ] ; ^-- local z is not shown, as it was already specialized to a value
But it still has to maintain a link to the original information about the parameter types, in order to validate them. So there's a pointer from each of these "non-exemplar" FRAME!s to the "exemplar" FRAME! with the type information.
So ACTION!s are FRAME!s w/typesets in slots where args would be?
Kind of. I glossed over some details there (there's more information in the parameter description than the type block...you need to know if it's quoted, or if it's a refinement, etc.)
Right now every FRAME! points at an ACTION! instance. So you can ask action of frame and get an answer. That answer leads you to the thing that the system will use to do type checking on that frame (and you can in theory use it yourself).
It could truthfully be said that you never execute ACTION!s, you only execute FRAME!s. But when we say we "execute an action" all we mean is that a new frame is created for it, and then that frame is executed.
One might wonder why something like SPECIALIZE would create an ACTION! instead of just making a FRAME!. One good reason is that this offers an opportunity to completely erase the specialized variables from the interface. With the variables erased, the words become available again for use in AUGMENT-ing the composite function.
So There's Some Info...
Things are sort of stable with this, but a big thing on the horizon is the possibility that there might be FRAME! instances for evaluators besides actions. So you could have a FRAME! representing just the evaluation of a BLOCK!. (Today that's sort of what a VARARGS! is.)