Nebula
Nebula is a typed orchestration language for AI agents, MCP tools, and operational runbooks. Author deterministic workflows in Nebula and deploy them anywhere Python runs.
Nebula trades symbolic brevity for machine legibility: operators are keywords (plus, eq, lt), types are always explicit, and the toolchain speaks JSON — structured diagnostics, AST export, IR export, and run records with stable NEB-* error codes.
Author in Nebula, ship as Python. Agents iterate in Nebula against check --json / run --json (driven by the scripts/nebula_agent.py loop harness — see AGENTS.md), then compile --target python produces a self-contained package you run in existing Python infrastructure — Nebula is the typed authoring layer, Python is the bytecode. Full path: docs/author-in-nebula-ship-as-python.md.
This repository is the reference implementation: a Rust compiler and interpreter with a Python backend, probe host, and in-process embedding API.
Status: v0.1 — language and toolchain are experimental but fully tested.
Hello, Nebula
mission main {
print("Hello from Nebula");
}
cargo build --release
./target/release/nebula run examples/hello.neb
# Hello from Nebula
Requirements
Install
The nebula CLI — build from source:
git clone https://github.com/PeytonNowlin/Nebula.git
cd Nebula
cargo build --release
./target/release/nebula --help # or: cargo run -- --help
The Python runtime — nebula compile --target python vendors nebula_runtime/ into each output by default, so deployments are self-contained. To share one runtime across many compiled modules instead, install it from PyPI:
pip install nebula-runtime
The nebula CLI
Build once, then invoke subcommands directly or via cargo run --:
| Command | Purpose |
|---|---|
check <file> |
Parse, resolve imports, typecheck |
run <file> |
Typecheck and execute |
parse <file> --json [--load] |
Export AST (optionally merged workspace) |
ir <file> --json |
Export lowered IR |
fmt <file> [--write] |
Canonical formatter |
compile <file> --target python --out <dir> [--json] |
Transpile to Python (--json emits a compile record: target, out dir, entry module, module count) |
probes list --probes <manifest> [--mcp] [--json] |
Inspect probe bindings and MCP tools |
Agent-oriented flags
Validation
nebula check examples/fizzbuzz.neb
nebula check examples/fizzbuzz.neb --json # success → `[]` on stdout; failure → JSON on stderr
Execution
nebula run examples/agent_counter.neb
nebula run examples/agent_counter.neb --telemetry trace.jsonl
nebula run examples/agent_counter.neb --probes probes/bundle.json
nebula run examples/runbook.neb --probes probes/runbook.json --json
Resource limits (interpreter defaults: 30s wall clock, 1M while-loop iterations, 64 MiB approximate memory)
nebula run examples/hello.neb --max-runtime-ms 5000
nebula run examples/hello.neb --max-loop-iterations 100000
nebula run examples/hello.neb --max-memory-mb 128
nebula run examples/hello.neb --no-resource-limits
Introspection and transpilation
nebula parse examples/import_demo.neb --json --load
nebula ir examples/hello.neb --json
nebula probes list --probes probes/mcp_stdio.json --mcp --json
nebula compile examples/import_demo.neb --target python --out dist/
python dist/examples/import_demo.py
JSON output
check --json emits a diagnostic array on stderr when validation fails:
[
{
"code": "NEB-T002",
"span": { "file": "example.neb", "start": 42, "end": 58, "line": 3, "column": 15 },
"message": "type mismatch: expected Int, found Str"
}
]
run --json always emits one run record on stdout (success or failure). Schema: schemas/run-record.schema.json.
{
"program": "examples/runbook.neb",
"exit": 0,
"diagnostics": [],
"telemetry_path": "trace.jsonl",
"probe_events": [
{ "ts": 1718380800, "probe": "log", "args": { "level": "info", "message": "ready" } }
],
"duration_ms": 42,
"printed": ["deploy ok"],
"return_value": null,
"probes_called": []
}
Other schemas live under schemas/: diagnostics, compile records, telemetry events, probe JSONL, probe manifests, and Nebula value encoding.
Language overview
Full specification: nebula-spec/SPEC.md. Constrained-generation grammar: grammar/nebula.gbnf.
A program is a set of sector libraries, import statements, and exactly one mission entry point (conventionally main).
sector math {
fn double(n: Int) -> Int {
return n times 2;
}
}
mission main {
let mut i: Int = 1;
while i le 20 do
print(int_to_str(math.double(i)));
set i = i plus 1;
end
}
Design choices
| Topic | Behavior |
|---|---|
| Operators | Keywords, not symbols: plus, minus, times, div, mod, eq, lt, … |
| Control flow | if / while / telemetry use end-delimited blocks; brace blocks for control flow are rejected (NEB-S005) |
| Comparisons | Only lt, gt, le, ge, eq, ne; synonyms like less than are rejected (NEB-S004) |
| Types | All bindings, parameters, and returns are annotated; no implicit coercion |
| Namespacing | Sector symbols are qualified (math.double); same-sector calls may be unqualified inside the sector |
| Numeric semantics | Matching Int/Int or Float/Float operands; checked 64-bit integer arithmetic (NEB-R007 on overflow); div/mod truncate toward zero |
| Collections | List<T>, Map<K,V>, Option<T>; empty [] / {} infer from context or default to List<Int> / Map<Str,Int> |
| Strings | UTF-8; len and string builtins operate on Unicode code points; both backends agree (parity-tested) |
Builtins
Builtins are implemented in the runtime — not loaded from source. The canonical surface is nebula-builtins/builtins.toml (signatures stay in sync across the typechecker, interpreter, and Python shim). Human-readable notes: std/core.neb.
| Category | Functions |
|---|---|
| I/O | print |
| Collections | len, push, at, get, has, insert |
| Conversions | str_to_int, int_to_str, str_to_float, float_to_str, int_to_float, float_to_int |
| Strings | substr, contains, index_of, starts_with, ends_with, to_upper, to_lower, trim, replace, split, join |
| Integers | abs, min, max |
push and insert mutate a variable in place; their first argument must be an identifier.
Imports
import "../std/math.neb";
mission main {
print(int_to_str(math.triple(7)));
}
Import paths are string literals, resolved relative to the importing file. Library modules may define sectors and nested imports but not a mission. Duplicate symbols across modules fail at load time (NEB-L003).
Importable standard module: std/math.neb.
Probes and telemetry
Probes declare host-provided capabilities; call dispatches them through a JSON manifest (--probes):
mission main {
probe log(level: Str, message: Str) -> Void;
telemetry
call log(level: "info", message: "starting");
end
}
| Handler kind | Role |
|---|---|
jsonl |
Structured logging (log probe) |
command |
External process with stdin/stdout JSON protocol |
mcp |
Model Context Protocol tool call (stdio or HTTP) |
read_file, write_file, http_get, json_parse, env_get, secret_get |
Native bundle handlers |
Example manifests: probes/host.json, probes/bundle.json, probes/mcp_stdio.json, probes/runbook.json.
Secrets are declared in the manifest secrets map (resolved from environment variables), substituted into handler config via ${secret:name}, and read at runtime with secret_get — never embed secret values in .neb source.
With --telemetry, statements inside telemetry blocks append JSONL events (schemas/telemetry-event.schema.json).
Error codes
| Prefix | Category |
|---|---|
NEB-S |
Syntax / lex |
NEB-T |
Type checking |
NEB-L |
Module load / imports |
NEB-R |
Runtime |
NEB-P |
Probes / MCP |
Type checking reports multiple errors with spans in one pass.
Embedding (nebula-host)
For agent loops that should not shell out to the CLI:
use nebula_host::{Host, HostConfig, ResourceLimits};
let host = Host::with_config(HostConfig {
probe_manifest: Some("probes/bundle.json".into()),
telemetry_path: Some("trace.jsonl".into()),
resource_limits: ResourceLimits::agent_defaults(),
..HostConfig::default()
});
let check = host.check_source(r#"mission main { let x: Int = 1; }"#);
assert!(check.ok);
let run = host.run_file("examples/hello.neb");
assert!(run.ok);
assert_eq!(run.printed, vec!["Hello from Nebula"]);
// run.record is a RunRecord (same shape as `nebula run --json`)
Pipeline stages are also available directly via Host::try_parse_file, try_compile_file, try_emit_python, and list_probes.
Compiler pipeline
.neb source
→ nebula-syntax (lex + parse)
→ nebula-load (imports, workspace merge)
→ nebula-types (typecheck; builtins from nebula-builtins)
→ nebula-ir (lower)
→ nebula-runtime (interpret) | nebula-python (transpile)
| Crate | Role |
|---|---|
nebula-builtins |
Canonical builtin manifest (builtins.toml) |
nebula-syntax |
Lexer and parser |
nebula-ast |
AST types, NebError, JSON diagnostic types |
nebula-load |
Import graph and symbol merge |
nebula-types |
Type checker |
nebula-ir |
Intermediate representation |
nebula-runtime |
Interpreter, probe host, resource limits |
nebula-mcp |
MCP client transport |
nebula-host |
Unified pipeline and embedding API |
nebula-diagnostics |
miette::Report → JSON diagnostic extraction |
nebula-fmt |
Formatter |
nebula-python |
Python transpiler and runtime shim |
nebula-cli |
nebula binary |
nebula-test-support |
Integration tests and golden files (internal) |
Examples
| File | Demonstrates |
|---|---|
examples/hello.neb |
Minimal program |
examples/fizzbuzz.neb |
Sectors, conditionals, loops |
examples/end_demo.neb |
end-delimited control flow |
examples/push_demo.neb |
Lists, push, len |
examples/import_demo.neb |
Importing std/math.neb |
examples/agent_counter.neb |
Probes, telemetry, mutable state |
examples/io_agent.neb |
Bundle probes (read_file, http_get, …) |
examples/runbook.neb |
Retry loop, command + MCP probes |
examples/agent_lib.neb + agent_lib_harness.py |
Compile a Nebula library, import and call it from Python |
Python backend
nebula compile --target python --out <dir> emits:
- One
.pymodule per.nebfile in the import graph - A copied
nebula_runtime/shim (builtins, probes, telemetry, checked arithmetic) - Sector namespaces as Python classes with
@staticmethodfunctions
The Rust interpreter and Python backend are kept in parity by an integration test suite.
Development
cargo test # full workspace: unit, integration, CLI e2e, parity
cargo fmt && cargo clippy
NEBULA_UPDATE_GOLDEN=1 cargo test -p nebula-test-support # refresh golden files
Roadmap
- Package manager / module registry beyond relative imports
- Additional compile targets beyond Python
- Standard library modules beyond
std/math.neb
License
MIT — see LICENSE.
No comments yet
Be the first to share your take.