Experiments
taal · dutch, “language” · rhymes with “ahl”

Humans write Taal.
Agents verify Go.

Taal is a small, strict ML that compiles to gofmt-clean, idiomatic Go — and a framework that rebuilds the load-bearing ideas of the BEAM on the Go runtime. One binary. One database. Actors, supervision, live views, declarative resources, transactional jobs, durable execution. No externalities except Postgres.

invoice.taal what humans author
taalc the whole framework
invoice.go what agents verify
./mono one static binary + postgres
Premise

The monolith that scales by attaching monoliths

Every node runs the same binary and carries the whole system's code. What differs is tuning: at boot, a node is told which roles to serve — web, live views, workers, analytics — and its supervision tree starts only those subtrees. Capacity is added by starting another binary and pointing it at the swarm. There is no orchestration layer, no message broker, no cache tier, no job service. The database is the only externality, and it doubles as the coordination fabric.

This is the architecture the BEAM was built for, transplanted onto a substrate chosen for a different virtue: Go's compiler, formatter, and runtime form the fastest, most unambiguous verification loop in mainstream software. Taal exists to close the gap between those two worlds — Erlang's shape, Go's feedback.

The inversion

The generated code is the interface

Most compile-to-X languages treat their output as exhaust. Taal treats it as a contract: everything taalc emits is gofmt-formatted, idiomatically named, and structured the way a careful Go engineer would have written it by hand. That single decision does most of the work in this system:

Agents never need to learn Taal

An agent reviews, tests, profiles, and debugs the emitted Go with tools it already masters — go vet, the race detector, pprof, delve. Taal is upstream of the loop, never inside it.

The escape hatch is always open

Any file can be ejected: delete the .taal source, keep the .go output, and continue by hand. Adopting Taal is never a one-way door.

Two verifiers, one edit

Every change is checked twice — first by Taal's typechecker (ADTs, exhaustiveness, no nil), then by the Go compiler. Both are sub-second. Both produce pointed, one-sentence errors.

The corpus stays canonical

gofmt means there is exactly one way the output looks. A model trained or prompted on this corpus sees zero stylistic variance — the property that makes generation reliable.

The macro layer Go never had. Elixir's Ash derives APIs, authorization, and state machines from declarations using macros. Go can't express that — but a compiler can. Taal performs the same derivation as code generation, and the result is arguably better for agents than macros: the derived behavior exists as inspectable Go, not compile-time magic.
The stack

Five layers, one binary, one database

Each layer is a small framework kernel with a Dutch name and a familiar ancestor. Every layer compiles into the same binary; a node's role tuning decides which ones wake up.

Taal the language
ancestor · elixir, gleam A strict, eager ML: algebraic data types, exhaustive matching, Result over if err != nil, no nil, local inference. Compiles every layer below into readable Go.
Bron “source”
ancestor · ash Declarative resources. Fields, types, actions, and policies in one place — REST handlers, authorization checks, validations, migrations, and state machines derived at compile time.
Zicht “sight”
ancestor · liveview Stateful views over websockets. One goroutine per socket holds the view's state; templates render to a static/dynamic split so only changed slots cross the wire. Postgres LISTEN/NOTIFY is the pubsub bus.
Werk “work”
ancestor · oban Jobs and durable execution on Postgres. Enqueue is transactional — a job commits atomically with the data that caused it, or not at all. Workflows checkpoint each step and survive restarts.
Zwerm “swarm”
ancestor · otp + distributed erlang Actors, mailboxes, supervision trees, and the node mesh. Attaching a monolith is a flag. Crashed actors restart by strategy; crashed nodes shed work back to the swarm.
Go + Postgres substrate
the only dependencies Goroutines, channels, the stdlib's HTTP/TLS/crypto, and a from-scratch Postgres wire protocol client over net.Conn. No cgo, no drivers, no third-party modules.
One honest asymmetry with the BEAM. Goroutines share memory, so a panicking actor can't promise it corrupted nothing — the guarantee Erlang's isolated heaps give for free. Taal's answer is linguistic, not runtime: actor state is only reachable through messages, and the typechecker rejects code that smuggles references across a mailbox. A small language can make that rule unbreakable.
The language

One declaration in, one system out

A Bron resource is the canonical Taal artifact: the domain model, its legal state transitions, and its derived surface in one screen of code. English keywords, always — the corpus should read natively to every model that touches it.

