The module is github.com/craggy-io/script-services. It knows nothing about
registries or capabilities: a host describes its own built-ins as Starlark values
(for execution) and check.Callable entries (for static checking). The capability
registry and the computational-research platform both build on it; neither is
imported here.
Features
- One dialect everywhere: Exec, Check, load() and the test runner all parse through one dialect (Starlark plus
set(),while, top-level control flow and global reassignment; no recursion). - Bounded, classified execution: run a program and its entry function under step and time limits, with JSON input and output,
*script.Errorcodes, positions, backtraces and host errors reachable througherrors.As. - Content-addressed modules: resolve
load()specs (./,../,//, bare, and@alias/for a dependency bundle), serve them from a bundle contribution, execute each module once, coalesce concurrent loads, detect cycles, and digest source plus eachload()spec paired with its module's digest rather than paths, statically or by running. - One compiler for bundle scripts:
starcompileturns a.starcontribution into a checked Unit with file:line diagnostics; domains plug in their built-ins and build their own values, and the genericcraggy.script@1kind needs no domain at all. - Types from Go syntax: turn
types.godeclarations into JSON Schema with the shapes and required rulesjsonschema.Forderives (with a few documented deviations), and let scripts declare schemas with aschemamodule. - Static checking: validate dotted callable paths, literal refs, keyword arguments and load() targets against a host-supplied Env, with nearest-name suggestions.
- Starlark tests and a CLI: run the
*_test.starfiles a bundle declares (craggy.script-test@1) with anassertmodule from Go tests or thecraggy-scriptcommand, which also checks and runs a bundle's scripts and prints its type schemas (craggy.types@1). - One value model: convert Starlark, Go and JSON values with a single fixed set of rules.
Package architecture
Read it top to bottom: each layer imports only the layers below it. The full direct import list per package:
| Package | Responsibilities | Imports (in this module) | Guide |
|---|---|---|---|
dialect |
The one Starlark dialect: Parse, Resolve, Compile. | none | Dialect |
values |
Starlark ↔ Go ↔ JSON conversion; built-in kwargs to JSON. | none | Values |
types |
Go-syntax type declarations to JSON Schema; the craggy.types@1 kind. |
none | Types |
schema |
The Starlark schema module; schema values to *jsonschema.Schema. |
types, values | Schema |
modules |
load() resolution, sources (including bundle contributions), execution, caching, cycles, native modules and digests. |
dialect | Modules |
script |
Exec, limits, classified errors, Run/Call helpers. | dialect, modules, values | Script |
check |
Static checking against an Env of callables. | dialect, modules, script | Check |
starcompile |
The shared compiler from a .star contribution to a checked Unit; the craggy.script@1 kind. |
check, dialect, modules, script | Starcompile |
startest |
*_test.star runner, assert module and the craggy.script-test@1 kind. |
check, dialect, modules, script, starcompile | Startest |
cmd/craggy-script |
check, run, test and types commands over a bundle. |
script, starcompile, startest, types | CLI |
External dependencies are go.starlark.net, github.com/google/jsonschema-go
and github.com/craggy-io/foundation-kit (bundle and kind, imported by
modules, types, starcompile, startest and the CLI). startest imports the
standard testing package. The architecture diagram predates starcompile,
which sits between check and startest.
Quick start
Run a program whose load() statements are served from memory. Exec runs the
module body, then run(input); the result is JSON, with the modules used and a
digest over the source and its direct load()s, each spec paired with the
loaded module's digest (the same digest the file would have as a module). This is
ExampleExec, compiled and checked by go test:
loader := modules.NewLoader(modules.Map(map[string]string{
"lib/text.star": "load('./util.star', 'twice')\ndef shout(s):\n return twice(s.upper())\n",
"lib/util.star": "def twice(s):\n return s + s\n",
}), modules.Options{MaxSteps: script.DefaultLimits.MaxSteps})
src := []byte(`
load("./lib/text.star", "shout")
print("starting")
def run(input):
return {"greeting": shout(input["who"])}
`)
res, err := script.Exec(ctx, script.Program{
Filename: "main.star",
Source: src,
Input: map[string]any{"who": "ab"},
Modules: loader,
}, script.Options{
Limits: script.DefaultLimits,
Print: func(pos script.Position, msg string) {
fmt.Printf("%s:%d: %s\n", pos.File, pos.Line, msg)
},
})
// main.star:4: starting
// res.Value == {"greeting":"ABAB"}
// res.Modules == [lib/text.star lib/util.star] (with digests)
// res.Digest == modules.Digest(src, []modules.Dep{{Spec: "./lib/text.star", Digest: <lib/text.star's digest>}})Failures are *script.Error values (Invalid or Cancelled) with a position in
the script, a backtrace, and Unwrap to the original cause; see
script errors.
Load scripts from bundles
Authored scripts live in bundles: a folder with a bundle.yaml that names each
contribution's kind and path. A folder without a bundle.yaml is not loaded,
and nothing generates one. A domain declares its kind and builds its compiler
from starcompile.Compiler, passing its own built-ins; bundle.Load does the
rest. From ExampleCompiler:
# bundle.yaml
apiVersion: craggy.io/bundle/v1
metadata: {id: example.app, version: 1.0.0}
dependencies:
- id: example.names # load('@example.names/names.star', ...)
contributions:
- {id: reviewers, kind: example.team@1, path: teams/reviewers.star}var TeamKind = kind.New[Team]("example.team", 1)
opts := starcompile.Options{Check: check.Env{Predeclared: []string{"team"}}}
compiler := bundle.Compile(TeamKind, starcompile.Compiler(opts,
func(ctx context.Context, u *starcompile.Unit) (bundle.Compiled[Team], []bundle.Diagnostic) {
var t Team
team := starlark.NewBuiltin("team", collectInto(&t)) // a fresh collector per file
if _, err := u.Exec(ctx, starcompile.ExecOptions{Predeclared: starlark.StringDict{"team": team}}); err != nil {
return bundle.Compiled[Team]{}, u.Diagnostics(err) // file:line diagnostics
}
return bundle.Compiled[Team]{Value: t}, nil
}))
cat, err := bundle.Load(ctx, bundle.Options{Sources: sources, Compilers: []bundle.AnyCompiler{compiler}})Check before running
Describe what the host will predeclare and check the script without executing it.
From ExampleAnalyze:
get := check.Callable{Name: "storage.artifact.get", Input: getIn, Value: "host payload"}
env := check.Env{
Callables: []check.Callable{get},
Predeclared: []string{"log"},
RefFuncs: map[string]func(string) (check.Callable, bool){"call": resolveRef},
Modules: modules.Map(map[string]string{"lib/util.star": "def twice(s):\n return s + s\n"}),
Noun: "capability", // messages say "callable" by default
}
a := check.Analyze(env, "main.star", src)
// 3:5 error: unknown capability storage.artifact.gte (did you mean storage.artifact.get?)
// 4:5 error: storage.artifact.get has no input "idd" (did you mean id?)
// a.Uses == [storage.artifact.get] a.Loads == [lib/util.star]Types to schema
Declare inputs and outputs in Go syntax and derive JSON Schema without compiling
the file. From ExamplePackage_Schema:
// types.go — parsed, never compiled
package api
// DecompileIn is what apk.decompile takes.
type DecompileIn struct {
Artifact string `json:"artifact" jsonschema:"artifact id"`
Depth *int `json:"depth,omitempty"` // how deep to go
Level Level `json:"level"`
}
// Level is a decompiler effort level.
// enum: fast, full
type Level stringpkg, err := types.Parse("types.go", src)
s, err := pkg.Schema("DecompileIn")
// closed object (additionalProperties false); required [artifact level]
// (depth is omitempty; a pointer without omitempty would be required);
// depth is {"type":["null","integer"]}; level carries "enum":["fast","full"]Pass schema.WithTypes(pkg) to schema.Module and schema.Of so scripts can
write schema.type("DecompileIn"); see schema.
The CLI
Every command takes a bundle directory, optionally with #ID to pick one
contribution. Single files and loose folders are not accepted.
go run ./cmd/craggy-script run -input '{"who":"ab"}' ./myscripts#main
go run ./cmd/craggy-script check ./myscripts
go run ./cmd/craggy-script test -dep ./shared-lib ./myscripts
go run ./cmd/craggy-script types ./myscripts#api DecompileInSee the CLI guide for the kinds it reads, output formats and exit codes.
Start with the documentation index. For coding-agent integration and repository development guidance, see AGENTS.md.
Development
Requires Go 1.27 or newer. Dependencies: go.starlark.net,
github.com/google/jsonschema-go and github.com/craggy-io/foundation-kit.
go test ./...
go test -race ./...
go vet ./...
go build ./...Package examples (example_test.go) carry // Output: checks, so go test
verifies the code shown in the guides.