Skip to content

client: programs run on the coeval kernel (coeval D13, roadmap item M14) - #134

Open
mark1russell7 wants to merge 2 commits into
mainfrom
feat/kernel-programs
Open

mark1russell7 wants to merge 2 commits into
mainfrom
feat/kernel-programs

Conversation

@mark1russell7

@mark1russell7 mark1russell7 commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Programs of the client now run on the kernel of coeval (@mark1russell7/coeval 0.3.1, decision D13). The program format and its rules do not change: the existing suites define the semantics, and they stay green.

What changed

One machine, one run. packages/client/src/procedures/program.ts holds what is the program format's own:

  • The world of a program. It is open (coeval WORLD.OPEN): each procedure path is a call kind (effects: "type", deep parts). The control-flow kinds declare the names that they bind (scope rules), so check() knows them: client.chain binds the step names and last (sequential), client.map binds item/as and index, client.reduce binds acc, item and index, and client.tryCatch binds error. Internal kinds: lit, ref, program.data, program.record, program.deferred, program.fail.
  • The desugarer. desugarProgram()/desugarInput() change the $proc form into a run document. The rules of hydration decide its shape: implicit chains, $when, $literal, the depth limit, the data depth and cycles (a lazy program.fail, so an error comes where the run reaches it, with the same message). The envelope form goes through fromEnvelopeProgram() first, so the two forms share one path.
  • The boundary. Each call is an effect. perform is the client's own invocation path (execInternal / ctx.client.call), so invokeProcedure() stays THE authority for admission, validation, interceptors, effects/clearance/deadline capabilities and exposure rules. Kernel capabilities permit every call (Machine.permits): direct HTTP/MCP calls skip the kernel and get the same admission. A failed call becomes #CALC! with the error as cause; the run rethrows the cause, so InvocationError codes and the normative messages of PROGRAM-FORMAT.md come out unchanged.

Callers on the kernel. client.exec() / execStream() (one run whose root document is the root call; a stream root returns its iterable through the run), hydrateInput(), prepareRootCall(), executeRef(), the body of a defined procedure (runProgram() with the scope input) and client.eval (runOperand()). The interpreter of ref.ts (hydrateValue, hydrateRef, the scope plumbing) is gone; ref.ts keeps the format (types, builders, JSON helpers, the scope carrier of a raw input, the raw-input rules). Exported names that other packages use (hydrateInput, prepareRootCall, executeRef, observeHydration, withScope, scopeOf, RefScope) keep their signatures.

Control flow. client.chain, parallel, conditional, and, or, map, reduce and tryCatch keep their registrations and schemas (MCP tools). Each handler gives its raw input to the kernel as a document of its kind (runControl(), an own entry names the runner). client.parallel uses all() of the kernel with concurrency and failFast.

Envelope helpers. envelope.ts reads envelopes with coeval's isTypeRef, typePath, ref and ENVELOPE_KEYS instead of copied checks. The literal kind is lit (coeval D12; PR #133's literal was unreleased).

Compiler (phase B). The name rules (unknown-name, use-before-definition, the info for a deferred input) run through check() of the coeval static kit on the expanded run document (nameDiagnostics()), with Issue → Diagnostic mapping by the origin pointer of each ref document (RFC 6901). scopes.ts keeps only the binding resolution that code generation needs, and the warnings for the raw input of runs-refs procedures. resolveScopes() gives the same diagnostics as before.