what you writeinvoice.taal
resource Invoice {
  field id     Uuid    @primary
  field total  Money   @min(0)
  field status Status  = Draft

  type Status = Draft | Sent | Paid | Void

  action send(self) -> Result(Invoice, SendError) {
    match self.status {
      Draft -> Ok({ ..self, status: Sent })
      _     -> Err(AlreadySent)
    }
  }

  derive api.rest        // handlers + routes
  derive authz(owner)    // policy checks
  derive migration       // schema diff → sql
}
taalc
what agents verifyinvoice.go
// Code generated by taalc.
// Readable by design — edit invoice.taal, or
// delete it and own this file.

type InvoiceStatus int

const (
  InvoiceDraft InvoiceStatus = iota
  InvoiceSent
  InvoicePaid
  InvoiceVoid
)

func (i Invoice) Send() (Invoice, error) {
  switch i.Status {
  case InvoiceDraft:
    i.Status = InvoiceSent
    return i, nil
  default:
    return i, ErrAlreadySent
  }
}

// + invoice_http.go, invoice_authz.go,
//   0007_invoice.sql — all inspectable.

Supervision reads like OTP because it is OTP's shape. A node's --role flags select which supervisors boot:

zwerm · actors & tuningbilling.taal
supervisor Billing {
  strategy one_for_one
  roles [worker]                        // only wakes on worker-tuned nodes

  child Ledger        { restart: permanent }
  child InvoiceMailer { restart: transient, pool: 8 }
}

actor Ledger {
  state { balances Map(AccountId, Money) }

  handle Post(entry Entry) -> Result(Money, LedgerError) {
    // state is reachable only here — the checker forbids leaking it
    ...
  }
}
werk · transactional jobscheckout.taal
tx {
  invoice = Invoice.create(order)?
  Werk.enqueue(SendInvoice { id: invoice.id })   // commits with the row, or not at all
}
zicht · a live viewboard.taal
view InvoiceBoard {
  state { invoices List(Invoice) = [] }

  mount(socket) {
    subscribe("invoices")                    // postgres LISTEN under the hood
    { ..state, invoices: Invoice.list()? }
  }

  event "mark_paid"(id Uuid) {
    Invoice.get(id)?.pay()?                  // NOTIFY fans the diff to every node
  }
}
zwerm · attaching a monolithshell
$ ./mono --role=web,zicht --join=10.0.0.12:4369
$ ./mono --role=worker    --join=10.0.0.12:4369   # capacity is a process, not a platform
Restraint

What Taal refuses to have

The surface area is the product. Every exclusion below is load-bearing — each one removes a way for two codebases to disagree, or for an error message to point somewhere far from the mistake.

laziness
Go's runtime is strict; so is Taal. Costs stay visible in the source, and a tuned node's resource profile stays predictable.
typeclasses / HKTs
Go's generics can't express them, and inference distance is how Haskell's errors got indirect. Dispatch is explicit; errors stay one sentence long.
language extensions
There is one Taal. No pragma stacks, no dialects — the property that keeps a corpus canonical.
global inference
Signatures are annotated; bodies are inferred. Every error points at the line that caused it.
nil
Option and Result, checked exhaustively. The emitted Go handles every branch because the source had to.
third-party modules
Go stdlib plus a from-scratch Postgres wire client. The dependency graph fits in a sentence.
Agent experience

The loop is the product

An agent's effectiveness is a function of its edit-verify loop: how fast the signal arrives and how little it can be misread. Taal is designed backward from that loop.

Diagnostics as API

Every taalc error states what's wrong, where, and one concrete fix — machine-stable format, human-legible prose. The checker is treated as an interface with an SLA, not a gatekeeper with opinions.

Sub-second, twice

taalc is a small strict-ML compiler; go build is go build. Two full verifications per edit, both under a second on a workshop-scale codebase.

The corpus ships with the compiler

Because the framework and language are built together, the canonical corpus — resources, actors, views, jobs, with their emitted Go — is authored once and stays in lockstep. Agents learn one dialect because only one exists.

Debug in Go, always

Stack traces, pprof flames, race reports, and delve sessions all land in the emitted Go — which was written to be read. The agent never hits a layer it can't see through.

The endgame is a division of labor with a clean seam: humans and agents author intent in a language with almost no surface, and agents verify consequence in a language with almost no ambiguity. The compiler is the whole framework; the framework is one binary; the binary needs nothing but Postgres and a peer to join.