GENO

Docs/Reference

Embedding API

Geno can be embedded in Python applications as a library. Parse, type-check, and execute Geno programs from Python with full control over capabilities, timeouts, and host-provided builtins.

Quick Start

import geno

result = geno.run("""
func main() -> Int
    return 42
end func
""")

print(result.ok)      # True
print(result.value)   # 42

geno.run() is value-oriented: it returns main()'s value in RunResult and never converts an integer result into SystemExit or otherwise terminates the host process.

Core Functions

geno.run(source, filename="<api>", config=None) -> RunResult

Parse, type-check, and execute a Geno program.

from geno import RunConfig

result = geno.run(source, config=RunConfig(
    timeout=10.0,
    capabilities={"print", "fs"},
))

geno.run_path(path, config=None) -> RunResult

Resolve a Geno file or project from disk, then execute it. Discovers the project graph and dependency modules automatically.

result = geno.run_path("my_project/Main.geno", config=RunConfig(
    capabilities={"print", "http"},
))

geno.check(source, filename="<api>", modules=None) -> CheckResult

Parse and type-check Geno source code without executing it.

result = geno.check(source)
if not result.ok:
    for d in result.diagnostics:
        print(f"{d.code.value}: {d.message}")

geno.check_path(path, modules=None) -> CheckResult

Resolve a Geno file or project from disk, then type-check it.

result = geno.check_path("my_project/Main.geno")

geno.constrain_prefix(prefix) -> ConstraintResult

Compute next-token constraints for a partial Geno source prefix. Used by LLM runtimes to guide code generation.

result = geno.constrain_prefix("func main() -> ")
print(result.valid)          # True
print(result.unclosed_blocks) # ("func",)

Configuration: RunConfig

FieldTypeDefaultDescription
timeoutfloat | None5.0Cooperative execution deadline. None = no limit.
max_stepsint | None1_000_000Maximum interpreter steps. Explicit None disables the cooperative step limit.
max_recursion_depthint500Maximum call stack depth.
max_output_lengthint100_000Maximum characters of print output.
max_collection_sizeint10_000_000Maximum size for strings and collections.
max_integer_bitsint33_219Maximum bit length for integer arithmetic results.
capabilitiesset[str] | NoneNoneCapability grants. Omitted/None = deny all gated builtins.
host_callbacksdict[str, Callable] | NoneNoneHost-provided builtin implementations.
modulesdict[str, str] | NoneNoneIn-memory module sources for import resolution.
check_examplesboolTrueVerify example clauses at runtime.
monitoring_hookCallable | NoneNoneReceives RunMetrics after every run.

The JavaScript backend has the tighter portable Int range -(2^53 - 1) through 2^53 - 1; max_integer_bits governs interpreter and compiled Python execution. See Portable Runtime Semantics.

The timeout, step budget, and deadline checks are cooperative. A long-running builtin cannot be forcibly interrupted while it holds control. Never use this API as the sole isolation boundary for untrusted code.

RunConfig uses the in-process interpreter. OS-backed process limits (max_memory_bytes, max_cpu_time, max_file_size_bytes, and max_processes) are intentionally unavailable in this API because they cannot be enforced inside the caller's process. Use geno.sandbox.ProcessSandboxConfig with run_in_process() or SandboxConfig with run_sandboxed() when you need those process-isolation limits.

Result Types

RunResult

@dataclass
class RunResult:
    ok: bool                      # True if execution succeeded
    value: Any                    # Return value of main() (JSON-serializable)
    value_raw: Any                # Raw Python value before conversion
    output: str                   # Captured print() output
    diagnostics: list[Diagnostic] # Structured error/warning list
    timing: Timing                # Per-phase timing breakdown
    steps_used: int               # Interpreter steps consumed
    entrypoint_kind: EntrypointResultKind | None  # How a host would treat the result