Kernel additions (coeval PR mark1russell7/coeval#6, released as 0.3.1)

  • WORLD.OPEN: World.fallback, so any procedure path is a call kind.
  • RUN.FAIL-FAST: all(parts, { failFast }).
  • EVAL.FILL: fill(items) on the inputs of evaluators and runners, for deep kinds.

Behavior notes

  • With failFast, the other tasks of client.parallel (and the refs of HydrateOptions.parallel) stop at their next wait; before, they went on in the background. A call that started goes on in its procedure. Recorded in Appendix A of PROGRAM-FORMAT.md.
  • pnpm-workspace.yaml: @mark1russell7/coeval@0.3.1 in minimumReleaseAgeExclude (published today).

Performance

The kernel compiles a run document for each run (prepare) and evaluates it with generators. For programs of trivial procedures, the interpreter is slower: about 120 ms against 45 ms for 500 random programs of the compiler's property tests (local, warm). A real procedure call (validation, interceptors, I/O) dominates in practice. The second commit removes the cheap overheads (pointers only for a check, a machine cache per registry rule, no wrapper objects for primitive items). The heavy property tests of compiler/src/equivalence.test.ts get 30 s each, because CI runners vary and the check now also runs the names pass. A follow-up for the kernel: plan caching or a cheaper compile for documents that a loop runs again (client.map over a control-flow fn).

Versions

One changeset: @procedures/client and @procedures/compiler, patch (fixed group). SemVer: PATCH — before 1.0.0 a feature or a fix raises PATCH; the public API only grows (new exports runProgram, runDocument, runControl, runOperand, programMachine, PROGRAM_WORLD, desugarProgram, desugarInput, desugarControlValue, originOf, documentAt, namesOfScope, scopeOfNames, nameKey, nameOfKey; compiler nameDiagnostics, bindScopes), and the semantics of programs stay. API snapshot updated on purpose.

Tests

  • New: program.test.ts (a program is one prepare/run of coeval; control flow runs with the runner of its kind; effects carry their places; errors keep their code; check() finds unbound names and later-step reads and knows the names of map/reduce/tryCatch/body) and compiler/src/names.test.ts.
  • Locally green: the client suite (2124 tests), the compiler suite (149, including the property-based equivalence of the compiled closure with the interpreter, repeated with fresh seeds), client-dag, client-fs, typecheck-tests for client and compiler, lint:ste (0 errors).
  • PROGRAM-FORMAT.md revision 2 (section 13), READMEs of client and compiler.

🤖 Generated with Claude Code

mark1russell7 and others added 2 commits October 21, 2026 01:11
A program becomes a run document of @mark1russell7/coeval (0.3.1), and one
run of the kernel runs it. The program format and its rules do not change.

- program.ts: the world of a program (an open world: each procedure path is
  a call; the control-flow kinds with the names that they bind), the
  desugarer from the $proc form to a run document (implicit chains, $when,
  $literal and the limits decide its shape), the runners of the kinds and
  the boundary. Each call is an effect that the host does with
  client.call(), so invokeProcedure() stays the authority for admission,
  validation, interceptors and exposure. A failed call throws the error of
  the host (the cause of the #CALC! value), with its message and its code.
- client.exec(), execStream(), hydrateInput(), prepareRootCall(),
  executeRef() and the body of a defined procedure run on the kernel. The
  interpreter of ref.ts (hydrateValue, hydrateRef) is gone.
- The control-flow procedures keep their registrations and schemas. Their
  logic is a runner of the kernel: a sequence that binds the step names and
  last, the parts at the same time with concurrency and failFast, map and
  reduce with the names of the item, tryCatch with error.
- envelope.ts reads envelopes with the helpers of coeval (isTypeRef,
  typePath, ref). The literal kind is lit (coeval D12).
- compiler: the name rules (unknown-name, use-before-definition) run
  through check() of the coeval static kit on the run document
  (nameDiagnostics); resolveScopes() gives the same diagnostics.
- PROGRAM-FORMAT.md revision 2: section 13, programs on the kernel.

The client suite, the compiler suite (with the property tests that compare
the compiled closure with the interpreter) and client-dag stay green. New
tests show that a program runs through prepare/run of coeval and that
check() finds the names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A step of client.chain that is data is a literal document, so the
  sequential names of the kernel count it: $last in the step after it
  reads it (the site analysis found the difference; a compiler test
  covers it).
- The desugarer makes pointers only for a check, skips the objects of
  primitive items, keeps the normalized call in the origin, and sets a
  property with an assignment (an own property only for __proto__).
- rawInputRule() gives one rule for each registry, so the machine of a
  run comes from a cache.
- compiler: the property tests of equivalence.test.ts get 30 s each. They
  run hundreds of programs three ways, and the names pass and the kernel
  run take more time on the runners of CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant