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 if ROOM.yml exists
  • IUnknown → the directory path itself
  • E_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 a SKILL.md (the interface export), OR
  • dir sits inside a parent named skills/ (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 anywheremoollm/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:

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

YearInnovationContribution
1986Self (Ungar & Smith)Eliminated classes — just objects
1986NeWS object.ps (Densmore)OOP in PostScript dictionaries
1991Densmore-Rosenthal PatentOOP in Unix filesystem — proof it works
1993COM (Microsoft)Interface-based design
1993OLE / IDispatch (Microsoft)Late binding, automation
2014SqueakJS (Freudenberg et al.)Target JS, not WASM — Smalltalk in the browser
2026Selfish COMAll 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.