Vidyax Concurrency — Design Document (v1 draft)¶
Status: all phases shipped (A-E) — go/wait and agent run on
all FOUR engines (walker, fast, VVM, native).
1. Goals and hard constraints¶
- Four-engine parity. Everything observable must behave the same on the tree-walker, the transpiler, the VVM, and the native backend — the differential suite and the fuzzer are the project's backbone and concurrency must not break them.
- Beginner-first. One new concept, not five. No locks, no channels,
no
async-colored functions in v1. - AI-native payoff. The killer use case is overlapping slow I/O:
several
ai.ask/get()calls at once. Pure-compute parallelism is NOT a goal (it would demand a thread-safe GC and buy a beginner language little).
2. Models considered¶
| Model | Verdict | Why |
|---|---|---|
| Free-running OS threads | rejected | data races reach the user; GC and both Python engines can't match C semantics — parity impossible |
| async/await event loop | rejected | colors every function; two dialects of the language; hardest to teach |
| actor/message passing | rejected (v1) | strongest model, but needs channels + serialization — too much surface for v1 |
| tasks + one interpreter lock, I/O releases it | chosen | exactly Python's GIL model, so the Python engines get it for free; C engines replicate it with one mutex |
3. The chosen design: go / wait¶
func tanya(topik):
return ai.ask "jelaskan " + topik
t1: go tanya("gravitasi") # starts a task, returns immediately
t2: go tanya("fotosintesis") # runs concurrently with t1
print wait(t1) # result of t1 (blocks until done)
print wait(t2)
go f(args)— prefix keyword, valid only in front of a call. Arguments are evaluated eagerly in the caller (same order as a normal call), then the call itself runs as a task.goyields a value of the new type"task".wait(t)— a builtin, not a keyword (no new grammar). Returns the task's result; if the task raised, the same error re-raises at thewaitsite and is catchable with try/catch there.- Execution model: one global interpreter lock. A task only runs
while holding it; the lock is released only inside blocking
builtins —
get,ai.ask,readfile,writefile,sleep,ask. Consequences, in beginner terms: - pure computation never interleaves → no data races, ever;
- overlap happens exactly where it pays: waiting on the network/disk;
- a program with no
gobehaves byte-for-byte as today. - Program exit: the main task implicitly
waits every still-running task; an error in a never-waited task is reported like an uncaught top-level error (nothing is silently lost). type(t)is"task"; printing a task shows<task f>. Tasks are not lists/text — passing one to other builtins is a type error.
4. Per-engine implementation sketch¶
| Engine | Mechanism |
|---|---|
| walker + fast | threading.Thread — CPython's GIL is the model; wait = join + re-raise |
| VVM | the big refactor: today the VM state (stack/frames/handlers) is global; it must move into a VmCtx struct so each task owns one. One global pthread_mutex; blocking builtins wrap their syscall in unlock/lock. GC: stop-the-world while holding the lock; roots = every ctx's stack+frames |
| native | same VmCtx + mutex; go at a call site spawns a pthread running the callee's np_K with a fresh ctx |
Honest cost estimate: the Python engines are days; the VmCtx
refactor is the single biggest change the VM has seen — it touches
gc.c, vm.c, debug.c, profile.c and the generated native code, and it is
the reason this document exists before any code.
5. Testing strategy¶
- Deterministic subset for the suite: tasks that don't overlap I/O (compute-only, or a single task) must produce identical output on all engines — these go into tests.py as usual.
- Ordering-free assertions for real overlap: run N tasks appending to a list, assert on the SORTED result.
- The fuzzer keeps excluding
go(nondeterministic interleaving can't be diffed byte-for-byte); a separatefuzz_tasks.pycan fuzz the deterministic subset.
6. Phases¶
- A (done):
sleep(secs)+now()builtins on every engine. - B (done):
go/waiton walker + fast, differential tests for the deterministic subset. - C (done): the
VmCtxrefactor — execution state moved into a per-task context (macro-aliased through the thread-localvx_ctx, so the dispatch loop reads unchanged); onevx_gilmutex, released only inside blocking builtins; GC roots iterate every live context; byte accounting is atomic;OP_GO+vm/task.c. Verified with the full suite under --gc-stress, ASan, AND ThreadSanitizer. - D (done): native backend — task.c became engine-agnostic via a
vx_task_runnerhook (VM: bytecode loop; native: the compiled NFN[] call);ntrywent thread-local. Verified under ThreadSanitizer. - E (done): the
agentkeyword —agent name:declares a stateful AI persona (model/systemfixed at declaration, conversation history per agent). Lives in the shared runtime (_Agent) for the Python engines and asOAgent+OP_AGENTin the C engines; callable like a function and compatible withgo/wait. Rule of thumb documented: one agent = one conversation; parallelize with several agents.
7. Decisions (signed off by the language owner, 6 Jul 2026)¶
wait(t)is a builtin — zero new grammar.goaccepts builtin calls too (go get(url)) — that IS the main use case.- No timeout in v1 —
wait(t, max_secs)and task cancellation are v2 questions.
Status: design APPROVED; all phases (A-E) shipped.