Documentation
¶
Overview ¶
Package jsrun is THE JavaScript runtime for subharnesses — one generic Go runner parameterized by a bundle.
There is not one runner per subharness and there must never be: there is one goja host, and a bundle is its argument (New). That is PRD §5's shape taken literally, and it is what makes "the person cannot tell which is which" a structural fact — a JS subharness and a Go one arrive at exec.Registry as the same exec.Runner and are described by the same exec.Manifest.
THE SANDBOX IS THE HOST API. goja has no filesystem, no network and no clock unless the host hands them in, so the six doors bound in host.go are the whole capability surface of a bundle, and they are exactly exec.Env's six. There is no seventh way for a program to spend, reach out, or ask — which is what makes summing the journal the same thing as summing the run.
THIS PACKAGE NEVER RUNS THE FALLBACK ITSELF. A guard that does not pass, or a program that breaks partway, comes back as exec.RunResult with FellBack set and NO error: the work still has to get done, and the caller that dispatched this runner is the one holding exec.Registry.Generalist and the original input. Deciding to run `linear` here would put the decision in the one place that cannot see whether the caller wanted a fallback at all — a headless invocation and a task node want different things — so the runner reports and the caller chooses.
Index ¶
Constants ¶
const DefaultSteps = 500
DefaultSteps is how many host calls a run gets when nobody said. It is generous on purpose: the ceiling is a backstop against a loop that calls out forever, not a design constraint an author should be shaping a program around.
Variables ¶
This section is empty.
Functions ¶
func WithJournal ¶
WithJournal puts this run's journal on the context.
THE CONTEXT CARRIES IT BECAUSE THE JOURNAL IS A FACT ABOUT ONE RUN. exec.Runner.Run takes exactly one other per-run thing — the input — and the runner itself is per-BUNDLE and outlives any run of it, so a journal field on Bundle would be a per-run value living on a shared object. The door lane wraps the context it already builds for cancellation.
Types ¶
type Bundle ¶
type Bundle struct {
// Manifest is everything true before the run: identity, cost shape, the
// whitelist tool() filters through, the guards checked on the way in, and
// the output shape a finished run has to produce.
Manifest exec.Manifest
// Program is the source of program.js, whole. It is compiled once by [New],
// so a bundle that will not compile is refused at load rather than at run —
// which is what makes a syntax error an AUTHORING error with a line number
// rather than a run that mysteriously fell back.
Program string
// Source is the name errors are reported against. Empty is "program.js",
// which is what the file is actually called; a store that keeps several
// files in one bundle names the one it handed over.
Source string
// Prompts is the prompt assets by name, the map ai()'s first argument
// selects from. The key is the bare name — `summarise` for
// `prompts/summarise.md` — and [Runner] normalizes a `prompts/` prefix and a
// `.md` suffix on the way in, so a program that names the file the way an
// author thinks of it still resolves.
//
// A NAME THAT IS NOT IN HERE IS REFUSED, which is the whole enforcement of
// the no-inline-prompts law (PRD §5, §12): prompts are files so they can be
// diffed, reviewed and revised, and a program that could pass its prompt as a
// string would put the one thing worth improving somewhere nothing can
// improve it.
Prompts map[string]string
// Memory is this subharness's OWN memory — its memory.md in its own bundle,
// not the session's. Nil is a bundle whose memory the store has not opened,
// and then remember() and recall() fall through to the [exec.Env]'s own
// doors, which is the honest fallback rather than a silent no-op.
Memory Memory
// Look is the free way a guard checks the world before a run: does this file
// exist, is this tool on the belt. It is a door rather than a filesystem
// because this package has none — goja has no filesystem unless the host
// hands one in, and a runtime that quietly grew one for its own guards would
// have handed itself the thing it refuses the program.
//
// NIL MEANS THE WORLD CANNOT BE LOOKED AT, and a file or tool guard that
// cannot be checked DOES NOT PASS. The safe answer to "I could not tell" is
// the long way, not the fast path.
Look Look
// Fuel is what this run may spend before it stops. The zero value takes the
// manifest's own cost shape and this package's defaults; see [Fuel].
Fuel Fuel
}
THE BUNDLE, as this runtime needs it — and no more than that.
PRD §6 describes a directory on disk: manifest.json, program.js, prompts/*.md, memory.md, evals/. That directory is the STORE LANE'S business, and this struct is deliberately not a picture of it. It is the four things a run actually reads plus the two doors that let a run look at the world without this package owning a filesystem: what to run, what it is allowed to do, the prompt assets its ai() calls may name, and where its own memory lives.
KEEPING IT SMALL IS THE POINT. The store lane constructs one of these; a field here is a thing the store has to be able to fill, so every field that is not load-bearing for a run is a coupling nobody asked for. `evals/` is absent because nothing executes evals yet, and a slot for it here would be half-built machinery pretending to work.
type Fuel ¶
type Fuel struct {
// Steps is how many host calls the program may make. Zero takes
// [DefaultSteps].
Steps int
// Tokens is the ceiling on what the run's own ledger reports — input plus
// output, summed across every ai() and every tool() as the journal records
// them. Zero is NO token ceiling, which is the honest zero value: a caller
// that has not been given a grant must not have one invented for it.
Tokens int
// Wall is the deadline. Zero takes the manifest's own cost shape —
// [exec.SubharnessInfo.Deadline] of [Fuel.Tokens] — which is the one
// spelling of a budget the whole wave uses and the reason there is no second
// deadline constant in this package.
Wall time.Duration
}
Fuel is what one run may spend before it stops. All three are ceilings, all three end the same way — exec.RunResult.Incomplete with a sentence saying what ran out — and none of them is ever a hang.
THE OPERATION BUDGET IS COUNTED IN HOST CALLS AND NOT IN VM INSTRUCTIONS, and that is a deviation from PRD §5's wording made deliberately rather than quietly. goja exposes interruption (any goroutine may call goja.Runtime.Interrupt) but no per-instruction hook, so nothing outside the interpreter can count instructions without rewriting the program's source. A host call is the operation this runtime can count exactly and the only one it can count for free — and the runaway loop that an instruction budget was wanted for is caught by Fuel.Wall, which interrupts the VM mid-loop no matter what the loop is doing.
type Look ¶
type Look interface {
// FileExists says whether the path a [exec.GuardFile] names is there.
FileExists(path string) bool
// ToolOnBelt says whether the tool a [exec.GuardTool] names is actually
// available to this session. It is a different question from the whitelist:
// the whitelist is a ceiling the manifest declares, and the belt is what the
// session really has.
ToolOnBelt(name string) bool
}
Look answers the two cheap questions a guard asks about the world. Both are free — no model call, no tool call, no spend — which is what makes a guard a guard rather than the first step of the run.
type Memory ¶
type Memory interface {
Remember(ctx context.Context, note string) error
Recall(ctx context.Context, query string) ([]exec.Note, error)
}
Memory is a subharness's own accumulated domain notes — the memory.md in its bundle, opened by the store lane. The two methods are exactly the two doors exec.Env spells, so a store that already implements the Env halves implements this by having them.
type Prompted ¶
Prompted is an exec.Env that wants this bundle's prompt assets.
IT EXISTS BECAUSE THE CONTRACT PASSES A REF AND THE MODEL NEEDS TEXT. exec.Env.AI takes a promptRef by design — the ref is what gets journaled, what a later revision is argued about, and what a per-call-site cache will one day be keyed on — but the Env that makes the actual model call has no bundle to resolve the ref against. So a run hands its prompts to an Env that asks for them, once, before the first call. An Env that does not implement this is handed nothing and resolves refs its own way; nothing about the contract changes either way.
type Runner ¶
type Runner struct {
// contains filtered or unexported fields
}
Runner is one bundle, compiled and ready to run. It implements exec.Runner, which is the only interface anything outside this package needs from it.
IT IS COMPILED ONCE, AT CONSTRUCTION. A bundle whose program.js will not parse is refused by New with the compiler's own message and its own line number, so a syntax error is an authoring error the store lane can hand straight back to whoever wrote it — never a run that mysteriously took the long way.
func New ¶
New compiles a bundle into a runner.
THE COMPILE ERROR IS THE WHOLE AUTHORING ERROR STORY (PRD §5). It is returned VERBATIM, with the line and column goja put in it, and nothing here trims, rewords or salvages it. There is deliberately no repair ladder: the old system's four-rung JSON salvage (internal/subharness/salvage.go) has no equivalent here and must not be rebuilt, because accepting almost-JavaScript is how a program comes to mean something its author did not write.
func (*Runner) Prompts ¶
Prompts is this bundle's prompt assets, for a caller that has to resolve a ref the journal recorded. See Prompted for why the resolution lives outside this package.
func (*Runner) Run ¶
func (r *Runner) Run(ctx context.Context, input json.RawMessage, env exec.Env) (exec.RunResult, error)
Run does the work: guards, then the program, then the promised shape.
THE THREE ENDINGS ARE THE CONTRACT'S THREE AND NO OTHERS. It finished (Output set), it did not finish (Incomplete, with a sentence saying what ran out), or it needed a closer look and the caller should do the work the long way (FellBack). An error means the run could not be MADE to happen, and after New has compiled the program the only thing left in that class is input that is not JSON.