Kotoba
A Clojure-shaped language that compiles to WebAssembly components — where a program can only touch what it was explicitly handed.
Three things to know
1 · Identity by content
A definition is named by what it means, not by what it was called.
The identity is computed after desugar, type checking, effect inference and ability elaboration — and it seals the effect row, so a pure definition and one requiring network authority can never share a name. Unison's idea; not Unison's syntax and not a global codebase.
2 · Memory safety, as a consequence
The thesis is confinement. Memory safety falls out of it.
Admitted components cannot address runtime or native memory, and component memory operations are bounded or trap. That holds without a general ownership/borrow system: affine consumption is scoped to capability values alone, because what a program must not forge is authority, not pointers.
3 · Component-first execution
The unit that runs is a Wasm component, not a process.
Each component gets its own WIT world built from its declared effects. Undeclared imports are rejected and there is no ambient WASI. The tender links and instantiates, binding only what policy granted. Native AOT for ordinary applications is an explicit non-goal — the boundary is the point.
Kotoba source is an EDN/Lisp subset: .kotoba is the canonical extension and .cljc is common source across Clojure, ClojureScript and Kotoba. It is a source profile with its own compatibility contract — not "any JVM Clojure program runs".
Memory safety is a consequence, not the thesis
Most safe-language pitches begin and end with memory. Kotoba treats that as necessary and insufficient: a program that cannot corrupt memory but can still open a socket it was never given has not been contained. The safest program is not the one written in the strongest type system — it is the one that, when it is fully compromised, can still do nothing. So confinement is ranked above ownership:
Against a mythos-class adversarial agent, a linter's red underline is a polite signpost. When something comes through the wall, the only thing that works is to have kept nothing outside it.
This is why there is no borrow checker over every value. T1 — admitted components cannot address runtime or native memory, and component memory operations are bounded or trap — is met by the admitted grammar and the runtime, not by an ownership system. Affine consumption exists, but only where forging a duplicate would create authority: a capability value may be consumed at most once per execution path.
The ladder is the language's own accepted design position (ADR — safe capability language). What it does not claim: the Wasm runtime engine stays inside the trusted computing base, and native loaders still require a second OS isolation boundary.
Source, and the policy that admits it
A module
(ns example.greet)
(defn greeting [name :string] :string
(string-concat "hello, " name))
(defn main [] :string
(greeting "kotoba"))Types are inline. No macros, no interop, no eval — those forms are not "discouraged", they are absent from the admitted grammar.
A policy
;; effective scope =
;; requested ∩ delegated ∩ local policy
{:policy/allow
#{{:cap/kind :host/http
:cap/resource "https://api.example.com/"
:cap/expires "2026-12-31T00:00:00Z"}}
:policy/forbid-wildcard true}what denies
empty-intersectionexpired-grantmissing-grantunknown-kind
Scope may only attenuate, never widen, and the handler receives a concrete post-intersection capability. Production policy must forbid wildcard scope; every attempt is receipted whether or not it succeeds.
Eight claims, and what each still risks
These are the language's qualification claims, read directly out of lang/safety-claims.edn when this page was built. Each one ships with its trusted computing base and its residual risk, because a safety claim without a stated boundary is marketing.
T1 · memory
Admitted components cannot address runtime/native memory and component memory operations are bounded or trap.
trusted computing base
bounded reader · frontend admission · artifact verifier · Wasm/native runtime
residual risk
- runtime-engine vulnerabilities remain in the TCB
- native loaders require a second OS isolation boundary
T2 · effect
Every transitive component effect is declared and admitted before emission, including effects used by Kotoba-written providers.
trusted computing base
effect inference · capability catalog · frontend call graph
residual risk
- kotoba and compiler grammar/effect parity must be continuously compared
T3 · confinement
An ungranted capability is absent or unbound and cannot reach a provider or native handler.
trusted computing base
policy intersection · compiler import emission · tender import binding · host guard
residual risk
- provider and native implementations must independently validate resource scope
- production effective grants must forbid wildcard scope
T4 · determinism
The same admitted source, target, policy and lock produce the same observable pure result and artifact bytes.
trusted computing base
canonical reader · deterministic lowering · pinned toolchain
residual risk
- host effects are deterministic only where their capability contract says so
T5 · resource bounds
Source, admission, execution, memory and output use explicit finite bounds.
trusted computing base
admission limits · fuel meter · runtime quota · supervisor timeout
residual risk
- platform supervisors do not yet have equal production isolation evidence
T6 · supply chain
Release admission binds artifact identity, trusted signer, validity and reproducible evidence.
trusted computing base
signature verifier · trusted signer configuration · clock · revocation set
residual risk
- key custody and external revocation distribution remain operational TCB
T7 · backend parity
A shared portable component has equal acceptance, result and effect trace across qualified backends.
trusted computing base
shared conformance manifest · backend adapters · comparison runner
residual risk
- compiler-only features are not portable and must be rejected by portable profiles
T8 · host resource scope
A component import reaches its provider or native handler only with a concrete post-intersection resource scope and emits a receipt.
trusted computing base
capability intersection · host guard · provider handler · receipt sink
residual risk
- provider-specific path, redirect, symlink and tenant checks require Q5 kits
Qualification level Q1, as of 2026-07-18.
Deliberately absent
Every entry below is a security constraint, not an unfinished feature. The distinction is tracked in lang/surface-status.edn so that "not implemented yet" can never be quietly confused with "refused on purpose".
eval load load-file ns-resolve require resolve use
Components cannot manufacture code or authority from ambient process state.
. .. import new
Arbitrary JVM/JS object and method access bypasses capability admission.
alter-var-root atom binding dosync ref reset! set! swap! var volatile!
External mutable state is provider-owned and capability/policy mediated; component-local state must use an explicitly bounded model.
agent future locking pmap send send-off
Component scheduling and resources must remain tender-controlled and bounded.
defmacro
The safe component surface must be statically inspectable before execution.
catch throw try
Component/provider effects use explicit result/error values rather than hidden exception paths.
Bounded admission
Untrusted and generated programs must fail closed under finite admission and execution resources.
max-bindings4096max-expression-nodes50000max-functions1024max-list-items128max-parameters5max-reader-depth512max-source-bytes1048576max-symbol-chars128
Profile version 5, as of 2026-08-03. One more intentional simplification: affine consumption is scoped to capability values only — a general ownership/borrow/lifetime system is intentionally absent.
From source to an admitted artifact
Ten stages, each with a named owner and its own fail-closed rules. Note where effects enter: they are inferred across the call graph, and a declaration is a ceiling, not a floor — you cannot widen your own authority by writing a larger annotation.
closed-reader
closed-edn-subset · fail-closed-on-unknown-reader-macro
bounded-pure-desugar
no-host-ops · node-depth-bounded · registered-defdesugar-only
name-module-resolution
closed-require-export · no-ambient-require
type-schema-inference
closed-schemas · fail-closed-on-mismatch
interprocedural-effect-inference
declaration-is-ceiling-not-floor · fixpoint-over-calls
implicit-ability-elaboration
named-ops-to-typed-abilities · no-user-facing-numeric-ids
typed-hir-kir
deterministic-encoding
definition-cid
alpha-rename-independent · contract-versions-sealed
target-lowering
deny-by-default-admission · exact-imports
host-binding
grant-policy-intersection · quota-deadline-revocation-receipt
What you write
bounded-pure-desugardefdefndestructuringhigher-order-functionsifletloop-recurmapnsprotocolsrecordssetvector
What you never write
host-objectnumeric-capability-idportable-effect-envelopeprovider-callbackwit-import-syntax
Numeric capability IDs and WIT import syntax are a wire ABI, not source vocabulary. Explicit capability values are for attenuation, delegation, resource scope, quota and deadline — not for ordinary calls.
Component-first, and what that rules out
WebAssembly components
- Component Model pinned at
e6bb1e456e94 - WASI
0.3.0baseline - world construction:
per-component - undeclared imports:
reject - ambient WASI:
false
Async functions, futures and streams are explicit bounded effects with cancellation, deadline and budgets — never ambient authority.
Two lowering targets
The same checked intermediate representation lowers to either restricted-esm, wasm-component — under the same rules: exact imports, deny-by-default admission.
Ordinary-application native AOT is an explicit non-goal: the execution boundary is the component.
Who does what
- tender — Runtime that verifies/instantiates already-emitted Wasm components, links declared imports and exports, and binds only granted capabilities. kototama owns this role.
- broker — Authority above the tender that decides which principal may receive which scoped grant. aiueos is the named OS/capability broker.
- native-primitive — Minimal trusted boundary from typed component imports to the Wasm engine, WASI, OS syscall, device or root secret store. It revalidates concrete resource scope and cannot be removed by changing an extension.
Identity by content, without the Unison surface
Kotoba takes Unison's idea — a definition is named by what it is, not by the label someone typed above it — and deliberately leaves the rest. The identity is computed after desugar, type checking, effect inference and ability elaboration, so it names normalized semantics rather than text. Source formatting, package name and git ref are excluded.
What the identity seals:
typed-kirprofile-versiondesugar-contract-versioneffect-rowinterfacedirect-definition-dependencies
The effect row is in that list for a concrete reason. Without it, a pure definition and one requiring :host/http with identical KIR hash to the same identity — so a lock pinning the pure one would admit the effectful one. There is a negative fixture for exactly that substitution.
What is explicitly not adopted:
ambient-ioambient-loadingbroad-wasi-grantsframework-specific-application-dslgeneral-effect-handlersglobal-codebase-namespaceimplicit-durable-executionordinary-app-native-aotruntime-evalunison-syntax
Delivery stages
CI0 — the contract
CI1 — canonical typed-KIR encoding and identity test vectors
Canonical DAG-CBOR encoding over a closed, injective, tagged value domain; 10 frozen vectors are recomputed by the test suite.
CI2 — definition-addressed manifest fields and positive fixtures
:registry/definition-cids resolves through the signed record to :dep/definition-cids, so a name is an alias and never a substitution point.
CI3 — negative fixtures for every sealed input
One negative fixture per sealed canonical input (body, profile, interface, dependency, effect-row, desugar-contract) plus the linking rules themselves.
CI4 — safe-build verifies identity against the package lock
admit-build closes the omission hole: verify-locked-definitions alone returns ok? true when a build presents nothing, which is the mutable-name fallback reached by omission. A dependency pinning definition CIDs must now actually resolve all of them.
CI5 — typed ability/effect checking and the narrow WIT ABI
Compiler WIT lowering must exclusively select guard-component-ability-call for effectful imports.
CI6 — cross-implementation conformance
The identity half holds: all 10 frozen vectors produce identical canonical bytes and CIDs under ClojureScript (nbb) and Clojure. This runner is what found that integer literals past 2^53-1 are rounded by the cljs reader before any encoder runs, which the i64 exact form now refuses rather than hides.
CI7 — friendly source operations elaborate identically
Friendly source operations elaborate to the same typed ability/effect KIR across project and single-source builds.
Read from lang/code-identity.edn at build time, so this table cannot claim more than the repository does. The ADR's own rule is that until CI4 and CI5 both land the design is described as proposed — CI4 has landed and CI5 has not, so proposed is what it is. Hash-native authoring, browse-by-hash and a deployed codebase network are not claimed at all.
Where this actually is
Kotoba is a working compiler and a qualified bounded slice — not a finished general-purpose platform. The distinction is kept in the repo rather than softened here:
- Q1–Q8 pass for the bounded reference slice, including a CLJC-shadowed pure port, a denied/allowed capability port, and guarded native OS-isolation conformance.
- Q9 fleet migration is authorized only for bounded Wave 1 tranches. Later waves and production deployment are not authorized, and the ClojureScript oracle is retained.
- Runtime-engine vulnerabilities remain inside the trusted computing base; native loaders still require a second OS isolation boundary.
- Key custody and revocation distribution remain operational, not linguistic, guarantees.
If a claim is not on this page, assume it is not being made.
Read the source
kotoba-lang/kotoba-lang
The language authority: profile, grammar, capability semantics, safety claims, conformance fixtures.
kotoba-lang/compiler
Frontend admission, effect inference, KIR, and the emit backends.
kotoba-lang/kotoba
Language and library substrate, host implementations, semantic-code identity, integration tests.
kotoba-lang/kototama
The tender: admits emitted components, links imports and exports, binds granted capabilities, enforces limits.
This page is generated by site/generate.cljs in kotoba-lang/kotoba-lang, from the authority files it cites: lang/safety-claims.edn, lang/surface-status.edn, lang/capability-semantics.edn, lang/elaboration-pipeline.edn, lang/wasm-component-platform.edn, lang/code-identity.edn, lang/safety-qualification.edn. Change the spec and the page changes; it cannot claim more than the spec claims.