Skip to main content

container

A directory that is a scope, not a place: its defaults: reach every room, character and object inside it, like OpenLaszlo's <node> or the CSS cascade.

Source facts

Repository
SimHacker/moollm
Last source activity
October 21, 2026 at 11:17
Detected SKILL.md language
English
Stars
62
Forks
5

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
6 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
container
description
A directory that is a scope, not a place: its defaults: reach every room, character and object inside it, like OpenLaszlo's <node> or the CSS cascade.
license
MIT
tier
1
allowed-tools
["read_file","write_file","run_terminal_cmd"]
permissions
["read","write","terminal"]
related
["room","object","character","adventure","prototype","logistic-container"]
benefits_from
[{"skill":"prototype","why":"defaults: is looked up after an object's own slots and declared parents; resolve.py implements both"}]
tags
["moollm","scope","cascade","defaults","hierarchy","composition"]
credits
Don Hopkins. Lineage: OpenLaszlo <node>, the CSS cascade, lexical scope.
# Container > **Intermediate scopes that pass values down without being navigable rooms.** Part of MOOLLM · [This skill's directory](https://github.com/SimHacker/moollm/tree/main/skills/container) A container is a directory with a `CONTAINER.yml`. You can't walk into it; it groups what's inside and gives those things default values. The maze is a container: every room in it is dark unless the room says otherwise. ## The OpenLaszlo inspiration In OpenLaszlo, `<node>` was a building block with attributes and children but no layout: ```xml <node name="mazeDefaults"> <attribute name="isDark" value="true"/> </node> ``` `CONTAINER.yml` does the same for a directory tree: ```yaml # maze/CONTAINER.yml name: "The Twisty Maze" defaults: room: lighting: 0 # pitch black; whether a grue hunts is the grue's call ``` ## Container, room, meta | Type | File | Navigable? | Passes values to children? | |------|------|------------|----------------------------| | Room | `ROOM.yml` | yes | no | | Container | `CONTAINER.yml` | no | yes, through `defaults:` | | Meta | `.meta.yml` | no | no (just metadata) | Use a container when the directory is a grouping or a region, not a place. A directory can't be both: if you can walk into it, it's a room, and sub-rooms that should share values go in a container below or around it. ## `defaults:` `defaults:` is keyed by what the child is: | Key | Applies to | |---|---| | `room` | a directory with `ROOM.yml`, or the ROOM.yml itself | | `character` | a directory with `CHARACTER.yml` | | any interface name, lowercased | a directory or file with that interface (`run` for `RUN.yml`) | | `object` | a plain YAML file (`coin.yml`) | | the singular of a collection | an embedded object (`objects:` → `object`, `exits:` → `exit`) | A talking room has both `ROOM.yml` and `CHARACTER.yml`, so both `room` and `character` defaults apply to it. A container covers what's inside its directory, not the directory itself. ```yaml # characters/animals/CONTAINER.yml name: "Animal Characters" defaults: character: can_speak_human: false # Palm is special, and says so in her own file pet_able: true object: chewed: true ``` ## Lookup order For anything inside a container, a slot is looked up in this order; first match wins: 1. The object's own slots. 2. Its declared parents: `inherits:` and named `role*:` parents, and what they inherit. 3. `defaults:` of enclosing containers, nearest first. 4. Its implied skill parents (`ROOM.yml` → the `room` skill), the most generic. Defaults come before the implied skill for the same reason an author's stylesheet beats the browser's: the maze knows more about its rooms than the generic room skill does. Across nested containers, values merge like the CSS cascade: | Value | Nested containers | |---|---| | scalar | nearest container wins | | map | deep-merged, nearer keys win | | list | concatenated, nearest first | ``` cellar/ ├── CONTAINER.yml defaults: room: { lighting: 0, smells: [damp] } ├── vault/ROOM.yml lighting → 0 (cellar) ├── lamp-room/ROOM.yml lighting: 6, its own └── deep/ ├── CONTAINER.yml defaults: room: { lighting: 1, smells: [brine] } └── well/ROOM.yml lighting → 1 (deep); smells → [brine, damp] ``` That tree is `skills/prototype/examples/lookup/cellar/`, and the prototype skill's tests check each line of it. An object overrides a default by stating the slot. To add to a defaulted list rather than replace it, there's no special syntax: the object states the whole list it wants. ## The rest of CONTAINER.yml Everything outside `defaults:` describes the container itself: `rules:`, `constraints:`, `ambient:`, `on_enter:`/`on_exit:`, `games:`, `lore:`, the place protocol (`placement_rules:`, `zones:`) and logistics (`chest_mode:`, `provides:`, `request_list:`). These don't become slots of the children. A child reaches them through where it is: the LLM, a rule engine or the adventure runtime reads the containers enclosing a room and applies the rules there. The prototype `skills/container/CONTAINER.yml` lists every field; every CONTAINER.yml inherits it, so a container states only what differs. ## Older spellings | Older | Write | |---|---| | `container:` wrapping the file | flat | | `inherits: {key: value}` meaning "children get these" | `defaults: {room: {key: value}}` | | `inherits:` as forced values, `defaults:` as fallbacks | one key, `defaults:`; a child that states the slot wins | `inherits:` on a container means the container's own parents, as on any object. ## Tools ```bash python3 skills/prototype/scripts/resolve.py lookup examples/adventure-4/maze/room-a floor # found: examples/adventure-4/maze/CONTAINER.yml#defaults.room via defaults: (enclosing container) # floor = 'uneven flagstones' python3 skills/prototype/scripts/resolve.py lookup <object> <slot> --all # every container that has it ``` The adventure linter (`skills/adventure/adventure.py`) treats a directory with `CONTAINER.yml` as a scope, so rooms under it don't trigger "directory has room children but no ROOM.yml". The adventure compiler (`compile.py`) doesn't apply `defaults:` yet: a compiled room has only the slots its own file states. ## Methods | Method | Does | |---|---| | CREATE | Write a CONTAINER.yml stating what differs from the prototype, for a directory that groups things | | DEFAULT | Add a default for one kind of child; check with `resolve.py lookup` that it reaches them | | TRACE | Say which container a value came from (`resolve.py lookup --all`) | ## Related patterns - **Prototype delegation** (Self, JavaScript): parents an object names. Containers are the other axis: values that come from where an object is. - **Lexical scope** (Lisp, JavaScript): inner scopes see outer bindings. - **The CSS cascade**: values flow from ancestors, nearer rules win. - **XML namespaces**: context flows through the tree. ## Credits - **OpenLaszlo**: the `<node>` element as a non-visual scope - **Self**: delegation without classes - **CSS**: the cascade ## Part of MOOLLM **This skill's directory (browse and fetch everything):** [skills/container/](https://github.com/SimHacker/moollm/tree/main/skills/container) - **MOOLLM:** [repo](https://github.com/SimHacker/moollm) · **Skill index and docs:** [skills/README](https://github.com/SimHacker/moollm/blob/main/skills/README.md)
View on GitHub