Specifications - JulTob/Tag_Oriented_Programming GitHub Wiki
🏷️ Tag-Oriented Programming: The Specification 🔖
OOP owns inherent structure; TOP owns semantic context.
Tag-Oriented Programming (TOP) provides utility without changing identity.
A hero is one character. Over a game she becomes a Human, a Wizard, a Sage, a Harper, and stays the same hero. TOP adds each meaning as a Tag; the object keeps its identity the whole way. Traditional programming asks "what is this object?" TOP asks "what is this object for, here?", and lets the answer grow.
TOP was born from tabletop character creation: species, class, background and feats are independent choices, and the character sheet is what they compose. A spell list is written by all of them. TOP gives that idea a precise algebra: one identity, many layers, clear rules for how the layers meet.
This document is the source of truth. It is written in rings, from the kernel outward. Everything in an inner ring holds without the outer rings. A conforming implementation must preserve the observable semantics of every ring it claims. Examples are in Python; the laws are language-neutral.
| Ring | Contents |
|---|---|
| 0 · Kernel | identity, membership, Geometry, the tagging sequence, Rip |
| 1 · Contributions | Actions, Records, Operations, Reports, Overlay and Underlay, publication, access |
| 2 · Contracts | Preconditions, Imprints, Postconditions, defective Agents |
| 3 · Lifecycle | teardown protocols, Scope, deletion |
| 4 · Edges | what TOP does not promise, and why |
The last sections give the failure model, the conformance obligations, and where TOP stands among paradigms.
Ring 0 · The Kernel
0.1 Identity
A Target is any object a program already has. Tagging a Target never replaces it: before and after, it is the same object. Precisely:
- object identity is preserved (
tagged is original, sameid, same hash, same equality); - every attribute and method the Target had keeps working the way it did, unless a Tag deliberately contributes a member of that name;
- the Target's own special methods (
__contains__,__len__,__bool__, operators,__getattr__) keep working, with one deliberate exception:bool(agent)gains contract meaning once a Postcondition is visible (§2.5); - the name of the object's type is unchanged.
What TOP does not promise is nominal type identity: a Python
implementation may swap the object's class for a runtime subclass of it.
isinstance(agent, Host) stays true; type(agent) is Host may not. See
Ring 4.
0.2 Vocabulary
| Term | Meaning |
|---|---|
| Tag | A semantic category that can contribute meaning, behaviour, values and constraints. A noun: something you are (ser). |
| Target | An object before or during tagging. |
| Tagging | Applying a Tag to a Target: Human(charlie). |
| Agent | A Target that belongs to at least one Tag. |
| Field | The population of Agents currently carrying a Tag. |
| Base | A broader Tag that a Shape specializes. |
| Shape | A more specific Tag built over one or more Bases. |
| Form | The ordered, duplicate-free, Base-first closure of one Tag, ending with the Tag itself. |
| Geometry | The whole relationship structure of Tags, Bases and Shapes. |
| Layer | One Tag's position in an Agent's composition. |
| Overlay | The currently visible result of all active Layers. |
| Underlay | The prior visible contribution of a name, captured for a later Layer to extend. |
| Pin | A Tag marked @Pin, whose Targets are Tags (§1.9). |
| Pinning | Applying a Pin to a Tag: Rare(Wizard). The pinned Tag is the Pin's Agent. |
TOP uses Base and Shape, never parent and child: the words
describe a different model. A Tag may be both: Person is a Shape of
Being and a Base of Wizard.
0.3 Membership
The primary result of a Tagging is membership. An otherwise empty Tag is complete: it still defines a category and a Field.
class Wizard(Tag):
pass
Wizard(charlie)
assert charlie in Wizard # active membership
for wizard in Wizard: # the Field (its sound members, §2.5)
Observe(wizard)
Membership is closed upward through Bases: an Agent of a Shape is an Agent of every Base in the Shape's Form, and appears in each of those Fields.
class Mortal(Tag): pass
class Human(Mortal): pass
Human(charlie)
assert charlie in Human
assert charlie in Mortal
Membership is monotonic by design. Only Rip (§0.7) ends it, and Rip
does not erase history: an Agent that has ever been a member of a Tag
remains an instance of that Tag. In Python, isinstance(agent, Tag) is
that has-been check and stays true after Rip. agent in Tag is the is-now
check.
A Field never keeps an Agent alive. When an Agent ceases to exist it vanishes from every Field. Fields are indexed by identity: two equal but distinct Agents are two members.
0.4 Geometry
Bases and Shapes form a directed graph, the Geometry. Each Tag has one Form: its Bases, deepest first, in declaration order, each once, then the Tag.
class Spellcaster(Tag): pass
class Duelist(Tag): pass
class Arcane_Duelist(Spellcaster, Duelist):
pass
assert Form(Arcane_Duelist) == (Spellcaster, Duelist, Arcane_Duelist)
assert f"{Arcane_Duelist:form}" == "Spellcaster → Duelist → Arcane_Duelist"
A Base reachable through several paths appears once. Forming follows the Form downward, general to specific. Deforming (Rip) goes the other way: a Shape leaves before the Bases that support it.
0.5 Applying a Tag
The canonical act is:
Tag(target, **inputs)
It applies every Tag in the Form that is not yet active, Bases first, then returns the same Target. Keyword inputs are handed by name to the Preconditions, Record builders and Imprints run during that call (§1.3, §2.2, §2.3).
MI6(bond, code="007") # every protocol that names `code` receives it
Reapplying an active Tag does nothing. It does not duplicate membership, does not rerun Imprints, does not reset Records. Resetting is a deliberate act: Rip, then apply again.
0.6 The tagging sequence
Think of a factory line. Once, for the whole call:
- Gate. The Preconditions visible in the Form this call will produce inspect the incoming Agent and the inputs. A Shape's Precondition overrides its Base's, so a Shape can relax the gate. A failed gate stops the call before anything changes.
Then for each Tag in the Form, in order:
- Parts. That Tag's Records are built, each allowed to read the value already stored under its name.
- Commit. The Agent enters the Tag's Field; the Overlay and the Agent-bound view are set.
- Write. That Tag's Imprints run, in declaration order.
Then, once for the whole call:
- Quality check. Every visible Postcondition inspects the finished Agent.
The call boundary decides what a failure means:
- A failure in steps 1 or 2, for any Tag in the Form, rolls the whole call back. The Agent is exactly as it was at the call, including Bases pulled in by this call. Tags committed by earlier calls are untouched. Nothing partial is ever published.
- A failure in steps 4 or 5 raises, but the Tags stay. The product left the line. A defective product is not melted back to materials: it is flagged, repaired, or Ripped (§2.5).
Citadel(ari) Form: Territory, Citadel
gate Has_Charter FAILS
→ TagPreconditionError; ari in Territory == False, ari in Citadel == False
Broken_Wizard(ari) Form: Person, Broken_Wizard
gate ok
Person parts, commit, write
Wizard parts, commit, write
check Has_Spellbook FAILS
→ TagPostconditionError; ari in Person and ari in Wizard; bool(ari) == False
Tagging is otherwise side-effect free: TOP-managed state is restored on
rollback. An Imprint's in-place mutation of a pre-existing mutable value
(events.append(...)) is outside what TOP can undo; keep such effects for
step 4, where they are never rolled back, or hold them in Records.
0.7 Rip
Tags can be seen like a pass, a sticker, a badge. Ripping one is an exceptional, violent act of Field expulsion: "Give me your badge and weapon. You can no longer go into the evidence room."
del MI6[bond]
assert bond not in MI6
assert isinstance(bond, MI6) # once an agent, always an agent
Rip is the only exit from a Field, and it obeys three laws:
- Contributions are sticky. Actions and Records stay on the Agent after
Rip unless a
@Ripprotocol (§3.1) changes them. The result is a Rogue Agent: it has what it learned, but no active access to the Tag's Field, Operations, Reports or Agent-bound view. Rogue Agents are dangerous… but useful. - Rip is refused while a Shape needs the Base.
del Beast[wolf]fails whileWolfis active. Deform the Shape first. Rip never cascades: TOP does not run other Tags' protocols behind your back. - Conditions are sticky too (STEP-SPEC-12). Rip does not touch a
Tag's Preconditions or Postconditions: a promise that outlives its
Tag fails loud, never silently. Rip does one thing, and it is the one
act nothing can roll back, so it is not the place for a second rule.
The author ends a condition, in one of two visible ways: a guard
in the condition (
if agent not in Wizard: return True), which can follow any membership at all, another Tag's, a keyword on a Tag, the Tag under an Underlay; or an explicit deletion from the Tag's own@Ripprotocol,Contract.Delete(agent, "Has_Book"), one deliberate name at a time. A name that is not a condition on the Agent is a Resolution Failure. - Reapplying a Ripped Tag is a fresh Tagging. Imprints run again; Records are rebuilt.
- A Scope Rips what it applied, and only that. A Tag the Agent already carried at entry is left as it was on exit; a Tag that applied and reported a broken promise at the Scope's door did apply, and is Ripped on the way out with the rest.
0.8 Spellings
TOP borrows the language's own syntax for every Tag-level act and leaves
the Tag's dotted namespace, Wizard.something, to the program. A Report
called Field, Form or Rip must be possible. Structure without
stepping on the programmer's choices: TOP should feel like part of the
language, not a library's naming.
| Act | Python spelling |
|---|---|
| apply | Wizard(agent, **inputs) |
| active member? | agent in Wizard |
| carries a keyword? (Flags, §1.8) | "Undead" in ghoul, Undead in ghoul, Keyword(ghoul, "Undead"); a Flag's words the same, "Wolf" in howler |
| ever a member? | isinstance(agent, Wizard) |
| the sound population | for w in Wizard, len(Wizard), if Wizard: |
| the defective population | for w in ~Wizard, if ~Wizard: |
| everyone in the Field | Wizard[:], if Wizard[:]: |
| populations combined (§2.5) | Wizard | Fighter, Wizard & Fighter, Wizard - Sworn; the same on Wizard[:] and ~Wizard |
| one condition, read on the Agent (§2.5) | agent.Has_Book |
| the Agent-bound view | Wizard[agent] |
| leave the Field (Rip) | del Wizard[agent] |
| the Form, as Tags | Form(Wizard) |
| the Form, as text | f"{Wizard:form}" |
| an Agent's Tags, Outline, contract, as text | f"{agent:tags}", f"{agent:outline}", f"{agent:contract}" |
| catch one check's failure (§2.6) | except Precondition.Is_A_Caster:, except Postcondition.Has_Book:, except Imprint.Arm: |
| catch a Rogue Agent's access (§1.5) | except TagRogueAccessError: |
end a condition explicitly (§0.7), from a @Rip protocol |
Contract.Delete(agent, "Has_Book") |
| necessary to enter and to stay (§2.7) | @Pre + @Post, or @Requirement |
| pin a Tag (§1.9), and every act above with a Tag in the Agent's seat | Rare(Wizard), Wizard in Rare, for tag in Rare, Rare[Wizard], del Rare[Wizard] |
| a Tag carries a keyword? (Flag Pins, §1.9) | "Deprecated" in Wizard, Keyword(Wizard, "Deprecated") |
| a Tag's Pins and contract, as text | f"{Wizard:pins}", f"{Wizard:contract}" |
Truth on a Tag is truth on a collection: if Wizard: asks whether anyone
sound is a Wizard right now, while Enemy: fights while an enemy stands,
if not Boss: spawns one, if ~Wizard: asks whether anyone waits for
repair.
The Agent's own in is reserved for keywords: Tags marked @Flag
(§1.8). Ordinary Tags are asked from the Tag's side, agent in Wizard.
Format specs are the display door: a language's own string formatting renders what a Tag or Agent is, without a method on either. On an Agent the door follows the empty-seat rule: a host with its own formatting keeps it.
Queries that need a name are functions (Form, Tags, Keyword,
Apply, Outline, Contract, Scope), never members of the Tag or of
the Agent.
Another language profile chooses its own native spellings; the acts and
their distinctions are what must survive.
Ring 1 · Contributions
1.1 Two scopes
Every contribution has a receiver, and the receiver decides its scope.
| Scope | Contributions | Receiver | Meaning |
|---|---|---|---|
| Agent | Action, Record | one Agent | what this Agent does and holds |
| Tag | Operation, Report | the Tag | what the whole Field shares |
Where a contribution is declared does not change its scope. An Action written inside a Tag class is still an Agent contribution: the Tag stores it once, every Agent is its receiver.
The receiver may itself be a Tag. When a Tag is applied to another Tag (pinning, §1.9), the pinned Tag is the receiver, so the Pin's Records land as Reports and its Actions as Operations of that Tag: Tag scope, because a Tag is what receives them.
In Ada's terms, the Tag is the package specification and the Agent is the body. The specification declares what is shared; the body is what acts.
A name identifies one slot per scope: (scope, name). In Agent scope
that slot holds an Action or a Record, never both at once:
- Independent Tags (neither in the other's Form) cannot place an Action and a Record at the same Agent name. The Tagging fails at step 1, atomic.
- Within one Form, a Shape may change the kind of a Base slot: fix a Base Action as a Record, or compute a Base Record with an Action. The Base's view (§1.7) keeps the prior kind.
- Across scopes, equal names do not collide.
Fire.colour(Tag) andember.colour(Agent) are two slots with two histories.
1.2 Actions
An Action is Agent behaviour. Declared as a plain method on the Tag or with
@Action; the first parameter is the Agent, by discipline, under any name.
class Person(Tag):
def Attack(agent) -> str:
return "Attack!"
The latest applied Layer is the visible Overlay for a name. An Action
declared without an Underlay replaces; with @Underlay it extends:
class Elf(Person):
@Action
@Underlay
def Attack(agent, underlay) -> str:
return "With elven grace " + underlay()
The Underlay is captured when the Tag applies: it is the complete Action visible immediately before this Layer. It forms a backward chain of callables and never resolves again later. Calling it with no arguments forwards the current call's arguments; calling it with arguments passes those instead. If no prior Action exists, a Tag asking for an Underlay cannot apply (Tag Resolution Failure).
An Action that calls another Action through the Agent uses the current Overlay at play time:
class Combatant(Tag):
def Combat(agent) -> str:
return agent.Attack() # whatever Attack is visible now
Replacing an Action of an independent Tag without an Underlay is allowed but diagnosed (a warning): something was overwritten that another meaning still relies on.
The same rules bind special methods: a Tag may contribute __add__,
__eq__ and the like, and they actualize the Agent.
1.3 Records
A Record is Agent state contributed by a Tag. A Tag is something you are; a Record is something you are currently (estar): an adjective.
| Tag, you became this | Record, this can change |
|---|---|
| Human, Elf: species | asleep, poisoned: conditions |
| Wizard, Paladin: class | hit points, gold: resources |
| Sage, Soldier: background | location, morals |
| Harpers: affiliation | affinity, standing: current status |
| Spellcaster: learned capability | spell slots left |
| Access Pass: recognized by the system | clearance: the specific level |
A Record is declared as a builder. The builder runs at step 2 of the tagging sequence; its value is stored on the Agent, fresh for each Agent:
class Inventory(Tag):
@Record
def items(agent) -> list[str]:
return []
A builder may declare a second parameter: it receives the value already
stored under that name, or None when there is none. This is how
independent Tags pile up on one Record, in application order, and how a
Tag extends an ordinary attribute the host already had:
class Elf(Species):
@Record
def spells(agent, stored) -> list[str]:
return (stored or []) + ["Light"]
class Wizard(Class):
@Record
def spells(agent, stored) -> list[str]:
return (stored or []) + ["Magic Missile", "Shield"]
class Sage(Tag):
@Record
def spells(agent, stored) -> list[str]:
return (stored or []) + ["Identify"]
Elf(ari); Wizard(ari); Sage(ari)
assert ari.spells == ["Light", "Magic Missile", "Shield", "Identify"]
The author writes the merge. stored + new, max(stored, new),
stored | new: whatever the domain means. A builder without the second
parameter replaces; replacing the Record of an independent Tag is
diagnosed, like an Action.
A Record signature has three seats: the Agent, the stored value, and the
application inputs, which bind by name. The second positional
parameter is always the stored seat; when there is nothing to read, a *
holds the seat empty and the inputs follow it by name:
class MI6(Tag):
@Record
def code(agent, *, code): # agent, nothing stored, code from the call
return code
MI6(bond, code="007")
bond.code # "007"
A builder whose second positional parameter is named like a supplied input
is refused at tagging with a Declaration Failure that shows the spelling
above: the stored value must never be mistaken for the input in silence.
An input the caller did not supply keeps the parameter's default, or is
None when it has none.
After tagging, a Record is an ordinary attribute: read it, assign it,
delete it with the language's own del agent.record. Deleting is allowed
but rarely good design; frequent deletion means unclear state ownership.
Mutable Record values must be fresh per Agent unless sharing is the explicit intent. Shared values belong in a Report.
1.4 Reports and Operations
A Report is shared data belonging to a Tag; an Operation is shared behaviour. Both take the Tag as their first input, the way Agent members take the Agent. The four kinds are one pattern, two scopes:
| behaviour | data | |
|---|---|---|
| Agent | @Action def f(agent, ...) |
@Record def r(agent, stored) |
| Tag | @Operation def f(tag, ...) |
@Report def r(tag, inherited) |
class Community(Tag):
@Report
def colour(tag) -> str:
return "green"
@Operation
def Greet(tag, name) -> str:
return f"{tag.__name__}:{name}"
Community.colour # "green"
Community.Greet("Ari") # "Community:Ari"
A Report builder runs once per Tag, on first read, and its value is held
on the Tag: one copy for the whole Field. Like a Record, it may declare a
second parameter, which receives the value the Tag's Bases give that name,
or None, so a Shape can extend a Base's Report rather than replace it.
Reports and Operations are not visible on the Agent. ari.colour does
not exist after Community(ari), and neither does ari.Greet. Projecting
Tag members onto Agents would invent an Agent receiver for Tag behaviour,
silently shadow host methods, and confuse the two histories. The Agent
reaches them through its Tag-bound view (§1.7) or through publication.
A shared counter is a Report; the Imprint that changes it acts on the Tag:
class Secret_Agent(Tag):
@Report
def active(tag) -> int:
return 0
@Imprint
def Activate(agent):
Secret_Agent.active += 1
1.5 Publication
Scope says who owns a member. Publication says who may reach it from outside. The defaults follow the Agency/Agent picture: what the Agent does is public; what the Agency keeps is internal.
| Contribution | Default | Mark |
|---|---|---|
| Action, Record | external | @Secret makes it internal |
| Operation, Report | internal | @Public publishes it on the Agent |
Composition is the door. Inside it, everything of the relevant scopes resolves; outside it, only external members and the control plane (apply, Rip, membership, Fields, views of carried meaning). Inside composition means: while an Action, Imprint, Record builder, condition, or Rip protocol bound to this Agent is running.
@Secret on an Action or Record:
class Fire(Tag):
@Secret
@Record
def ember_heat(agent) -> int:
return 3
@Secret
@Action
def ignite(agent) -> int:
return agent.ember_heat * 2
@Action
def strike(agent) -> int:
return agent.ignite() + 1 # inside composition: resolves
ember.strike() # 7
ember.ignite() # AttributeError: secret member
ember.ember_heat # AttributeError: secret member
A secret handle captured inside and called outside fails closed. A
secret still occupies its (Agent, name) slot; it is not a third kind.
@Public on a Report or Operation constructs the publishing member for
you, so field economy (one value, one body on the Tag) needs no hand-written
adapter:
- A published Report appears on the Agent as a read-only name that reads the Tag's current value. One copy lives on the Tag; the Agent does not carry it.
- A published Operation appears on the Agent as an Action that forwards to the Operation with the Agent as its second input, after the Tag.
class Agency(Tag):
@Public
@Report
def colour(tag) -> str:
return "navy"
@Public
@Operation
def dispatch(agency, sender, message):
return network.broadcast(sender, message) # a Rogue sender never gets here
Agency(agent)
agent.colour # "navy", read-only
agent.dispatch(message) # Agency.dispatch(Agency, agent, message)
The published Action is a normal Action: it overlays and underlays at
(Agent, name) and stays on the Agent after Rip. Its use does not: a
published member answers members only, and only sound ones
(STEP-SPEC-10). At every use, an Operation's call or a Report's read, TOP
checks that the Agent still belongs to the publishing Tag and that every
promise on the Agent holds, whichever Tag made it: the same soundness
if agent: and the loop ask about. A Rogue Agent (it left) gets a
Rogue Access Failure, a Resolution Failure; a stale
send = agent.dispatch captured before Rip fails the same way. A
defective Agent (a promise broke) gets the broken promise by name,
except Postcondition.Has_Homeland, so it can repair what the failure
names and retry. Membership or repair opens the member again; nothing is
re-declared. The Agent's own Actions and Records are untouched: what it
became stays. A defective Agent reads the Tag's shared values through the
Tag directly, which asks nothing about the Agent. TOP is not a security
system; a long-running task may still re-check authority before an
irreversible step.
A Rogue Access Failure is a TOP failure and nothing else. It is not
dressed as a failure of the host language's attribute lookup: the
question is this Agent a member? belongs to TOP, and answering it in
the lower layer's words would let a lower-layer question (hasattr)
swallow it and report no such name for a name that plainly exists.
@Secret and @Public are modifiers: they stack with @Action,
@Record, @Operation and @Report in either order. A modifier that
restates the default (@Public @Record, @Secret @Report) is accepted;
both on one member is a contradiction and is rejected at declaration.
1.6 Delete
@Delete on a name removes the visible contribution, or the host member,
of that name. It frees the slot: the next contribution may occupy it with
either Agent kind, and an Underlay-seeking contribution finds nothing.
class Pacifist(Tag):
@Delete
def Attack(agent): ...
1.7 Access model
Three forms, three meanings. An implementation may spell them differently but must keep them distinct.
Current Agent access: the visible Overlay now.
agent.Attack()
agent.weapon
Agent-bound Tag access: the Overlay as it was immediately after that Tag applied to this Agent, including prior independent Tags. It does not change when a later Tag actualizes the same name. It is a snapshot: read only. Two spellings:
agent.Paladin.Attack() # by name: the last active Tag called Paladin
Paladin[agent].Attack() # by class: exact, even when names collide
Agent-bound access requires active membership; a Rogue Agent keeps its sticky members but loses the view. Secret members obey the same door in a view.
Direct Tag access: the Tag itself.
Paladin.HONOUR_CODE
Paladin.Operation(...)
Paladin[:] # its Field
Worked example:
class Character:
def Attack(agent) -> str:
return "Faulty OOP attack."
class Person(Tag):
def Attack(agent) -> str:
return "Attack!"
class Elf(Person):
@Action
@Underlay
def Attack(agent, underlay) -> str:
return "With elven grace " + underlay()
class Paladin(Person):
@Action
@Underlay
def Attack(agent, underlay) -> str:
return underlay() + " For your holy oath!"
ari = Character()
Paladin(ari)
Elf(ari)
ari.Attack() # "With elven grace Attack! For your holy oath!"
ari.Paladin.Attack() # "Attack! For your holy oath!"
Person applies before Paladin and stays active when Elf arrives. Elf
captures the complete Attack Overlay (Person, then Paladin). The later Tag
replaces ari.Attack() without changing what ari.Paladin.Attack() means.
1.8 Flags: Tags as keywords
Tabletop rules have keywords: Undead, Flying, Fey. A rule does not
import the creature's species; it asks whether the word is on the sheet. A
Tag marked @Flag is such a word. Its name becomes searchable from the
Agent's side, by name or by class:
@Flag
class Undead(Tag):
pass
Undead(ghoul)
assert "Undead" in ghoul # by name
assert Undead in ghoul # by class
assert Keyword(ghoul, "Undead") # the function form, works on any object
assert ghoul in Undead # membership, as for every Tag
This is what lets rules live as data. A table entry "Undead-Flying"
splits into words, and Keyword(agent, *words) decides; the rule ports to
any program whose Tags carry those names.
Flags are opt-in on purpose. Matching by name is global: two Tags that share a name are one word to a string. Only Tags that ask to be words carry that risk, and an ordinary Tag is never found by name. Names match exactly.
Words. A keyword has variants and neighbours: to the rules that name
it so, a werewolf is also a Wolf and a Lycanthrope. @Flag takes the
words a Tag also answers to:
@Flag("Wolf", "Lycanthrope")
class Werewolf(Tag):
pass
Werewolf(howler)
assert "Wolf" in howler # a word
assert "Werewolf" in howler # the name always flags
assert Werewolf in howler # the class form is unchanged
assert Keyword(howler, "Lycanthrope")
A word is only a word. It answers in and Keyword, never membership:
an Agent may carry the word Beast without being a Beast, the way a
secret identity carries a public name. A word that names another Tag is
accepted, and says nothing about that Tag's Field. Many Tags may share a
word; it answers while any of them is active. The words are the Tag's
own: a Shape answers its Base's words because the Base is active (§0.3),
never by inheriting them. Bare @Flag is the name alone. Words are
non-empty strings and match exactly, like names.
A Flag needs the Agent's in, and one seat holds one meaning. Something
else may already answer it: the host, through its own __contains__ or
__iter__ (a container, a party that iterates its members), or a Tag's
Action of either name, a published Operation included. A Flag and any of
them collide, in either order, and within one Form before any of it
applies. The later one fails with a Composition Failure naming both
sides, like a Record over a host property, and nothing changes: a seat
never changes meaning in silence. A Flag that declares such an Action
itself is a Declaration Failure.
The host's seat is read as the language reads in. In Python, for
__contains__ and then __iter__, the first class in the MRO that
defines the name decides. The value None, Python's "in is
unavailable", leaves the seat free. __getitem__ alone is not a seat, and
a Flag takes it: on a keyed host its in fails or never ends; on an
index-style host (the old sequence protocol) its in worked, and stops
meaning membership once a Flag lands.
Keyword(...) answers for every object, tagged or not.
1.9 Pins: Tags as Targets
Categories have categories. A game marks Tags as Rare or Homebrew; a
rules engine keeps Tags by School; a tool walks every Tag that is
Deprecated. A Tag marked @Pin applies to Tags, and the pinned Tag is
its Agent in every sense of Ring 0: membership, a Field to walk, a gate,
a promise, a view, a Rip.
@Pin
class Rare(Tag):
@Record
def rarity(tag): # lands as Wizard.rarity: a Report
return "rare"
@Action
def Describe(tag): # lands as Wizard.Describe: an Operation
return f"{tag.__name__} is {tag.rarity}"
Rare(Wizard)
assert Wizard in Rare # active membership, from the Pin's side
assert list(Rare) == [Wizard] # a Field of Tags
assert Wizard.rarity == "rare" # one value, held on the Tag
assert Wizard.Describe() == "Wizard is rare"
assert not hasattr(charlie, "rarity") # never on the Tag's Agents
Who may be a Target. A Pin applies to Tags only; on an object it fails with a Composition Failure. An ordinary Tag applies to objects only; on a class it is refused, as before. A Pin may not pin itself or any Tag of its own Form. A Shape of a Pin is a Pin, and one Form is all Pins or no Pins: mixing them is a Declaration Failure. Fields therefore never mix Agents and Tags.
The receiver rule (§1.1). The first parameter of a Pin's Agent-scope
member is the pinned Tag; write it tag. A Pin's Record lands as a
Report of the pinned Tag: one value, held on the Tag, inherited by its
Shapes, and readable through the inherited seat of a Shape's own Report
(§1.4). A Pin's Action lands as an Operation: read from the pinned Tag or
any of its Shapes, it receives the Tag it was read from, as an Operation
does. Membership does not inherit: War_Caster in Rare is False until
Rare(War_Caster). Nothing a Pin lands is ever projected onto the Tag's
Agents. A Pin's own Operations and Reports stay on the Pin.
Patching, with collision control. The pinned Tag's own Tag-scope
declarations are host members to a Pin, as a host method is to an
Action. A Pin Action over the Tag's own Operation replaces it silently
with the Operation as its Underlay, so def Control(tag, underlay, ...)
can call the engine it patches; every Agent that reaches the Operation
through the Tag is actualized at once, because an Operation is looked up
on the Tag at call time. A Pin Record over the Tag's own Report receives
the Report's value in the stored seat. A failed pinning rolls the
declaration back; a Ripped Pin leaves the patch, sticky, and a Pin's
@Rip teardown that declares a second seat receives the Tag's
original declarations under the patched names, so un-patching is one
line: def Unpatch(tag, original): tag.Control = original.Control. A Pin
member
named like something the Tag declares in Agent scope or as a
protocol, itself or in a Base, is refused at the gate: on a class the
scopes share one dictionary, and the write would silently remove the
declaration from every future Agent's contract. So is a name every Tag
answers through its metaclass. Names another Pin landed follow the
Overlay laws of §1.2 and §1.3.
Publication on a Tag. @Secret on a Pin's Record or Action makes it
Pin-private state on the Tag: held in the Tag's state, resolved only
while the Tag's own protocols or pinned Operations run, an Attribute
failure from main, not inherited by Shapes. @Public publishes it onto
the pinned Tag's Field: every present Agent receives it at pinning
and every future Agent through the Tag, as the Tag's own published
Report or Operation (def Control(tag, agent, ...), the Agent second),
under the membership rule of §1.5. The Field is checked on copies first;
a collision on any one Agent refuses the pinning and touches none. A
Pin's own Reports and Operations are not published, and @Delete and
special-method Actions on a Pin are Declaration Failures.
Keywords on a Tag. A Pin may be a Flag. A string can never be a
member, so on a Tag a string in the in seat asks for a keyword:
"Deprecated" in Wizard is True while the Flag Pin Deprecated is
active on it, and so is each word it lists; objects and classes in that
seat ask membership.
Keyword(Wizard, "Deprecated") and Keyword(Wizard, Deprecated) answer
the same. A word like Deprecated is a Pin and not a Report for the
reason Undead is a Tag and not asleep = True: a Report is a value,
and a value implies it may be flipped back; a Pin is membership, with a
Field, a gate, a promise, a Rip that is an act, and a history
isinstance remembers. A Tag says what something is; a Record says
what it has right now.
Seats already taken. bool(Wizard) remains "is anyone a sound
Wizard" (§0.8); a pinned Tag's own promises are read from the Pin's side,
Wizard in ~Rare, Contract.Display(Wizard), f"{Wizard:contract}".
f"{Wizard:pins}" names its Pins. Wizard.Rare is the Pin-bound view
by name, on the same miss-path rule as charlie.Wizard.
Contracts, Imprints, Rip. A Pin's Preconditions gate the pinning and
receive the Tag; a failed gate leaves the Tag exactly as it was, its
metaclass included. Postconditions are checked once per pinning and
re-checked at later pinning boundaries of that Tag; a broken promise
leaves the Tag pinned and defective (§2.5). Imprints run after commit.
del Rare[Wizard] runs the Pin's Rip protocols; landed values and
Operations stay, sticky, and isinstance(Wizard, Rare) stays True.
Pinning again after a Rip is a fresh pinning (§0.7).
A Pin does not alter the pinned Tag's own gate over its Agents. A Tag that should refuse new members while Deprecated writes that as its own Precondition, reading its Pins.
Ring 2 · Contracts
Conditions are TOP's quality control. They run at tagging boundaries, never continuously during play, and they never wrap an ordinary Action call.
2.1 A condition is strictly boolean
A condition is a function whose first input is the Agent. It holds on
True or on falling through without a return (an assert-style body),
fails on False, and is rejected on anything else.
class Wizard(Tag):
@Pre
def Level_Over_Zero(agent):
return agent.level > 0
@Post
def Has_Spellbook(agent):
assert agent.spellbook is not None
TOP does not coerce truthy and falsy values: a Record of 0 spell slots is
a real value, not a failure. return agent.spell_slots raises a Contract
Failure telling you to write the comparison you mean. The absence of a
return is permission: a condition is a restriction, and saying nothing
permits. Legal until written into law.
Contracts should be picky. The Layer's Lawyer
2.2 Preconditions: the gate
A Precondition inspects the incoming materials: may this Agent, with these inputs, enter the line? It runs at step 1, once per call, for the Tags applied in the current call only, as they will be composed: a Shape's gate replaces its Base's. An earlier Tag's gate is not re-asked when a later, unrelated Tag arrives; it already let its Agent in. Because the gate runs before any Base applies, a Precondition sees the Agent as it arrives, never what a Base's Imprint is about to write.
Preconditions, like Record builders and Imprints, receive application inputs by name:
class Coded(Tag):
@Pre
def Has_Code(agent, code):
return code is not None
Coded(bond) # TagPreconditionError
Coded(bond, code="007") # applies
A parameter the caller did not supply keeps its declared default, or is
None when it has none.
Preconditions relax backward. A Shape may ask for less than its Base,
never more, so a Shape can stand in wherever its Base is expected. Override
freely; compose with @Underlay when you want the Base's gate too:
class Founder(Guild):
@Pre
def Dues_Paid(agent):
return True # founders skip the dues gate
class Apprentice(Wizard):
@Pre
@Underlay
def Level_Over_Zero(agent, base):
assert agent.mentor # also needs a mentor
return base() # then the Base's gate
This is also how synergy is expressed: a feat that requires two other Tags gates on both.
class War_Caster(Tag):
@Pre
def Is_A_Caster(agent):
return agent in Wizard
2.3 Imprints: the writing
An Imprint performs the work of tagging: it runs at step 4, after the Tag has committed, in declaration order, with the application inputs by name.
class MI6(Tag):
@Imprint
def SetUp(agent, code):
agent.code = code
agent.status = "Full"
An Imprint may apply further Tags to the same Agent; those are ordinary later calls. If an Imprint fails, the Tag stays and the call raises an Imprint Failure: the machine broke while writing, and the product is on the line for inspection.
2.4 Postconditions: the promise
A Postcondition inspects the finished product. Every visible Postcondition runs at step 5, once per call, after the whole Form, so a Base may promise what its Shape delivers. Postconditions take no inputs: they judge the Agent, not the materials.
Every visible Postcondition is re-checked at each later tagging boundary, so a later Tagging can be refused by an earlier Tag's promise. Conditions do not check themselves during play; what must be watched continuously is a Record.
Postconditions strengthen forward. A Shape promises at least what its
Base promised: compose with @Underlay and and.
class Knight(Soldier):
@Post
@Underlay
def Is_Equipped(agent, base):
return base() and agent.oath
Sometimes a specific case really is looser than the rule: "no Strength above 20", except Barbarians. Overriding a Base Postcondition without its Underlay is a weakened promise. TOP allows it, because forbidding it would break the refactoring that is the point of TOP, but it is never silent: a Contract Warning is raised at tagging. If the relaxation is intended, the warning is your receipt; if it was a slip, it is your alarm.
The discipline in three lines:
- Strengthen a Post →
@Post @Underlay,return base() and …. Silent. - Relax a Pre → override freely. Silent.
- Weaken a Post → override without the Underlay. Allowed, diagnosed.
2.5 Defective Agents
A Tag whose Postcondition failed is applied and defective. The Agent is a member; its promise is broken; it must be repaired or Ripped.
An Agent is truthy exactly when every visible Postcondition holds.
if is the conditional and Postconditions are conditions:
if agent: # every promise holds
proceed(agent)
assert agent # or raise
Fields partition accordingly:
for wizard in Wizard: # the sound population: the ones fit to play
for broken in ~Wizard: # the defective population: repair them
for anyone in Wizard[:]: # everyone, sound or defective
assert broken in Wizard # a defective Agent is still a member
The plain loop is the working population, and if Wizard: asks whether
it is empty. A broken Agent does not stop being a member (in), does not
leave Wizard[:], and waits in ~Wizard for repair or Rip. Membership and the loop deliberately disagree for it:
the loop is the line, and a defective product is off the line.
Populations combine (STEP-SPEC-13). | is either, & is both, -
is the left without the right, on any population: a whole Field, the
sound view, the defective view, or a combination. A Tag in an operator
seat is its sound population, as it is in the loop; Wizard[:] | Fighter[:] is everyone who is either; the levels mix. The result is a
lazy view that reads its Fields when walked, keeps application order
within each side, answers in, len, truth and iteration, and has no
complement (~ on a union has no universe). A Tag with anything that is
not a population keeps the language's own class union (Wizard | None).
for c in Wizard | Fighter: # sound in either, each once
for b in Wizard & Fighter: # sound in both
for u in (Wizard[:] | Fighter[:]) - Sworn: # anyone with a role who has not sworn
A condition is read on the Agent by its name (STEP-SPEC-14).
agent.Has_Book is True while the promise called Has_Book holds and
False when it does not: the language's boolean, computed on read, never
stored, never callable. A Postcondition answers before a Precondition of
the same name; a condition that raises reads False; a non-boolean is a
Contract Failure on read, as always. Because the name is read on the
Agent, a condition may not share its name with an Action, a Record, a
member the host defines or a value the Agent already holds: the tagging
is refused at the door with a Composition Failure, and so is an Action or
a Record laid over a condition's name. Nothing is silently shadowed. A
pinned Tag reads its own conditions the same way (Wizard.Has_Members).
A condition that outlived its Tag (§0.7) still reads by name until the
author ends it.
Truthiness on a plain object is vacuously true, so this fills an empty
seat. A host that defines its own __bool__ or __len__ keeps it until a
Postcondition becomes visible on that Agent.
2.6 Naming the culprit
A failed check raises a failure that carries the check's name, as a
subclass of the general failure: the Precondition declared as
Is_A_Caster refuses with TagPreconditionError.Is_A_Caster, an
Imprint called Arm fails with TagImprintError.Arm, a Postcondition
called Has_Book with TagPostconditionError.Has_Book. The marks are
the short spelling of the same classes, so a program catches a refusal in
its own words:
try:
War_Caster(bruk)
except Precondition.Is_A_Caster: # this one refusal
...
except TagPreconditionError: # any refusal
...
Rules:
- The named failure is the general failure (
issubclass), so a handler for the general one still catches everything.error.nameis the check's name; the general failure'snameisNone. - A name exists from the moment its Tag class exists, not from its first
tagging. A name that no Tag declared is an error at the
exceptline (AttributeError), never a handler that silently fails to match. - Names are per kind, not per Tag: two Tags that each declare
ReadysharePrecondition.Ready. A program that needs to tell them apart readsf"{agent:contract}"or names the checks differently. - A check that raises instead of returning
Falseis named the same way.
bool(agent) is the verdict on a finished Agent. The Contract namespace
names the promise that broke:
Contract.Postconditions(agent) # promises hold, or raise naming one
Contract.Preconditions(agent) # gates still hold, or raise
Contract.Conditions(agent) # both, Pre then Post
Contract.Holds(agent) # the boolean form, spelled out
Contract.Status(agent) # {condition: holds?}, never raises
print(Contract.Display(agent))
# Hero[Wizard] contract:
# Pre:
# OK Level_Over_Zero
# Post:
# XX Has_Spellbook
2.7 Necessary to enter, necessary to stay
@Pre and @Post stacked on one function make it a condition:
checked at the door as a Precondition and kept as a Postcondition. It is
one body, named once, and it fails under both names:
class Elf(Tag):
@Pre
@Post
def Alive(agent):
return agent.alive
Elf(dead) # refused: Precondition.Alive
Elf(ari) # in; and from now on Ari must stay alive
Being alive is necessary to become an Elf and necessary to remain a sound one; it is not sufficient, since other living things are not Elves. A condition says necessary; membership says is.
@Requirement is the same mark said in one word, for a claim that reads
better as a necessity than as a pair of checks:
class Elf(Tag):
@Requirement
def Alive(agent):
return agent.alive
A Requirement names no failure of its own. It fails at the door as
Precondition.Alive and afterwards as Postcondition.Alive, because a
program repairs the two differently: one is an Agent that may not come
in, the other an Agent already in and now broken. Reading
Requirement.Alive is a Declaration-time mistake and says so.
2.8 Writing a check
A condition usually asks whether an Agent has something. assert agent.spellbook reads well, but asks two questions at once: defined and
truthy. That is fine for a spellbook object; it is a trap for a
spell_slots of 0. Separate having a contribution from its value:
- Is it there at all?
assert agent.spell_slots is not None, orassert hasattr(agent, "spell_slots")whenNoneis itself valid. - Does it have a particular value?
assert agent.spell_slots > 0.
Ring 3 · Lifecycle
3.1 Rip protocols
Imprint and Rip are duals: constructor and destructor, __enter__ and
__exit__. A @Rip Action runs when the Agent leaves the Tag's Field. It
is also an ordinary, callable Action.
class MI6(Tag):
@Imprint
def SetUp(agent, code):
agent.code = code
agent.status = "Full"
@Rip
def SetDown(agent):
del agent.code
agent.status = "Former MI6 Agent"
MI6(bond, code="007")
del MI6[bond]
bond.status # "Former MI6 Agent"
Teardowns run after membership has ended, in declaration order, every
one of them; failures are collected and reported once as a Composition
Failure. A @Rip Action with an @Underlay runs composed, like any Action.
Ripping a Tag may apply another Tag, even itself. That is outside good TOP use: it could keep an Agent from ever leaving a Field.
3.2 Deleting an Agent
Deletion of an Agent Rips it from its active Tags, so exit protocols run. An implementation provides three tiers and says which is which:
| Tier | Guarantee |
|---|---|
Finalizer (__del__) |
best effort: when the Agent is collected, its teardowns run, then its __del__ Layers; at interpreter exit only the __del__ Layers run; the language may not run finalizers at shutdown or inside reference cycles |
Scope(agent, *tags) |
guaranteed: Tags apply on entry and Rip, in reverse, on exit, even if the block raises |
At_Exit(agent) |
opt-in: teardowns also run at normal interpreter exit; registration is weak |
Every teardown runs at most once, whichever tier reaches it first.
The Agent's own finalizer is a member in Layers (STEP-SPEC-18). The
host's __del__ is its first Layer, found as the language finds it. A
Tag's __del__ replaces the Layers beneath it, or with @Underlay
extends them; @Delete removes them. With nothing stated, the host's own
__del__ runs, as it would for the untagged object. The Layer beneath a
__del__ is always callable, and where there is nothing it does nothing.
Deletion runs in that order: every teardown still due runs, then the
__del__ runs as the Overlay shows it, top Layer first; both run inside
the composition door (§1.5). A __del__ never stops a teardown:
replacing the Layers replaces only them, and an interrupted teardown
does not skip them. At interpreter exit only the Layers run; teardowns
at exit are At_Exit's. An error raised by a __del__ Layer is reported
as the language reports any finalizer's; the teardowns stay best effort
and silent. @Rip on a __del__ is a Declaration Failure: a __del__
Layer already runs at deletion. The language calls __del__, not the
program: in Python, agent.__del__ reads the kit's finalizer, and
calling it by hand runs the teardowns while the Agent is still a member.
class Lantern:
def __del__(self):
log.append("wick out") # the first Layer
class Carried(Tag):
@Rip
def Put_Down(agent):
log.append("put down") # a teardown: runs first
class Enchanted(Tag):
@Underlay
def __del__(agent, underlay): # a Layer over the host's own
log.append("spell fades")
underlay()
lamp = Lantern()
Carried(lamp)
Enchanted(lamp)
del lamp
assert log == ["put down", "spell fades", "wick out"]
with Scope(agent, Sentry):
guard_the_gate(agent)
# Sentry's teardown has run here, exception or not
Ring 4 · Edges
What TOP does not promise, stated so nobody has to discover it.
- Nominal type.
type(agent) is Hostmay be false after tagging. The type's name is unchanged andisinstance(agent, Host)is true. Code that keys on exact type identity is outside the guarantee. A pinned Tag's metaclass may be wrapped the same way; its Shapes' is not. - Copying and pickling. Cloning an Agent is domain work: build a new
Target and apply its Tags again (
Tags(agent)lists them). A Python implementation refusescopy.copyexplicitly rather than aliasing state. - Threads. One Agent, one thread. Fields are not synchronized.
- Host descriptors. A Record cannot share a name with a host property or slot; the Tagging fails with a Composition Failure.
- Raw side effects. Rollback restores TOP-managed state and the Agent's attributes at call entry. It cannot undo an in-place mutation of a pre-existing mutable value.
- Operations and the door. An Operation called directly on the Tag is outside composition unless it was reached from an Action, Imprint, condition or Rip of the Agent whose secrets it reads.
🚨 Failure model
Every failure names the law it violates. A language profile may use its own types but must keep these distinct.
| Failure | Meaning | Effect |
|---|---|---|
| Tag Declaration Failure | A Tag is written wrong: illegal mark combination, @Underlay without a parameter to receive it. |
at class use |
| Tag Composition Failure | Contributions cannot form the Overlay: cross-kind collision, Record over a host descriptor, a Record builder or teardown that failed, a Target that cannot carry state, a Base still required. | call rolled back (or Rip refused) |
| Tag Resolution Failure | A required Underlay, view, or membership is unavailable. | call rolled back |
| Tag Rogue Access Failure | A Rogue Agent reached a published member of a Tag it has left. A Resolution Failure, and a TOP failure only: never dressed as a host-language attribute failure. | use refused |
| Tag Precondition Failure | A gate refused the incoming Agent. | call rolled back |
| Tag Imprint Failure | An Imprint failed after commit. | Tags stay |
| Tag Postcondition Failure | The finished Agent breaks a promise. | Tags stay, Agent defective |
| Tag Contract Failure | A condition returned a non-boolean. | call rolled back |
| Overwrite Warning | An independent Tag replaced a visible Action or Record without an Underlay. | diagnostic |
| Contract Warning | A Shape weakened a Base Postcondition. | diagnostic |
🧰 Conformance obligations
A conforming implementation provides, ring by ring:
Ring 0
- stable object identity under tagging, and preserved host behaviour;
- membership and Base membership (
agent in Tag), closed upward, with a has-been check that survives Rip; - non-owning, identity-indexed, iterable Fields;
- Base-first Form application, each Base once, active reapply a no-op;
- the five-step tagging sequence with the call boundary: rollback on gate and Record failure, Tags stay on Imprint and Postcondition failure;
- Rip: sticky contributions and sticky conditions, ended by the author
(a guard, or
Contract.Deletefrom the@Ripprotocol), refusal while a Shape requires the Base, no cascade; - native spellings for every Tag-level act, leaving the Tag's dotted namespace to the program.
Ring 1
- Agent and Tag scopes,
(scope, name)slots, cross-kind collision rules; - latest-Layer Overlay; captured, callable Underlays for Actions and conditions; the stored value for Records;
- Reports and Operations invisible on the Agent;
- publication:
@Secretwith a composition door that fails closed;PublicReports as read-only live names andPublicOperations as Actions with the Agent as second input; illegal marks rejected at declaration; - Flags: opt-in keyword Tags searchable from the Agent's side by name, by
the words they list, and by class, a word never standing for
membership, colliding in either order with a host or a Tag's Action
that already answers
in, never matched for ordinary Tags; - Pins: opt-in Tags whose Targets are Tags, the pinned Tag as Agent under
every Ring 0 act, the receiver rule (Records as Reports, Actions as
Operations), Fields never mixed, the Tag's Tag-scope declarations as
host members (Underlay, stored seat) with Agent-scope and protocol
names refused,
@Secretas Pin-private state,@Publicas publication onto the Field, Flag Pins as keywords by string; - published members answering members only, and only sound ones: a Rogue Access Failure on a Rogue Agent, the broken promise by name on a defective one, open again on membership or repair;
@Preand@Poststacked on one function as one condition, spelled@Requirementin one word;- populations combined with
|,&and-at every level, lazily, a Tag in an operator seat meaning its sound population; - every condition read on the Agent by its name as a plain boolean, with a condition's name refused to Actions, Records and host members;
- Delete; the three access forms, with Agent-bound views as read-only snapshots requiring active membership.
Ring 2
- strict boolean conditions; Preconditions gating only the current call, with inputs; Imprints after commit, with inputs; Postconditions once per call after the whole Form, re-checked at every later boundary, without inputs;
- the contract direction, with weakened Postconditions diagnosed;
- defective Agents: contract truthiness; the plain loop as the sound
population,
~Tagthe defective one,Tag[:]everyone, membership unchanged; a namespace that names the culprit.
Ring 3
@Ripprotocols run after membership ends, once, composed, failures reported; the three deletion tiers; the Agent's__del__as Layers of its Overlay, run after the teardowns, and alone at interpreter exit.
Everywhere
- the failure types above, distinct and named.
An implementation may choose runtime type composition, generated wrappers, proxies, trait machinery, or static code generation. Those are its business. TopKit is the Python reference; any gap in TopKit is TopKit's to fix, not a change to TOP.
🧭 TOP among paradigms
TOP was arrived at independently, from storage systems, organizational models, and character creation. Several established ideas share pieces of it:
- Mixins and traits compose behaviour per class, resolved once. TOP composes per instance, at runtime, and freezes each Layer as an Underlay.
- The Role Object pattern adds roles to a live identity but treats removal as ordinary. TOP treats removal as exceptional: a role is something you became; only active membership can be rescinded; changing state lives in Records.
- Entity-Component systems attach data to an identity. TOP keeps behaviour with the category and adds membership: "every Wizard".
- Aspect-oriented programming layers behaviour across a hierarchy. TOP's Imprints and conditions are a tag-time form of the same idea, scoped to one semantic act.
- Design by Contract (Meyer, Eiffel; Ada 2012 aspects) gives TOP its contract direction and its refusal to coerce booleans.
- Ada packages give TOP its specification/body picture: the Tag declares what is shared, the Agent is what acts.
TOP goes beyond these to one vocabulary: sticky runtime contributions, active Field membership, captured Overlays, tag-time contracts, two scopes with publication, and one stable identity.
More importantly, it is a thinking system: a simple idea with analogies to the real world, strong enough to make complex systems intuitive. TOP is not just a library; it is a way to think.
🧯 Commitments
- A Target keeps one stable identity.
- Tags add semantic context; they never replace inherent structure.
- Membership is meaningful even when a Tag contributes nothing else.
- Mutable Agent state belongs in Records; shared Tag data in Reports.
- Composition is explicit, ordered, and inspectable.
- A failed gate publishes nothing; a failed promise is never silent.
- Rip ends active membership without pretending history never happened.
- What the Agent does is public; what the Agency keeps is internal.
- Language profiles feel native to their language.
- Implementations serve the paradigm; the paradigm serves no single application.
Surface spellings may evolve. Implementation strategies may evolve. The semantic laws stay.