Skip to content

Add a fixed pure-compute stdlib to the execution universe (sum, json, math) #42

Description

@jmgilman

Problem

Models write Python. The worker's Starlark universe is missing a small set of pure functions and modules that LLMs reach for habitually, and each miss costs an execute round-trip that fails with invalid program.

Empirical case from the live UX review at a5031b5: the first plausible composite program written against the server used sum(...). Python has it; the Starlark universe does not (abs, min, max, sorted, any, all, zip, enumerate exist — sum does not). The program failed with the bare coarse error, and nothing model-facing lists which builtins exist. #24 makes such failures diagnosable; this issue makes the most common ones not happen.

Proposal

Extend the worker interpreter's universe with a small, fixed, pure compute surface — always on, not opt-in, with no configuration knob.

Addition Source Notes
sum (and, if trivially agreed, round) small hand-rolled builtins The observed failure class; keep the list minimal and grow only on evidence
json.encode / json.decode / json.indent go.starlark.net/lib/json Lets programs reshape capability results; pure
math.* go.starlark.net/lib/math Pure, deterministic constants and functions

Why always-on rather than an opt-in package

  • One language, not dialects. The value of the fixed three-tool surface is that a model's knowledge transfers between every CodeMode server. If sum exists on server A but not server B, "invalid program" gains a new deployment-dependent meaning and the recovery loop gets worse. The model-facing contract should have exactly one version.
  • There is nothing to opt out of. Every candidate is pure compute: no I/O, no capability dispatch, no authorization surface, no nondeterminism. Execution happens inside the worker process under the existing step and elapsed-time budgets. A knob controlling this has no beneficiary and adds exactly the kind of first-touch decision Reduce first-touch API ceremony: stock resolvers, default limits, deferred registration errors, ID default #25 removed.
  • Compatibility commitment is the real cost. Baking builtins in means carrying them forever. Keeping the initial set tiny is what makes that commitment cheap; additions are easy, removals are breaking.

Dependency grounding (no new supply-chain surface)

lib/json and lib/math live inside the exact go.starlark.net module revision the repository already pins — same authors, repo, license, and revision as the interpreter and the already-imported starlarkstruct. The Starlark spec defines json and math as its standard optional modules; these are the reference implementations. Adding them requires zero new go.mod entries and cannot skew versions against the interpreter.

Exclusions (deliberate)

  • lib/time — wall-clock nondeterminism. The Rego adapter already strips nondeterministic builtins; the execution surface holds the same line.
  • re / regex — no upstream Starlark module, real implementation scope, no observed demand. Evidence-gated.
  • filter / map — upstream omits them deliberately; comprehensions cover the need, and the language-surface note can say so.
  • Host-extensible preludes or custom builtins — a different feature with real trust questions (module loading is disabled by design). Out of scope here.

Coordination with #24 (important)

An agent is currently implementing #24 against the same model-facing contract text. The two changes must converge on one documented language surface:

  • The execute tool description and mcp-tools.md must enumerate what exists: Starlark plus sum, json, math; no import, no while, no f-strings; filter/map absent, use comprehensions.
  • Add one cheap permanent test asserting the documented name list matches the worker's actual universe/predeclared set, so contract and behavior cannot drift.
  • A stdlib the model does not know about only shrinks the failure rate; a documented one prevents the failures.

Invariants that must not change

  • Additions are pure and deterministic; no I/O, clock, or randomness enters the universe.
  • All added compute runs in the worker under existing MaxExecutionSteps / MaxExecutionTime budgets.
  • Capability namespaces remain the only native dispatch path; stdlib names must not collide with or shadow registered capability namespace roots (json, math, and universe names must be rejected as top-level namespace segments at registration if they are not already).
  • The final-value and crossing-value conversion surfaces are unchanged.

Acceptance sketch

  • def main(): return sum([1, 2, 3]) executes successfully on any server with no host configuration.
  • json.decode(json.encode({"a": 1}))["a"] == 1 inside a program.
  • math.sqrt(4.0) returns 2.0; lib/time names remain undefined.
  • Registering a capability under a namespace root that collides with a universe or stdlib name (json.fetch, math.add, sum.x) fails at registration with a clear host-side error.
  • The documented language surface in mcp-tools.md is asserted against the actual universe by a test.
  • Docs: mcp-tools.md language-surface section and the execute listed description updated in lockstep with Echo model-derived diagnostics through the coarse error taxonomy #24's contract wording.

Origin

Follow-up to the live UX review at a5031b5 (see #24 comment for the sum reproduction). Complements #24: diagnostics make failures legible; this makes the most common failure class not occur.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions