Selfish COM: Directory-as-Object
April 24, 2026 · View on GitHub
The insight: Directories are objects. Files are interfaces. The filesystem IS the object graph.
The deeper insight: Every directory is an agent. Every file is an agent. Every YAML section can be an agent. Agents all the way down. See ARCHITECTURE.md → "The Universal Foundation".
Why Care?
- Human-readable — YAML files, not binary blobs
- LLM-friendly — AI can read and write your "code"
- Git-friendly — merge conflicts are manageable text
- Hot-reloadable — edit files, see changes
- Zero ceremony — no build step to add an interface
The Pattern
Real example: don-hopkins/
don-hopkins/ ← Object (identity = path)
├── CHARACTER.yml ← queryInterface('character')
├── ROOM.yml ← queryInterface('room')
├── portrait.png ← resource
└── memories/ ← child object
That's it. The directory IS the object. Each .yml file IS an interface.
See it at scale: 96 ROOM.yml files across nested directories, all following this pattern.
COM parallel:
pUnk->QueryInterface(IID_IRoom, &pRoom)→ check ifROOM.ymlexistsIUnknown→ the directory path itselfE_NOINTERFACE→ file not found
Self parallel:
- No classes, just objects (directories)
- Prototypal inheritance via
parents:arrays in YAML - Slots = file contents
Two Object Forms
Directory objects — multiple interfaces:
pub/
├── ROOM.yml ← room interface
├── MENU.yml ← menu interface
└── bar/ ← child object
File objects — single interface:
characters/
├── bob.yml ← one object, one interface
└── alice.yml ← one object, one interface
QueryInterface
queryInterface('don-hopkins/', 'room') // → don-hopkins/ROOM.yml
queryInterface('don-hopkins/', 'character') // → don-hopkins/CHARACTER.yml
queryInterface('don-hopkins/', null) // → don-hopkins/ (identity)
queryInterface('bob.yml', 'character') // → bob.yml (file IS interface)
No pointer adjustment. No vtables. Just file lookup.
Inheritance (Self-Style)
# don-hopkins/CHARACTER.yml
parents:
- characters/abstract/notorious-hacker
- characters/abstract/pie-menu-freak
name: Don Hopkins
tags: [hacker, artist]
The compiler flattens this at build time. Runtime gets simple property lookup.
Compiled Closures
Natural language compiles to JavaScript:
# BEFORE (human writes)
unlock_condition: Player has the red key and hasn't triggered the alarm.
# AFTER (LLM compiles, both visible)
unlock_condition: Player has the red key and hasn't triggered the alarm.
unlock_condition_js: return subject?.hasItem('red-key') && !world.flags.alarm_triggered
Body-only closures. Engine wraps with (world, subject, verb, object) => { ... }.
Implementation: compileJs() at engine.js:181, resolveText() at engine.js:3083
Skills Are Directory-Objects Too — Duck Typing All The Way Down
Skills are a special case of the directory-as-object pattern. A directory is a skill if either test passes:
queryInterface(dir, 'skill')returns aSKILL.md(the interface export), ORdirsits inside a parent namedskills/(the scoping-container type declaration)
🦆 Quack-quack duck typing. No registration, no registry, no manifest. You walk like a skill and quack like a skill, you're a skill.
This means skills can live anywhere — moollm/skills/, central/skills/, <some-customer-repo>/skills/, <project>/skills/, <adventure>/skills/. The resolver walks outward from the referring location and then consults every mounted workspace root; first match wins. See kernel/constitution-core.md § 14 for the invariant, and skills/file-system-object/SKILL.md for the full resolver algorithm.
What We Skip
All the C++ COM complexity:
- No pointer adjustment or thunks
- No reference counting (filesystem handles lifetime)
- No GUIDs (human-readable interface names)
- No registry (filesystem IS the registry)
- No binary formats (YAML text files)
The Stack
YAML source files → Python compiler + LLM → JSON trees → JS runtime
(human edits) (flattens, compiles) (fast load) (executes)
The directory tree is source of truth. JSON is compiled artifact.
Live code:
- Source: adventure-4/ — 96 rooms, 50+ characters
- Compiler: compile.py
- Runtime: engine.js (4200 lines)
Prior Art: The Densmore-Rosenthal Patent
This isn't a new idea. Owen Densmore and David Rosenthal proved it works in 1991.
US Patent 5187786A — "Method and apparatus for implementing a class hierarchy of objects in a hierarchical file system" (Sun Microsystems, filed 1991-04-05)
The patent describes exactly this architecture:
- Directories represent classes and instances
- Files within directories contain methods (shell scripts)
- PATH files encode inheritance chains
- Message sending via shell:
SEND aF001 methodA args
Owen Densmore also created object.ps for NeWS — OOP in PostScript using dictionaries as objects. The same pattern: data structures ARE the objects, the interpreter IS the runtime.
Tom Stambaugh (C2 Wiki) on the origin:
"Owen and I discussed his 'crazy' idea at a poolside table at the now-demolished Hyatt Palo Alto, on El Camino. I told him that it made sense to me, we scribbled furiously on napkins..."
John Warnock's vision (as recalled by Densmore):
"PostScript is a linguistic 'mother board', which has 'slots' for several 'cards'. The first card we built was a graphics card. We're considering other cards."
This "pluggable slots" concept maps directly to our interface files.
The Lineage
| Year | Innovation | Contribution |
|---|---|---|
| 1986 | Self (Ungar & Smith) | Eliminated classes — just objects |
| 1986 | NeWS object.ps (Densmore) | OOP in PostScript dictionaries |
| 1991 | Densmore-Rosenthal Patent | OOP in Unix filesystem — proof it works |
| 1993 | COM (Microsoft) | Interface-based design |
| 1993 | OLE / IDispatch (Microsoft) | Late binding, automation |
| 2014 | SqueakJS (Freudenberg et al.) | Target JS, not WASM — Smalltalk in the browser |
| 2026 | Selfish COM | All of the above + LLM-friendly YAML |
Vanessa Freudenberg: Target JavaScript
SqueakJS: A Modern and Practical Smalltalk That Runs in Any Browser (DLS 2014) — Vanessa Freudenberg, Dan Ingalls, Tim Felgentreff, Tobias Pape, Robert Hirschfeld.
The key insight that validates our approach: compile to JavaScript, not WebAssembly.
SqueakJS runs a complete Smalltalk system in pure JavaScript. Not via emscripten. Not WASM. Direct JS generation. Why?
- JavaScript IS the universal runtime — it runs everywhere
- WASM adds a layer without adding value for most applications
- JS engines are phenomenally optimized
- Debugging JS is easier than debugging WASM
- JS interop with the DOM is trivial
"Why add a layer when JS already runs everywhere?"
This directly informs Selfish COM's compilation target: we generate JavaScript closures, not WASM modules. The LLM writes JS. The runtime executes JS. No intermediate representation needed.
See also: SqueakJS live demo, GitHub, ACM DL
We're not inventing — we're combining. The patent proves the filesystem approach works. SqueakJS proves JavaScript is the right target. We add YAML (human/LLM readable), prototypal inheritance (Self-style), and compiled closures (LLM as compiler).
COM gave us interfaces. Self eliminated classes. Densmore showed us the filesystem IS the object graph. Selfish COM combines all three — filesystem-native, human-readable, LLM-friendly.
Deep dive: SELFISH-COM-IMPLEMENTATION.md — Full examples, code patterns, transformation specs.