TIES πŸͺ’

SkillFiles & storage

A manifest file type and authoring discipline for making a directory tree legible to an orchestrator without opening it. TIES.yml carries a semantic pyramid from emoji through label, name, one-paragraph definition, description and body, plus synonyms for name resolution, typed links, costs and read conditions for children, and foreign schemas attached as isolated organelles. Descends from HyperTIES (HCIL, 1988), whose articles were required to carry a previewable definition. Use when deciding what to page into context from an unfamiliar tree, when authoring a manifest for a node, when linting a corpus for silent name collisions, or when attaching external metadata such as Dublin Core.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the TIES πŸͺ’ skill

What this skill tells your AI

The instructions your AI receives, as published by simhacker/moollm in skills/ties/SKILL.md and read by ahel’s review.

A name is not a description, and ls gives you names.

You arrive in a directory you have never seen. ls returns twelve words. Which do you open? You cannot tell, so you open several and pay for all of them, or you open one and guess. Now do it a hundred times in one session, under a context budget, and the guessing is the dominant cost of the whole enterprise.

TIES.yml is a small file that answers the question without being the answer. It descends from HyperTIES β€” The Interactive Encyclopedia System, Ben Shneiderman's group at the University of Maryland HCIL, mid-1980s, with a NeWS implementation by Don Hopkins. HyperTIES had a property the web threw away: every article was required to carry a short definition, so a link could be summarised before it was followed. One click previewed, two clicks went. The reader could ask "should I go there?" and get an answer that cost a paragraph instead of a page.

The web kept the link and dropped the definition. Everything that feels revelatory about gwern.net's link popups is that rung rebuilt by hand, per link, as annotations β€” work HyperTIES got free in 1988 because its schema refused to let an article exist without one.

The one idea: position implies cost

A TIES.yml orders its keys cheap to dear:

RungSizeAnswers
glyphs1–5 emojiwhat KIND of thing is this
label1–4 wordswhat do I call this in a menu
nameone linewhat is this called, canonically
definition1–3 sentencesshould I go there
descriptiona paragraph to a pagewhat can it do, what is in it
bodythe whole documenthow does it actually work

Reading top-down is therefore reading cheapest-first, and stopping early is a decision rather than an accident. The ladder with its canonical weights lives in RUNGS.yml and is cited from elsewhere rather than retyped β€” it was once stated in two documents with different bottoms, and they drifted.

definition is the only required rung besides name. It is the one the web dropped and the one that makes the file worth reading. An LLM can draft a candidate, so "required" never has to mean "blocked" β€” mark it in provenance.generated and let a human confirm it.

What this is not: it screens, it does not advertise

The distinction that keeps the file small, and the one most likely to be got wrong:

TIES.ymlCARD.yml
Jobscreenadvertise
Answersis this a candidate at allhow much would this help, given my state
Reports onpresence, cost, staleness, whether a richer declaration existsvalue to this asker under these conditions
Grammaradvisory notes on pointerstriggers, conditions, scores, scopes, rationales
Weightdeliberately lightas heavy as it needs to be

If you find yourself writing a trigger with a score in a TIES.yml, you are writing an advertisement and it belongs in the CARD. The most useful thing a screen often says is description: CARD.yml β€” a full advertisement exists, one cheap fetch away. Two stages, deliberately unequal.

The borrowed insight from the Sims' Find Best Action, and the only one borrowed: usefulness is a delta against what the reader already has. The Sims scores a person twice, once with current motives and once with motives plus the advertisement, and subtracts; "+50 Hunger" is worth nothing to a full Sim. Likewise "693 KB of masters" is worth nothing to a reader holding the index. So a hint names the condition under which its value collapses, not merely its size. Motives, check trees and cutoff curves stay in the advertisement model where they belong.

Read in bulk, and everything follows from it

A TIES.yml is not read one at a time. A hundred are read at once to decide what to page in next. That single fact sets every other constraint:

  • Inline the screening set β€” glyphs, label, name, definition. These are what the screen reads.
  • Point at the rest β€” description and body are paths. Inlining them puts the answer inside the thing you were deciding whether to open.
  • Budget: 500 bytes for a leaf that inherits, ~3 KB for a hub, 4 KB hard. A hundred leaves at 500 bytes is 50 KB, which is the number that decides whether bulk reading is a real technique.
  • Caches go in a sidecar. cache: TIES-CACHE.yml. Derived values are bulky, they churn, and they are exactly what a screen does not need.

Carry only what is not derivable

Generally, not absolutely. The licence comes from dry-piles: duplication is allowed when it is declared, validatable and recomputable.

Already available, so do not retype it:

  • from the filesystem β€” names, counts, sizes, mtimes. ls and wc produce these, and they rot.
  • from the path β€” type and class. lenses/fitts.md already says {class: lens, id: fitts} and nobody typed it. The path is the guard expression.
  • from the pointee β€” the definition is in the CARD. Point; do not paraphrase.

