I've already explored the idea of defining a new kind of scope like you suggested. The problem is that at evaluation time (when the byte codes are being interpreted), names can be accessed only if they appear in the locals or globals. There's no mechanism for accessing a name in "my parent scope's locals". To do this, you'd need to capture it in a closure and add it to the inner scope's "locals". That's not possible for class scopes because they don't support cell variables.
We could add new byte codes that loads from the parent scope, but that would be a pretty invasive change — one that probably has implications that I don't fully understand. It also wouldn't fully solve the problem because any approach that involves a new implicit scope breaks `eval` when applied to annotations for forward declarations, which means `typing.get_type_hints` would no longer work for a class of annotations. So I'm pretty convinced at this point that using a new scope won't work.
I also explored the idea of creating a `__type_params__` dictionary within the global scope. The global scope is accessible from all scopes via a LOAD_GLOBAL bytecode op. The compiler could assign a globally-unique key for each type parameter defined within the module. TypeVars could then be constructed and cached in the `__type_params__` dictionary using the associated key name. This approach works as long as the compiler is able to analyze all of the code within the module and assign unique key names to each type param. It unfortunately breaks for interactive sessions because the compiler is invoked once for each REPL input, and there's no state retained between compiler invocations. That means the compiler can't guarantee unique key names. The same is true for `eval` called on forward declared annotation expressions. So I've abandoned that approach.
I have a few more ideas that I still need to explore.