<!-- Sema documentation — Packaging & Providers
     Sema is a new AI-native, neurosymbolic programming language most models
     have not been trained on. Full corpus: https://sema.49.12.246.95.sslip.io/llms-full.txt
     Install language support: https://sema.49.12.246.95.sslip.io/install-skill/ -->

# Packaging & Providers

> Install packages with sema add, ship native Sema packages, configure sema.toml, wire typed config + DI, and override any model backend with @provides.

A language is only as usable as its ecosystem and its defaults. Sema ships a
package manager that speaks *both* ecosystems, native Sema packages, a declarative
config layer, first-class dependency injection, and a seam to override any model
backend — all so a project works out of the box and then bends to whatever you
need.

This guide walks the packaging and configuration surface, with the DI example
drawn from the [`finops-ledger`](/reference/examples-api/finops-ledger/) project.
See also [Project Layout](/start/project-layout/) for where these files live.

## Installing packages

`sema add` handles PyPI and native Sema packages with one command:

```bash
sema add numpy          # install a PyPI package into .sema/venv (via uv; pip fallback)
sema list               # discovery
sema remove numpy
```

For PyPI, `sema add` creates a project-local environment (`.sema/venv`), installs
with **uv** (the fast pip) or pip, records the package in `.sema/packages.txt`,
and points `sema.toml [python] bin` at the env — so the package is immediately
usable from Sema via `python.import(...)` (see
[Python Interop](/guides/python-interop/)), classes and methods included. The
generic adaptation layer avoids per-package bindings, but compatibility across
native extensions, ABIs, worker protocols, and platforms is not universal.

:::note
The runtime currently installs from source. A maturin `sema-lang` wheel has
local build/install evidence, but the PyPI project is not published; the pip/uv
install commands remain a planned release channel.
:::

## Native Sema packages

A native Sema package is a directory with a `sema-pkg.toml` (`[package] name = …`)
plus `src/*.sema`. Install it from a local path or a git URL:

```bash
sema add ./greetings                 # a local Sema package
sema add git+https://example.com/greetings.git
```

The loader resolves imports from installed packages, so their modules are used
like any other — `from greetings.greet import hello`. Installed packages live in
`.sema/packages/<name>/` (gitignored, reproducible), and a **project module
overrides a package module of the same name**.

### The standard library is written in Sema

The `std` package ships embedded in the compiler — its source lives in
`stdlib/sema/*.sema` — and is available to every project with no installation:

```sema
from std.belief import Belief
from std.cache import memoize
from std.document import Report, render
from std.agent_loop import loop_until
```

Because these neurosymbolic components are expressed in Sema, not hardcoded in
Rust, their parameters and logic are changeable in the language, and a user module
of the same stem shadows the stdlib one.

## The config layer: `sema.toml`

An optional `sema.toml` at the project root overrides defaults without touching
code, and every value is readable from a program via `config.get` /
`config.model` / `config.temperature`. Embedding-driven ops (`~=`) run on a
built-in engine even unconfigured — the hash embedder. Semantic and generative
ops use a real local GGUF model when the `real-model` backend is linked and
configured; without a backend they fail with a typed error unless
`[engine] deterministic = true` opts into the hermetic deterministic engine.

```toml
[engine]
seed = 12345
temperature = 0.7

[stream]           # long-stream compaction defaults
window = 1000
budget = 4000

[models]           # which model backs each capability — swap in your own
embed    = "my-custom-embedder"
vision   = "siglip2-base"
generate = "models/qwen3-0.6b.Q4_K_M.gguf"

[journal]          # audit-trail preset; default "standard"
level = "audit"            # off | minimal | standard | audit
retention_days = 186
```

Missing file means all defaults; present keys override; an unspecified capability
keeps its smart default. This is the single place to change model-specific traits,
register custom models, and adjust compaction behaviour — flexible when you need
it, invisible when you don't.

## Typed config and dependency injection

For real applications, configuration is *typed program data*, not a bag of strings.
Sema has `args` (a typed CLI), `config` (a validated config tree with ordered
sources), and a lexical dependency container. `finops-ledger` wires all three:

```sema
args LedgerCli:
    config: str = option("--config", default="config/ledger.yaml")
    tenant: str = option("--tenant")
    dry_run: bool = flag("--dry-run")
    overrides: list[ConfigPatch] = option("--set")

config LedgerConfig:
    source yaml LedgerCli.config
    source env prefix "FINOPS_"
    source cli LedgerCli.overrides
    tenant: str sem "Tenant id used for ledger reads and regulatory routing"
    dry_run: bool = LedgerCli.dry_run
    paths:
        inbound_dir: str = "inbound/statements"
        ledger_snapshot: str = "state/ledger/current.json"
    models:
        anomaly_writer:
            temperature: f32 = 0.1 where 0.0 <= value <= 2.0
            top_p: f32 = 0.9 where 0.0 < value <= 1.0
            max_tokens: int = 2048 where value > 0
    require tenant == LedgerCli.tenant
```

