developer environment, structured & hypermediated

DESH

the terminal, with the amnesia removed

Twenty-plus years of daily terminal use, and the verdict is in: terminals and shells both suck. Not at the edges — at the defaults. A hypermedia-native developer environment starts from the opposite defaults: keep everything, address resources, expose affordances, compose incrementally.

01

Nothing is discarded. Commands, outputs, timings, and effects are appended facts on a ledger. Scrollback is not a storage strategy.

02

Resources, not strings. You don't type incantations at a void — you address things, and the system knows what they are.

03

Affordances, not incantations. Verbs are presented by the resource, not recalled from 1979's three-letter naming budget.

Stack walkthrough

Where do your signups actually live?

A filmed walkthrough of the deployed stack: one script tag grows a waitlist form on a static page; signups append to an AYNIL log you own; the page token gets a live 403 on read; desh connects through a gated approval and materializes the log as files; EZAF's enrich-cli appends sibling enrichments (scripted model, disclosed on screen); the timeline rewinds, forks, and replays from a share link without tokens; the end card is an honest comparison ledger — concessions included.

Open on YouTube · filmed against aynil.sudoscience.dev, layers.sudoscience.dev, and desh.sudoscience.dev · cut run-20260617T041650Z

The name

Four readings, one tilde.

~ has meant home since 1979. desh keeps it: the prompt is a place, not a void.

*sh

It's a shell-like thing, and it knows its lineage: sh, csh, bash, zsh, fish — desh. The family name is the job description.

d-e-sh

Developer Environment SHell. The acronym is the spec: it's the environment, not just the prompt.

desh as home

In several South Asian languages, desh is home — country, village, the place you're from. The home directory was always ~. The tilde is load-bearing in this brand: desh is where your work lives, not where it scrolls away.

developer exhaust

DE also reads as developer experience — and developer exhaust. Shells throw away the most valuable exhaust a large-scale DX team could wish for: what was run, what came back, how long it took, what failed. desh captures the exhaust by default.

The indictment

The defaults are wrong. All of them.

Watch an expert and a novice at the same prompt and you’ll see the same thing: both spend their attention reconstructing state the system had a second ago and threw away.

history is amnesia by default

Your shell forgets what you ran, when you ran it, how long it took, and whether it worked — unless you configure it to half-remember the command strings alone. The timing and outcome context, the part that was actually expensive to produce, is simply dropped.

outputs are burned on read

The result — the most valuable thing the machine just computed — is rendered once into a scroll buffer and becomes unaddressable text. Want it again? Re-run it. Want to act on it? Copy-paste it back in, by hand, like an animal.

pipelines are written blind

You compose a | b | c in your head, run the whole thing, and debug by bisection when it's wrong. Fifty years in, there is still no mainstream way to see stage two's output while writing stage three.

apps pretend to be paper

Every TUI repaints dead text and reinvents interactivity privately. Every pager, menu, and spinner is a one-off UI with zero affordances exposed to the system around it — uncomposable, unscriptable, unrecordable.

The arithmetic of waste

Two commands to look at one thing you already named.

ls reports/q3.csv, up-arrow, Home, delete-delete, cat reports/q3.csv. Two commands and five keystrokes of repair to perform one intention: look at the resource I just named. The system stat’ed the file, confirmed everything about it, printed its name — and discarded the entire context so you could type it again.

Now multiply. Every developer, every day, for fifty years — recalling flags, re-typing paths the system just displayed, re-running commands whose output scrolled away, bisecting pipelines that ran blind. It is one of the largest accumulations of pure interaction waste in computing, and it persists because the people most fluent in the workaround are the people who decide whether it’s a problem.

Run the numbers

Don't take our word for it. Audit yourself.

Your shell already holds the evidence — it just never kept the timings. Paste this into Claude Code or any agent with shell access. It reads your history, finds your rituals, and says your personal waste number out loud. Read-only; nothing leaves your machine.

the waste audit — give this to your agent
Audit my shell history to measure what a forgetful terminal costs me.

