KUJO THE COURSE

THE KUJO COURSE · VERIFIED 1.3.1

Modules and explicit exports

Separate reusable code from entrypoint effects.

STAGE 3 / Projects and tooling · LESSON 15

Why this exists

A script becomes a project when another file needs to reuse its behavior. Modules make that boundary explicit. Imports should expose a small useful interface rather than execute surprising top-level effects.

Language contract

Kujo supports import module_name and from module_name import symbols, including dotted paths such as src.util. Export imported functions with export func. Normal module use works on the default VM path, though imported implementation and callback bridges have their own documented boundaries.

Resolution searches deterministic roots: the importing package context, project and modules roots, explicit KUJO_MODULE_PATH entries, and installed roots named by a nearest Kennel lockfile where applicable. Imports do not implicitly fetch packages. The loader rejects unsafe traversal, symlink escapes, and import cycles with diagnostics.

Read the example

The module exports a pure total function. The entrypoint owns the input, calls the module, and prints the result. There is no network or filesystem work inside the exported calculation. Reading module source is loader behavior; it is distinct from giving your program arbitrary read_file authority.

The file lives under examples/modules/report.kujo. Run from the repository root as shown. If resolution fails from another working directory, diagnose roots and invocation context rather than adding unrelated directories to the module search path until the import happens to work.

Professional pattern

Use a small entrypoint for input validation, capability-aware effects, and output rendering. Keep calculations in modules. Make dependencies explicit in the project rather than relying on an unrelated parent checkout or a developer's personal environment.

Common mistakes

A function declaration without export is not the intended public module API. A local directory that looks like an installed package is not proof it is in a locked resolution root. Circular imports usually indicate that shared definitions need their own lower-level module.

The package build later creates a standalone project and validates it from its own root, so the course repository cannot accidentally provide missing imports.

Working example

from examples.modules.report import total
assert_equal(total([2, 3]), 5)
print(total([2, 3]))

Run it

From the course repository root, use the pinned Kujo 1.3.1 runtime.

kujo check examples/15.kujo
kujo run --untrusted  examples/15.kujo

Captured output

5

Break it and diagnose it

The requested module does not exist in the configured roots. This is a resolution failure, not a provider installation request.

from missing_course_module import total
print(total([]))

kujo run --untrusted  examples/15-break.kujo

Exit status: 4. Captured diagnostic:

[KUJOVM001] [vm] Runtime Error: Module not found: missing_course_module; Check that 'missing_course_module' exists as a flat <module>.kujo file or a nested src/... path under the package root, and confirm the import name matches the on-disk layout.
  --> 0:0
   = help: Check that the module exists as <module>.kujo or as a dotted src/... path under the package root.


Exercise

Extract a validator and a renderer into separate modules. Export only their public functions. Run from an independent project root and remove any ambient module path before verifying it.

Checkpoint

  • I declare reusable functions with export.
  • I know imports do not fetch dependencies.
  • I can diagnose module roots and cycles.

Verified 2026-09-06 · Official Kujo 1.3.1 release · Source contract · Download example