# Trust audit URL: /docs/agents/audit Markdown: /docs/agents/audit.md > Inventory trust assumptions and explicit refinement conversions with sakura audit. `sakura audit` is available in the source-built CLI. Run it from a project directory: ```sh sakura audit src sakura audit --json src ``` It lists user-package `@trust` expressions (including reason and resolved sink), `@total` declarations, `@external` declarations, `@resource` types and explicit refined-type `T.from` calls. The human report groups sites by file. Std sites are excluded here; the [build manifest](/docs/agents/build-manifest) includes the std trusted base. The following program checks and runs; its audit contains one `total`, one `from` and one `trust` site: ```sakura @total fn identity(n: Int) -> Int { n } type Positive = Int where it > 0 pub fn main() -> Unit ! {Console} { let n = Positive.from(identity(1)) let raw = Untrusted.of(n) Console.print("{Untrusted.trust(raw, @trust("locally constructed positive value"))}") } ``` JSON output is JSON Lines: each site has `schema_version: "1.0"`, `kind: "audit"`, `site`, `symbol_id`, `span` and `reason` (nullable). `trust` and `from` records also identify the resolved `sink`. A final `summary` has the snapshot hash, site count and counts by site kind. Spans identify the source URI, content hash and byte range. An audit is an inventory, not a successful type check or a security certification. It exits 0 regardless of the sites found; loading and usage errors can still fail. Run `sakura check` separately. ## Literal conversion limitation [#literal-conversion-limitation] A literal whose refinement succeeds at compile time needs no startup conversion and does not appear in the report. An undecided literal still has a startup check, but the current audit also omits that conversion. Design §17.78 requires a `from` record with `reason: "literal"`; that part has not landed. Do not use an empty audit to infer that there are no runtime refinement checks. See [contracts](/docs/tour/contracts#literal-refinements-at-compile-time). # Build manifest URL: /docs/agents/build-manifest Markdown: /docs/agents/build-manifest.md > Inspect the trusted base and runtime contract obligations emitted by sakura build --manifest. The source-built JS compiler supports: ```sh sakura build --manifest src ``` A successful build writes JavaScript to `target/sakura/js` and the manifest to `target/sakura/manifest.json`, relative to the working directory. `--out-dir` changes the JavaScript directory; the manifest path stays `target/sakura/manifest.json`. A failed build does not write a new manifest, so check the exit status before consuming an existing file. The manifest is one JSON object with: - `schema_version: "1.0"` and the input `snapshot` hash. - `compiler.version` and `compiler.git`; local builds may have a null git value. - `trusted` entries for the compiler, emitted runtime (with SHA-256), std host bindings, user `@external` declarations, `@trust` reasons and `@total` declarations. - `obligations` with an ID, symbol ID, clause, status and source span. Current contract records have status `runtime_checked`. Obligation IDs use `o:` followed by a digest of the symbol, clause name and normalized predicate. A function-body edit that preserves these does not change its obligation ID. That identity is not a proof-cache validity guarantee: M4 verification must also track bodies and other dependencies. This records assumptions and runtime obligations; it does not certify the program. [Compile-time literal evaluation](/docs/tour/contracts#literal-refinements-at-compile-time) already decides some introductions, while `sakura verify` and proof-driven check removal remain planned M4 integration. See the [roadmap](/docs/roadmap). # Offline cassettes and recording URL: /docs/agents/cassettes Markdown: /docs/agents/cassettes.md > Replay HTTP and LLM exchanges with Test.fixtures, and opt into recording with --record. Ordinary `sakura test` denies network access. `Http.recording` and `Llm.replay` let tests exercise provider-shaped exchanges offline using JSON Lines cassettes under `tests/fixtures/`. ## HTTP replay [#http-replay] `Test.fixtures()` returns a `Dir` rooted at the package's `tests/fixtures` directory. It is read-only during ordinary tests. Install it with `Fs.rooted`, then put `Http.recording` inside an outer HTTP handler. The first handler in a `with` list is outermost. This test belongs in the repository's `compiler/examples/secrets` package, alongside its committed `billing.jsonl` fixture: ```sakura import std/decimal.{Decimal} import std/fs.{Fs} import std/http.{Http} import std/path.{Path} import std/request.{Request} import std/url.{Url} test "billing cassette replays offline" { with Fs.rooted(Test.fixtures()), Http.scripted([]), Http.recording(Path.of("billing.jsonl")) { let amount = Decimal.parse("12.34").unwrap_or(Decimal.zero()) let req = Request.post_json(Url.from("https://billing/charge"), amount) expect_eq(Http.send(req).status, 200) } } ``` A request matches on method, URL, ordinary headers and body. `Request.secret_headers` are neither recorded nor matched. Keep credentials in that secret channel; ordinary headers and bodies are cassette data. Identical requests consume matching responses in order; the last response repeats. Without recording mode, a miss fails with `HttpError` and a message pointing to `sakura test --record`. ## LLM replay [#llm-replay] `Llm.replay(path)` installs the Anthropic adapter over `Http.recording`, an empty scripted outer HTTP handler and `Budget.unlimited()`, using a placeholder secret. It needs `Fs`, not live provider credentials. It replays generation, free text, partial callbacks and tool turns; missing exchanges fail as `ProviderError`. The [LLM extraction example](https://github.com/icantcodefyi/sakura-lang/tree/master/compiler/examples/llm_extract) uses `Fs.rooted(Test.fixtures())`, an outer `Budget.unlimited()` for its generated `@llm` function, and `Llm.replay(Path.of("extract_ticket.jsonl"))`. The [agent-loop example](https://github.com/icantcodefyi/sakura-lang/tree/master/compiler/examples/agent_loop) also scripts its knowledge-base tool. Each `Llm.replay` operation currently reopens the cassette. Repeated identical requests therefore replay the first matching response on each operation. For sequential responses to the same request, keep one `Http.recording` handler around an explicit `Llm.anthropic` handler instead. From the repository root, after building the CLI: ```sh cd compiler/examples/llm_extract ../../target/debug/sakura check ../../target/debug/sakura test ``` ## Recording is opt-in [#recording-is-opt-in] `--record` is present in `sakura test --help`. It sets recording mode, makes the fixture directory writable and grants only the manifest's network hosts. With no `net` entries it refuses to start (exit 75). Run from the prepared project directory: ```sh sakura test --record ``` A cassette miss can then forward through the outer `Http` handler and append the response. Existing hits still replay. For live provider recording, explicitly install `Llm.anthropic` with a real key and `Http.recording` over a host HTTP handler. `Llm.replay` keeps its empty scripted outer handler even with `--record`, so it cannot fetch a missing provider exchange itself. Review cassette diffs before committing them. The verification for this page used synthetic responses and local CLI integration tests, not a live provider. ## Synthetic cassette convention [#synthetic-cassette-convention] The committed fixtures in `llm_extract`, `agent_loop` and `secrets` are **synthetic**, not captured live responses. Each fixture directory has a README saying that bodies, IDs and token counts were invented, naming the exchanges and describing regeneration. Their request keys were generated by executing the program with a scripted outer HTTP response under recording mode, so they match the real adapter's serialization. A changed request fails replay and requires deliberate regeneration and review. All twelve examples now execute in run or test mode; these three replay committed fixtures. This is evidence for the example gate, not evidence that all M3 exit gates have passed. ## Still planned [#still-planned] `--contracts` is absent from the tested CLI. Generated tests from contracts remain [planned](/docs/tour/testing#property-tests-from-contracts-planned). Streaming HTTP (`Http.stream`, `Http.read_chunk`, `Body`, `Incoming`, design §17.88) is also absent from the embedded std library; the current cassette handler implements `Http.send`. # Diagnostics URL: /docs/agents/diagnostics Markdown: /docs/agents/diagnostics.md > Reference for the JSON Lines diagnostics produced by sakura check --json, including record kinds, error codes, and fix impact classes. > **Pre-alpha** > > The source-built compiler runs the core language and much of M3 on JavaScript: contracts, typed effects and failures, std handlers, permissions, LSP and MCP. M3 is still in progress; no release or stable compatibility contract exists. See the [roadmap](/docs/roadmap) for implemented features and remaining gates. Diagnostics are an API. Every diagnostic has a stable code, byte spans, structured expected and actual values, causal links, and fixes that declare their impact. The CLI, the LSP server, and the [MCP server](/docs/agents/mcp) share one analysis library and one format. ```bash sakura check --json sakura test --json ``` `--json` writes **JSON Lines**: one record per line. Human-readable output is rendered from the same data. ## Record kinds [#record-kinds] | `kind` | Meaning | | --- | --- | | `diagnostic` | An error, warning, or note. Shape below. | | `hole` | A [typed hole](/docs/agents/typed-holes) with its context. | | `obligation` | A claim about the program and its status: `proved`, `runtime_checked`, `tested`, or `trusted`. | | `progress` | Progress of a long run. | | `summary` | End of run. `summary.truncated` is `true` if output hit the cap. | ## The diagnostic record [#the-diagnostic-record] The program below omits `Console` from `greet`'s effect row: ```sakura fn greet(name: String) -> Unit { Console.print("hello, {name}") } ``` `sakura check --json` would report it as one line, shown here pretty-printed. Hashes and ids are shortened. ```json { "schema_version": "1.0", "kind": "diagnostic", "id": "d:7f3a", "code": "V3001", "severity": "error", "phase": "effect", "message": "function `greet` performs `Console`, which its signature does not allow", "snapshot": "sha256:91c0", "symbol_id": "hello::main::greet", "spans": [ { "uri": "file:///work/hello/src/main.sakura", "content_hash": "sha256:4be2", "start_byte": 3, "end_byte": 8, "role": "primary", "label": "`greet` is declared pure" }, { "uri": "file:///work/hello/src/main.sakura", "content_hash": "sha256:4be2", "start_byte": 35, "end_byte": 65, "role": "cause", "label": "`Console.print` introduces `Console` here" } ], "related_ids": [], "expected": { "render": "{}", "row": { "labels": [], "tail": null } }, "actual": { "render": "{Console}", "row": { "labels": [{ "effect": "std::Console", "args": [] }], "tail": null } }, "obligation": null, "counterexample": null, "explanation_uri": "sakura://explain/V3001", "fixes": [ { "id": "f:01", "title": "Add `Console` to the signature of `greet`", "applicability": "suggested", "impact": "interface_change", "edits": [ { "span": { "uri": "file:///work/hello/src/main.sakura", "content_hash": "sha256:4be2", "start_byte": 30, "end_byte": 30, "role": "primary", "label": "" }, "replacement": " ! {Console}" } ] } ] } ``` For `V3001`, the design specifies two fixes: add the effect to the signature (`interface_change`) or handle it locally with `with` (`local`). Only the first is shown. ## Fields [#fields] | Field | Type | Meaning | | --- | --- | --- | | `schema_version` | string | Version of this format. `"1.0"` in v0. | | `kind` | string | `"diagnostic"`. | | `id` | string | Unique per occurrence in this run. | | `code` | string | Stable class, such as `V3001`. See [codes](#error-codes). | | `severity` | string | `error`, `warning`, or `note`. | | `phase` | string | `lex`, `parse`, `resolve`, `type`, `effect`, `pattern`, `contract`, `capability`, `verify`, `package`, or `internal`. | | `message` | string | One sentence naming the symbols involved in backticks. | | `snapshot` | string | Hash of the workspace snapshot that was checked. | | `symbol_id` | string or null | The symbol the diagnostic is about. | | `spans` | array | Source locations, each with `uri`, `content_hash`, `start_byte`, `end_byte`, `role`, `label`. | | `related_ids` | array | Ids of root-cause diagnostics this one cascades from. Empty for a root cause. | | `expected` | object or null | What was required: a `render` string, and a structured `row` for effect rows. | | `actual` | object or null | What was found, in the same shape. | | `obligation` | object or null | `id`, `clause` (for example `"ensures bounded"`), and `status`. | | `counterexample` | object or null | `inputs` and a `trace`, when one is known. | | `explanation_uri` | string | `sakura://explain/`. Same content as `sakura explain ` and the MCP `explain_diagnostic` tool. | | `fixes` | array | Zero or more fixes. See below. | Span `role` is one of `primary`, `cause`, `definition`, or `use`. ### Rules [#rules] - Byte offsets are zero-based, half-open, and count UTF-8 bytes. There are no line or column numbers. - `content_hash` must match the file before any edit is applied. - The edits in one fix are applied atomically and rejected if they overlap. - Records are ordered root causes first (empty `related_ids`), then by position. - Output is capped at 200 diagnostics per run, with `summary.truncated: true`. ## Fixes [#fixes] | Field | Meaning | | --- | --- | | `id` | Fix id, accepted by `sakura fix --apply`. | | `title` | Short imperative description. | | `applicability` | `machine_applicable`, `suggested`, or `incomplete`. | | `impact` | What the fix changes. See below. | | `edits` | List of `{ span, replacement }`. A span with equal start and end is an insertion. | ### Impact classes [#impact-classes] | `impact` | Changes | Suggested agent policy | | --- | --- | --- | | `local` | Code inside one body; no signature changes. | Apply if `machine_applicable`. | | `behavior_change` | What the code does, without changing its signature. | Apply only if it matches the task. | | `interface_change` | A signature, effect row, or error union. | Apply only if the task needs it, and report it for review. | | `contract_change` | A `requires`, `ensures`, or `where` clause. | Never apply unless a human asks. | | `authority_change` | Permissions or capabilities. | Never apply unless a human asks. | Compiler fixes with `interface_change`, `contract_change`, or `authority_change` impact are never `machine_applicable`. No fix ever widens authority or weakens a contract by itself. `sakura fix` lists fixes with their impact class. It applies only `machine_applicable` fixes unless given ids with `--apply id,…`, and it never applies `contract_change` or `authority_change`. `--preview` shows the edits without writing. ### Frozen specs [#frozen-specs] `sakura check --frozen-specs `, `sakura fix`, and the MCP `apply_patch` tool reject any patch that changes a frozen symbol's signature, effect row, error union, `requires`/`ensures`/`where` clauses, or any `test`/`property` body. The diagnostic is `V8020 spec_frozen` and names the clause. This stops an agent from passing a check by editing the spec. ## Error codes [#error-codes] Codes are `V` + a phase digit + three digits for errors, `W` for warnings, and `D` for runtime defects. Codes are never reused or renumbered; retired codes stay documented. Every code has an explain page with a minimal pair: wrong program, diagnostic, fixed program. | Phase digit | Area | Examples | | --- | --- | --- | | `0` | Lexing and parsing | `V0001` syntax error, `V0007` reserved keyword, `V0010` chained comparison, `V0011` call-site type arguments, `V0014` named argument | | `1` | Resolution and modules | `V1002` unresolved name (offers the import as a fix), `V1004` wrong casing, `V1006` item not `pub` or opaque constructor, `V1007` wrong kind of name, `V1010` unknown attribute, `V1030` import cycle, `V1040` assignment to a `let`, `V1041` jump outside a loop | | `2` | Types | `V2031` `Secret` is not `Show`, `V2032` constraint not satisfied, `V2040` unused value, `V2041` lambda or `try` body assigns an outer `var`, `V2050` hole in build | | `3` | Effects and handlers | `V3001` effect not allowed, `V3011` handler clauses do not match the operations, `V3020` test needs a handler | | `4` | Patterns | `V4001` non-exhaustive match, `V4002` redundant arm | | `5` | Contracts and refinements | `V5001` predicate outside the contract subset | | `6` | Capabilities, resources, escape | `V6002` capability escapes its scope, `V6003` resource escapes, `V6010` non-std effect in `main` | | `7` | Tests and verification | None named in the design yet | | `8` | Packages, build, frozen specs | `V8020` spec frozen | | `9` | Internal | Compiler bugs | Warnings named in the design include `W1002` (a local shadows an import), `W4010` (catch-all `_` on an open error union), `W5005` (an O(n) contract on a hot path), `W7001` (property generator discard budget exceeded), and `W8001` (a collection update copies in a loop). Runtime defects are not diagnostics from `sakura check`. They terminate the task; if `main` defects, the program writes one `defect` record to stderr and exits with status 70: ```json {"schema_version":"1.0","kind":"defect","code":"D002","message":"division by zero"} ``` | Code | Raised by | | --- | --- | | `D001` | `Int` overflow | | `D002` | `/` or `%` by zero | | `D003` | `Int` beyond ±(2^53−1) on the JavaScript backend | | `D004` | `Float.round` or `Float.to_int_checked` of NaN or ±∞ | | `D010` | failed refinement in a checked introduction (`T.from`) | | `D011` | a contract predicate itself defected | | `D020`, `D021` | `requires` violated (caller blamed), `ensures` violated (callee blamed) | | `D030` | `Option.expect` on `None` | | `D031` | `String.slice` off a character boundary | | `D090`, `D091` | runtime-internal error, host operation failed | ## Exit codes [#exit-codes] | Code | Meaning | | --- | --- | | `0` | OK. | | `1` | Diagnostics with errors, or `main` ended with an uncaught `Fail` (stderr: `error: main failed with `). | | `2` | Usage error. | | `3` | Test failures. | | `70` | Defect at runtime. | | `75` | Permission denied at startup. | ## Next [#next] [Typed holes](/docs/agents/typed-holes): writing a program one `?hole` at a time. # Offline API search URL: /docs/agents/doc Markdown: /docs/agents/doc.md > Find signatures, effects, contracts and documentation with sakura doc. `sakura doc` searches the current project and the embedded std library without contacting a service. Run these commands from a project directory: ```sh sakura doc --search "fn(List) -> Int" sakura doc std::list::len sakura doc --json std::list::len ``` A symbol argument containing `::` is an exact symbol ID; other arguments search by name. `--search` matches a type pattern. Human output includes the symbol ID, signature, effect row, contracts and doc comment when present. `--json` prints one JSON object, not JSON Lines: `snapshot`, `total`, `symbols` and `truncated`. The symbol records use the compiler MCP `symbol_query` representation with a `type` field. No matches gives `total: 0` and exit 0; the human command prints “no symbol matches”. Treat that as an empty search result, not a compiler diagnostic. Use [MCP](/docs/agents/mcp) when an agent needs structured queries alongside checking and edits. The [standard-library index](/docs/stdlib) is a browsable copy of the source signature inventory. `sakura doc --index` is not implemented. # Overview URL: /docs/agents Markdown: /docs/agents.md > How an AI agent is expected to work with Sakura Lang, from reading the docs to handing signature changes to a human. > **Pre-alpha** > > The source-built compiler runs the core language and much of M3 on JavaScript: contracts, typed effects and failures, std handlers, permissions, LSP and MCP. M3 is still in progress; no release or stable compatibility contract exists. See the [roadmap](/docs/roadmap) for implemented features and remaining gates. Sakura Lang is designed to be written by agents and reviewed by humans. The signature is the contract: parameter types with refinements, result type, effect row, error union, and `requires`/`ensures`. Agents write bodies against it; humans review signatures and the diff of signatures. ## Available today [#available-today] | Resource | What it is | | --- | --- | | [`/llms.txt`](/llms.txt) | Index of every page on this site, with titles, descriptions, and URLs. | | [`/llms-full.txt`](/llms-full.txt) | Every page on this site concatenated into one Markdown file. | | `/docs/.md` | Any page on this site as Markdown. Append `.md` to its URL; the docs root is `/docs.md`. | | Copy as Markdown | A button at the top of every page, next to a menu with the `.md` link and the GitHub source. | | [`docs/llms.txt`](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/llms.txt) | The language reference for agents: the whole core language in one file of about 4,500 tokens. Lives in the repository. | | [`docs/design.md`](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md) | The authoritative v0 design. When anything else disagrees with it, it wins. | The two `llms.txt` files are different things. See [llms.txt](/docs/agents/llms-txt). ## Toolchain by milestone [#toolchain-by-milestone] | Tool | Purpose | Design | | --- | --- | --- | | `sakura check --json` | Diagnostics, holes, and obligations as JSON Lines. | design §12.2, M0 | | `?name` typed holes | Placeholders the checker reports with expected type, effects, locals, and candidates. | design §12.4, M2 | | `sakura prompt` | Emits `docs/llms.txt` matched to the compiler's edition, plus requested library signatures. | design §12.1, M2 | | `sakura fix`, `--frozen-specs` | Applies compiler fixes by impact class and rejects patches that touch frozen specs. | design §7.3, M3 | | [`sakura audit`](/docs/agents/audit), [`sakura doc`](/docs/agents/doc) | Trust inventory and offline signature search. | design §17.78, M3 | | [`sakura build --manifest`](/docs/agents/build-manifest) | Trusted components and runtime contract obligations. | design §17.76, M3 | | [`sakura test --record`](/docs/agents/cassettes) | Opt-in cassette recording; ordinary tests replay offline. | design §17.73, M3 | | `sakura mcp serve` | The compiler as Model Context Protocol tools. | design §12.6, M3 | Milestones are defined in [design §14.3](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md). See also the [roadmap](/docs/roadmap). ## The intended loop [#the-intended-loop] This is how an agent is expected to work with the full v0 toolchain. `sakura fix`, `--frozen-specs`, and the compiler MCP server are available. ### Load the language [#load-the-language] Read `docs/llms.txt` once per session (or run `sakura prompt`). It is the entire core language. Look up library signatures with the MCP `symbol_query` tool instead of guessing; [`sakura doc --search`](/docs/agents/doc) is also available offline. ### Write signatures first, bodies as holes [#write-signatures-first-bodies-as-holes] ```sakura fn clamp(x: Int, lo: Int, hi: Int) -> Int requires ordered: lo <= hi ensures bounded: lo <= result && result <= hi { ?body } ``` `sakura check` accepts holes. Signatures are what a human reviews, so get them right first. ### Check [#check] Run `sakura check --json`, or call the MCP `check` tool. Each [typed hole](/docs/agents/typed-holes) comes back as a `hole` record with its expected type, allowed effects, locals, and ranked candidates. ### Fill holes and fix diagnostics [#fill-holes-and-fix-diagnostics] Each problem is a `diagnostic` record with a stable code, byte spans, structured expected/actual, and fixes. See [Diagnostics](/docs/agents/diagnostics). `sakura explain V3001` gives the rationale for a code. ### Apply fixes by impact class [#apply-fixes-by-impact-class] `local` fixes that are `machine_applicable` are safe to apply. `interface_change`, `contract_change`, and `authority_change` fixes are never `machine_applicable`; `sakura fix` never applies the last two. Run `sakura test --json`, then `sakura fmt`. ### Report signature changes [#report-signature-changes] If the task really needs a new effect, error tag, permission, or a changed contract, make the change and list it for the reviewer. During repair, specs can be frozen (`--frozen-specs`), and any patch that touches them is rejected with `V8020`. ## What an agent should never do [#what-an-agent-should-never-do] - Add an effect, error tag, or `[permissions]` entry without flagging it. - Weaken or delete a `requires`, `ensures`, or `where` clause, or edit a `test` body, to make a check pass. Contract violations (`D020`, `D021`) mean the caller or callee is wrong, not the spec. - Use `.trust(@trust("..."))`, `Secret.expose`, `.expect`, or `Email.from` outside trusted boundaries, or without a true reason. - Leave a `?hole` in code meant to build. `sakura build` and `sakura run` reject it with `V2050`. - Borrow syntax from other languages: call-site type arguments (`f(x)`, `V0011`) and named arguments (`f(name: v)`, `V0014`) do not exist. Annotate the binding (`let rows: List = Sql.query(q)`) and pass a record for options. Import every non-prelude name explicitly (`import std/http.{Http}` and `import std/request.{Request}`). ## Pages in this section [#pages-in-this-section] - [llms.txt](/docs/agents/llms-txt): The site's Markdown exports and the language's `docs/llms.txt`. - [MCP server](/docs/agents/mcp): The implemented `sakura mcp serve` compiler tools and `@tool` export. - [Diagnostics](/docs/agents/diagnostics): The JSON Lines format, error codes, and fix impact classes. - [Typed holes](/docs/agents/typed-holes): Writing programs incrementally with `?name`. - [Trust audit](/docs/agents/audit): Inventory explicit trust, totality and refinement boundaries. - [Offline API search](/docs/agents/doc): Query signatures, effect rows and contracts with sakura doc. - [Build manifest](/docs/agents/build-manifest): Inspect trusted dependencies and runtime contract obligations. - [Cassettes and recording](/docs/agents/cassettes): Replay HTTP and LLM exchanges offline; opt into recording. # llms.txt and Markdown exports URL: /docs/agents/llms-txt Markdown: /docs/agents/llms-txt.md > The two llms.txt files for Sakura Lang - this site's Markdown exports, and the language reference in docs/llms.txt that sakura prompt will emit. There are two different `llms.txt` files in the Sakura Lang project. Do not confuse them. | | Site exports | Language reference | | --- | --- | --- | | Where | This website: `/llms.txt`, `/llms-full.txt`, `/docs/.md` | The repository: [`docs/llms.txt`](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/llms.txt) | | Contents | These docs pages, as Markdown | The whole core language in one file, written for a model's context | | Status | Live today | Exists today as a file; `sakura prompt` emits it | | Use it for | Explanations, the tour, agent tooling docs | Writing Sakura Lang code | ## Site exports [#site-exports] > **Available today** > > These endpoints are served by this site now. They export the docs, not a compiler. | URL | Contents | | --- | --- | | [`/llms.txt`](/llms.txt) | An index of every docs page with title, description, and URL, grouped by sidebar section, following the [llms.txt](https://llmstxt.org) convention. | | [`/llms-full.txt`](/llms-full.txt) | Every docs page concatenated into one Markdown file. | | `/docs.md` | The docs root as Markdown. | | `/docs/.md` | Any docs page as Markdown. Append `.md` to its URL. `.mdx` works too. | ```bash curl https:///llms.txt curl https:///llms-full.txt curl https:///docs/tour/effects.md ``` Each exported page starts with its title, its URL, its Markdown URL, and its description. Components are flattened to plain Markdown: callouts become blockquotes and card grids become link lists. Every rendered page also has a **Copy as Markdown** button and a menu linking to its `.md` version and its source on GitHub. Use `/llms.txt` to choose pages when context is tight. Use a per-page `.md` URL to refresh one topic. `/llms-full.txt` is the whole site, including design rationale and planned tooling, so it is larger than the language reference. ## The language reference: `docs/llms.txt` [#the-language-reference-docsllmstxt] [`docs/llms.txt`](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/llms.txt) is the one-file reference an agent needs to write correct Sakura Lang. It is about 16 KB, roughly 4,500 tokens, and covers: - the toolchain loop (`sakura check --json`, `sakura test --json`, `sakura run`) and fix impact rules; - the syntax on one screen, plus the rules agents get wrong (newlines, casing, no call-site type arguments, no named arguments, explicit imports, one lambda form); - types, refinements, and the derived constraints `Eq`, `Ord`, `Hash`, `Show`, `Schema`; - effects, `Fail`, `try`/`catch`, defects, handlers, and authority; - concurrency and memory; - the most used standard library names; - common diagnostics and their fixes. It is kept in sync with [`docs/design.md`](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md), which is authoritative if they ever disagree. Library signatures are deliberately not in it; look them up with the implemented MCP `symbol_query` tool; `sakura doc --search` is planned. ### `sakura prompt` [#sakura-prompt] ```bash sakura prompt sakura prompt --budget 8000 ``` `sakura prompt [--budget tokens]` will print `docs/llms.txt` for the edition the installed compiler implements, plus requested library signatures within the token budget (design §12.1). Until it exists, fetch the file from the repository: ```bash curl https://raw.githubusercontent.com/icantcodefyi/sakura-lang/master/docs/llms.txt ``` ## System prompt snippet [#system-prompt-snippet] A starting point for an agent that writes Sakura Lang. `sakura prompt` is available; consult its status section for planned interfaces. ```text You write code in Sakura Lang. Before writing code, read the Sakura Lang language reference (docs/llms.txt, or the output of `sakura prompt`) and follow it exactly. Do not borrow syntax from other languages. - Write full signatures first: parameter types, `-> T`, the effect row after `!`, the `Fail<...>` error union, and `requires`/`ensures`. Leave bodies as typed holes (`?body`) and run `sakura check --json`. - Fill holes using the `hole` records: expected_type, allowed_effects, locals, candidates, contracts_in_scope. - Apply only fixes with "impact": "local". Never apply a fix whose impact is "contract_change" or "authority_change" unless asked. List every "interface_change" in your summary for human review. - Never weaken or delete requires/ensures/where clauses or test bodies to pass a check. Never use .trust(@trust(...)) or Secret.expose without a true reason. - No call-site type arguments and no named arguments: annotate the binding (`let rows: List = Sql.query(q)`) and pass records for options. Import every non-prelude name explicitly (`import std/http.{Http, Request}`). - Use `sakura explain ` before guessing at a fix. ``` ## Next [#next] [MCP server](/docs/agents/mcp): the compiler tools agents call directly. # MCP server URL: /docs/agents/mcp Markdown: /docs/agents/mcp.md > The sakura mcp serve server, which exposes the compiler to agents as Model Context Protocol tools, and @tool export of your own functions. > **Pre-alpha** > > The source-built compiler runs the core language and much of M3 on JavaScript: contracts, typed effects and failures, std handlers, permissions, LSP and MCP. M3 is still in progress; no release or stable compatibility contract exists. See the [roadmap](/docs/roadmap) for implemented features and remaining gates. `sakura mcp serve` runs the compiler as a [Model Context Protocol](https://modelcontextprotocol.io) server. An agent calls compiler functions directly instead of running shell commands and parsing their output. The CLI, the language server (`sakura lsp`), and the MCP server are thin adapters over one Rust analysis library (`sakura_analysis`). They report the same diagnostics, in the same [JSON format](/docs/agents/diagnostics), for the same input. ## Client configuration [#client-configuration] A client entry would look like this. The transport options are not fixed yet. ```json { "mcpServers": { "sakura": { "command": "sakura", "args": ["mcp", "serve"] } } } ``` ## Snapshots [#snapshots] The compiler never reads the filesystem during analysis. It works on an immutable **workspace snapshot**. Every tool returns a `snapshot` id, and tools that read or change code take one. This makes results reproducible and lets the server detect stale edits: a patch built against an old snapshot conflicts instead of applying to the wrong text. ## Tools in v0 [#tools-in-v0] | Tool | Parameters | Returns | | --- | --- | --- | | `compiler_info` | none | Information about the running compiler. Exact fields are not specified yet. | | `check` | `snapshot`, `paths`, `budget_ms` | Diagnostic, hole, and obligation records ([Diagnostics](/docs/agents/diagnostics)). | | `symbol_query` | `name`, `id`, or `type_pattern` | Matching symbols with full signatures, including effect rows, error unions, and contracts. | | `hole_context` | `snapshot`, `hole_id` | One [typed hole](/docs/agents/typed-holes) record. | | `docs_query` | `topic` or `symbol`, `budget_tokens` | Documentation text within a token budget. | | `explain_diagnostic` | `code` or `id` | The explanation for a code such as `V3001`, the same as `sakura explain`. | | `format` | `snapshot`, `paths` | A patch with the canonical formatting. | | `check_patch` | `snapshot`, `edits` | Diagnostics for the patched code, plus the interface delta: which signatures the patch changes. | | `apply_patch` | `snapshot`, `patch_id` | A new snapshot, or a conflict. | | `test` | `snapshot`, `selectors`, `seed`, `budget` | Test results under deterministic handlers. | Planned for v1: `verify`, `refactor`, `impact`. ### Checking a patch before applying it [#checking-a-patch-before-applying-it] `check_patch` lets an agent try an edit without changing anything. The interface delta tells the agent, and the reviewer, whether the edit changes a public signature, effect row, or error union. `apply_patch` then commits it and returns the new snapshot. `apply_patch` enforces **frozen specs** (design §7.3). It rejects any patch that modifies a frozen symbol's signature, effect row, error union, `requires`/`ensures`/`where` clauses, or any `test`/`property` body, with diagnostic `V8020 spec_frozen` naming the clause. The CLI equivalent is `sakura check --frozen-specs `. ### Authority [#authority] Tools that execute code, such as `test`, run with capabilities issued by the host. A caller cannot name authority in a tool call. Tests run with deterministic default handlers and network access denied. ## Exporting your own functions as tools [#exporting-your-own-functions-as-tools] Planned separately from the implemented compiler tools: `sakura mcp serve --tools a,b` would export functions from your program as MCP tools (design §11.2). `Tool.of` works in-process today; the export flag is not implemented. A tool function is marked `@tool("description")`. Its parameters and result must be `Schema` types, and its error union must be made of `Schema` tags. From design §15.8: ```sakura import std/http.{Http, HttpError, RateLimited, Response, Status} import std/json.{DecodeError} error SearchDown @tool("Search the internal knowledge base; returns up to 5 snippets") fn kb_search(query: String) -> List ! {Http, Fail} { let resp = Http.get(url"https://kb.internal/search?q={query}") if resp.status != Status.from(200) { fail SearchDown } let snippets: List = Response.json(resp, 100_000) snippets } ``` `Tool.of(kb_search)` builds a `Tool` value. Its JSON Schema comes from the parameter types, refinements included. When called, it validates the JSON arguments, runs the function under the caller's current handlers, and maps failures to a public `ToolError` with the tag name and payload. The same `Tool` values are used in-process by `Agent.run`, which takes them in the `tools` field of its `Agent` configuration record (see the [standard library](/docs/stdlib)). When exported over MCP, the model never supplies authority. The handlers installed by the serving program decide what `Http` can reach, and the manifest's `[permissions]` table is the ceiling. ## Next [#next] [Diagnostics](/docs/agents/diagnostics): the JSON format every tool returns. # Typed holes URL: /docs/agents/typed-holes Markdown: /docs/agents/typed-holes.md > How ?name holes let agents write a Sakura Lang program incrementally, with the checker reporting what fits in each gap. > **Pre-alpha** > > The source-built compiler runs the core language and much of M3 on JavaScript: contracts, typed effects and failures, std handlers, permissions, LSP and MCP. M3 is still in progress; no release or stable compatibility contract exists. See the [roadmap](/docs/roadmap) for implemented features and remaining gates. A typed hole is a placeholder for code you have not written yet. In Sakura Lang it is written `?name`: a `?` immediately followed by an identifier, with no space. A hole is an expression of a fresh type, so a program with holes can be checked before it is finished. Holes turn "write the whole function and hope it compiles" into small steps, each with precise feedback. ## Writing a hole [#writing-a-hole] ```sakura fn clamp(x: Int, lo: Int, hi: Int) -> Int requires ordered: lo <= hi ensures bounded: lo <= result && result <= hi { ?body } ``` `sakura check` accepts this program and reports one `hole` record. `sakura build` and `sakura run` reject it with `V2050`, so a hole cannot reach a build by accident. The name is for you and for tooling. Use one that says what belongs there: `?body`, `?user`, `?retry_policy`. `?` also appears as postfix raise on a `Result` (`r?`). The two never clash: `?name` with no space before the identifier is always a hole, and `?` after an operand is always raise. ## What a hole reports [#what-a-hole-reports] After solving the enclosing body, `sakura check --json` emits one `hole` record per hole, on its own line. The record is exactly this shape ([design §12.2](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)): | Field | Meaning | | --- | --- | | `id` | `h:` plus a hash of the name, the file's content hash, and the span. Stable across runs while the text is unchanged. | | `name` | The identifier after `?`. | | `snapshot` | Hash of the workspace snapshot that was checked. | | `span` | Where the hole is, in the same shape as a diagnostic span, with `role` `primary`. | | `expected_type` | The type the hole must produce, fully solved where possible, as `{ "render", "structured" }`. | | `allowed_effects` | The full effect row allowed at this point, as `{ "render", "row" }`. Effects already used elsewhere in the body are not subtracted. | | `locals` | Every local in scope, innermost last, each with `name` and `type`. | | `candidates` | Up to 10 ranked expressions that fit, each with `expr` and its `fit` kind. | | `contracts_in_scope` | The refinements and contracts assumed at this point. | For the `clamp` program above, saved as `src/main.sakura`, the record looks like this, pretty-printed, with hashes shortened: ```json { "schema_version": "1.0", "kind": "hole", "id": "h:5d0c8e2b1a7f4396", "name": "body", "snapshot": "sha256:…", "span": { "uri": "file:///…/src/main.sakura", "content_hash": "sha256:…", "start_byte": 123, "end_byte": 128, "role": "primary" }, "expected_type": { "render": "Int", "structured": null }, "allowed_effects": { "render": "{}", "row": { "labels": [], "tail": null } }, "locals": [ { "name": "x", "type": { "render": "Int" } }, { "name": "lo", "type": { "render": "Int" } }, { "name": "hi", "type": { "render": "Int" } } ], "candidates": [ { "expr": "x", "fit": "exact_local" }, { "expr": "lo", "fit": "exact_local" }, { "expr": "hi", "fit": "exact_local" }, { "expr": "Int.min(?a, ?b)", "fit": "function" }, { "expr": "Int.max(?a, ?b)", "fit": "function" } ], "contracts_in_scope": ["requires ordered: lo <= hi"] } ``` A function candidate carries holes for its own arguments, named after its parameters, so filling the hole with it leaves smaller holes to fill next. ## How candidates are ranked [#how-candidates-are-ranked] - Exact fits from locals come first. - Then constructors and functions whose result type unifies with the expected type, such as `Int.min` or `Int.max` here. Each is tried in a rollback snapshot, so trying a candidate never changes the inference state. - Candidates that would need an effect outside the allowed row are filtered out. - At most 10 candidates are reported. A hole never affects inference of other bindings beyond its own fresh type variable. The rest of the body, including effects and contracts, is checked normally around it. ## Filling a hole [#filling-a-hole] Following the report, an agent fills the hole: ```sakura fn clamp(x: Int, lo: Int, hi: Int) -> Int requires ordered: lo <= hi ensures bounded: lo <= result && result <= hi { if x < lo { lo } else if x > hi { hi } else { x } } ``` The `ensures` clause is checked at runtime on every normal return, in every build mode, including `--release`. A wrong fill is a `D021` defect blamed on `clamp`, not a silent wrong answer. ## Narrower holes [#narrower-holes] A hole takes its expected type from context. An annotation gives a hole a type when the context does not: ```sakura fn greet(name: String) -> Unit ! {Console, Clock} { let now: Instant = ?now Console.print("hello, {name}; it is {Instant.format_iso(now)}") } ``` Here the report expects `Instant`, and `allowed_effects` is `{Console, Clock}`. A function such as `Clock.now` fits on both counts. ## Holes in the MCP server [#holes-in-the-mcp-server] The [MCP server](/docs/agents/mcp) has a `hole_context(snapshot, hole_id)` tool. It returns the same record for one hole, which is cheaper than re-checking the project. The M3 acceptance tests in design §14.3 require the language server, after replaying the same edits, to match `hole_context` byte for byte. ## Recommended agent workflow [#recommended-agent-workflow] 1. Write every signature the task needs: types, effect rows, `Fail<...>` unions, and contracts. Bodies are holes. 2. Run `sakura check --json`. Fix signature-level errors first; these are what a human reviews. 3. Write tests for the behavior the task asks for. 4. Fill holes one at a time, using `candidates`, `locals`, and `contracts_in_scope`. Re-check after each. 5. When no `hole` records remain, run `sakura test --json`, then summarize any signature changes for review. The design does not yet say whether `sakura test` runs a program that still contains holes. Assume it does not. ## Next [#next] Back to the [For Agents overview](/docs/agents), or see the [standard library](/docs/stdlib). # FAQ URL: /docs/faq Markdown: /docs/faq.md > Honest answers to common questions about Sakura Lang's status, design choices, and relationship to other languages. ## Is Sakura Lang usable today? [#is-sakura-lang-usable-today] Not for real work yet. The v0 language is fully specified in [docs/design.md](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md), and the compiler is a Rust workspace in [compiler/](https://github.com/icantcodefyi/sakura-lang/tree/master/compiler). It parses, resolves, type- and effect-checks and runs the core language and substantial M3 functionality on JavaScript. All twelve examples pass their configured modes, with `agent_loop` and `secrets` still check-only. M3 completion, WASM/static verification and native execution remain ahead. See the [roadmap](/docs/roadmap). ## Why not just use TypeScript with Effect-TS? [#why-not-just-use-typescript-with-effect-ts] Effect-TS is excellent and shows that typed effects and errors work for real services. Sakura Lang takes its application model directly. The difference is enforcement. In TypeScript, the effect system is a library: any function can still throw, call `fetch` directly, read `process.env`, or cast to `any`, and nothing in the language stops it. An agent under pressure to make a test pass will find those exits. In Sakura Lang, effects are the only way to do IO and `Fail` is the only way to fail with an expected error, so the signature is a guarantee rather than a convention. Sakura Lang also adds things a library cannot: refinement types and contracts, authority limited by handlers and `[permissions]`, `Untrusted` and `Secret`, and compiler-managed memory. ## What does Sakura Lang take from other languages? [#what-does-sakura-lang-take-from-other-languages] A lot, and it says so. The [prior art](/docs/prior-art) page goes through Koka, Effect-TS, Unison, Roc, Rust, Go, Gleam, Lean, Dafny, Elm, and others, and for each one lists what Sakura Lang takes, what it rejects, and why. In short: Koka's effect rows and Perceus reference counting, Effect-TS's typed errors and requirements, Unison's plain handlers and compiler MCP server, Roc's platform-owned authority, Rust's `Result` and exhaustive matching, and Go's single toolchain and option-free formatter. Sakura Lang is a new language, not a fork of any of them. ## Why reference counting? What about cycles? [#why-reference-counting-what-about-cycles] On the native backend (milestone M5), reference counting inserted at compile time gives predictable memory use and no collector pauses, without asking the programmer to reason about lifetimes. With Perceus-style reuse, an update to a uniquely owned value becomes an in-place write. The JavaScript backend, which comes first, uses the host GC with identical semantics. Cycles cannot be built, because values have value semantics: a value can only refer to values that existed before it. Cyclic data such as graphs uses `Graph` with `NodeId` keys. The only stateful constructs are handler `var` state and `Graph`, and both are confined. See [Memory model](/docs/tour/memory). ## Why `!` for effects? [#why--for-effects] It reads as "and also": `-> User ! {Users, Clock}` returns a `User` and also performs `Users` and `Clock`. It is short and visually separates the return type from the effect row. Omitting `!` always means pure. ## Why no `async`? [#why-no-async] Every Sakura Lang function may suspend, and nothing in application code can observe whether it did, so an `Async` label would carry no information. Suspension is a property of handlers: `Clock.sleep` suspends under the live clock and returns at once under `Clock.fixed`. Concurrency uses the `Task` effect with structured scopes. See [Concurrency](/docs/tour/concurrency). ## Will humans enjoy writing Sakura Lang? [#will-humans-enjoy-writing-sakura-lang] That is a goal, but not the primary one. Sakura Lang is designed first to be easy for agents to write correctly and for humans to review. In practice those overlap: a small grammar, no null, no exceptions, and signatures that say what a function does are pleasant for people too. Whether effect rows stay readable in large programs is something the first milestones are meant to find out. ## Is Sakura Lang tied to one AI vendor? [#is-sakura-lang-tied-to-one-ai-vendor] No. The interfaces are open formats: Markdown docs and `llms.txt`, JSON Lines diagnostics, and the Model Context Protocol. Any agent that can run a command or speak MCP can use the toolchain. The `Llm` effect is provider-neutral; the installed handler chooses the provider. v0 ships one provider handler (`Llm.anthropic`) plus `Llm.scripted` and `Llm.replay` for offline tests. ## Are there performance numbers? [#are-there-performance-numbers] No. The plan is JavaScript first, then WASM (M4), then native code via Cranelift with Perceus reference counting (M5). Benchmarks will be published when there is something real to benchmark, with the code to reproduce them. ## Why no `mut`? [#why-no-mut] Mutation plus aliasing is the source of many hard bugs, and it is what forces Rust to have a borrow checker. Sakura Lang has `var` for rebinding a name, but no value can be mutated through an alias: `u.email = e` on a `var u` is sugar for `u = User { ..u, email: e }`, and closures capture by value. The compiler reuses buffers in place when it can prove a value is unshared, so loops that build lists stay cheap. ## What happens when a contract cannot be proven? [#what-happens-when-a-contract-cannot-be-proven] In v0, nothing is proven by a solver: every `requires`, `ensures`, and `where` clause is checked at runtime in every build mode, including `--release`. A violation is a defect that terminates the task. The planned `sakura test --contracts` will also generate property tests from contracts. From milestone M4, `sakura verify` tries to prove each obligation with an SMT solver; only a `proved` status removes that one check, and `unknown` keeps it. See [Contracts](/docs/tour/contracts). ## Can Sakura Lang call existing libraries in other languages? [#can-sakura-lang-call-existing-libraries-in-other-languages] On the JavaScript backend, yes, through `@external` declarations. Foreign calls perform a `Foreign` effect that only the host root handler can provide, so `main` must declare it. Exceptions become a typed `ForeignError`, callbacks passed to foreign code must be pure, and any package that uses `@external` is marked `trusted` in the build manifest. See [design §13.6](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md). ## What is the license? [#what-is-the-license] Dual-licensed under MIT or Apache-2.0, at your option. See `LICENSE-MIT` and `LICENSE-APACHE` in the repository. ## How can I help? [#how-can-i-help] - Read [docs/design.md](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md) and the [Language Tour](/docs/tour/basics), and open issues on [GitHub](https://github.com/icantcodefyi/sakura-lang) where the design is unclear, inconsistent, or wrong. - Try writing a small program you know well in Sakura Lang syntax, by hand or with an agent, and report what was awkward. - Point out prior art the design should learn from. - Language changes go through [RFCs](https://github.com/icantcodefyi/sakura-lang/tree/master/docs/rfcs); see [CONTRIBUTING.md](https://github.com/icantcodefyi/sakura-lang/blob/master/CONTRIBUTING.md). # Introduction URL: /docs Markdown: /docs.md > Sakura Lang is an agent-first, general-purpose programming language in which the signature is the contract. > **Pre-alpha** > > The source-built compiler runs the core language and much of M3 on JavaScript: contracts, typed effects and failures, std handlers, permissions, LSP and MCP. M3 is still in progress; no release or stable compatibility contract exists. See the [roadmap](/docs/roadmap) for implemented features and remaining gates. Sakura Lang is the language agents write: code that keeps its promises. Most new code is now written by AI agents. Reviewing every line of that code does not scale, and the parts that matter most (what a function touches, how it can fail, what it guarantees) are usually invisible in mainstream languages. Sakura Lang is designed so that those parts are always written down, in one place, and checked by the compiler. ## The core idea [#the-core-idea] **The signature is the contract.** Humans review signatures. The compiler checks that the code keeps its promises. A Sakura Lang signature states the inputs (with refinements), the output, every effect the function may perform, every way it can fail, and its `requires`/`ensures` clauses. The body cannot do anything the signature does not allow. ```sakura title="src/users.sakura" type Email = String where String.len(it) <= 254 && String.contains(it, "@") type User = { id: Uuid, email: Email, created: Instant } error EmailTaken(email: Email) error Banned type SignupError = EmailTaken | Banned effect Users { fn email_exists(email: Email) -> Bool fn is_banned(email: Email) -> Bool fn insert(user: User) -> Unit } fn signup(email: Email) -> User ! {Users, Clock, Random, Fail} ensures result.email == email { if Users.is_banned(email) { fail Banned } if Users.email_exists(email) { fail EmailTaken { email } } let user = User { id: Random.uuid(), email, created: Clock.now() } Users.insert(user) user } fn memory_users() -> Handler { handler Users { var users: List = List.empty() fn email_exists(email) { List.any(users, fn(u) { u.email == email }) } fn is_banned(email) { false } fn insert(user) { users = List.push(users, user) } } } test "duplicate email fails" { with memory_users() { let _ = signup(Email.from("a@b.co")) expect_fail(fn() { signup(Email.from("a@b.co")) }, EmailTaken { email: Email.from("a@b.co") }) } } ``` Reading only the signature of `signup`, a reviewer learns that it: - accepts only a string that satisfies the `Email` refinement, - performs `Users`, `Clock`, and `Random`, and nothing else (no network, no file system, no environment), - can fail only with `EmailTaken` or `Banned`, - returns a user whose email is the one passed in. This `ensures` clause is checked at runtime on every return. The test installs an in-memory handler for `Users`. `Clock` and `Random` need no handler in a test, because tests run under deterministic defaults (`Clock.fixed`, `Random.seeded`). No mocking library is involved: these are just different handlers for the same effects. ## Six pillars [#six-pillars] **1. Effects you can see.** Every function type carries an effect row after `!`. Expected failure is the built-in `Fail` effect over a union of `error` tags. There are no exceptions and no null. Defects such as overflow or a violated contract are not effects; they terminate the task. See [Effects](/docs/tour/effects) and [Handlers](/docs/tour/handlers). **2. Memory without a borrow checker.** Every value has value semantics: no aliasable mutation, no reference cells, no cycles in the safe heap, no lifetimes. The first backend is JavaScript and uses the host GC. The native backend (milestone M5) inserts reference counting at compile time with Perceus-style reuse and has no tracing GC. Speed is a design goal; nothing has been measured. See [Memory model](/docs/tour/memory). **3. Contracts, always checked.** Refinement types (`where`), `requires`, and `ensures` are checked at runtime in every build mode, including `--release`. The planned `sakura test --contracts` will generate property tests from them. Later, `sakura verify` (M4) uses an SMT solver to prove individual obligations and remove only those checks. See [Contracts](/docs/tour/contracts) and [Testing](/docs/tour/testing). **4. Secure by construction.** Effects are requirements; handlers are authority. The host supplies root handlers only for the std effects `main` declares, limited by `[permissions]` in `sakura.toml`, and code can only narrow that authority. `Untrusted` marks outside data, `Secret` cannot be shown or serialized, and tagged templates such as `sql"..."` never splice raw text. See [Capabilities and security](/docs/tour/capabilities). **5. Built for agents.** A grammar that fits on one page, one way to do each thing, typed holes (`?name`), JSON Lines diagnostics with byte spans and fixes labelled by impact, specs frozen during agent repair, and a compiler MCP server. See [For Agents](/docs/agents). **6. Batteries included.** One `sakura` binary for `new`, `check`, `build`, `run`, `test`, `fmt`, `fix`, `doc`, packages, LSP, and MCP. The v0 standard library covers HTTP client and server, routing, SQL (SQLite first), JSON with derived schemas, CLI parsing, testing, and typed LLM calls with tools and budgets. See [Standard library](/docs/stdlib). ## Status [#status] - **Design:** [docs/design.md](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md) is the authoritative v0 design. Anything it does not specify is not in v0. - **Compiler:** a Rust workspace in [compiler/](https://github.com/icantcodefyi/sakura-lang/tree/master/compiler). It parses, resolves, type- and effect-checks and executes the core language and implemented M3 features on JavaScript. - **Code on this site** follows the v0 design. The repository examples have executable coverage; planned features are labeled and the roadmap records the remaining M3 gates. The [roadmap](/docs/roadmap) lists every milestone, M0 to M5. ## Where to go next [#where-to-go-next] - [Why Sakura Lang](/docs/why-sakura-lang): The problem with agent-written code and the design principles behind Sakura Lang. - [Language Tour](/docs/tour/basics): Nine short pages covering the whole language, from bindings to concurrency. - [For Agents](/docs/agents): llms.txt, the MCP server, JSON diagnostics, and typed holes. - [Prior art](/docs/prior-art): What Sakura Lang takes from other languages, and what it rejects. # Prior art URL: /docs/prior-art Markdown: /docs/prior-art.md > What Sakura Lang takes from Koka, Effect-TS, Rust, Lean, Dafny, Elm, Gleam and others, what it rejects, and why. Sakura Lang invents very little. Most of the design is borrowed from languages and systems that already proved an idea works. The new part is the combination, and the decision to make all of it the default for code an agent writes. This page summarizes [`docs/prior-art.md`](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/prior-art.md), which gives, for every entry, what Sakura Lang takes, what it rejects, why, and links to primary sources. The design itself is [`docs/design.md`](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md). ## The short version [#the-short-version] - **Effects and handlers** come from Koka, Effect-TS and Unison: inferred effect rows, effect-polymorphic `map`, handlers as plain values, tests that swap handlers instead of mocking. Sakura Lang ships only tail-resumptive handlers (no `resume`), as Koka's `fun` operations, and keeps rows as sets of unique labels. - **Typed errors** come from Rust, Zig, Swift and ZIO: errors as data with payloads, error sets that grow by union, `Never` as the bottom, and a hard line between expected failures (`Fail`) and defects. - **Memory** comes from Lean 4, Koka's Perceus, Swift and Hylo: value semantics, compile-time reference counting, and in-place reuse of unshared buffers. Rust's guarantees without Rust's lifetimes is a design bet, not a measured result. - **Contracts** come from Dafny, Eiffel, Racket and the refinement-type family (F\*, Liquid Haskell, Flux): `requires`/`ensures` in the signature, blame on the caller or the callee, named refinements, and checks that are never switched off in release. - **Authority** comes from Austral, Pony, Roc, Deno and LavaMoat: no ambient authority, root capabilities handed to `main` by the host, deny-by-default permissions, and authority graphs a person can review. - **Agent tooling** comes from Elm, Haskell, Hazel, Go and Gleam: typed holes as a normal editing state, diagnostics with expected and actual types and fixes, one formatter with no options, and one compiler-owned CLI. ## What Sakura Lang leaves out [#what-sakura-lang-leaves-out] Several popular features are rejected on purpose, each because it is a common source of silent bugs or of code that is hard to review: - `null`, exceptions, `any` and implicit conversions (TypeScript, Python, Go). - User-visible lifetimes and borrows (Rust), and manual `free` (Zig). - Laziness, type classes and monad transformers (Haskell). - Multi-shot handlers, effect masking and first-class continuations (Koka, Eff, Frank). - `async` coloring (TypeScript, Kotlin, Swift). Every Sakura Lang function may suspend. - Macros, operator overloading, inheritance and significant indentation. - A codebase database instead of files and Git (Unison). ## Summary table [#summary-table] | Language / system | Sakura Lang takes | Sakura Lang rejects | |---|---|---| | Koka | inferred effect rows, effect-polymorphic HOFs, tail-resumptive ops, Perceus, dot-call | duplicate-label rows, `ctl`/multi-shot, masking | | Effect-TS | typed errors + requirements, layers, schedules, schema, fibers, test clocks | wrapper type, generators, combinator sprawl | | Unison | abilities with plain handlers, content-hashed artifacts, compiler MCP | implicit ability variables, codebase database | | OCaml 5 | one-shot continuations for native runtime | unchecked effects | | Flix | purity requirements, subeffecting precedent | Boolean effect formulas | | Eff / Frank / Links | op/handler split, auto effect polymorphism, row theory, CPS reference | multi-shot, instances, multihandlers, `wild` | | Effekt | Core → CPS → JS pipeline with trampoline | capability-passing surface types | | effectful / Polysemy / Bluefin | fast service dispatch, test interpreters, scoped capability values | type-level machinery, manual capability threading | | ZIO | failures vs defects vs interruption, concurrent causes | `Nothing` as termination proof | | Swift | typed throws, `Never`, value semantics + COW, ownership modifiers (internal) | `any Error`, classes, retain cycles | | Rust | `Result`/`?`, exhaustiveness, affine resources, performance baseline, workspace model | lifetimes, borrows, traits, macros, `unwrap` culture | | Zig | inferred error sets, explicit costs, no build scripts | manual free, payload-less errors | | Gleam | small language, no null/exceptions, `pub opaque type`, compiler CLI, readable JS | `use` sugar | | Roc | platform authority, in-place mutation behind values, compact strings | no effect polymorphism | | Lean 4 | borrowed/owned conventions, reset/reuse, uniqueness diagnostics; optional proofs | dependent types everywhere | | Hylo | mutable value semantics | `inout` projections | | Vale | generational IDs for graphs | traps as lifetime strategy | | Lobster | RC elision by inferred borrows | acceptance tied to optimization | | Mojo | inferred lifetimes, explicit transfer for resources | lifetime checker, `^`, Python superset | | Austral | linear capabilities, root capability to `main` | linear collections | | Pony | object vs reference capabilities, share-immutable/transfer-isolated | capability lattice in syntax, actor GC | | Go | one toolchain, `fmt` without options, MVS, cost model | nil, ignorable errors, shared mutation | | Dafny / Verus | `requires`/`ensures` in signatures, modular proofs, frozen specs for agents | mandatory invariants/frames in v0 | | F* / Liquid Haskell / Flux | named refinement types, aliasing factored out of logic | string predicates, dependent effects | | Eiffel / Racket | contracts with blame | disable-in-release | | QuickCheck / Hypothesis / TARGET | properties, shrinking, refinement-derived generators | filter-only generation | | FoundationDB / TigerBeetle | deterministic simulation via swapped handlers | exhaustiveness claims | | Elm | `Option`/`Result`, humane diagnostics, computed semver, MVU | browser-only scope | | Haskell | typed holes, purity default, HM + signatures | laziness, type classes, transformers | | OCaml | HM with levels, value restriction, match compilation | objects, functors, structural subtyping | | TypeScript | familiar syntax, discriminated unions, type-aware decoding evidence, ESM output | `any`, null, exceptions, async coloring | | Python | readability, batteries, structured-output libraries | dynamic typing, indentation syntax | | Elixir / Erlang | supervision shape, config split, integrated tooling | dynamic typing, BEAM-only semantics | | Kotlin | structured concurrency, newline rule | `suspend` coloring, nullable absence, extensions | | MoonBit | definition-sized edits, fast checks, doc tests | unmeasured token claims | | BAML | typed LLM functions, prompt preview, partial types, output checks | separate DSL, silent coercion | | Deno / Bun / LavaMoat | deny-by-default grants, single distribution, lockfiles, authority graphs | process-wide-only permissions | | Hazel | holes as normal state, hole context for agents | structure editing only | | Vera / Magpie / Mog / AILANG | assurance statuses, MCP tooling, no ambient authority, compiler-emitted prompt | SSA surface, provider syntax, self-reported benchmarks | ## Read more [#read-more] - [`docs/prior-art.md`](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/prior-art.md): the full entry for each language, with sources. - [Why Sakura Lang](/docs/why-sakura-lang): the problem these borrowings are meant to solve. - [FAQ](/docs/faq): short answers to common comparisons. # Quickstart URL: /docs/quickstart Markdown: /docs/quickstart.md > Build the Sakura Lang compiler from source, then check, test and run a first program. > **Pre-alpha: build from source** > > The compiler runs the core language and substantial M3 functionality on JavaScript, including contracts, std handlers, permissions, scaffolding, LSP and MCP. No release or installer is published. See the [roadmap](/docs/roadmap) for remaining M3 gates and planned M4/M5 work. ## Install from source [#install-from-source] You need [rustup](https://rustup.rs) and [Node.js](https://nodejs.org) (`sakura run` and `sakura test` execute the generated JavaScript with `node`; CI uses Node 26). The repository pins its Rust toolchain in `compiler/rust-toolchain.toml`, so rustup fetches the right version on first build. ```bash git clone https://github.com/icantcodefyi/sakura-lang cd sakura-lang/compiler cargo install --locked --path crates/sakura_cli sakura --version ``` ```text sakura 0.0.0 ``` The standard library is embedded in the binary, so `sakura` works from any directory. To try it without installing, run `cargo run -p sakura_cli -- ` inside `compiler/`. ## Run an example [#run-an-example] `compiler/examples/` holds the design's example programs as projects. All twelve pass the configured run/test/check modes listed on the [roadmap](/docs/roadmap): ```bash sakura run compiler/examples/counter sakura test compiler/examples/typed_errors ``` ```text 1. milk 2. eggs 1. bread ``` `counter` is a user-defined effect whose handler keeps state; `typed_errors` recovers from typed failures with `try`/`catch`. The examples README records each mode; `agent_loop` and `secrets` currently have check-only coverage. ## Create a project [#create-a-project] Run `sakura new cli hello` to scaffold a project (`lib`, `agent`, and `api` templates also exist), or write the two files below by hand. A project is a `sakura.toml` manifest and a `src/` directory, and one file is one module. ```toml title="hello/sakura.toml" [package] name = "hello" version = "0.1.0" edition = "2026" [permissions] ``` `[permissions]` is the ceiling on what the program may do (design §6). This program only prints, so it is empty. `sakura run` enforces startup grants and passes the table as `SAKURA_PERMISSIONS` for host-handler enforcement. Missing required root authority exits with 75. ```sakura title="hello/src/main.sakura" /// Build a greeting for a person. fn greet(name: String) -> String { "Hello, {name}" } pub fn main() -> Unit ! {Console} { Console.print(greet("world")) } test "greets by name" { expect_eq(greet("Ada"), "Hello, Ada") } property "always starts with Hello"(name: String) { expect(String.starts_with(greet(name), "Hello, ")) } ``` `greet` has no `!`, so it is pure. `main` declares `Console`, the only effect the program performs. This is the canonical layout: `sakura fmt` keeps a short body on its signature's line. ## Check, test and run [#check-test-and-run] Run these from `hello/`. With no path, each command uses `src/`. ```bash sakura check ``` ```text no problems ``` `sakura check` parses, resolves names, checks types and effects, and checks that every `match` is exhaustive. It generates no code, and it accepts typed holes. ```bash sakura test ``` ```text test greets by name ... ok test always starts with Hello ... ok 2 passed, 0 failed, 0 skipped (seed 0) ``` `sakura test` runs every `test` and `property` block under deterministic handlers (a fixed clock, a seeded random source, captured console output) with the network denied. `--seed 7` changes the property inputs, `--filter greets` runs matching tests only, and `--json` writes one record per test. ```bash sakura run ``` ```text Hello, world ``` `sakura run` checks the program, compiles it to JavaScript under `target/sakura/js/`, and runs `main` with `node`. Arguments after `--` go to the program. The exit code is the program's: `0` for success, `1` when `main` fails with an uncaught typed error, `70` for a defect such as integer overflow, with one JSON `defect` record on stderr. `sakura fmt` rewrites files in the one canonical format; `sakura fmt --check` changes nothing and exits `1` if a file is not formatted. ## Check it, as an agent would [#check-it-as-an-agent-would] Suppose `greet` starts printing without declaring it: ```sakura title="hello/src/main.sakura" /// Build a greeting for a person. fn greet(name: String) -> String { Console.print("greeting {name}") "Hello, {name}" } pub fn main() -> Unit ! {Console} { Console.print(greet("world")) } ``` ```text error[V3001]: function `greet` performs `Console`, which its signature does not allow (allowed: `{}`) ┌─ src/main.sakura:2:4 │ 2 │ fn greet(name: String) -> String { │ ^^^^^ 3 │ Console.print("greeting {name}") │ -------------------------------- `Console.print` performs `Console` here │ = expected: {} = found: {Console} = help: Add `Console` to the signature = help: Handle the effect locally with `with` = see `sakura explain V3001` 1 error ``` `sakura check --json` writes the same error as JSON Lines, one record per line, followed by a `summary` record. The `diagnostic` record, pretty-printed with hashes shortened: ```json { "schema_version": "1.0", "kind": "diagnostic", "id": "d:e8f9a0e1d729d3d1", "code": "V3001", "severity": "error", "phase": "effect", "message": "function `greet` performs `Console`, which its signature does not allow (allowed: `{}`)", "snapshot": "sha256:9470da24…", "symbol_id": null, "spans": [ { "uri": "file:///…/hello/src/main.sakura", "content_hash": "sha256:f1ee9f10…", "start_byte": 38, "end_byte": 43, "role": "primary" }, { "uri": "file:///…/hello/src/main.sakura", "content_hash": "sha256:f1ee9f10…", "start_byte": 72, "end_byte": 104, "role": "cause", "label": "`Console.print` performs `Console` here" } ], "related_ids": [], "expected": { "render": "{}", "row": { "labels": [], "tail": null } }, "actual": { "render": "{Console}", "row": { "labels": [{ "effect": "std::Console", "args": [] }], "tail": null } }, "obligation": null, "counterexample": null, "explanation_uri": "sakura://explain/V3001", "fixes": [ { "id": "f:e8f9a0e1d729d3d1.0", "title": "Add `Console` to the signature", "applicability": "suggested", "impact": "interface_change", "edits": [{ "span": { "uri": "file:///…/hello/src/main.sakura", "content_hash": "sha256:f1ee9f10…", "start_byte": 67, "end_byte": 67, "role": "primary" }, "replacement": " ! {Console}" }] }, { "id": "f:e8f9a0e1d729d3d1.1", "title": "Handle the effect locally with `with`", "applicability": "incomplete", "impact": "local", "edits": [/* wraps the body in `with ?handler { … }` */] } ] } ``` Spans are zero-based, half-open UTF-8 byte offsets, and an edit applies only while `content_hash` still matches. The first fix changes the function's interface, so it is `suggested`, never `machine_applicable`: an agent must not widen a signature silently. `sakura explain V3001` prints the rationale with a wrong and a fixed program. See [Diagnostics](/docs/agents/diagnostics) for the format. A typed hole is how an agent asks the compiler what fits: ```sakura fn unfinished(x: Int) -> Int { ?result } ``` ```text hole ?result: expected Int, effects {}; locals [x: Int]; candidates [x, Int.abs(?i), Int.clamp(?i, ?lo, ?hi), …] ``` `sakura check` accepts holes; `sakura build` and `sakura run` reject them with `V2050`. See [Typed holes](/docs/agents/typed-holes). ## Commands [#commands] | Command | Today | | --- | --- | | `sakura check [--json] [paths]` | Parse, resolve, type and effect check, exhaustiveness. Holes allowed | | `sakura build [paths]` | Check, then generate JavaScript. Holes rejected (`V2050`) | | `sakura run [path] [-- args]` | Build and run `main` with `node` | | `sakura test [--json] [--seed N] [--filter text] [--update-snapshots]` | Run tests and properties with deterministic handlers | | `sakura fmt [--check] [paths]` | Canonical format. There are no options | | `sakura explain V3001` | Rationale, causes and repairs for a diagnostic code | | `sakura prompt` | Print the language guide for agents ([llms.txt](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/llms.txt)) | Also available: `sakura new`, `sakura init`, `sakura fix`, `sakura check --frozen-specs all [--since rev]`, `sakura lsp`, and `sakura mcp serve`. `sakura doc` and trust-site `sakura audit` are M3 completion work. Package commands remain planned; verification/WASM are M4 and native is M5. ## Next [#next] Start the [Language Tour](/docs/tour/basics). # Roadmap URL: /docs/roadmap Markdown: /docs/roadmap.md > Finish M3, establish WASM and verification in M4, then add native code and Perceus in M5. > **Where things stand** > > Sakura Lang is pre-alpha and builds from source. The core language and substantial M3 functionality run on JavaScript. M3 is not complete. WASM execution and static contract verification are not available through the CLI; isolated M4 work is allowed before the M3 gates. There are no milestone dates or published releases. The sequence is **M3 → M4 → M5**. The [completion plan](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/plans/m3-completion-and-m4.md) defines the gates; [design §17.81–§17.99](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md) defines the provisional M4 decisions. M0–M2 provide the existing parser, type/effect checker, JS backend, handlers, typed holes, formatter and test runner. ## M3: finish the JS language and agent tooling [#m3-finish-the-js-language-and-agent-tooling] Working today: - Runtime contracts and refinements, bounded compile-time literal evaluation (`V5004`, with `V5002` retired), refined-target `Untrusted.validate` (`V5003`) and collection-only `W5005`; opaque constructors and fields rejected outside their module with `V1006`; compiler-known prelude `Trust`, with `@trust("reason")` restricted to trusted sink arguments (`V6001`). - Scoped capability/resource escape checks (`V6002`/`V6003`) and `V6001` for user externals receiving or constructing another package's capabilities or any handler. - Std data, JSON schemas and codecs, SQLite and memory SQL, filesystem and file callbacks, HTTP, serving, CLI, structured tasks, budgets, tools and agent loops. `sql"…"` arguments use `SqlValue.from` conversions and become bound parameters. - `Tool.of` on a named `@tool` function, `@llm` body generation, and effect-row parameters on types: `Tool`, `Agent`, `Router`. - `sakura new `, `init`, LSP, compiler MCP, impact-aware fixes and frozen-spec checks. Analysis reuses unchanged modules' signatures and bodies; a private-body edit parses, lowers and checks only its module. Item collection, imports and exports remain global. Bodies reachable from a refinement predicate are part of the interface hash, so editing one invalidates reuse. - [`sakura audit`](/docs/agents/audit), [`sakura doc`](/docs/agents/doc), [`sakura build --manifest`](/docs/agents/build-manifest) and [`sakura test --record`](/docs/agents/cassettes). The audit still omits undecided literal conversions. - `Html.from` and nested `Html` templates, plus `Http.recording`, `Llm.replay` and `Test.fixtures` for offline cassettes. - `sakura run` checks declared root permissions (exit 75 on denial) and passes `[permissions]` as `SAKURA_PERMISSIONS`; host handlers enforce filesystem, network and environment grants. This is not an OS sandbox for arbitrary JS or trusted FFI. - Tests accept escaping typed failures and report them as failed tests. `Llm.anthropic` requests SSE and delivers one partial callback per text delta; local mock tests cover this, not a live-provider guarantee. All twelve repository examples pass their current harness modes: seven run plus test (`hello`, `contracts`, `counter`, `typed_errors`, `word_count`, `graph`, `secrets`) and five test (`json_api`, `llm_extract`, `property`, `concurrency`, `agent_loop`). None is check-only. `llm_extract`, `agent_loop` and `secrets` replay committed **synthetic** cassettes with the test network denied. Their fixture READMEs describe provenance; this is not live-provider validation. ### M3 exit gates [#m3-exit-gates] All ten gates must pass on `origin/master` on Ubuntu and macOS before integrating the M4 backend with the existing compiler. The isolated early milestones below are the plan §3.5 exception. Commands are run in `compiler/` unless stated otherwise. These are acceptance requirements, not a claim that every command or flag is available today. 1. `cargo fmt --all -- --check`, `cargo clippy --workspace --all-targets --locked -- -D warnings`, and `cargo test --workspace --locked` pass with both `UI_MILESTONE` and `SEMANTICS_MILESTONE` at 3. 2. Every registered diagnostic except `V9999`, M5's `W8001` and retired codes such as `V5002` is constructed by a crate and has a passing UI fixture. `W8001` has a fixture skipped by milestone. Compare `grep -rhoE '\b[VWD][0-9]{3,4}\b' crates --exclude-dir=sakura_diagnostics | sort -u` against the registry. 3. M3 acceptance tests cover contract blame (`m3.rs::m3_acceptance_contracts`, `D020`/`D010`), CLI exit 75, `tests/ui/V6003_route_handler_returns_file.sakura`, stale/Unicode LSP replay matching MCP `hole_context`, and `recheck.rs` counters. 4. `cargo test -p sakura_cli --test examples`: all twelve examples execute in run or test mode, none only check. `llm_extract`, `agent_loop` and `secrets` replay committed cassettes with network denied. 5. `sakura test --std` passes; `std/INDEX.md` has no Planned section; `docs/llms.txt` has no remaining M3 items except `Sql.postgres` if the owner defers it. 6. CLI integration tests cover `sakura audit --json`, `sakura doc --search "fn(List) -> Int"`, `sakura build --manifest`, `sakura test --contracts`, and `sakura test --record`. 7. `SAKURA_DIFF_PROGRAMS=2000 cargo test -p sakura_eval --test differential` passes with the expanded generator. Each of `parse`, `check`, `fmt_idempotent`, and `row_unify` runs clean via `cargo fuzz run -- -max_total_time=600` on the gate-review day. 8. `cargo test -p sakura_fmt --test props` and the formatter corpus pass; `sakura fmt --check` is clean on `std/`, `examples/`, and `tests/`. 9. In `website/`, `bun run fmt:check`, `bun run lint`, `bun run typecheck`, and `bun run build` pass; documentation matches the implemented behavior and this plan. 10. The named CODEOWNERS maintainer reviews the escape rules and cassette format because both affect security. The local documentation verification passed the CLI integration suite, all twelve example modes and all 101 std tests. It did not establish all ten gates on both CI platforms. `--contracts` is absent, streaming HTTP remains planned, and the audit literal inventory is incomplete. Gate 5 still has a Planned section in `std/INDEX.md`; named maintainer review and the full gate-day fuzz/diagnostic evidence remain required. ## M4: WASM and contract verification [#m4-wasm-and-contract-verification] The [ABI and encoding research](https://github.com/icantcodefyi/sakura-lang/blob/master/research/09-m4-abi-and-encoding.md) and [runtime ABI](https://github.com/icantcodefyi/sakura-lang/blob/master/compiler/runtime/rt/ABI.md) have landed. Plan §3.5 starts three isolated tracks before the M3 gates; it does not declare their implementation complete: - **R1 → R2/R3:** runtime skeleton, allocator and bounded reference counting; then values/collections (R2) and frames, trampoline and scheduler (R3). - **L1 → L2:** LIR model, layout/ownership validation and printing; then lowering and an interpreter checked against stored differential expectations. - **V1 → V2:** verification conditions and SMT-LIB2 output; then bounded solver workers, failure handling, cross-checks and cache records. Before the gates, these tracks may add new crates, `compiler/runtime/rt` and ABI tests. They may not change M0–M3 crates, `runtime/js`, std, the CLI or CI. L1/V1 consume committed Core snapshots instead of a live `sakura_core` dependency. R4, L3, V3 and W1 integrate after the gates and their listed prerequisites. In particular, `sakura verify`, `--emit lir` and a WASM CLI target are not available today. - **LIR:** closure conversion, explicit continuations and handler environments, boxing at polymorphic boundaries, conservative balanced `Dup`/`Drop`, and a layout table. An interpreter must agree with Core on the differential corpus. - **Runtime:** a bounded `no_std` wasm32 runtime, size-class allocator, conservative reference counting, non-recursive destruction, persistent collections, scheduler and explicit value/fail/defect/cancel/pending outcomes. Follow the committed ownership graph and versioned ABI. - **WASM emission:** one bounded, non-shared linear memory, tables, multi-value and bulk memory, validated against an explicit feature allowlist. Node imports reuse the JS host handlers through copies. Wasmtime is an additional gate for pure and fake-handler programs. - **Verification:** bounded Z3 workers generate definedness and predicate obligations from Core. Only a matching `proved` result permits removal of that individual runtime check. `disproved`, `unknown` and `unsupported` keep checks; cvc5 cross-checks every corpus `unsat`. Body edits invalidate the function's own proofs, even when its signature stays unchanged. - **Parity and layout:** compare stdout and effect/finalizer/cancellation traces across JS and WASM on Ubuntu and macOS. JS's safe-integer range and WASM's i64 range have explicit backend-specific fixtures. Tagged `Option` is the baseline; niche layout remains opt-in until nested options and generic calls preserve values, traces and ownership. For `scale(p, total) = total * p / 100`, proving `ensures result <= total` on normal return does not prove the multiplication cannot overflow. Overflow is a separate obligation. The wrong `/ 50` body has the counterexample `p = 100, total = 1`. M4 excludes WasmGC, JSPI, typed continuations, WASI components, browser std support, threads, Perceus reuse/borrow inference, loop/recursion proofs, Float/String solver theories and proof certificates. ## M5: native code and Perceus [#m5-native-code-and-perceus] A Cranelift backend adds native execution, borrow inference and reference-counted in-place reuse, `W8001` copy diagnostics, and internal region optimizations. Acceptance requires that live aliases preserve values, dead aliases allow reuse, cancellation and handler state pass sanitizer checks, and JS, WASM and native agree on the applicable corpus. No native performance claim is established yet. ## Decisions still open [#decisions-still-open] Plan §6 retains questions about package-management placement, real-provider recording ownership and Windows coverage. The solver decision is now recorded in §3.5: a pinned external binary on `PATH` or `--solver`, without automatic downloads (V2 pins Z3 5.3.1 and cvc5 1.4.2). The std index records PostgreSQL as deferred past M4; SQLite is implemented and `Sql.postgres` is absent. These are scope decisions, not release dates. # Standard library URL: /docs/stdlib Markdown: /docs/stdlib.md > Public std signatures generated from compiler/std/INDEX.md, with current JS limitations. {/* Generated by scripts/generate-stdlib.mjs. Edit compiler/std/INDEX.md for signatures. */} The source-built compiler embeds this standard library. This inventory is generated from [compiler/std/INDEX.md](https://github.com/icantcodefyi/sakura-lang/blob/master/compiler/std/INDEX.md). The signature blocks below are API declarations, not standalone programs. Their owning modules are checked and their tests exercised by `sakura test --std`. Regenerate this page from `website/` with `bun scripts/generate-stdlib.mjs`. Use [`sakura doc`](/docs/agents/doc) for offline type-pattern search and symbol documentation. The [capabilities tour](/docs/tour/capabilities), [contracts tour](/docs/tour/contracts) and [cassette guide](/docs/agents/cassettes) give executable examples of the recently landed APIs. ## Current scope and limitations [#current-scope-and-limitations] - `Http.recording`, `Llm.replay`, `Test.fixtures`, `Untrusted.validate` and `Html.from` are implemented. - Streaming HTTP (`Http.stream`, `Http.read_chunk`, `Body`, `Incoming`, design §17.88) remains planned; it is absent from these signatures. - `Sql.postgres` is not implemented. The source index records it as deferred past M4; SQLite and memory SQL cover the current examples. - Direct `Untrusted` SQL interpolation currently fails with `V2032`, despite the broader claim in the source index's deviations. Validate to a refined string first. - `Task.par_map` lives in `std/task`, not `List`. `Env.host()` takes no scope argument; use `Env.only` for attenuation. - JS `Int` arithmetic has a safe-integer range of ±(2^53−1); full-range i64 execution is planned for WASM. WASM and native execution are not CLI targets yet. - M3 still has [exit gates](/docs/roadmap). This signature inventory does not claim that M3, multi-package tooling or static verification is complete. ## Prelude [#prelude] In scope in every module without an import (design §9, with the additions marked +): | Kind | Names | |---|---| | Compiler-known types | `Unit Bool Int Float String Bytes Never List Map Set Range Handler Secret Untrusted` | | Std types | `Option Result` · + `Duration Instant Uuid Json Level` (prelude effects mention them; §15.1 and §15.4 use `Instant.format_iso`, `Level.Warn`, `Json.String` unimported) | | Constructors | `Some None Ok Err` (other variants are used qualified: `Level.Warn`, `Json.String(s)`) | | Effects | `Fail` (compiler-known) `Console Clock Random Env Log Task` · + `Test` | | Functions | `show` · + `expect expect_eq expect_fail expect_fails_with expect_defect expect_output expect_snapshot` (they need `Test`, which only `test`/`property` bodies have; elsewhere they are `V3001`) | | Tags (M3) | `sql html url` (`shell` is reserved by the prelude; no `std/shell` exists yet) | Not in the prelude, import them: `import std/retry.{Policy, retry}`, `import std/task.{Timeout, timeout}`, `import std/gen` (`gen.int(0, 9)`), `import std/graph.{Graph, NodeId}`, and the M3 modules. Home modules (design §4.9): `T.f(…)` and `x.f(…)` with `x: T` call `f` in the module that declares `T`; for compiler-known types it is `std/` (`List` → `std/list`). An effect's home module is the one declaring it, and `Eff.f(…)` calls a home-module function when `f` is not an operation (`Console.captured()`, `Clock.fixed(t)`, `Random.seeded(1)`). ## Signatures [#signatures] ### `std/core` [#stdcore] ```sakura fn show(a: A) -> String // @external type Violation = { type_name: String, clause: String, value_summary: String } type Trust ``` ### `std/unit` [#stdunit] ```sakura fn ignore(a: A) -> Unit ``` ### `std/option` [#stdoption] ```sakura type Option = None | Some(A) fn map(o: Option, f: fn(A) -> B ! e) -> Option ! e fn unwrap_or(o: Option, default: A) -> A fn or_fail(o: Option, err: E) -> A ! {Fail} fn expect(o: Option, msg: String) -> A // @external fn is_some(o: Option) -> Bool // @total ``` ### `std/result` [#stdresult] ```sakura type Result = Ok(A) | Err(E) fn map(r: Result, f: fn(A) -> B ! e) -> Result ! e fn map_err(r: Result, f: fn(E) -> F) -> Result fn unwrap_or(r: Result, default: A) -> A fn raise(r: Result) -> A ! {Fail} fn is_ok(r: Result) -> Bool // @total fn is_err(r: Result) -> Bool // @total ``` ### `std/list` [#stdlist] ```sakura fn len(xs: List) -> Int // @external, @total fn get(xs: List, i: Int) -> Option // @external, @total fn push(xs: List, x: A) -> List // @external fn slice(xs: List, start: Int, end: Int) -> List // @external fn empty() -> List fn concat(a: List, b: List) -> List fn map(xs: List, f: fn(A) -> B ! e) -> List ! e fn filter(xs: List, f: fn(A) -> Bool ! e) -> List ! e fn filter_map(xs: List, f: fn(A) -> Option ! e) -> List ! e fn flat_map(xs: List, f: fn(A) -> List ! e) -> List ! e fn fold(xs: List, init: B, f: fn(B, A) -> B ! e) -> B ! e fn find(xs: List, f: fn(A) -> Bool ! e) -> Option ! e fn all(xs: List, f: fn(A) -> Bool ! e) -> Bool ! e // @total fn any(xs: List, f: fn(A) -> Bool ! e) -> Bool ! e // @total fn sort(xs: List) -> List fn sort_by(xs: List, key: fn(A) -> K) -> List fn zip(a: List, b: List) -> List<(A, B)> fn enumerate(xs: List) -> List<(Int, A)> fn reverse(xs: List) -> List fn join(xs: List, sep: String) -> String fn sum(xs: List) -> Int fn first(xs: List) -> Option // @total fn last(xs: List) -> Option // @total fn contains(xs: List, x: A) -> Bool // @total fn unique(xs: List) -> List fn range(start: Int, end: Int) -> List ``` ### `std/map` [#stdmap] ```sakura fn empty() -> Map // @external fn get(m: Map, k: K) -> Option // @external, @total fn insert(m: Map, k: K, v: V) -> Map // @external fn remove(m: Map, k: K) -> Map // @external fn len(m: Map) -> Int // @external, @total fn entries(m: Map) -> List<(K, V)> // @external fn of(pairs: List<(K, V)>) -> Map fn contains(m: Map, k: K) -> Bool // @total fn update(m: Map, k: K, f: fn(Option) -> V ! e) -> Map ! e fn keys(m: Map) -> List fn values(m: Map) -> List ``` ### `std/set` [#stdset] ```sakura fn empty() -> Set // @external fn insert(s: Set, x: A) -> Set // @external fn contains(s: Set, x: A) -> Bool // @external, @total fn len(s: Set) -> Int // @external, @total fn to_list(s: Set) -> List // @external fn of(xs: List) -> Set ``` ### `std/string` [#stdstring] ```sakura error Utf8Error(offset: Int) fn len(s: String) -> Int // @external, @total fn chars(s: String) -> List // @external fn graphemes(s: String) -> List // @external fn split(s: String, sep: String) -> List // @external fn trim(s: String) -> String // @external fn starts_with(s: String, prefix: String) -> Bool // @external, @total fn ends_with(s: String, suffix: String) -> Bool // @external, @total fn contains(s: String, needle: String) -> Bool // @external, @total fn replace(s: String, from: String, to: String) -> String // @external fn to_lower(s: String) -> String // @external fn to_upper(s: String) -> String // @external fn parse_int(s: String) -> Option // @external fn parse_float(s: String) -> Option // @external fn to_bytes(s: String) -> Bytes // @external fn from_bytes(b: Bytes) -> Result // @external fn slice(s: String, start: Int, end: Int) -> String // @external fn repeat(s: String, n: Int) -> String requires n >= 0 fn pad_left(s: String, width: Int, fill: String) -> String requires len(fill) > 0 ``` ### `std/int` [#stdint] ```sakura fn to_float(i: Int) -> Float // @external fn to_string(i: Int) -> String // @external fn checked_add(a: Int, b: Int) -> Option // @external fn wrapping_add(a: Int, b: Int) -> Int // @external fn abs(i: Int) -> Int // @total fn min(a: Int, b: Int) -> Int // @total fn max(a: Int, b: Int) -> Int // @total fn clamp(i: Int, lo: Int, hi: Int) -> Int requires lo <= hi fn pow(base: Int, exp: Int) -> Int requires exp >= 0 ``` ### `std/float` [#stdfloat] ```sakura fn round(f: Float) -> Int // @external fn to_int_checked(f: Float) -> Option // @external fn sqrt(f: Float) -> Float // @external fn format(f: Float, decimals: Int) -> String requires decimals >= 0 // @external fn is_nan(f: Float) -> Bool ``` ### `std/bytes` [#stdbytes] ```sakura fn len(b: Bytes) -> Int // @external, @total fn get(b: Bytes, i: Int) -> Option // @external, @total fn from_list(xs: List) -> Bytes requires List.all(xs, fn(x) { x >= 0 && x <= 255 }) // @external ``` ### `std/duration` [#stdduration] ```sakura type Duration = Duration(Int) fn millis(n: Int) -> Duration fn seconds(n: Int) -> Duration fn minutes(n: Int) -> Duration fn to_millis(d: Duration) -> Int ``` ### `std/instant` [#stdinstant] ```sakura type Instant = Instant(Int) error TimeError(input: String) fn from_unix_millis(ms: Int) -> Instant fn to_unix_millis(i: Instant) -> Int fn add(i: Instant, d: Duration) -> Instant fn diff(a: Instant, b: Instant) -> Duration fn format_iso(i: Instant) -> String fn parse(text: String) -> Result ``` ### `std/uuid` [#stduuid] ```sakura opaque type Uuid = Uuid(String) error UuidError(input: String) fn to_string(u: Uuid) -> String fn parse(text: String) -> Result fn from_canonical(text: String) -> Uuid requires is_canonical(text) fn is_canonical(text: String) -> Bool // @total ``` ### `std/json` [#stdjson] ```sakura type Json = Null | Bool(Bool) | Number(Float) | String(String) | Array(List) | Object(Map) error DecodeError(path: String, message: String) fn encode(value: T) -> String // @external fn decode(text: String) -> Result // @external fn parse(text: String) -> Result // @external fn schema(witness: Option) -> Json // @external fn get(j: Json, key: String) -> Option fn get_string(j: Json, key: String) -> Option ``` ### `std/test` [#stdtest] ```sakura effect Test { fn record_failure(message: String) -> Unit fn failures() -> Int fn output(line: String) -> Unit fn output_lines() -> List fn snapshot(name: String, rendered: String) -> Unit fn fixtures() -> Dir } fn expect(cond: Bool) -> Unit ! {Test} fn expect_eq(actual: A, expected: A) -> Unit ! {Test} fn expect_fail(body: fn() -> T ! {Fail | e}, err: E) -> Unit ! {Test | e} fn expect_fails_with(body: fn() -> T ! {Fail | e}, pred: fn(E) -> Bool) -> Unit ! {Test | e} fn expect_defect(body: fn() -> T ! e) -> Unit ! {Test | e} // @external fn expect_output(lines: List) -> Unit ! {Test} fn expect_snapshot(name: String, value: A) -> Unit ! {Test} ``` ### `std/console` [#stdconsole] ```sakura effect Console { fn print(line: String) -> Unit fn read_line() -> Option } fn host() -> Handler // @external fn captured() -> Handler ``` ### `std/clock` [#stdclock] ```sakura effect Clock { fn now() -> Instant fn monotonic() -> Duration fn sleep(d: Duration) -> Unit } fn host() -> Handler // @external fn fixed(at: Instant) -> Handler fn virtual() -> Handler ``` ### `std/random` [#stdrandom] ```sakura effect Random { fn int(lo: Int, hi: Int) -> Int requires lo <= hi fn float() -> Float fn bytes(n: Int) -> Bytes requires n >= 0 fn uuid() -> Uuid } fn host() -> Handler // @external fn seeded(seed: Int) -> Handler ``` ### `std/env` [#stdenv] ```sakura effect Env { fn get(key: String) -> Option fn secret(key: String) -> Option> } fn host() -> Handler fn of(vars: Map) -> Handler fn only(keys: List) -> Handler ``` ### `std/log` [#stdlog] ```sakura type Level = Debug | Info | Warn | Error effect Log { fn event(level: Level, message: String, fields: List<(String, Json)>) -> Unit } fn stderr() -> Handler // @external fn host() -> Handler fn json() -> Handler // @external fn captured() -> Handler ``` ### `std/gen` [#stdgen] ```sakura type Gen = { sample: fn(Int) -> A ! {Random} } fn int(lo: Int, hi: Int) -> Gen requires lo <= hi fn bool() -> Gen fn one_of(choices: List) -> Gen requires List.len(choices) > 0 fn list(item: Gen, max_len: Int) -> Gen> requires max_len >= 0 fn string(max_len: Int) -> Gen requires max_len >= 0 fn map(g: Gen, f: fn(A) -> B) -> Gen fn pair(a: Gen, b: Gen) -> Gen<(A, B)> fn for_all(g: Gen, runs: Int, prop: fn(A) -> Unit ! {Test | e}) -> Unit ! {Random, Test | e} ``` ### `std/retry` [#stdretry] ```sakura type Policy = { retry_on: fn(E) -> Bool, max_attempts: Int, backoff: Duration, jitter: Bool, deadline: Option } fn policy(retry_on: fn(E) -> Bool, max_attempts: Int) -> Policy requires max_attempts >= 1 fn retry(p: Policy, action: fn() -> T ! {Fail | e}) -> T ! {Clock, Random, Fail | e} ``` ### `std/task` [#stdtask] ```sakura type Scope type Fiber effect Task { fn scope(body: fn(Scope) -> T ! e) -> T ! e fn spawn(s: Scope, body: fn() -> T ! e) -> Fiber ! e fn join(f: Fiber) -> T fn all(bodies: List T ! e>) -> List ! e fn race(bodies: List T ! e>) -> T ! e fn yield_now() -> Unit } fn host() -> Handler // @external fn deterministic(seed: Int) -> Handler // @external error Timeout(limit: Duration) fn timeout(limit: Duration, action: fn() -> T ! e) -> T ! {Clock, Task, Fail | e} fn par_map(xs: List, f: fn(A) -> B ! e) -> List ! {Task | e} ``` ### `std/graph` [#stdgraph] ```sakura type NodeId = NodeId(Int) type Graph = { nodes: Map, edges: Map>, next: Int } fn empty() -> Graph fn insert(g: Graph, n: N) -> (Graph, NodeId) fn get(g: Graph, id: NodeId) -> Option // @total fn connect(g: Graph, a: NodeId, b: NodeId) -> Graph requires get(g, a).is_some() && get(g, b).is_some() fn neighbors(g: Graph, id: NodeId) -> List fn len(g: Graph) -> Int ``` ### `std/untrusted` [#stduntrusted] ```sakura fn of(value: T) -> Untrusted // @external fn validate(u: Untrusted) -> Result // @external, compiler-known (§17.71) fn trust(u: Untrusted, reason: Trust) -> T fn check(u: Untrusted, clause: String, pred: fn(T) -> Bool) -> Result fn len(u: Untrusted) -> Int ``` ### `std/secret` [#stdsecret] ```sakura fn of(value: T) -> Secret // @external fn expose(s: Secret, reason: Trust) -> T fn map(s: Secret, f: fn(T) -> U) -> Secret ``` ### `std/decimal` [#stddecimal] ```sakura opaque type Decimal = { whole: Int, frac: List } error DecimalError(input: String) fn from_int(n: Int) -> Decimal fn zero() -> Decimal fn parse(text: String) -> Result fn to_string(d: Decimal) -> String fn add(a: Decimal, b: Decimal) -> Decimal fn sub(a: Decimal, b: Decimal) -> Decimal fn neg(a: Decimal) -> Decimal fn mul(a: Decimal, b: Decimal) -> Decimal fn div_checked(a: Decimal, b: Decimal, places: Int) -> Option requires places >= 0 fn round(d: Decimal, places: Int) -> Decimal requires places >= 0 ``` ### `std/path` [#stdpath] ```sakura type Path = String where is_relative(it) fn is_relative(s: String) -> Bool // @total fn of(s: String) -> Path requires is_relative(s) fn join(p: Path, rest: String) -> Path requires is_relative(rest) fn to_string(p: Path) -> String ``` ### `std/fs` [#stdfs] ```sakura type Dir type Entry = { name: String, is_dir: Bool } type Mode = Read | Write | Append opaque type File = { id: Int, path: Path, mode: Mode } // @resource (§17.70) error FsError(path: String, reason: String) effect Fs { fn root() -> Dir fn read(path: Path) -> Bytes ! {Fail} fn read_text(path: Path) -> Untrusted ! {Fail} fn write(path: Path, data: Bytes) -> Unit ! {Fail} fn list(dir: Path) -> List ! {Fail} fn exists(path: Path) -> Bool fn with_file(path: Path, mode: Mode, body: fn(File) -> T ! e) -> T ! {Fail | e} fn read_chunk(file: File, max: Int) -> Bytes ! {Fail} requires max > 0 fn write_chunk(file: File, data: Bytes) -> Unit ! {Fail} } fn host() -> Handler fn rooted(dir: Dir) -> Handler fn subdir(d: Dir, name: String) -> Dir ! {Fail} fn memory(files: List<(Path, Bytes)>) -> Handler fn read_all(file: File) -> Bytes ! {Fs, Fail} ``` ### `std/cli` [#stdcli] ```sakura effect Cli { fn args() -> List fn exit(code: Int) -> Never requires code >= 0 && code <= 255 } error CliError(message: String) fn host() -> Handler fn of(args: List) -> Handler fn parse(args: List) -> T ! {Fail} // @external ``` ### `std/url` [#stdurl] ```sakura type Url = String where is_valid(it) fn is_valid(s: String) -> Bool // @total fn host(u: Url) -> Option fn path(u: Url) -> String fn query_param(u: Url, name: String) -> Option fn template(parts: List, args: List) -> Url fn encode_component(s: String) -> String ``` ### `std/request` [#stdrequest] ```sakura type Method = Get | Head | Post | Put | Patch | Delete type Request = { method: Method, url: Url, headers: List<(String, String)>, body: String, secret_headers: List<(String, Secret)>, params: List<(String, String)> } error BadRequest(detail: String) fn of(method: Method, url: Url) -> Request fn post(path: String, body: String) -> Request fn post_json(url: Url, body: T) -> Request fn json(req: Request, limit: Int) -> T ! {Fail} fn param(req: Request, name: String) -> Option fn header(req: Request, name: String) -> Option ``` ### `std/response` [#stdresponse] ```sakura type Status = Int where it >= 100 && it <= 599 type Response = { status: Status, headers: List<(String, String)>, body: String } error HttpError(status: Option, reason: String) fn status(code: Int, body: String) -> Response requires code >= 100 && code <= 599 fn ok_json(value: T) -> Response fn html(h: Html) -> Response fn json(r: Response, limit: Int) -> T ! {Fail} fn text(r: Response, limit: Int) -> Untrusted ! {Fail} ``` ### `std/http` [#stdhttp] ```sakura type Matcher = Any | Path(String) | Method(Method) error RateLimited(retry_after: Option) effect Http { fn send(req: Request) -> Response ! {Fail} } fn get(url: Url) -> Response ! {Http, Fail} fn post_json(url: Url, body: T) -> Response ! {Http, Fail} fn bearer(req: Request, token: Secret) -> Request fn host() -> Handler fn scoped(hosts: List) -> Handler fn recording(path: Path) -> Handler fn scripted(script: List<(Matcher, Response)>) -> Handler ``` ### `std/html` [#stdhtml] ```sakura opaque type Html = Html(String) fn text(s: String) -> Html fn untrusted(u: Untrusted) -> Html fn raw(markup: String, reason: Trust) -> Html fn from(v: T) -> Html // each non-`Html` html"…" value goes through it (§17.77) fn concat(parts: List) -> Html fn to_string(h: Html) -> String fn template(parts: List, args: List) -> Html ``` ### `std/router` [#stdrouter] ```sakura opaque type Router = { routes: List> } fn new() -> Router fn route(r: Router, method: Method, pattern: String, h: fn(Request) -> Response ! e) -> Router fn get(r: Router, pattern: String, h: fn(Request) -> Response ! e) -> Router fn post(r: Router, pattern: String, h: fn(Request) -> Response ! e) -> Router fn put(r: Router, pattern: String, h: fn(Request) -> Response ! e) -> Router fn delete(r: Router, pattern: String, h: fn(Request) -> Response ! e) -> Router fn handle(r: Router, req: Request) -> Response ! e ``` ### `std/serve` [#stdserve] ```sakura type Address = { host: String, port: Int } error ServeError(reason: String) effect Serve { fn serve(handle: fn(Request) -> Response ! e, addr: Address) -> Never ! {Fail | e} } fn listen(router: Router, addr: Address) -> Never ! {Serve, Fail | e} fn of(host: String, port: Int) -> Address requires port >= 0 && port <= 65_535 fn host() -> Handler fn scripted(requests: List) -> Handler ``` ### `std/budget` [#stdbudget] ```sakura type Limits = { tokens: Option, usd: Option, calls: Option } type Cost = { tokens: Int, usd: Float, calls: Int } type Reservation = { estimate: Cost } type BudgetKind = Tokens | Usd | Calls error BudgetExceeded(kind: BudgetKind) effect Budget { fn reserve(est: Cost) -> Reservation ! {Fail} fn settle(r: Reservation, actual: Cost) -> Unit } fn limits(l: Limits) -> Handler fn unlimited() -> Handler fn zero() -> Cost ``` ### `std/prompt` [#stdprompt] ```sakura type PromptPart = Text(String) | Untrusted(Untrusted) | Image(Bytes) opaque type Prompt = Prompt(List) fn of(parts: List) -> Prompt fn text(s: String) -> Prompt fn parts(p: Prompt) -> List ``` ### `std/llm` [#stdllm] ```sakura type Model = Model(String) type Request = { system: String, user: Prompt, model: Model, max_tokens: Int, temperature: Float } error Refused error InvalidOutput(violations: List) error Truncated error ProviderError(status: Int) type LlmError = Refused | InvalidOutput | Truncated | RateLimited | Timeout | ProviderError | BudgetExceeded type ToolSpec = { name: String, description: String, schema: Json } type ToolCall = { id: String, name: String, input: Json } type ToolResult = { id: String, output: Json } type Turn = Asked(List) | Answered(List) type Step = Answer(T) | Use(List) type Partial = { text: String, step: Int, calls: List } effect Llm { fn generate(req: Request) -> T ! {Fail} fn generate_text(req: Request) -> Untrusted ! {Fail} fn stream(req: Request, on_partial: fn(Partial) -> Unit ! e) -> T ! {Fail | e} fn step(req: Request, tools: List, transcript: List) -> Step ! {Fail} } fn request(system: String, user: Prompt) -> Request fn of(name: String) -> Model fn default() -> Model fn anthropic(key: Secret, model: Model) -> Handler // @external fn replay(path: Path) -> Handler fn scripted(responses: List) -> Handler ``` ### `std/tool` [#stdtool] ```sakura opaque type Tool = { spec: ToolSpec, run: fn(Json) -> Json ! {Fail | e} } error ToolError(tool: String, tag: String, payload: Json) fn from(name: String, description: String, f: fn(A) -> R ! {Fail | e}) -> Tool fn name(t: Tool) -> String fn spec(t: Tool) -> ToolSpec fn call(t: Tool, input: Json) -> Json ! {Fail | e} ``` ### `std/agent` [#stdagent] ```sakura type Agent = { request: Request, tools: List>, max_steps: Int, stop: fn(Partial) -> Bool } fn run(a: Agent) -> T ! {Budget, Llm, Fail | e} requires a.max_steps > 0 ``` ### `std/sql` [#stdsql] ```sakura type SqlValue = Null | Int(Int) | Float(Float) | Text(String) | Blob(Bytes) opaque type Query = { text: String, params: List } error SqlError(message: String) effect Sql { fn query(q: Query) -> List ! {Fail} fn execute(q: Query) -> Int ! {Fail} fn transaction(body: fn() -> T ! {Sql | e}) -> T ! {Fail | e} } fn from(value: T) -> SqlValue // each sql"…" value goes through it (§4.11) fn template(parts: List, args: List) -> Query fn migrate(statements: List>) -> Unit ! {Sql, Fail} fn sqlite(path: Path) -> Handler // @external fn memory() -> Handler // @external ``` # Basics URL: /docs/tour/basics Markdown: /docs/tour/basics.md > Lexical rules, bindings, functions, control flow, operators, strings, lambdas, loops, and modules in Sakura Lang. > **Pre-alpha** > > The source-built compiler runs the core language and much of M3 on JavaScript: contracts, typed effects and failures, std handlers, permissions, LSP and MCP. M3 is still in progress; no release or stable compatibility contract exists. See the [roadmap](/docs/roadmap) for implemented features and remaining gates. Sakura Lang has a small surface: braces, `fn`, `let`/`var`, `match`, `if`/`else`. The keywords are: ```text fn let var type error effect handler with try catch fail if else match for in while return break continue import pub opaque as test property requires ensures where true false ``` `verify proof ghost control resume region spawn actor interface impl` are reserved for later and rejected today (`V0007`). A reserved word is still accepted directly after `.` or `fn`, so `Task.spawn(s, body)` is legal. Two names are special only in one place: `it` inside a `where` refinement and `result` inside an `ensures` clause. There is no `null`, no `class`, no `self`, no exceptions, no `async`/`await`, no macros, and no operator overloading. ## Names and casing [#names-and-casing] Casing is part of the grammar. Values, functions, modules, fields, and effect-row variables are `snake_case`. Types, variants, error tags, effects, and type variables are `PascalCase`. A `TypeName` is never a value and an `Ident` is never a type; the resolver reports `V1004` on a mismatch. ## Comments and attributes [#comments-and-attributes] ```sakura // A line comment. /* Block comments */ nest. /// A doc comment. It attaches to the next item and is exported with its signature. pub fn double(n: Int) -> Int { n * 2 } ``` `@name(args)` attributes precede items. The v0 set is `@tool`, `@llm`, `@external`, `@total`, `@contract_test`, `@deprecated`, `@server`, and `@test_only`. Attribute arguments are positional or `name: value`, as in `@llm(system: "…")` or `@contract_test(handlers: [Sql.memory()])`; this is the only place `name: value` appears in an argument list. `@trust("reason")` is the one attribute written as an expression, at a trust sink. An unknown attribute is `V1010`. ## Statements and newlines [#statements-and-newlines] There are no mandatory semicolons. The lexer ends a statement at a line break only when **both** hold ([design §2.4](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)): 1. The previous token can end an expression: a name, a hole, a literal, `)`, `]`, `}`, `>`, postfix `?`, `true`, `false`, `break`, `continue`, or `return`. 2. The next token cannot continue one: it is not `.`, `else`, `catch`, `)`, `]`, `}`, `,`, `=>`, `=`, `->`, `|`, `..`, or a binary operator other than `-`. In practice: - A line ending in an operator other than `>`, or in `(`, `[`, `{`, or `,`, continues. `>` ends a line so that `type Ids = List` terminates; write a continued comparison with `>` at the start of the next line. - A line starting with `.`, `else`, or `catch` continues the previous one, so method chains and `} else {` work across lines. - `foo` followed by `(bar)` on the next line is **two** statements. So is `foo` followed by `-1`. - Newlines inside `(` and `[` are never significant. `;` is always accepted; `sakura fmt` removes redundant ones. ## Bindings [#bindings] `let` binds an immutable name. `var` binds a name that can be reassigned. Local types are inferred. ```sakura fn bindings() -> Int { let big = 20_000 // underscores between digits let mask = 0xFF // also 0b1010 let ratio = 0.75 // Float needs a digit on both sides of the dot let primes = [2, 3, 5, 7] // List let pair = (1, "one") // tuple; pair.0, pair.1 var count = 0 count = count + List.len(primes) count + big + mask } ``` `()` is the `Unit` value. There is no unary plus; `-5` is unary minus applied to `5`. Reassigning a `var` changes the binding, never a value seen elsewhere ([Memory model](/docs/tour/memory)). Top-level `let` declares a constant; a `pub let` needs a type annotation. ## Functions [#functions] Every top-level function has a full signature: parameter types, `-> Result`, and an effect row unless it is pure. ```sakura fn add(a: Int, b: Int) -> Int { a + b } fn clamp(n: Int, lo: Int, hi: Int) -> Int { if n < lo { return lo } if n > hi { return hi } n } pub fn greet(name: String) -> Unit ! {Console} { Console.print("hello, {name}") } ``` - A block's value is its final expression. `return e` exits the enclosing function or lambda early. - A signature without `!` is pure (`! {}`). Effects are covered in [Effects](/docs/tour/effects). - `-> Unit` is written out; there is no default return type. - Items are private to their module unless marked `pub`. - A non-final expression statement whose value is not `Unit` is an error (`V2040`). Write `let _ = e` to discard on purpose. ## Control flow [#control-flow] `if` is an expression. With `else`, both branches must have the same type. Without `else`, its type is `Unit`. ```sakura fn label(score: Int) -> String { if score >= 90 { "excellent" } else if score >= 50 { "pass" } else { "keep going" } } fn can_vote(age: Int, registered: Bool, suspended: Bool) -> Bool { age >= 18 && registered && !suspended } ``` `match` branches on the shape of a value. Arms end with a newline or `,`. Guards use `if`. A match must be exhaustive, and a literal pattern on `Int`, `String`, or `Float` needs a `_` arm. See [Types](/docs/tour/types#pattern-matching). ```sakura fn describe(n: Option) -> String { match n { Some(0) => "zero" Some(x) if x > 0 => "positive" Some(_) => "negative" None => "missing" } } ``` ## Operators [#operators] Tightest first ([design §3.1](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)): | Level | Operators | Associativity | | --- | --- | --- | | 1 | call `f(x)`, field `.x`, tuple index `.0`, index `[i]`, raise `?` | postfix | | 2 | `-` `!` | prefix | | 3 | `*` `/` `%` | left | | 4 | `+` `-` | left | | 5 | `..` (range, exclusive end) | none | | 6 | `==` `!=` `<` `<=` `>` `>=` | none | | 7 | `&&` | left | | 8 | `\|\|` | left | - Both operands must have the same type. `Int + Float` is a type error; convert with `Int.to_float` or `Float.round`. - `+` also concatenates `String`. Nothing else is overloaded, and there are no user-defined operators. - `Int` arithmetic is checked. Overflow (`D001`) and division by zero (`D002`) are defects, not errors. - `a < b < c` is `V0010`. `&&` and `||` short-circuit. - `xs[i]` is `List.get(xs, i)` and returns `Option`. Four parsing rules remove guesswork ([design §3.2](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)): - In expressions, `<` is always comparison. There are no call-site type arguments: `List.empty()` and `f(x)` are `V0011`. Name the type with a binding annotation instead: `let xs: List = List.empty()`, `let rows: List = Sql.query(q)`. - There are no named arguments: `f(name: v)` is `V0014`. Pass values positionally, or pass a record when there are many options (`Budget.limits(Limits { tokens: Some(20_000), usd: None, calls: None })`). - In the header of `if`, `while`, `for`, `match`, and `with`, `Name {` starts the block, not a record literal. Parenthesize record literals there. - `if`, `match`, `with`, `try`, `fn`, `handler`, and `fail` expressions are never operands without parentheses: `(match x { … }).len()`, not `match x { … }.len()` (`V0012`). ## Strings [#strings] Strings are UTF-8 and may span lines. `{expr}` interpolates any expression whose type is `Show`. Escapes are `\\ \" \n \r \t \0 \{ \}` and `\u{…}`; write `\{` for a literal brace. ```sakura fn inbox(name: String, unread: List) -> String { "Hello, {name}. You have {List.len(unread)} messages. \{not interpolated\}" } ``` Interpolating a `Secret` is a compile error (`V2031`). Tagged strings `sql"…"`, `html"…"`, `url"…"`, and `shell"…"` pass interpolations to the tag as values and never splice them raw. See [Types](/docs/tour/types#tagged-templates). ## Lambdas and function types [#lambdas-and-function-types] There is one lambda form: `fn(params) { body }`, with optional parameter and result annotations. ```sakura fn twice(f: fn(Int) -> Int ! e, x: Int) -> Int ! e { f(f(x)) } test "twice applies f two times" { let add3 = fn(x) { x + 3 } let mul2 = fn(x: Int) -> Int { x * 2 } expect_eq(twice(add3, 3), 9) expect_eq(twice(mul2, 3), 12) } ``` A function type is `fn(A, B) -> C ! row`; without `!` it is pure. The lowercase `e` is an effect-row variable, so `twice` performs whatever `f` performs. Lambdas capture values, not variables, and cannot assign to an outer `var` (`V2041`). A local lambda cannot call itself (`V1020`); lift it to a top-level function. ## Method-call syntax [#method-call-syntax] `x.f(a)` means `T.f(x, a)`, where `T` is the type of `x` and `f` is looked up in `T`'s home module. So `xs.map(f)` is `List.map(xs, f)` and `s.len()` is `String.len(s)`. There are no methods, no `self`, and no pipe operator. Chains continue across lines with a leading `.`: ```sakura type User = { name: String, active: Bool } fn names(users: List) -> List { users .filter(fn(u) { u.active }) .map(fn(u) { u.name }) } ``` The full lookup order is in [Types](/docs/tour/types#method-call-resolution). ## Loops [#loops] `for pattern in expr { }` iterates a `List`, `Range`, `Set`, or `Map` (as `(key, value)` pairs). `while cond { }` loops while a condition holds. `break` and `continue` work in both. Loops are statements and produce no value; accumulate in a `var`, or use `List.fold`. ```sakura fn total(xs: List) -> Int { var sum = 0 for x in xs { if x < 0 { continue } sum = sum + x } sum } fn total_fold(xs: List) -> Int { List.fold(xs, 0, fn(acc, x) { if x < 0 { acc } else { acc + x } }) } fn countdown(n: Int) -> Unit ! {Console} { for i in 0..n { Console.print("{n - i}") } } ``` ## Holes [#holes] `?name` is a typed hole: an unfinished expression. `sakura check` accepts holes and reports each one's expected type, the locals in scope, and ranked candidates. `sakura build` and `sakura run` reject them (`V2050`). See [Typed holes](/docs/agents/typed-holes). ```sakura fn unfinished(x: Int) -> Int { ?result } ``` ## Modules [#modules] One file is one module. The module path is the file path under the package's `src/`: `src/users/repo.sakura` is module `users/repo`. The first import segment is a package name (`std`, the current package, or a dependency in `sakura.toml`). ```sakura import std/http.{Http, Request, Response} // bind names directly import std/sql // bind only the module name `sql` import acme/billing/invoices as inv // rename ``` - Nothing outside the prelude is in scope implicitly. `import std/sql` binds `sql` and nothing else, so a row that mentions `Sql` needs `import std/sql.{Sql}`. On an unresolved std name, `V1002` offers the import as a machine-applicable fix. - No wildcard imports, no relative imports, no re-exports, and no import cycles (`V1030`). - Variants and error tags live directly in their module's namespace (`Some`, `EmailTaken`); `Option.Some` is also accepted. - `pub type` exports a type with its constructors and fields. `pub opaque type` exports only the name: other modules can use its values but not construct or destructure them (`V1006`). - The prelude needs no import: the built-in types (`Int`, `String`, `List`, `Map`, `Set`, `Secret`, `Untrusted`, …), `Option`, `Result`, `Duration`, `Instant`, `Uuid`, `Json`, `Level`, the constructors `Some None Ok Err`, the effects `Fail Console Clock Random Env Log Task Test`, `show` and the `expect…` test functions, and the tags `sql html url shell`. - The entry point is `pub fn main() -> Unit ! {…}` in `src/main.sakura`. ## What the compiler checks [#what-the-compiler-checks] - Every top-level function has a complete signature (`V3001` lists effects a body performs beyond it). - Casing matches the namespace (`V1004`). Reserved words cannot be identifiers (`V0007`). - Unused non-`Unit` results are errors (`V2040`). - Name conflicts within a module are errors (`V1001`). A local shadowing an import is a warning (`W1002`). - Every `match` is exhaustive (`V4001`). Unreachable arms are reported (`V4002`). - `sakura fmt` has no options: 2-space indent, width 100, one canonical layout. ## Next [#next] [Types](/docs/tour/types): records, variants, refinements, generics, and why there is no null. # Capabilities and security URL: /docs/tour/capabilities Markdown: /docs/tour/capabilities.md > How Sakura Lang separates effects from authority, limits root handlers with sakura.toml, narrows authority for dependencies, and blocks injection and secret leaks. > **Pre-alpha** > > The source-built compiler runs the core language and much of M3 on JavaScript: contracts, typed effects and failures, std handlers, permissions, LSP and MCP. M3 is still in progress; no release or stable compatibility contract exists. See the [roadmap](/docs/roadmap) for implemented features and remaining gates. Most supply-chain attacks and injection bugs rely on code doing more than it should: a dependency that reads files it was never meant to see, a string from a request that reaches a SQL query, a token that ends up in a log. Sakura Lang uses effect checking, scoped handlers and explicit trust boundaries to make authority reviewable. These are language-level checks, not an OS sandbox for trusted FFI. ## Effects are requirements, handlers are authority [#effects-are-requirements-handlers-are-authority] `Fs` in a signature says "this function may perform filesystem operations". It does not say which files. The **installed handler** decides that ([design §6.1](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)). A handler for `Fs` is built from a `Dir` capability value. Every path is resolved relative to that directory, and symlink escapes are rejected. Authority lives in capability values that handlers close over, not in effect labels. The effect row still matters: a function can only perform the effects in its row, and a pure function cannot touch the outside world at all. But the row is what a function *needs*. What it may actually *reach* is set by whoever installs the handler. ## Root authority comes from the host [#root-authority-comes-from-the-host] The entry point is `pub fn main() -> Unit ! {…}`. The host supplies root handlers for exactly the std effects `main` declares, and nothing else. Only std effects may appear in `main`'s row (`V6010`), because only they have host root handlers. Root handlers are limited by the `[permissions]` table in the project manifest: ```toml title="sakura.toml" [permissions] fs.read = ["./data", "./templates"] fs.write = ["./data/out"] net = ["api.anthropic.com:443", "localhost:5432"] env = ["DATABASE_URL", "ANTHROPIC_API_KEY"] process = [] # no subprocesses ``` `sakura run` passes this table as JSON in `SAKURA_PERMISSIONS`; the host filesystem, HTTP/serving and environment adapters enforce it. Running emitted JS directly without that variable does not establish this ceiling. Trusted FFI is outside this language-level boundary. There is no separate `Net` effect: the `net` list limits the network effects `Http` and `Serve` (`Sql.postgres` is not implemented), and `env` limits which variables `Env` can read. A `main` that declares an effect whose permission list is empty, such as `Http` with an empty `net` list, fails at **startup**, not at first use, and exits with status `75` (permission denied). ## Narrowing authority [#narrowing-authority] `Dir` and `File` have no public constructors. `Fs.root()` supplies a directory capability, and `Dir.subdir` narrows it. `Http.scoped(hosts: List)` and `Env.only(keys)` attenuate outer handlers by data; `NetScope`, `EnvScope`, and `SecretStore` are not current APIs. Attenuating handlers are ordinary std functions: `Fs.rooted(dir)`, `Http.scoped(hosts)`, and `Env.only(keys)`. This test narrows a scripted HTTP handler to one host, without using the network: ```sakura import std/http.{Any, Http} import std/request.{Method, Request} import std/response.{Response} import std/url.{Url} test "only the allowed HTTP host is forwarded" { with Http.scripted([(Any, Response.status(200, "hello"))]), Http.scoped(["example.com:443"]) { let req = Request.of(Method.Get, Url.from("https://example.com/")) expect_eq(Http.send(req).status, 200) let denied = Request.of(Method.Get, Url.from("https://other.example/")) expect((try { Http.send(denied) }).is_err()) } } ``` The same attenuation wraps a host HTTP handler, which additionally enforces manifest permissions. ## Forged capabilities are rejected [#forged-capabilities-are-rejected] A user `@external` cannot declare a parameter or result containing a capability from another package, or any `Handler<…>` (`V6001`, design §17.91). The check follows records, collections, options and function types; another package's `@resource` type also counts. An FFI package may declare externals over its own external types. This declaration is intentionally rejected with `V6001`; no JavaScript is executed: ```sakura import std/fs.{Dir} @external(js, "./forge.js", "root") fn forge() -> Dir ``` ## Scoped escape rules [#scoped-escape-rules] An external type, a handler, an `@resource` type, or a value transitively containing one is **scoped**. Closures also carry the scoped locals they capture ([design §6.3](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)). Scoped values may be passed down the call stack, stored in scoped records, and returned from ordinary functions. They may not flow into: - a `Task.spawn` closure whose `Scope` outlives the `with` that produced the value (there are no detached tasks in v0), - a top-level `let`, - a value returned from the `with` body that introduced the scope. A violation is `V6002`, including an explicit `return` out of the `with`. Calls returning function-containing values conservatively retain scoped argument captures. `V6003` rejects a resource escaping the callback that borrows it, or being stored in handler `var` state. The callback rule also applies to wrapper functions and to a callback parameter whose type contains a resource, such as a record with a `File` field (design §17.89). This example is intentionally rejected: ```sakura import std/fs.{File, Fs, FsError, Mode} import std/path.{Path} fn keep_open(path: Path) -> File ! {Fs, Fail} { Fs.with_file(path, Mode.Read, fn(file) { file }) } ``` Return ordinary data read inside the callback instead. Files are finalized on callback exit. v0 does not check affine use counts; runtime file operations still reject access after closure. ## Dependency authority (design; package tooling planned) [#dependency-authority-design-package-tooling-planned] The design calls for computed package authority and `sakura deps capabilities`; that command and multi-package tooling are not implemented. Imports do not grant authority: callers install handlers around dependency calls. Adding an effect to a dependency's signature requires its callers to account for that effect. The implemented [build manifest](/docs/agents/build-manifest) inventories user externals alongside other trusted components. It is not yet the planned dependency authority graph. ## Untrusted data [#untrusted-data] `of`, `check`, `trust`, `len` and refined-target `validate` work today. A validation target must be a known refinement of the wrapped base; otherwise the compiler reports `V5003`. Using `Untrusted.validate` as a function value is also rejected. See [contracts](/docs/tour/contracts#validating-untrusted-input). `Untrusted` wraps data from outside the deployment: raw request bodies, file contents read as text (`Fs.read_text`), response text (`Response.text`), and free-text LLM output (`Llm.generate_text`). Environment variables are not in that list: they are set by the operator and gated by the manifest's `env` list, so `Env.get(key)` returns a plain `Option`. `Untrusted` supports: | Operation | Result | | -------------------------------- | ----------------------------------------------------------------- | | `.validate()` | `Result` for any refined type `V`, named by annotation: `let e: Email = Untrusted.validate(u)?`. | | `.check(clause, pred)` | `Result`. | | `.len()` | The length, without trusting the content. | | `.trust(@trust("reason"))` | `T`. An explicit, greppable escape hatch. | | `Untrusted.of(t)` | Wraps a value, for tests. | Validating against a refinement is the normal path: ```sakura type Email = String where String.len(it) <= 254 && String.contains(it, "@") error InvalidEmail fn read_email(raw: Untrusted) -> Email ! {Fail} { match raw.validate() { // V = Email, inferred from the return type Ok(email) => email, Err(_) => fail InvalidEmail, } } ``` `Untrusted` is `Show`, so it can be logged. Schema-decoded values (`Request.json`, `Json.decode`, `Llm.generate`) are already validated against the target type's refinements and come back as `T`, not `Untrusted`. ## Tagged templates never splice [#tagged-templates-never-splice] `sql"…"`, `html"…"` and `url"…"` are implemented tagged templates; `shell` is reserved but has no std module. The design uses tagged templates ([design §4.11](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)). A tagged string desugars to a call to the tag module's `template` function, with the literal parts and the interpolated values passed separately: ```text sql"select * from users where email = {email}" ==> sql.template(["select * from users where email = ", ""], [SqlValue.from(email)]) ``` Interpolated values are **never spliced raw**. Each one is type checked against the tag's parameter type. For `sql` that is `SqlValue`, which becomes a bound parameter. `html` escapes text by construction. This query binds a string as a parameter: ```sakura import std/sql.{Sql, SqlError} type Post = { id: Int, title: String } fn search(q: String) -> List ! {Sql, Fail} { Sql.query(sql"select id, title from posts where title = {q}") } ``` `q` reaches the database as a parameter value, never as SQL text. Direct `Untrusted` SQL interpolation currently fails with `V2032` because `SqlValue.from` requires `Schema`; validate the input into a refined string before passing it here. The source index describes broader support than this binary accepts. HTML interpolations have element type `Html`. Existing `Html` values compose as markup; other `Show` values convert through `Html.from` and are escaped text (design §17.77). `Untrusted` interpolated directly is escaped in its shown wrapper form; `Html.untrusted` displays just the escaped contents: ```sakura import std/html.{Html} test "HTML composition keeps text escaped" { let heading = html"

{"Tom & Jerry"}

" let input = Untrusted.of("