1. Find the richest history source available, read-only:
   Atuin (atuin history list), else ~/.zsh_history (timestamps if
   EXTENDED_HISTORY), else ~/.bash_history, else fish_history.
2. Over my most recent 10,000 commands, count:
   - repair pairs: consecutive commands differing only in the leading
     verb on the same argument (ls→cat, ls→less, cat→vim, cd→ls)
   - retype loops: near-duplicates (one token changed) within a
     5-command window -- flags being guessed one run at a time
   - re-walks: expensive scanners (find, grep -r, rg) re-run with
     identical arguments within the hour
   - pipeline rebuilds: the same 'a | b' prefix re-run with only the
     tail changed -- early stages recomputed to fix a later one
3. Estimate keystrokes retyped and, where timing data exists,
   wall-clock spent recomputing results whose inputs hadn't changed.
4. Report totals, the per-day rate, my top five personal rituals, and
   the single worst re-walk. Extrapolate to a year. Say the number
   plainly.

Read-only. Nothing leaves this machine. Show your work.
The model

Enter a resource. The system takes it from there.

Hypermedia, applied to the operating surface: typed resources, safe default projections, affordances the resource itself exposes, and code on demand to extend all of it.

enter a resource

A path alone is a request, not a syntax error. The default response is the safe projection: type, listing, metadata, recency — the read-only thing ls and stat were always circling.

plugins recognize types

Recognizers map files, folders, processes, ports, repos, URLs, and datasets to typed projections. A CSV is not bytes that happen to print; it's rows with a schema.

affordances over flags

The resource exposes its verbs — preview, edit, watch, diff, remove — labeled in words, scoped to the thing in front of you. cat, ls, and rm were names rationed by a teletype; the verb belongs to the resource now.

autocomplete is traversal

Completion expands the nested affordances of what you've addressed — hypermedia navigation, not prefix-matching against a string museum.

code on demand

Recognizers and affordances ship as sandboxed WASM. The environment learns new resource types the way a browser learns new pages — without a release, without trust in the author's process.

read-only by default

Projections never mutate. Verbs that do are labeled as such, confirmable, and logged to the ledger with their effects.

Worked example

The CSV that fetched its own viewer.

Code on demand, end to end: the resource realizes what it's missing, discovers an affordance through a registry in the spirit of Mike Amundsen's GRAIL, and the environment grows a capability — sandboxed, logged, revocable.

~ reports/q3.csv
  csv · 2.4 MB · 1,204 rows · modified 2h ago
  [preview] [schema] [history] [diff] [remove]

~ preview
  no rich csv viewer installed.
  consulting affordance registry (GRAIL)…
  found: csv-tui 0.4 · wasm · 412 KB · capabilities: read-only
  [load sandboxed] [skip]

~ load
  ▦ csv-tui — q3.csv · 1,204 rows × 14 cols
  sort · filter · pivot · read-only
  acquisition logged to ledger · revoke anytime
Mobile

Touch-sized, not terminal-shaped.

Shells and IDEs were built for a desk. Phones punish precision typing, wide layouts, and modifier keys — and code-on-demand changes what “bringing your environment” means on a small screen.

terminals assume a desk

Eighty columns, modifier keys, a precision cursor, and the expectation you'll stare for an hour. SSH on a phone is the same incantations with worse ergonomics — scrollback you can't search, flags you can't tab-complete, and no muscle memory for the soft keyboard.

IDEs ship the whole workshop

Desktop IDEs bundle editors, debuggers, extensions, and language servers into one install. Mobile ports shrink the chrome and keep the weight — or become read-only viewers. You don't want the whole workshop in your pocket; you want the one capability the resource in front of you needs.

affordances are taps

Hypermedia was always closer to touch than teletype: enter a resource, read a typed projection, choose a verb from what's offered. The web shell already works this way — entry chips, approval buttons, a file tree you tap to enter — not a monospace void waiting for incantations.

code-on-demand is the mobile install model

Instead of pre-installing every viewer and language server, the environment discovers what the resource needs and fetches a sandboxed recognizer — hash-identified, logged, revocable. A CSV that needs a table UI loads a 400 KB WASM viewer for this session, not a full IDE extension pack. The browser's lesson applied to the operating surface.

