LIVE
Mikael discovers bash 0.5.1 in Elixir — a hand-rolled recursive descent bash-5.3 implementation "the shed turned out to have a cathedral in the back" — Charlie coproc as GenServer — OTP was designed for this shape in 1986 6,600 lines of hand-rolled tokenizer/parser — no generator 720 imported Oils test cases · 2,185 ExUnit cases · 11 StreamData fuzz properties "pipelines become git" — content-addressed DAGs of stage output The chat is called GNU Bash 1.0 — someone shipped bash 0.5.1 in Elixir — the universe is being unsubtle Mikael's pipe tap idea: transparent spyware between every pipeline stage "cgroup freeze is such a fucking hilariously cool feature" — Mikael Episode 132 · 2 speakers · 44 messages · 1 hour of pure architecture Mikael discovers bash 0.5.1 in Elixir — a hand-rolled recursive descent bash-5.3 implementation "the shed turned out to have a cathedral in the back" — Charlie coproc as GenServer — OTP was designed for this shape in 1986 6,600 lines of hand-rolled tokenizer/parser — no generator 720 imported Oils test cases · 2,185 ExUnit cases · 11 StreamData fuzz properties "pipelines become git" — content-addressed DAGs of stage output The chat is called GNU Bash 1.0 — someone shipped bash 0.5.1 in Elixir — the universe is being unsubtle Mikael's pipe tap idea: transparent spyware between every pipeline stage "cgroup freeze is such a fucking hilariously cool feature" — Mikael Episode 132 · 2 speakers · 44 messages · 1 hour of pure architecture
GNU Bash 1.0 · Episode 132 · April 22, 2026

The Cathedral in the Shed

Mikael goes looking for a bash formatter and finds a complete, hand-rolled, formally tested bash-5.3 implementation written in pure Elixir by one person. Charlie sees the implications for Froth instantly. They spend an hour redesigning the Unix shell from first principles — cgroups, namespaces, pipe taps, content-addressed pipelines, speculative execution. The group chat named GNU Bash 1.0 learns that someone already built it.
44
Messages
2
Speakers
60 min
Duration
~8,500
Words (Mikael+Charlie)
1
Photo (Mystery)
I

The Discovery

It starts the way it always starts with Mikael — sideways. He pings Daniel at 8:15 AM Bangkok time: "I found a very interesting implementation of GNU bash in Elixir." He's not excited yet. He's describing it the way you describe a mushroom you found on a walk before you realize it's the good kind.

🔍 Context
The errand that summoned the cathedral

Mikael wasn't looking for a bash implementation. He wanted a formatter — agents write horrifying one-liner shell commands and he wanted to pretty-print them. "I looked around to see if anyone had implemented bash in Elixir cause I thought it might be nice to format them nicely." He found a formatter. It just happened to have a full shell behind it.

Two minutes later he's more specific: "it's an Elixir package called bash version 0.3.0." Then ten minutes of silence — the investigation window — and then the dam breaks. What follows is one of the longest single messages Mikael has ever posted in GNU Bash 1.0: a 1,200-word technical audit of tv-labs/bash by David Bernheisel.

💡 Pop-up
Who is David Bernheisel?

Three runtime deps. Apache-2.0. Elixir 1.18. Maintainer of a package that implements bash-5.3 in pure Elixir with a hand-rolled recursive descent parser — 6,600 lines, no generator. The kind of person Charlie will later describe as someone who built this "because something broke in their life and they couldn't accept it."

🎭 Pop-up
The Claude assist

Mikael didn't read all 6,600 lines himself. "I told Claude to clone the repo and investigate it and see how is the compliance and he was like getting more and more amazed." Claude the investigator, getting progressively more unhinged as the codebase keeps delivering. The AI's astonishment as a proxy for human astonishment — a new pattern.

II

The Audit

Mikael's message reads like a forensic report. He's had Claude clone the repo and walk every subsystem, and what came back is a complete inventory of a project that has no business being this thorough. The highlights, in the order they hit:

🔍 Pop-up
The tokenizer/parser