Redundancy is a cache, and a cache is legitimate if and only if it can be invalidated and recomputed. So the line between good and bad duplication is not how much, it is whether it is declared. A count in a cache: sidecar with the command and the date is a cache. The same count hand-typed into prose is a fork.

This is not hypothetical. The first real manifest written for this skill claimed 37 lenses, 36 methods, 62 masters and 9 seeds. The truth was 35 / 34 / 60 / 7: the numbers came from ls | wc -l, which counts each pile's own CARD.yml and GLANCE.yml as if they were members. Every pile overcounted by exactly two. Worth separating from ordinary staleness, because a wrong formula refreshed on a schedule keeps producing the same wrong answer, on time, forever. The audit also found six files in one repo claiming those counts with no two agreeing, two of them loaded into every session.

What survives the test, and is therefore the actual payload: cost, the read condition, stale, synonyms, and why a tie exists.

Organelles: extension by endosymbiosis

A schema this file type does not own arrives the way a mitochondrion did β€” engulfed whole, kept inside a membrane, still carrying its own genome. Every guest lives under x: in a sub-object named for itself, with a schema: pointer to its own definition. The host validates the membrane and never the contents.

x:
  dc:                                            # the mitochondrion: ancient, universal, boring
    schema: "http://purl.org/dc/elements/1.1/"
    creator: Don Hopkins
  korz:                                          # a recent acquisition, may not survive the month
    schema: ../../designs/korz/KORZ-SCHEMA.yml
    dimensions: [self, purpose, whose_purpose]

Dublin Core gets no special status, and that is the claim: standard and experimental guests differ in age and adoption, not in kind, so one extension mechanism suffices instead of a blessed list plus an escape hatch. schema.org, PROV-O, SKOS and CIDOC-CRM arrive on identical terms. A guest can leave by deleting its sub-object, because nothing in the core referenced it.

Preserve what you do not understand

An unrecognised key, an unrecognised organelle, and every comment attached to either must survive a read-modify-write byte-identical. An unknown organelle is reported as INFO, never as an error β€” an unblessed guest is the design working.

The comment half is the part that gets lost. In yaml-jazz the comments are the data, so a round-trip that preserves structure and eats comments has destroyed the payload and kept the packaging β€” and that failure looks like success in every diff of parsed values.

implementation: never PyYAML safe_load/dump for anything that writes; it discards comments, key order and anchors. Use ruamel.yaml in round-trip mode, and assert byte-identity on a commented file as the tool's first test. ties_lint.py is a pure reader and never writes, which is why safe_load is honest there and nowhere else.

Prototypes: say the diff, inherit the rest

Manifests come in families. Every member of a pile shares a creator, a language, a type and most of its tags; what differs is a name, a definition and a few ties. Repeating the shared part per file is the redundancy that dominates a corpus, because it scales with the number of files rather than their size.

The pile's own TIES.yml is the prototype. It states shared values under inherited: so its own screen fields stay its own. Children declare proto: and carry only overrides.

# lenses/TIES.yml β€” the pile's screen AND its prototype
inherited:
  type: lens
  tags: [design, lens, ambient]
  x: {dc: {schema: "http://purl.org/dc/elements/1.1/", creator: Don Hopkins}}
# lenses/fitts.TIES.yml β€” 415 bytes, and this is all of it
proto: TIES.yml
name: fitts
glyphs: πŸ‘οΈπŸŽ―
definition: "Pointing cost grows with distance and shrinks with target size. Radial beats linear."
body: fitts.md

Resolution rules, chosen for a reader scanning a hundred files:

  • scalars β€” child wins. maps β€” deep merge, child wins per leaf.
  • lists REPLACE. Not append, because append makes a child's meaning depend on a parent it does not show you. tags+: [extra] is the visible opt-in.
  • absence means not-overridden-here, resolving to the prototype. If the prototype lacks it too, absence means unknown, as everywhere. The two senses compose and neither means false.
  • tombstone: unset: [tags, x.dc.subject]. Needed precisely because absence now means inherit, and dotted paths drop one organelle element without restating the organelle.

Budgets are amortised: a prototype is read once for N children, so it may be chattier; a leaf is read N times, so it may not. Keep the chain to one or two hops β€” a deep prototype chain is a class hierarchy, which is the thing Yertle warns about.

Sidecars, selectively

A per-member manifest is <subject>.TIES.yml, beside the thing it describes β€” big-endian, the subject leading, matching name.yml / name.md / name.png practice.

Do not generate one per member. A member earns a sidecar when it has synonyms or k-lines that must resolve to it, outbound ties an index line cannot carry, its own glyphs because something renders it as a pie slice or minimap dot, or references from outside its own pile. Otherwise the pile's CARD.yml index line is enough, and the sidecar would be a file whose entire content is inherited. The asymmetry decides it: one is cheap to add later, hundreds are expensive to delete once anything links to them.

Synonyms, and the failure that leaves no artifact

Take this one seriously. It is the reason the linter exists.

A reference resolves to a plausible wrong node. The page renders, the link works, nothing reports anything, and a corpus accumulates these faster than anyone re-reads it. Canonicalisation therefore folds case, collapses whitespace β€” the tilde is whitespace, so ~Founders~ and founders are one name β€” and strips punctuation.