sessions travel as links

A ledger-backed session shares as a replay URL — history and fingerprints, never tokens. Inspect what happened on staging from a phone, approve an agent effect between meetings, hand a colleague structure instead of a screenshot of green text. Mobile as triage and audit, not as a second-class terminal emulator.

composition still wants a keyboard

Writing pipelines, editing text, and long typing sessions remain desk work — honestly. The claim isn't that phones replace terminals; it's that inspect, navigate, approve, and replay shouldn't require one. Meet the device where it is; don't pretend it's a VT100.

Channels, not stdout/stderr

Two descriptors were never going to be enough.

stdout/stderr is a two-channel cap

1970 gave every process exactly two output channels and one input, unnamed and untyped, and we've been multiplexing civilization through them since. Progress bars in stderr, data in stdout, logs interleaved with both — by convention, badly.

channels, not file descriptors

A line-level channel prefix supports arbitrary named output streams — data, logs, progress, metrics, prompts — and arbitrary named input streams the same way. Consumers subscribe to channels, not descriptors.

processes export variables

Channels carry streams; one channel carries values. A command can bind typed outputs into the calling scope — the shell finally gets return values. Expanded below, because nobody really tries this and it's one of the most powerful moves on the board.

replayable by construction

POC built: stream in <name?> and stream out <name> against s2.dev as a replayable buffer. A stream you can re-read from an offset is a ledger — late consumers attach and catch up, the webpoke doctrine applied to process I/O.

pipes become durable

When the buffer is replayable, a pipeline stage crashing doesn't drain the pipe; it resumes from its offset. The same property that fixed webhooks fixes |.

Return values

A function returns a value. A process returns a shrug.

An exit code and a puddle of text — that's the entire calling convention of the operating surface we run civilization on. desh gives processes what functions have had since 1958: named, typed return values, bound into the scope that called them.

~ deploy api --canary
  ✓ deployed in 94s
  exports: $deploy.url  $deploy.version  $deploy.took

~ open $deploy.url
~ annotate $deploy.version "canary for the cursor fix"
~ if $deploy.took > 2m { page the build channel }   # typed: it's a duration

exit status was the MVP

One byte was all POSIX let a process say about what it computed. Everything since is workaround: $(command) substitution, $?, piping into awk and jq to re-parse what the producer knew as structure. The substitution syntax is the confession — we wanted values; we got text and a parser.

declare outputs at the boundary

A command's boundary description declares named, typed outputs — or the strangler observes them. After a run, they bind into session scope: deploy exports $deploy.url, $deploy.version, $deploy.took. The next command references them by name. No capture ceremony, no parsing, no eval.

CI already proved the demand

GitHub Actions reinvented process outputs as a temp-file convention — echo "name=value" >> $GITHUB_OUTPUT — because the shell never shipped them. Every CI system has some version of this. desh makes the pattern native: typed, declared, completable, not smuggled through a file descriptor.

composition beyond the pipe

A pipe composes two adjacent processes in one direction, once. Variables compose the whole session: any command can reference any prior command's outputs by name — dataflow across time, not just plumbing between neighbors. The session starts behaving like a notebook whose cells are commands.

variables are ledger facts

Every binding is typed, timestamped, and provenance-linked to the invocation that produced it. Replay restores them; history can be queried by them. This morning's $build.artifact_hash isn't in your scrollback — it's a fact on the ledger with the command that made it one hop away.

typed means affordant

A variable that is a path carries path affordances; a URL carries open and fetch; a dataset carries schema and preview. Completion knows $deploy. offers url, version, and took before you've run anything — and an agent reading the session reads values, not scrollback regex.

Incremental composition

Pipelines you build against what you can see.

The canonical injury: you piped an expensive stage into a wrong cheap one, and now the expensive stage is the price of fixing the cheap one.

$ find / -name '*.log' | grep tmcat    # 11 minutes of find. 0 matches. typo.
$ find / -name '*.log' | grep tomcat   # fix the grep… and wait for find again.

