Skip to content

When to Promote from :script

:script is the bazaar. :core is the monastery. The whole point of the Script Law is that the door between them is one mechanical command — janus desugar — and promotion loses nothing. The question is never can you promote. The question is when you should.

This page names the six triggers. When any of them becomes true, your .jans file has outgrown :script and the honest move is to promote.


Per SPEC-045 §1 (Non-Goals) and §7.4 (Publication Restriction), promote to :core when any of these becomes true:

1. The program wants to live longer than a session

Section titled “1. The program wants to live longer than a session”

:script artifacts cannot be published to a Hinge registry, installed as dependencies, or referenced from other Janus projects (E3101_PROFILE_NOT_PUBLISHABLE). The moment you reach for git to version this file, or you imagine another project importing it, you should have written :core. Scripts are throwaway artifacts by design — that is what keeps them honest.

2. It needs a capability outside the template’s default set

Section titled “2. It needs a capability outside the template’s default set”

OS threads, raw sockets, network listening, ptrace, or any capability beyond the §4 default set requires an escalate declaration. A one-off escalate :service do ... end for a single bounded operation is fine — that is what escalation is for. But if you need the capability permanently, you are in :service or :cluster, not :script. Escalation is for borrowing a rung; it is not for living there.

3. It needs tensors, actors, grains, or compute offload

Section titled “3. It needs tensors, actors, grains, or compute offload”

Those belong to the :compute, :cluster, and future game/scientific templates (SPEC-045 §0.1.3). They are deferred — not in the current-quarter product. Using :script for them is forcing a future product into the current profile. Write the :core (or :compute) module directly.

:script is single-file. Top-level statements desugar into one synthesized main; there is no multi-module :script story, and there will not be one. The moment you want to use a sibling module you wrote, you have crossed into :core territory. Multi-module programs are what :core is for.

Hot reload is deferred (SPEC-045 §6.4), not denied — it lights up when JIT-mode :script ships post-SovereignMC. But v1.0 :script is AOT-cached only. Production code does not run in :script. If your program needs to stay alive and swap code at runtime, it is a :service or :cluster program today, and it will be a JIT-mode :script program tomorrow. Today is today.

Same root as trigger 1, restated for clarity: libraries are :core. A library that cannot be published, cannot be a dependency, and cannot outlive a session is not a library — it is a script that forgot to promote.


Roberto Ierusalimschy describes Lua’s role in games with a sharp image: the C loop keeps the frame rate and the rhythm; Lua updates the characters and the images each frame. The hard, durable, resource-intensive substrate lives in the host language. The dynamic, frequently-changing, non-critical part lives in the script. That is the dual-language architecture that earns the word “scripting.”

Translated to Janus: {.script: sysadmin.} is for the part that changes often and gets thrown away or promoted. The durable substrate — the C side of Roberto’s games, the :core / :service side of ours — is where the hard parts live. The $-family, shell literals, stream pipelines (SPEC-045 §3.5) exist so the throwaway part stays readable. The moment it stops being throwaway, it stops being :script.

This is why the triggers above are all growth signals, not failures. A script that triggers one of them did its job: it let you explore fast, and now it is telling you it is ready to grow up.


You do not have to guess. The doctrinal test for “is this script ready to promote?” is already mechanical and falsifiable:

Terminal window
janus validate --promotable path/to/file.jans
  • Exit 0<file> is promotable to :core. The Script Law holds. Promotion is guaranteed to round-trip losslessly.
  • Exit 1Script Law violated for <file>: <error>. The diagnostic names which rule fired. Fix it, then promote.

Run this in CI before you ship any .jans file you intend to promote later. It is faster than a full compile (no lowering, no codegen, no linking) and it catches every Script-Law violation that sema can see. When it passes, the actual promotion is one command:

Terminal window
janus desugar path/to/file.jans -o path/to/file.jan # canonical :core output

That is the “when to promote” answer, automated. The triggers on this page tell you when to ask the question; validate --promotable answers it.


The triggers cut one way. The honest counter-list — when to stay in :script — is shorter:

  • The script is a one-shot experiment. You will read the answer once and delete the file.
  • You are still iterating on the algorithm and rewrites are cheap. The auto-imports and synthesized main are saving you time on every edit.
  • The whole point is “I want to run this, see the output, and move on.” Promoting adds ceremony that buys you nothing.

:script is the bazaar. :core is the monastery. Both speak the same language; the bazaar just makes the boring parts invisible. Promote when the boring parts start mattering. Stay when they don’t.