That last one is a receipt from the surviving 1988 index: one article carried "alphabetically", "alphabetically," and "alphabetically." as three separately registered synonyms, because a link picked out of running prose drags its trailing punctuation into the lookup key. Widen the equivalence class and the author stops paying for it.

Two claims on one canonical name in one scope is an error. A nearer scope shadowing an enclosing one is legal and must be reported β€” that half is what the 1988 tool left unbuilt.

Ties: the links, typed

HyperTIES links were untyped jumps. ties[] fixes that with to, rel, and a one-line why for a reader deciding whether to follow. The relation names come from the senses already inside the word:

  • binds β€” the Korz sense. This node binds that name to a meaning.
  • cross β€” a cross-tie. A railroad tie runs across the grain, holding two rails at gauge. That is a bidirectional relation, which is exactly what HyperTIES lacked and what the web shipped without.
  • lit_from β€” provenance, whose flame this came off.
  • sustains β€” a musical tie: two notes, one duration, continuity across an apparent break.
  • ties_with β€” a tie in a vote, two candidates with no dominant cut. Not an error; it is the ambiguity case, resolved by asking, which is where a pie menu belongs.

The authoring loop

  1. Name it and define it. The definition answers should I go there in one to three sentences.
  2. Point, do not inline. description: and body: are paths to files that exist.
  3. List inside[] in read order, never alphabetically, each with a cost and a read condition that names when its value collapses: "index is enough", "open exactly one, named", "skip unless gardening".
  4. Add synonyms only if something must resolve to this node under another name. Generic aliases are worse than none β€” "the forge" is good, "the thing where you do the work" will collide with everything.
  5. Hoist anything a sibling also says into a prototype.
  6. Move derived values to a cache: sidecar with the command that regenerates them and the date.
  7. Lint. python3 skills/ties/scripts/ties_lint.py --corpus DIR

Then re-read what you wrote and delete every line that the filesystem, the path, or the pointee already says. That pass removed 900 bytes from the first manifest written with this skill, including a header that duplicated an index file verbatim.

Linting

python3 skills/ties/scripts/ties_lint.py PATH...          # files or directories
python3 skills/ties/scripts/ties_lint.py --corpus DIR     # adds cross-file checks

Exit 0 clean or warnings only, 1 on any error. Every check is build-time and needs no model: required rungs, ladder order, inline-body refusal, budgets, pointer resolution (glob-aware, because a pile addresses its members as *.md), inside costs, type-versus-container agreement, undeclared caches, redundant inheritance, bogus tombstones, organelle membranes, and across files, synonym collision, shadowing and glyph set contrast.

Warnings are advisory and sometimes the right answer is to change the threshold rather than the file β€” BUDGET_HUB was moved from 2 KB to 3 KB once three real manifests showed that a hub's cost is dominated by its inside entries, so a flat target punished breadth rather than chattiness. Say so when you do it.

Why this belongs in a git repo specifically

Stephen Kell's diagnosis (The operating system: should there be one?, PLOS'13) is that the filesystem has no meta-level descriptive facility analogous to classes, so as its uses multiply, its semantics get less clear and you cannot tell whether the usual operations will work. TIES.yml is that facility, retrofitted in-band.

In-band is not a compromise, it is the only option: git does not store extended attributes. A tree entry is a path, a blob hash and a mode. Which makes Densmore and Rosenthal's US 5,187,786 (Sun, filed 1991, expired 2011) the encouraging precedent β€” a full class hierarchy in a plain hierarchical filesystem, classes as directories, instances as directories of variable files, the inheritance chain in a path file used as a dictionary stack, with Self and Super, and explicitly "without requiring additional file attributes." If a class system fits that budget, a screening layer certainly does.

The full argument, with Pike's Plan 9 anecdote and Kell's correction to it, is in ../../designs/korz/repo-as-everything.md.

Part of MOOLLM

This skill is part of MOOLLM β€” a microworld OS where the filesystem is navigable space, comments carry semantic meaning, and skills are prototypes you instantiate. See the skills index.

Related skills, and what each actually contributes here:

  • yaml-jazz β€” the notation. Comments are first-class data, and in a TIES.yml they carry the read conditions that no field expresses.
  • dry-piles β€” the path is the guard expression, so type comes free from position, and the licence for declared duplication comes from here.
  • k-lines β€” ~name~ references and scope-walk resolution: the late-binding half, with no symlinks to go stale.
  • design-sense β€” the first dogfood: a hub, a pile prototype, and a leaf sidecar, all lint-clean.
  • postel β€” accept messy manifests, emit clean ones, ask when a name is ambiguous.
  • robust-first β€” an unknown organelle degrades to preserved-and-reported. A tool that drops what it cannot parse is not conservative, it is lossy, and the loss is silent.

Signals

GitHub stars
53
Forks
5
Last commit
Sep 2026
Advanced
Catalog kind
skill
Key
ties
Source
github.com/simhacker/moollm