Project Layout
This page covers how a Sema project is organized once it grows past one file: the
src/ layout, splitting code into modules, imports and visibility, the
sema.toml manifest, and adding dependencies.
The src/ layout
Section titled “The src/ layout”A project is a directory. Its Sema source lives in src/, and the entry point
is src/main.sema, whose main() function runs when you sema run the project.
A larger project just has more files under src/:
finops/├── sema.toml # the manifest (optional; see below)└── src/ ├── main.sema # entry point: defines main() ├── domain.sema # data types └── policies.sema # governanceEach .sema file is one module. A package is the tree rooted at a
sema.toml manifest, and the manifest’s [package] name is the import root for
every module in that tree. So if sema.toml declares name = "finops", the file
src/domain.sema is the module finops.domain.
Splitting into modules and importing
Section titled “Splitting into modules and importing”Import from another module with from … import … or import … [as …]. Both
resolve at compile time against the package graph — there is no runtime path
search:
from finops.domain import LedgerEntry, Moneyimport finops.policies as policiesTwo rules that differ from Python and matter in practice:
- Wildcard imports do not exist. You name every symbol you import. This keeps code reviewable and lets the toolchain resolve names statically.
- Re-export is explicit. To surface a name your module imported as part of
your own public API, re-export it:
pub from finops.domain import Money.
Cyclic imports and unresolvable imports are compile errors, and two packages that export the same root name require manifest aliasing rather than silently shadowing each other.
Visibility: pub
Section titled “Visibility: pub”Declarations are module-private by default. Mark the public surface with pub
(a soft keyword). Anything without pub is invisible outside its own module:
pub struct ReconciliationDecision: matched: list[LedgerEntry]
pub def reconcile(lines: list[BankLine]) -> list[ReconciliationDecision] !{model.embed}: ...
def candidate_score(bank: BankLine, entry: LedgerEntry) -> f32 !{model.embed}: ... # module-private: only reconcile can call thisVisibility is per declaration, not per file, because the public signature — a
pub declaration’s full signature including its contract clauses and effect
row — is what the verification engine keys its caches on. A private-across-module
access is a compile error that names the missing pub.
The sema.toml manifest
Section titled “The sema.toml manifest”sema.toml at the project root is optional: with no manifest you get all
defaults. When present, it declares the package and overrides defaults without
touching code. A minimal manifest names the package and sets an edition:
[package]name = "finops"edition = "2026"version = "0.1.0"
[assurance]default = "silver" # module grade if a module doesn't declare its ownBeyond the package identity, the manifest is the single place to configure the runtime. The most useful sections:
[engine]seed = 12345 # deterministic seed for reproducible runstemperature = 0.7
[models] # which model backs each capability — swap in your ownembed = "my-custom-embedder"generate = "models/qwen3-0.6b.Q4_K_M.gguf"
[semantic]cluster_threshold = 0.9 # default threshold for semantic.cluster / semantic.dedup
[journal] # audit-trail preset (see governance)level = "standard" # off | minimal | standard | auditEverything the manifest sets is also readable from a program via config.get,
config.model, and config.temperature, so configuration is diffable,
tool-readable, and overridable without recompiling. A missing file means all
defaults; present keys override; unspecified capabilities keep their smart
default.
Packages and dependencies
Section titled “Packages and dependencies”sema add handles both ecosystems from one command, installing into a
project-local, gitignored .sema/ directory.
PyPI packages install into .sema/venv (via uv, with a pip fallback),
recorded in .sema/packages.txt, and become immediately usable from Sema through
the Python bridge — classes and methods included, no per-package glue:
sema add numpysema remove numpysema listNative Sema packages — a directory with a sema-pkg.toml ([package] name = …)
and its own src/*.sema — install from a local path or a git+<url> source into
.sema/packages/<name>/:
sema add ./greetingssema add git+https://example.com/greetings.gitInstalled package modules import exactly like your own (from greetings.greet import hello). A project module of the same name overrides a package module,
which is how you shadow or patch a dependency locally.
The standard library needs no installation
Section titled “The standard library needs no installation”The std package ships embedded in the compiler — its source lives in Sema
itself — so it is available to every project with no sema add:
from std.belief import Belieffrom std.cache import memoizefrom std.document import Report, renderfrom std.agent_loop import loop_untilBecause the standard library is written in Sema (not hardcoded in Rust), its parameters and logic are inspectable and changeable in the language, and a user module of the same stem shadows the stdlib one. See the Standard Library overview for the full module list.