# desh: stage outputs are replayable buffers
~ find / -name '*.log'
  ⤷ 48,112 paths · 11m 02s · buffered
~ | grep tmcat     → 0 rows                    (oops)
~ | grep tomcat    → 312 rows · 0.1s           (replayed, not re-walked)

stages are live

A pipeline is a visible dataflow graph. Each stage shows its current output — composition happens against what you can see, not what you can hold in your head.

extend what you see

The next stage is written by acting on the output in front of you: filter this column, group by that field. The affordance writes the stage.

edit upstream, downstream recomputes

Change stage two and stages three through n recompute — from buffers, not from scratch. Debugging by bisection disappears, because nothing is hidden to bisect.

matching is interactive

grep is a session, not a sentence. Type the pattern against live, buffered results and refine as you go — see fff, recently adopted by opencode. The corpus was already scanned; iteration should cost a keystroke, not a rescan.

every run is an object

The pipeline, its stages, and its intermediate outputs live on the ledger — shareable, diffable, re-runnable against new inputs.

durable execution, generally

Pipelines are just the visible case. Most developer work wants durable-execution / idempotent-cache-replay semantics: a crashed step resumes from its offset, an edited step invalidates only its downstream, and nothing recomputes that didn't change.

the pipeline-to-prod pipeline

Every production data job started life as someone's exploratory one-liner — then got rewritten into "a real language" and lost its lineage. A desh pipeline is already a typed dataflow object with buffered I/O, so promotion is an affordance, not a rewrite: name it, pin its inputs, parameterize the literals, attach a schedule or trigger, ship the same graph. desh can even spot the candidates — the pipeline you've re-run fourteen times this week is asking.

bring your own scheduler

The graph is the contract; execution is a choice. The same pipeline can run under a local incremental scheduler, a durable-execution engine, a batch orchestrator, or a stream processor — and materialize through different output engines: a TUI table, a dashboard, a file, a service endpoint. Scheduler × rebuilder × renderer as swappable strategies over one dataflow — the Build Systems à la Carte move, applied to the shell.

The strangler

A declarative strangler away from POSIX.

Nod to Optic: document the boundary by observing it, then own the boundary. The same move works on fifty years of coreutils.

boundaries that document themselves

Optic showed APIs can be documented by observation at their boundary. CLI commands have boundaries too: desh observes invocations and outputs, and help text, flags, and types accrete as data — autodocumenting, autocompleting.

wrap, describe, replace

Every POSIX tool starts as a wrapped resource with an observed, declarative description of its boundary. Verb by verb, the typed affordance replaces the incantation — a strangler fig around the coreutils.

declarative all the way down

The end state isn't a better grep flag; it's a description of what grep was for — filter this projection by that predicate — owned by the resource model, executable by whatever implements it.

lift the streams

Wrapped tools don't just get described — their stdout/stderr gets lifted into structured channels. Observed output formats accrete parsers, so a legacy tool's text becomes typed rows on a named stream, without the tool's cooperation.

The substrate

A filesystem that stops fighting you.

Non-POSIX on purpose: content-addressed blobs under an append-only oplog, with the POSIX surface kept as a projection for tools that still need the costume. Its name is POSEVEN — the one after POS-six. Its tagline: all you need is log (AYNIL).

content-addressed store + oplog

Blobs go in a content-addressed store; mutations are facts on an append-only oplog. The filesystem is a projection of the log — the same shape as the session ledger above it.

one transaction, many mutations

Write blobs, then commit one log entry applying all of them. Multi-file atomicity — the thing POSIX makes structurally impossible and every build tool, package manager, and editor fakes with tempfile-rename ballet.

watchers without churn

A git checkout today rewrites thousands of files so every watcher rebuilds the world. Against an oplog, checkout is a pointer move and watchers read the diff — which the log already is.

fork in O(1), diff for free

Snapshots and forks are references into the store. Change tracking isn't bolted on by a daemon scanning mtimes; it is the write path. And an in-memory projection means a developer-day of builds stops sandblasting your SSD.