entrypoint_kind classifies the entrypoint's declared result as MISSING, UNIT, INT or OTHER. It describes the program rather than this call, so a caller that is a process boundary can derive a status without resolving the program a second time:

from geno.entrypoint import EntrypointResultKind
from geno.exit_status import exit_status_for_int_result

result = geno.run(source)
if not result.ok:
    status = 1
elif result.entrypoint_kind is EntrypointResultKind.INT:
    status = exit_status_for_int_result(result.value_raw)
else:
    status = 0

Check ok first, as geno run does. A run that fails to parse, type-check or execute returns ok=False with entrypoint_kind and value_raw both None, so a host that reads the classification alone would report success for a program that never produced a result.

value and value_raw stay the raw result whatever the classification says: 258 is 258 here even where a process would exit 2. Acting on the derived status is the host's decision, and geno.run() never takes it.

CheckResult

@dataclass
class CheckResult:
    ok: bool                      # True if type-checking passed
    diagnostics: list[Diagnostic] # Structured error/warning list
    timing: Timing                # Per-phase timing breakdown

ok is true when the program type-checks. diagnostics can still hold warnings with Severity.WARNING, such as W300 for a let or var that redeclares a mutable variable. Warnings are reported only for programs that pass, and check_path() reports them for the project's own modules, not the standard library or installed dependencies.

ConstraintResult

@dataclass
class ConstraintResult:
    allowed_next: AllowedNext     # Valid next tokens
    valid: bool                   # Whether the prefix is valid so far
    error: str | None             # Error message if invalid
    unclosed_blocks: tuple[str, ...] # Open block types

Timing

@dataclass
class Timing:
    total_ms: float    # Total wall-clock time
    lex_ms: float      # Lexing phase
    parse_ms: float    # Parsing phase
    typecheck_ms: float # Type-checking phase
    run_ms: float      # Execution phase

Diagnostic

@dataclass
class Diagnostic:
    code: ErrorCode       # Machine-readable error code (e.g., E300)
    message: str          # Human-readable description
    severity: Severity    # ERROR, WARNING, or INFO
    location: SourceLocation | None

Host Callbacks

Provide custom implementations for capability-gated builtins:

def my_fs_read(path):
    # Custom file reading logic
    return open(path).read()

result = geno.run(source, config=RunConfig(
    capabilities={"fs"},
    host_callbacks={
        "fs_read_text": my_fs_read,
    },
))

The same explicit-callback rule applies to fs_metadata, fs_symlink_metadata, and fs_canonicalize; granting fs never installs ambient filesystem access in geno.run(). Metadata callbacks return the usual interpreter Result and built-in ADT values (ConstructorValue instances), so embedders retain full control over path policy and reported metadata.

Host callbacks run as trusted code inside the interpreter. They are not subject to the execution timeout. Implement your own timeouts for I/O operations.

Multi-Module Programs

Provide module sources in-memory for import resolution:

math_module = """
func double(x: Int) -> Int
    example 3 -> 6
    return x * 2
end func
"""

main_source = """
import Math

func main() -> Int
    return Math.double(21)
end func
"""

result = geno.run(main_source, config=RunConfig(
    modules={"Math": math_module},
))
print(result.value)  # 42

Error Handling

Check result.ok and inspect result.diagnostics for structured errors:

result = geno.run(source)
if not result.ok:
    for d in result.diagnostics:
        loc = f"{d.location.line}:{d.location.column}" if d.location else "?"
        print(f"[{d.code.value}] {loc}: {d.message}")

Error codes follow the pattern E<phase><number>:

  • E1xx: Lexer errors (unexpected character, unterminated string)
  • E2xx: Parse errors (unexpected token, invalid syntax)
  • E3xx: Type errors (mismatch, undefined variable, wrong arity)
  • E4xx: Runtime errors (division by zero, index out of bounds, capability denied)
  • E5xx: Sandbox errors (timeout, recursion limit, resource limit)

Rendered from docs/reference/embedding-api.md in the Geno repository.