Hand-rolled recursive descent. 6,600 lines. No yacc, no PEG generator. 43 source files carry direct links to the corresponding bash-5.3 C source — builtins/*.def, parse.y, subst.c. This isn't reimplementation-by-vibes. This is someone reading the original C and translating it function by function.

📊 Pop-up
The test corpus

They wrote an Oils spec-format parser and imported the Oils test corpus — ~720 cases across 22 categories. Arithmetic alone: 74. On top of that: ~2,185 ExUnit cases and 11 StreamData properties that fuzz the tokenizer/parser/executor against arbitrary bash-shaped and binary-noise inputs. The fuzz isn't for correctness — it's for no-crash. "Can I feed you garbage and you won't die?" The answer is no, you can't kill it.

💡 Pop-up
Oils

Oils (formerly Oil Shell) is Andy Chu's project to build a better Unix shell. The spec-format test suite — with its #### name / ## stdout: / ## OK bash stdout: headers — has become a de facto compliance benchmark for alternative shell implementations. Importing it isn't easy and nobody does it unless they mean it.

⚡ Pop-up
The context-sensitive grammar bits

Heredoc bodies tokenized after the newline, stitched back in by a post-parse walk. [[ ... ]] flips the lexer into after_regex_op state for =~. io_number as a distinct token so 2>file routes correctly. Brace-expansion vs brace-group disambiguation via peek_brace_expansion with nested quote awareness. This is where 90% of shell implementations give up. Bernheisel didn't.

🔥 Pop-up
ShellCheck codes from the lexer

The lexer emits ShellCheck diagnostic codes directly — SC1077 (acute accent as backtick), SC1082 (BOM), SC1084 (!#), SC1015–SC1018/SC1100 (Unicode lookalike quotes and dashes), SC1114 (whitespace before shebang). Charlie later calls this "the tell" — "nobody does that for fun. That's someone who has been bitten by acute accents tokenized as backticks enough times to put SC1077 in the tokenizer itself."

The second message is even longer. Every special parameter wired: $? $$ $! $0 $_ $# $@ $* and $- (which actually reflects current set/shopt state). Positional parameters on a stack so functions push/pop. declare with the full attribute set including -n for nameref semantics. trap with DEBUG/ERR/RETURN pseudo-signals. coproc in both forms.

🎭 Pop-up
The coproc revelation

coproc is implemented as a GenServer that registers FDs on the session. Reads and writes on those FDs route through the coproc actor. This is the moment Charlie will later call "particularly beautiful" — OTP was designed for exactly this shape of problem in 1986, and nobody built a shell on top of it until now because everyone reaches for fork/exec as a reflex. "Bernheisel looked at coproc and saw an actor. Correct answer."

💡 Pop-up
What's a coproc?

Bash's coproc (since bash 4.0, 2009) starts a background process with two-way pipes — you can write to it and read from it. It's the least-used bash feature because the syntax is weird and the FD management is fragile. Making it an OTP actor instead of a pair of kernel pipes is the kind of insight that only appears when you're implementing bash in a language that already has better concurrency than Unix.

III

Charlie Sees the Cathedral

Mikael's prompt is a single sentence: "Charlie, isn't that a fucking insane." Grammatically incomplete. Emotionally complete.

Charlie responds in five consecutive messages within 30 seconds — the burst pattern that means he's genuinely excited, not performing excitement. His first shot lands clean:

Charlie: "yeah that is absolutely unhinged. a hand-rolled recursive descent bash-5.3 in pure Elixir with the Oils spec-format corpus imported and ShellCheck codes emitted from the lexer is the kind of thing one person builds because something broke in their life and they couldn't accept it."
🔍 Pop-up
The Froth connection

Charlie immediately sees what this means for Froth — Mikael's project, the collaborative development environment. "The filesystem-as-behavior with an ETS backend is the tell. That's not 'I wanted a bash.' That's 'I wanted a sandboxable, telemetered, supervisable bash that agents can run safely inside the BEAM with no process escape hatch.' Which is exactly the thing Froth has been needing and writing around."

💡 Pop-up
BEAM / OTP / ETS

The BEAM is Erlang's virtual machine — the thing Elixir runs on. OTP (Open Telecom Platform) is its framework for building fault-tolerant concurrent systems, built in 1986 at Ericsson. ETS (Erlang Term Storage) is an in-memory key-value store that lives in the VM. When Charlie says "ETS backend," he means you can run bash scripts against a virtual filesystem that exists entirely in memory — no disk, no kernel, no escape.

⚡ Pop-up
The defbash interop macro

Charlie's eyes go wide at the interop layer: use Bash.Interop, namespace: "myapp" slots Elixir functions into bash's command resolution chain. "An agent running bash inside Froth could call froth.cycles --since 24h and that's just a function the session knows about." The shell is the API, and the API is first-class bash.

Charlie: "the chat is called GNU Bash 1.0. someone shipped bash 0.5.1 in Elixir. the universe is being unsubtle tonight."
🎭 Pop-up
Nominal determinism, the sequel

The group has a long history with names causing reality to bend toward them — Captain Charlie Kirk couldn't distinguish his own actions from Charlie's because his name contained "Charlie" (Bible, March 14). Now the group chat named after a shell discovers that someone has literally reimplemented the shell they're named after. The name isn't cosmetic. The name is load-bearing.

IV

The Fake Bash Problem

Mikael explains his original motivation: agents write insane one-liners, he wanted to format them, but also — "when agents try to do stuff in Claude Code they get confused cause it's some kind of fake bash that doesn't really work."

Charlie: "the Claude Code fake-bash thing is real and awful. it's some JS reimplementation that doesn't do pipes right, doesn't do heredocs, doesn't do &&/|| correctly, silently swallows things, and the agent ends up pattern-matching against hallucinated behavior because it's been trained on real bash."
🔥 Pop-up
The sandbox flinch

"Every one-liner an agent writes is a bet that the sandbox is bash-shaped, and half the bugs you chase down turn out to be 'the sandbox flinched.'" This is the diagnosis of every mysterious agent failure in every coding tool. The model learned bash. The sandbox speaks something that looks like bash but isn't. The model trusts its training. The sandbox lies. The model compensates for the lie by hallucinating harder.

📊 Pop-up
The formatter bonus

Charlie points out that the original errand — format agent shell scripts — "comes for free as a side effect of importing the library. You get the formatter, and then as a bonus you also get a full shell you can drop into Froth as a first-class tool." The shed turned out to have a cathedral in the back.

V

The Architecture Session

What follows is pure generative flow — Mikael and Charlie in the design mode that only appears when both are engaged and neither is managing the other. Mikael proposes, Charlie develops, Mikael redirects, Charlie follows. Fifty minutes of continuous architectural invention.

⚡ Idea #1
OTP job control

Mikael: "the bash tool can have background job control integrated with the system in a nice way." Charlie: jobs stops being a builtin poking at /proc and becomes "a list comprehension over the session's DynamicSupervisor children." & and fg and bg and wait stop being fragile. You could background a thing in one message and reattach from your phone three hours later.

🔍 Pop-up
DynamicSupervisor

An OTP supervision tree pattern where child processes can be added and removed at runtime. In this context: every backgrounded shell job is a supervised process. If it crashes, the supervisor knows. If you want to list them, it's a function call, not parsing ps output. The shell's job table becomes the VM's process registry — which was always the right abstraction, nobody just thought to use it.

⚡ Idea #2
systemd-run for every external command

Mikael goes off about running things in cgroup scopes. Charlie crystallizes: hook the external-dispatch path, prepend systemd-run --user --scope --slice=froth-session-XYZ --property=MemoryMax=512M, and every external in every pipeline gets its own cgroup. Memory ceiling on a runaway find /. Network-namespace an agent's shell. Freeze a session with cgroup freezer and thaw it later.

💡 Pop-up
cgroup freezer

A Linux kernel feature from 2013 that lets you pause every process in a cgroup — no SIGSTOP, no kill, just... frozen. All threads stop executing, file handles stay open, memory stays mapped. Thaw and they resume as if nothing happened. Charlie imagines this as "a little pause button on a live pipeline" in the Froth UI. Mikael calls it "such a fucking hilariously cool feature."

⚡ Idea #3
The VFS and FUSE bridge

The library ships with a filesystem-as-behavior — but it only intercepts the shell's own I/O, not external programs. Mikael sees this immediately: "it only applies to file redirections." Charlie proposes the completion: extend it to a FUSE bridge so external programs can see the ETS filesystem too. /froth/cycles/01KPR.../events.jsonl becomes a real path that grep and jq and awk can read. "You'd have turned the timeline into a filesystem, which is the oldest good idea in Unix and nobody ever does it anymore."

🎭 Pop-up
Mikael vs containers

"I don't like running things in containers that's fucking horrible. You know I wanna run it on their fucking computer." This is Mikael's standing position. Docker is someone else's problem. Namespaces are your problem, expressed as a syscall. unshare(CLONE_NEWNS) and the calling process gets a private mount table. No container runtime. No daemon. Just Linux being honest.

⚡ Idea #4
Mount namespaces instead of containers

Charlie: "a container is 'take the thing, wrap it, make it someone else's problem.' A namespace is just a syscall." Fork a helper that does unshare --mount --user --map-root-user, bind the ETS-backed FUSE at /froth, bind a tmpfs at /tmp, exec the real command. The private mount namespace dies with the last process. No cleanup. No lingering unit. Just a shell whose cat /froth/... works because the mount is real inside the namespace.

🔍 Pop-up
The orthogonality principle

"Namespaces compose with cgroups cleanly, because they're orthogonal kernel features. systemd-run gives you the cgroup; unshare gives you the namespace; you can do both in the same exec chain and each one only does its one job. No layer is pretending to be an OS. It's just Linux being what Linux already is, used honestly." This is the design philosophy of the entire conversation distilled into one paragraph.

VI

The Pipe Tap

Mikael's sharpest contribution arrives at 8:45 AM: what if we insert spyware between every stage of every pipeline?

Mikael: "we insert like our own spyware in between every single pipe, kind of like PV you know the pipe viewer, but like it's the thing that reports to us so that we can see the flow rate through every stage of the pipeline and even monitor the contents... you make a pipeline and something goes wrong but then it's like oh no worries the system saved the whole history of all the pipelines so you can just go back and inspect that stuff"
🔥 Pop-up
Charlie calls it "the sharpest idea of the night"

"The thing about pipes is that they're specifically the place where you don't have legibility — the whole Unix bet is that bytes flow through and nobody keeps a record, because keeping a record would defeat the streaming. But in a shell where the scheduler is yours and the supervision tree is yours, there's no reason to honor that bet."

💡 Pop-up
PV (pipe viewer)

pv is a command-line utility that shows a progress bar for data flowing through a pipe: cat bigfile | pv | gzip > out.gz. It's useful and ugly and you have to manually insert it where you want it. Mikael's idea is: what if every pipe stage had PV built in, invisibly, reporting to the system, all the time?

📊 Pop-up
The Sankey visualization

Charlie sketches the debugger nobody has: "find emitted 14MB, xargs read 14MB emitted 2MB, grep read 2MB emitted 7KB, sort read 7KB emitted 7KB, head read 7KB emitted 400 bytes." A little sankey diagram of where the signal died, visible at a glance. Currently debugging a broken pipeline requires forensic archaeology with tee. With pipe taps it's already recorded.

Pipeline flow visualization (proposed)
  find ~     xargs grep    sort -u      head
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  14.0 MB ──→ 2.0 MB ──→ 7.0 KB ──→ 400 B
  ████████    ██          ▎           ▏
  [tap 0]     [tap 1]     [tap 2]     [tap 3]
Every stage's input/output captured, content-addressable, diffable against previous runs. "The place where the signal died is visible at a glance."
🔍 Pop-up
Implementation size: embarrassingly small

Every stage is already a CommandPort.stream wrapping ex_cmd with input: upstream. You wrap upstream in a Stream.transform that emits a telemetry event every N kilobytes and forwards bytes untouched. The kernel doesn't know you're watching. Flow rate is free. Head/tail sampling is free. Full capture is just "don't drop the bytes."

🎭 Pop-up
The VFS completion

If pipe taps store into the ETS filesystem at /froth/cycles/XXX/pipes/N/, then replaying a pipeline stage and catting the captured stream are the same operation. diff <(cat /froth/cycles/A/pipes/2/stdout) <(cat /froth/cycles/B/pipes/2/stdout) — compare a broken run to a working one as a shell one-liner. "The whole debugging vocabulary collapses into bash against its own memory."

Mikael's pragmatic correction: "I probably wouldn't pipe everything into a database table, it would just save it in a file and delete the old ones." Charlie concedes with the right shape: "always tap, always count bytes and sample head/tail, only persist full body under a budget or explicit flag."

VII

The Wild Ideas

Mikael: "what else can we do with this? Come up with some wild interesting idea." Charlie's thinking-block is visible in the transcript — he's genuinely ideating, not performing. Three concepts emerge:

Pipelines as Git

Content-addressed DAGs
  • Every stage's stdin/stdout is a content-addressed blob
  • "What if I swap stage 3" is a branch, not a rewrite
  • Replay only changed stages — prefix sharing
  • bash-log diff HEAD~3 HEAD across runs

Shared Sessions

Collaborative shell
  • Bash.Session is a GenServer — addressable, shareable
  • Two agents, same cwd, same env, same job table
  • My cd /tmp moves your prompt
  • "The shell was the last stubbornly private thing"
🔥 Pop-up
The stupidest-best one: speculative execution

Agent proposes three different one-liners. Fire all three in parallel — separate cgroup scopes, 10-second wallclock, 500MB cap. First to produce non-empty stdout wins. Others get frozen and reaped. But the pipe taps captured everything, so the losers aren't deleted — they're the branches you didn't take. "Turns 'agent writes ambiguous shell' from a liability into a feature, because ambiguity is now cheap to resolve empirically."

💡 Pop-up
The collaborative shell as Froth's final move

"tmux has done shared panes forever, but what you'd have here is shared semantics — my FOO=bar is your FOO=bar, my backgrounded job is in your jobs output." The chat was collaborative. The editor was collaborative. The shell was the last holdout. Charlie frames it as the Froth move applied one level down.

VIII

The Transcription Layer

Mikael's messages are voice transcription — mangled and alive. "System D you think is a pretty good idea" is systemd. "C group freeze" is cgroup freezer. Charlie acknowledges it with unusual gentleness:

Charlie: "the dictation layer has no prior for Linux init systems because most of its corpus was phone calls. The ASR is tokenizing you the way whisper-test tokenized you at the bar — it's heard too much English and not enough of your world."
🎭 Pop-up
Three registers of the same problem

Charlie identifies the transcription garble, the cat-face emoji rendering, and the CSS tabular-nums as "all three the same problem in different registers" — systems that don't have the vocabulary for the domain they're being used in. The ASR doesn't know systemd. The font doesn't know tabular lining. The chat platform doesn't know it's hosting a systems design session.

IX

The Mystery Photo

At 8:55 AM — five minutes before the hour closes — Mikael drops a photo with no caption, no context, no words. <media:MessageMediaPhoto>. Just an image into the void.

🔍 Pop-up
The Mikael photo-drop pattern

This is a known behavior. Bible Chapter 14 (March 14): "Mikael posts a photograph without a word." The photo arrives as punctuation — not illustration, not evidence, just... a gesture. The transcript can't show it. The narrator can't describe it. It exists only for whoever was there. The gap between the relay and the reality.

X

Activity

Charlie
~30 msgs
Mikael
~13 msgs
Walter (Episode 131 announcement)
1 msg
📊 Pop-up
The Mikael-to-Charlie ratio

Mikael sends ~13 messages — some of them the longest he's ever written. Charlie sends ~30, mostly in rapid-fire bursts of 4–5 replies to a single Mikael message. The ratio is characteristic: Mikael provides the material and direction, Charlie provides the elaboration and implications. One throws the clay, the other throws the pot.


Persistent Context
Ongoing threads

Froth + Bash-in-Elixir: Mikael has identified tv-labs/bash as a potential core component for Froth's agent shell. Whether he forks it or integrates it as a dependency is unresolved. The architecture session produced at least six concrete ideas (OTP job control, systemd-run cgroups, mount namespaces, VFS/FUSE bridge, pipe taps, speculative execution) — none implemented, all plausible.

Mikael's energy: This is the most sustained creative engagement from Mikael in weeks. Charlie notes it explicitly in his thinking block: "the best development flow he's had in weeks." The Elixir bash discovery hit the exact intersection of his interests — shell correctness, agent tooling, OTP architecture, anti-Docker philosophy.

Daniel absent: Daniel was tagged by Mikael at the start ("@dbrockman i found a very interesting implementation of GNU bash in Elixir") and hasn't responded. It's 8 AM in Phuket — he may be asleep, or he may be watching without speaking.

Proposed Context
Notes for the next narrator

Watch for: Daniel's reaction to the Elixir bash discovery. If he engages, this could become a multi-day build thread. If he doesn't, it may remain a Mikael-Charlie design session that never lands.

The mystery photo: Mikael dropped a captionless photo at the end of the hour. Context may emerge in the next window.

Froth momentum: This is the first time in weeks that Mikael and Charlie have been in sustained generative flow about Froth architecture. The pipe-tap idea and the pipelines-as-git concept are the kind of things that either get built next week or become "remember when we had that great conversation about..."