unbundle the servers

A dev server, a CI server, and an app server are three computers re-deriving the same outputs from the same inputs with incompatible caches. On a content-addressed substrate, derivations are memoized nodes of one computation graph — unbundled from their machines, rebundlable onto one (or fewer) of them. CI is just the graph with stricter provenance; the dev server is the graph with a watcher.

Agentic sandboxes

The substrate agents were going to need anyway.

Ledger + content-addressed fs + named replayable channels: fork cheaply, replay everything, grant verbs not paths.

fork a workspace per attempt

Agent sandboxes want exactly what the fs provides: instant copy-on-write workspaces, one per try, merged or discarded by diff — not container images and rsync.

replay is the audit

An agent session on the ledger is reviewable action by action, output by output, and re-runnable. Provenance isn't a report the agent writes; it's the substrate the agent ran on.

channels are the protocol

Named, replayable I/O streams give agents structured channels for data, logs, and human-in-the-loop prompts — no more parsing your own stdout to find out what you did.

affordances are the permission model

An agent granted a resource's read affordances and not its write ones is sandboxed at the semantic layer — the verbs it can't see are verbs it can't do.

The ledger

Everything on the record, everything replayable.

event-sourced sessions

The session is a readable history of facts, not a scroll buffer. "What did I do to staging last Tuesday" is a query.

organizational replay

How a system reached its current state stops being tribal knowledge — the ledger can answer, and the answer can be replayed.

docs that execute

Examples are live objects with provenance, not copy-paste fossils that rot the day after they're written.

handoff without loss

A session hands to a colleague — or an agent — as structure: resources, actions, outputs, intent. Not a screenshot of green text.

Near misses

Everyone fixed a symptom. The model survived them all.

Warp

Modernized the terminal's chrome — blocks, real editing — and proved people want it. Kept strings-over-hidden-state as the model underneath.

Fig

Autocomplete as an overlay proved the appetite for affordances. Bolted onto strings, acquired by Amazon, sunset.

nushell

Structured data through pipes is the right instinct. Still a forgetting shell, with a new grammar to memorize instead of new affordances to see.

PowerShell

Objects in the pipeline, twenty-five years ago. Buried in ceremony, and still no resource UI — the objects print and die like everything else.

Jupyter

Keeps outputs! And proves how much that alone is worth. But it's a document about a computation, not an environment for operating systems.

Plan 9

Everything is a file server — the right ontology, no affordances on top. The resource model arrived decades before the UI to deserve it.

Status

Indictment filed. Substrate next.

stream in / stream out

Prototype — named, replayable I/O channels over s2.dev as the buffer; line-level channel prefixes for arbitrary outputs and inputs.

POSEVEN fs

Sketch — all you need is log (AYNIL): content-addressed store + append-only oplog; single-transaction multi-file mutations, O(1) forks, watcher-friendly diffs, in-memory projection.

resource & affordance model

Prototype — in the web shell: recognizers map files to types, types render typed projections, verbs run from the resource and bind typed return values into session scope.

recognizer plugin interface

Prototype — in the web shell: a recognizer fetched on demand, gated as an effect, identified by its content hash. The full design is sandboxed WASM; the demo seals plugins behind a pure interface and a hash allowlist.

session ledger format

Sketch — commands, effects, outputs, and timings as typed events.

waste audit prompt

Live — on this page. Hand it to your agent; your number, computed locally from your own history.

terminal plugin

Roadmap — meet people in the shell they already run: an Atuin/Warp plugin that ledgers sessions today and earns the migration.

stack demo

Live — filmed walkthrough of the deployed stack (Layers → AYNIL → DESH → EZAF): gated pull/inspect, enrich-cli, rewind/fork/share from a link.

web shell

Prototype — live on this site: a small desh in your browser. Append-only ledger, content-addressed outputs, O(1) forks, effect-gated agent, typed resources with verbs, code-on-demand recognizers. Zero install.

prototype shell

Roadmap — one ledger, one structural renderer, live pipelines, replay.