- `args` declares the CLI as typed data; the compiler generates parsing, help
  text, defaults, and completion.
- `config` declares a typed tree with ordered sources — defaults are lowest
  precedence, then file sources, then env and CLI at declared paths. Each leaf has
  a type, a `sem` descriptor, and validation (`where …`); a value that fails
  validation never enters the graph.

The container binds components to a lifetime and injects them:

```sema
component LedgerRuntime:
    sem "Scoped dependency object for one reconciliation invocation"
    lifetime scoped(run)
    inject:
        cfg: LedgerConfig
        writer: ModelClient named "anomaly_writer"
    def statement_files() -> list[str] !{fs.read}:
        return []

provide configured_anomaly_writer(cfg: LedgerConfig) -> ModelClient lifetime singleton:
    sem "Attach validated sampling config to the pinned anomaly writer model"
    return anomaly_writer.with(cfg.models.anomaly_writer)

container LedgerApp:
    args LedgerCli
    config LedgerConfig
    bind ModelClient named "anomaly_writer" = configured_anomaly_writer(LedgerConfig)
    bind LedgerRuntime lifetime scoped(run)
    expose main
```

The decorated entry point injects the runtime and uses it:

```sema
@LedgerApp
@LedgerOps
def main() -> None !{fs.read, fs.write, ffi.call, model.invoke, model.embed, net.connect, code.patch, observe.record}:
    runtime = inject LedgerRuntime
    batches = runtime.statement_files()
    for statement_path in batches:
        summary = run_reconciliation_batch(statement_path, runtime.cfg.paths.ledger_snapshot)
        log.info("reconciliation batch complete", lines=summary.statement_lines)
```

`container` is a lexical dependency graph, not a global service locator: every
`inject` must resolve to exactly one binding by `(type, qualifier)`, ambiguity is
a compile error unless disambiguated with `named "…"`, and a missing provider is a
compile error for a static entrypoint. Model configuration flows through the same
machinery — `writer.with(cfg.model)` turns sampling knobs into validated program
data instead of a long parameter list.

:::tip
Lifetime capture is checked: a `singleton` cannot depend on run-scoped state
unless it receives an explicit factory. This is what keeps a long-lived model
client from accidentally pinning per-request data.
:::

## Custom capability providers

Every model capability — `generate`, `embed`, `transcribe`, `caption`, `ocr`,
`vqa`, and the SQL `db` backend — is a **seam** the runtime resolves in a fixed
precedence: (1) a user-registered Sema provider, (2) the native candle backend,
(3) the built-in default. A provider is any Sema function tagged
`@provides("<cap>")` with the capability's signature — so you can wrap *anything*
without reimplementing a model in Rust:

```sema
import python

@provides("embed")                                  # override the embedder
def my_embed(text: str) -> list[f64] !{proc.run}:
    return python.call("sentence_transformers_helper", "encode", [text])

@provides("generate")                               # override text generation
def my_llm(prompt: str, max_tokens: int) -> str !{net.connect}:
    return http.post(endpoints.llm, prompt).text

@provides("ocr")                                    # override OCR
def my_ocr(path: str) -> str !{ffi.call}:
    return tesseract.read(path)
```

Registration is by decorator, scanned at load time, so it is visible to
`sema check` and reflection. Expected signatures include `embed(text) -> list[f64]`,
`generate(prompt, max_tokens) -> str`, `ocr(image) -> str`, and the SQL backend
`db(op, sql, params) -> any`. The override is transparent to callers — `~=` and
`semantic.*` route through your custom `embed`, tool calling through your custom
`generate`, every `db.*` call through your backend.

:::caution[Two safety properties]
A provider that **calls its own capability** falls through to the next backend (a
re-entrancy guard, so a wrapper like `generate(...) -> "wrap[" + generate(...) +
"]"` works without recursing). A provider that **fails** degrades loudly to the
default backend (journaled + stderr) — never a silent break.
:::

## Run and verify

From the `sema/` directory:

```bash
sema add ./greetings          # install a native package
sema check examples/finops-ledger
SEMA_STRICT=1 sema run examples/finops-ledger
sema assure examples/finops-ledger --grade gold
```

`sema check` sees `@provides` registrations (they are reflected and type-checked)
and flags a misconfigured provider signature before it ever runs.

## Variations

- **Wrap a remote API as `generate`.** A `@provides("generate")` with
  `!{net.connect}` routes all agent turns through your endpoint.
- **Per-tenant config.** Drive `config` from `--tenant` + an env prefix so one
  build serves many tenants — the `finops-ledger` shape.
- **Swap a model without a redeploy.** Change `[models] <cap>` in `sema.toml`; no
  code changes, and the language surface stays identical.

## See also

- [finops-ledger example (generated)](/reference/examples-api/finops-ledger/)
- [Project Layout](/start/project-layout/) · [The Toolchain](/start/toolchain/)
- [Python Interop](/guides/python-interop/) · [simulate & Models](/neurosymbolic/simulate/)
