Every mainstream language hands your code ambient authority. A function you call
can read ~/.ssh, open a socket, or shell out, and its signature won't tell you.
You find out by auditing the implementation, and then everything it calls.
witchy is an experimental capability-secure language that doesn't work that way. Code can touch the outside world only through authority it receives directly as a capability value or transitively as deliberately delegated behavior. Root grants and callable interfaces are visible in types, inspectable from artifacts, and enforceable at the host boundary; an ordinary callback's captured implementation remains opaque.
// This helper receives read authority, not write authority.
fn load(dir: Dir[Read], name: String) -> String:
dir.read(name)
fn main(console: Console, dir: Dir):
// Full Dir narrows to Dir[Read].
console.print(load(dir, "notes.txt"))
A web API where serve keeps possession of the listening Net, while this
data-only handler receives no authority-bearing input:
from http import Request, Response
from server import Router
fn greet(req: Request) -> Response:
server.send(200, .{hello: server.param_or(req, "name", "world")})
fn main(console: Console, net: Net):
let app = server.router().get("/hello/:name", greet)
console.print("serving on 127.0.0.1:8080")
server.serve(net, "127.0.0.1:8080", app)
An HTTP client holds an origin-scoped Fetch, never the raw network:
import http
from http import Response
fn main(console: Console, fetch: Fetch):
match http.get(fetch, "https://example.com/"):
Response(status, headers, body) -> console.print("status ${status}: ${body.length()} bytes")
And the toolchain makes authority a checkable fact rather than a claim:
$ witchy caps api.witchy # what can this program touch?
main Console, Net
total Console, Net
$ witchy --net 127.0.0.1:8080 api.witchy # grant exactly that, run it
$ witchy sandbox api.witchy # or run it confined in a WASM VM -
# ungranted authority fails closed
Prebuilt witchy binaries for x86-64 Linux, x86-64 macOS, and arm64 macOS are
on the releases page; verify
SHA256SUMS, then put bin/witchy on your PATH. Or build from source:
cargo build --release
witchy=./target/release/witchy
$witchy examples/hello/src/hello.witchy
$witchy parity examples/hello/src/hello.witchy # identical output on both backends
Indentation-based layout, expression-oriented, statically typed with inference:
type Shape:
Circle(Int)
Square(Int)
fn area(s: Shape) -> Int:
// Exhaustiveness-checked.
match s:
Circle(r) -> 3 * r * r
Square(w) -> w * w
fn main(console: Console):
let shapes = [Circle(2), Square(3)]
for s in shapes:
// String interpolation.
console.print("area: ${area(s)}")
Int/Float/Bool/String, native Duration literals (30s, 2hr),
List/Dict/tuples/records/ADTs, Option/Result with ?, traits with
where bounds, and Hylo-style parameter conventions (let/var/own, with
use-after-move as a compile error). Structural equality is deep on both
backends. Bounded async/channels, generators, and lazy iterators are supported
preview; comptime remains experimental.
A witchy function can't exercise host authority absent from its typed inputs.
Those inputs may include ordinary callbacks whose opaque behavior the caller
chose to delegate. witchy caps / caps-diff / grants-check / sandbox make
root capability demand inspectable and enforceable; they do not guess a
callback creator's hidden capture set.
That's a bounded guarantee, and the bound is worth being precise about. You
still trust the compiler, the runtime, whichever host bindings you link, and
whoever shipped you the binary. What you stop granting implicitly is ambient
access to all host resources. Ordinary callbacks still delegate behavior that
must be reviewed unless an API requires the explicit checked pure fn
contract. The capabilities guide states the exact model
and its limits.
Pre-1.0 and compatibility-unstable; anything may break without a deprecation
period. The dependable path is deliberately small - language fundamentals,
capability inspection, check/format/test, interpreter-versus-WASM parity,
portable WASM sandboxing, and self-contained
trusted-exe builds. Everything
else (runes + the Coven registry, the Glamour frontend, the in-browser
playground, editor tooling) is experimental dogfood.
PRODUCT-STATUS.md is the evidence-backed boundary.
Runes are experimental dogfood, but the round trip already works against the
hosted registry at https://witchy.fly.dev. The client picks its registry from
COVEN_URL; with none set it dials the local default 127.0.0.1:8787, so point
it at the hosted one first:
export COVEN_URL=https://witchy.fly.dev
witchy new demo-app && cd demo-app
witchy add insanitybit/hello --allow-fresh # --allow-fresh accepts a release still inside its staging cooldown
A freshly promoted release sits out a 72-hour staging cooldown before add
will resolve it - a window in which a compromised release can be noticed before
anyone installs it. On a young registry every release is inside that window, so
the first add needs --allow-fresh to opt in explicitly; a release past its
cooldown needs no flag. Then import the rune and use it:
// src/demo-app.witchy
import hello
fn main(console: Console):
console.print(hello.greeting()) // whatever the rune exports; `witchy doc` lists it
witchy run .
witchy tree . # the dependency and the capability footprint it pulls in
witchy tree shows the whole resolved tree's authority, so you can see exactly
what a dependency reaches for before you trust it. The
packages chapter walks the full model.
witchy is developed extensively with AI assistance; human judgment owns the language, capability-model, and product decisions. What determines supported behavior is executable evidence - the parity, sandbox, and artifact test suites - rather than who or what wrote the code. Contributions should disclose material AI use.
Every ```witchy block in the book, spec, and this README is a complete
program validated by the test suite; the runnable ones execute on both
backends. Editor support: a Zed extension with tree-sitter
highlighting and witchy lsp.
Dual-licensed under Apache-2.0 or MIT, at your option. Unless you explicitly state otherwise, contributions are dual licensed as above.
Rust
84.3%
JavaScript
6.8%
HTML
5.0%
Shell
3.1%
Every mainstream language hands your code ambient authority. A function you call
can read ~/.ssh, open a socket, or shell out, and its signature won't tell you.
You find out by auditing the implementation, and then everything it calls.
witchy is an experimental capability-secure language that doesn't work that way. Code can touch the outside world only through authority it receives directly as a capability value or transitively as deliberately delegated behavior. Root grants and callable interfaces are visible in types, inspectable from artifacts, and enforceable at the host boundary; an ordinary callback's captured implementation remains opaque.
// This helper receives read authority, not write authority.
fn load(dir: Dir[Read], name: String) -> String:
dir.read(name)
fn main(console: Console, dir: Dir):
// Full Dir narrows to Dir[Read].
console.print(load(dir, "notes.txt"))
A web API where serve keeps possession of the listening Net, while this
data-only handler receives no authority-bearing input:
from http import Request, Response
from server import Router
fn greet(req: Request) -> Response:
server.send(200, .{hello: server.param_or(req, "name", "world")})
fn main(console: Console, net: Net):
let app = server.router().get("/hello/:name", greet)
console.print("serving on 127.0.0.1:8080")
server.serve(net, "127.0.0.1:8080", app)
An HTTP client holds an origin-scoped Fetch, never the raw network:
import http
from http import Response
fn main(console: Console, fetch: Fetch):
match http.get(fetch, "https://example.com/"):
Response(status, headers, body) -> console.print("status ${status}: ${body.length()} bytes")
And the toolchain makes authority a checkable fact rather than a claim:
$ witchy caps api.witchy # what can this program touch?
main Console, Net
total Console, Net
$ witchy --net 127.0.0.1:8080 api.witchy # grant exactly that, run it
$ witchy sandbox api.witchy # or run it confined in a WASM VM -
# ungranted authority fails closed
Prebuilt witchy binaries for x86-64 Linux, x86-64 macOS, and arm64 macOS are
on the releases page; verify
SHA256SUMS, then put bin/witchy on your PATH. Or build from source:
cargo build --release
witchy=./target/release/witchy
$witchy examples/hello/src/hello.witchy
$witchy parity examples/hello/src/hello.witchy # identical output on both backends
Indentation-based layout, expression-oriented, statically typed with inference:
type Shape:
Circle(Int)
Square(Int)
fn area(s: Shape) -> Int:
// Exhaustiveness-checked.
match s:
Circle(r) -> 3 * r * r
Square(w) -> w * w
fn main(console: Console):
let shapes = [Circle(2), Square(3)]
for s in shapes:
// String interpolation.
console.print("area: ${area(s)}")
Int/Float/Bool/String, native Duration literals (30s, 2hr),
List/Dict/tuples/records/ADTs, Option/Result with ?, traits with
where bounds, and Hylo-style parameter conventions (let/var/own, with
use-after-move as a compile error). Structural equality is deep on both
backends. Bounded async/channels, generators, and lazy iterators are supported
preview; comptime remains experimental.
A witchy function can't exercise host authority absent from its typed inputs.
Those inputs may include ordinary callbacks whose opaque behavior the caller
chose to delegate. witchy caps / caps-diff / grants-check / sandbox make
root capability demand inspectable and enforceable; they do not guess a
callback creator's hidden capture set.
That's a bounded guarantee, and the bound is worth being precise about. You
still trust the compiler, the runtime, whichever host bindings you link, and
whoever shipped you the binary. What you stop granting implicitly is ambient
access to all host resources. Ordinary callbacks still delegate behavior that
must be reviewed unless an API requires the explicit checked pure fn
contract. The capabilities guide states the exact model
and its limits.
Pre-1.0 and compatibility-unstable; anything may break without a deprecation
period. The dependable path is deliberately small - language fundamentals,
capability inspection, check/format/test, interpreter-versus-WASM parity,
portable WASM sandboxing, and self-contained
trusted-exe builds. Everything
else (runes + the Coven registry, the Glamour frontend, the in-browser
playground, editor tooling) is experimental dogfood.
PRODUCT-STATUS.md is the evidence-backed boundary.
Runes are experimental dogfood, but the round trip already works against the
hosted registry at https://witchy.fly.dev. The client picks its registry from
COVEN_URL; with none set it dials the local default 127.0.0.1:8787, so point
it at the hosted one first:
export COVEN_URL=https://witchy.fly.dev
witchy new demo-app && cd demo-app
witchy add insanitybit/hello --allow-fresh # --allow-fresh accepts a release still inside its staging cooldown
A freshly promoted release sits out a 72-hour staging cooldown before add
will resolve it - a window in which a compromised release can be noticed before
anyone installs it. On a young registry every release is inside that window, so
the first add needs --allow-fresh to opt in explicitly; a release past its
cooldown needs no flag. Then import the rune and use it:
// src/demo-app.witchy
import hello
fn main(console: Console):
console.print(hello.greeting()) // whatever the rune exports; `witchy doc` lists it
witchy run .
witchy tree . # the dependency and the capability footprint it pulls in
witchy tree shows the whole resolved tree's authority, so you can see exactly
what a dependency reaches for before you trust it. The
packages chapter walks the full model.
witchy is developed extensively with AI assistance; human judgment owns the language, capability-model, and product decisions. What determines supported behavior is executable evidence - the parity, sandbox, and artifact test suites - rather than who or what wrote the code. Contributions should disclose material AI use.
Every ```witchy block in the book, spec, and this README is a complete
program validated by the test suite; the runnable ones execute on both
backends. Editor support: a Zed extension with tree-sitter
highlighting and witchy lsp.
Dual-licensed under Apache-2.0 or MIT, at your option. Unless you explicitly state otherwise, contributions are dual licensed as above.
Rust
84.3%
JavaScript
6.8%
HTML
5.0%
Shell
3.1%