Repository navigation
1. TopKit for Python: The Guide
Tag-Oriented Programming for people. Read this before the Specification.
This guide teaches TOP by building one thing: a character sheet the way a tabletop game builds it. Species, class, background and feats are separate choices, and the sheet is what they compose. Every code block runs, in order, on Python 3.12 or later, with nothing installed but TopKit.
If you want the laws, read the Specification.
If you want to see the whole thing at once, run
examples/dnd_character.py. This document
is for learning, one pattern at a time.
A hero is one character. Over a game she becomes a Human, a Wizard, a Fighter, a Harper. She stays the same hero. In TOP you write each of those meanings as a Tag, and you apply Tags to an object you already have. The object keeps its identity. Its meaning grows.
Two words from Spanish say the whole model. A Tag is something you are (ser): a noun, durable. A Record is how you are right now (estar): an adjective, changeable. Wizard is a Tag. Hit points are a Record.
from TopKit import (
Action, Contract, Delete, Flag, Form, Imprint, Keyword, Operation,
Outline, Pin, Post, Postcondition, Pre, Precondition, Public,
Record, Report, Requirement, Rip, Scope, Secret, Tag, Tags,
Underlay,
TagCompositionError, TagPostconditionError, TagPreconditionError,
TagResolutionError,
)
class Character:
"""The host: ordinary Python. TOP never asks you to change it."""
def __init__(self, name, level=1):
self.name = name
self.level = levelclass Wizard(Tag):
pass
ari = Character("Ari", level=3)
Wizard(ari)
assert ari in Wizard # Ari is a Wizard now
assert list(Wizard) == [ari] # the Field: everyone who is one
assert Wizard # "is anyone a Wizard?" (yes)An empty Tag is already useful. It defines a category and a population, and that is the primary thing a Tag does. Everything else is extra.
class Wizard(Tag):
@Record
def spell_slots(agent):
return 2 # built once per Agent, stored on it
def Attack(agent): # a plain method is an Action
return f"{agent.name} casts a spell"
ari = Character("Ari", level=3)
Wizard(ari)
assert ari.spell_slots == 2
assert ari.Attack() == "Ari casts a spell"
ari.spell_slots -= 1 # a Record is an ordinary attribute
assert ari.spell_slots == 1Notice the first parameter is agent, not self. A Tag's methods act on
someone else's object. The name is your choice; the position is the rule.
class Fighter(Tag):
def Attack(agent):
return f"{agent.name} cuts with a blade."
Fighter(ari)
assert ari.Attack() == "Ari cuts with a blade." # Fighter came last
assert ari.Wizard.Attack() == "Ari casts a spell" # the view after Wizardari.Wizard is a view: the sheet as it was right after Wizard applied.
It never changes, whatever comes later. Wizard[ari] is the same view by
class, exact even if two Tags share a name.
Ask "Attack" @ ari whether that named Contribution is present, without
calling it. Its kind and value do not affect presence. See
Presence: a name at an Agent for Post guards,
host Contributions and queries through Tag views.
del Fighter[ari] # Rip: leave the Field
assert ari not in Fighter
assert isinstance(ari, Fighter) # ever a Fighter, always a Fighter
assert ari.Attack() == "Ari cuts with a blade." # sticky: the Action staysThat last line surprises people. TOP calls this a Rogue Agent: it has
what it learned, but no active membership. If a Tag must clean up after
itself, it says so with @Rip (pattern 9).
That is the whole kernel. The rest of this guide is patterns.
Each pattern has the same shape: when you want something, write this, and watch out for that.
When you want to say what something is, so other code can ask.
class Undead(Tag):
pass
ghoul = Character("Ghoul")
Undead(ghoul)
corruption = 0
for creature in Undead: # every sound member
corruption += creature.level
if not Undead: # nobody is Undead: nothing to do
print("This area is safe")
else:
print(f"This area is corrupted by a level of {corruption}")Watch out. for creature in Undead walks the sound population: the
members whose conditions hold (pattern 6). Undead[:] is everyone, regardless of conditions.
When you want each Agent to carry its own value, fresh, set by the Tag.
class Wizard(Tag):
@Record
def spell_slots(agent):
return 2
@Record
def spellbook(agent):
return [] # a new list for every Agent
ada = Character("Ada")
bob = Character("Robert")
Wizard(ada)
Wizard(bob)
ada.spellbook.append("Light")
assert bob.spellbook == [] # not sharedWatch out. A Record builder runs once, when the Tag applies. It is not a property; it does not recompute. Reapplying an active Tag does nothing, so a Record is never silently reset. To reset, Rip and apply again.
When you want a value that several independent Tags contribute to: a spell list from species, class and background; hit points from class and a feat.
Declare a second parameter on the builder. It receives what is already
stored under that name, or None the first time.
class Elf(Tag):
@Record
def spells(agent, stored):
return (stored or []) + ["Light"]
class Wizard(Tag):
@Record
def spells(agent, stored):
return (stored or []) + ["Magic Missile", "Shield"]
@Record
def hit_points(agent, stored):
return (stored or 0) + 6 * agent.level
class Tough(Tag):
@Record
def hit_points(agent, stored):
return (stored or 0) + 2 * agent.level
ari = Character("Ari", level=3)
Elf(ari)
Wizard(ari)
Tough(ari)
assert ari.spells == ["Light", "Magic Missile", "Shield"]
assert ari.hit_points == 18 + 6You write the merge. stored + new, max(stored, new), stored | new:
whatever the domain means. There is nothing to configure.
Watch out. A builder without the second parameter replaces. If it replaces a Record that an unrelated Tag put there, TopKit warns, because something another meaning relied on was overwritten.
When you want a later Tag to add to an Action, not overwrite it.
class Person(Tag):
def Attack(agent):
return "Attack!"
class Elf(Person): # Elf is a Shape of Person
@Action
@Underlay
def Attack(agent, underlay):
return "With elven grace, " + underlay()
class Paladin(Person):
@Action
@Underlay
def Attack(agent, underlay):
return underlay() + " For the oath!"
ari = Character("Ari")
Elf(ari)
Paladin(ari)
assert ari.Attack() == "With elven grace, Attack! For the oath!"
assert ari.Elf.Attack() == "With elven grace, Attack!"@Underlay hands you the Action as it was just before yours. It is captured
when the Tag applies and never changes, so every view stays true.
class Elf(Person) makes Person a Base of Elf: applying Elf applies
Person first. Form(Elf) lists that order; f"{Elf:form}" prints it.
Watch out. An Action that calls another Action through the Agent
(agent.Attack() inside Combat) uses whatever is visible now, not what
was captured. Underlay is for the same name; the Agent is for the current
sheet.
When you want a Tag that not everyone may take. Think of a door with a
bouncer. Before a Tag is applied, the Tag looks at the Agent and decides:
in, or not. That look is a Precondition, written with @Pre. It is a
function that receives the Agent and answers True (come in) or False
(stay out).
class Wizard(Tag):
@Pre
def Can_Study(agent):
return agent.level >= 1
class War_Caster(Tag):
@Pre
def Is_A_Caster(agent): # synergy: needs another Tag
return agent in Wizard
bruk = Character("Bruk", level=2)
try:
War_Caster(bruk)
except Precondition.Is_A_Caster:
pass # Bruk is no caster: nothing changed
assert bruk not in War_Caster
Wizard(bruk)
War_Caster(bruk) # now it takesThree things to notice.
- A refused Agent is untouched. When a Precondition says no, the tagging stops before anything is written. Bruk is exactly as he was.
-
The error carries the name you wrote.
except Precondition.Is_A_Castercatches that one refusal and nothing else.except TagPreconditionErrorcatches every refusal. Both work; the named one reads better. A name you never declared, such as a typo, is an error at theexceptline, so a silent handler that never fires cannot happen. -
A gate can ask about other Tags.
agent in Wizardis an ordinary check, so a Tag can require another Tag. That is how synergies are written: the second Tag needs the first.
Inputs travel with the tagging. Sometimes the gate needs information
that is not on the Agent yet, like a code number. You give it at the call:
MI6(bond, code="007"). TopKit then hands code, by name, to every
Precondition, Record builder and Imprint of that Tag that has a parameter
called code. The usual use is a Record that simply keeps the input:
class MI6(Tag):
@Pre
def Has_A_Code(agent, code):
return code is not None
@Record
def code(agent, *, code): # keyword-only: "this comes from the call"
return code
bond = Character("Bond")
MI6(bond, code="007")
assert bond.code == "007"Read the * as a blank space. A Record's function has three places, in
order: the Agent, what was already stored under that name (pattern 3),
and what the call brings. Here nothing was stored, so the star holds that
seat empty: def code(agent, *, code) says "agent, nothing stored, then
code from the call". If you wrote def code(agent, code), Python would
put code in the stored seat, and TopKit refuses that at tagging time
and prints the spelling above. Preconditions and Imprints have no stored
seat, so there def Has_A_Code(agent, code) is enough.
Watch out. A gate must answer True, False, or nothing at all.
Do not return a number and hope: a count of 0 is not False to TopKit,
and it will refuse the raw value. Write the comparison you mean, such as
return agent.slots > 0. This keeps a real zero from being mistaken for
a refusal.
When you want a Tag to guarantee something about the finished Agent. Postconditions inspect the product after everything applied.
class Wizard(Tag):
@Post
def Has_Spellbook(agent):
assert agent.spellbook is not None
newt = Character("Newt")
newt.spellbook = None
try:
Wizard(newt)
except Postcondition.Has_Spellbook:
pass # the Tag stays; Newt is defective
assert newt in Wizard # a member
assert not newt # whose promise is broken
assert newt in ~Wizard # waiting in the repair queue
for broken in ~Wizard: # the repair loop
broken.spellbook = []
assert newt # sound again
assert newt in list(Wizard) # back in the working populationThis is the factory rule: a bad product is not melted back to materials. It
is flagged, repaired, or thrown away (del Wizard[newt]).
if agent: reads "are this agent's promises holding". When you need the
name of the broken one, Contract.Display(agent) or f"{agent:contract}"
prints them.
Necessary to enter, necessary to stay. Stack @Pre and @Post on
one function and it is both: a gate at the door and a promise kept after.
class Elf(Tag):
@Pre
@Post
def Alive(agent):
return agent.alive
ghost = Character("Ghost")
ghost.alive = False
try:
Elf(ghost)
except Precondition.Alive:
pass # not alive: cannot become an Elf
ari.alive = True
Elf(ari)
ari.alive = False # an Elf who died is a broken Elf
assert ari in ~ElfBeing alive is necessary to be an Elf, not sufficient: other living things are not Elves. That is the difference between a condition and a Tag.
When the claim reads better as one necessity than as two checks, write
@Requirement instead of the pair. It means exactly the same thing:
class Elf(Tag):
@Requirement
def Alive(agent):
return agent.aliveIt still fails under two names, because you repair the two differently:
Precondition.Alive is someone who may not come in, Postcondition.Alive
is an Elf already in and now broken.
Watch out. A Shape should promise at least what its Base promised.
Use @Post @Underlay and return base() and .... Overriding a Base's
promise without it weakens the contract; TopKit allows it and warns.
When you want rules that live in tables or files and check words, not
classes. Mark the Tags that are words with @Flag.
@Flag
class Undead(Tag):
pass
@Flag
class Flying(Tag):
pass
ghoul = Character("Ghoul")
Undead(ghoul)
assert "Undead" in ghoul # by name
assert Undead in ghoul # by class
assert "Flying" not in ghoul
TURNABLE = ["Undead", "Fiend"] # a rule as data
assert any(word in ghoul for word in TURNABLE)
assert Keyword(ghoul, "Undead") # the function form: any object
assert not Keyword(Character("x"), "Undead")When one word is not enough, list the others. The Tag's own name always counts; the words are its variants and neighbours:
@Flag("Wolf", "Lycanthrope", "Beast")
class Werewolf(Tag):
pass
howler = Character("Howler")
Werewolf(howler)
assert "Wolf" in howler # a word
assert "Werewolf" in howler # the name, always
assert Keyword(howler, "Beast", "Lycanthrope")
HUNTED = ["Beast", "Fiend"] # the table never imports Werewolf
assert any(word in howler for word in HUNTED)A word is a public name, not a membership: if there is also a Beast
Tag, "Beast" in howler is True while howler in Beast is False.
That is the point, a secret identity with a public face, but it is yours
to keep straight. When a word needs a Field to walk, a gate or a promise,
make it a Tag and a Base (class Werewolf(Beast)); when rules only match
it, a word is enough.
Watch out. A string never asks membership. An ordinary Tag never
answers to its name: "Wizard" in agent says nothing about being a
Wizard, and is True only while an active Flag is named Wizard or lists
the word. Words are strings, @Flag("Beast"); @Flag(Beast) is the bare
form applied to the class Beast, not a word.
One seat, one meaning. A Flag cannot join an object that already
answers in: a list, a party that iterates its members (__iter__), or
an Agent carrying a Tag with a __contains__ or __iter__ Action. Nor
can such a Tag join an Agent that carries a Flag. TopKit refuses either
way, and the message names both sides, so "alice" in party never
changes meaning in silence. A keyed sheet (sheet["STR"], only
__getitem__) has no working in, and a Flag may take it. So may an
old-style sequence that answers only line[0], line[1], …, and there
"alice" in line stops meaning membership: give such a class a real
__iter__ if its in matters.
A class that writes __contains__ = None tells Python its in is
unavailable, and a Flag may then take the seat. The line belongs to the
whole class, not to one Agent. Once a Flag lands, the class's own x in self answers keywords, and so do inherited container methods that ask
in (the comparisons of a collections.abc.Set). Write it only for a
class whose in nobody uses for membership.
When you want one value or one behaviour for the whole Field, and a clean line between what the Agent shows and what the Tag keeps.
class Agency(Tag):
@Public
@Report
def colour(tag):
return "navy" # one copy, published on the Agent
@Report
def HQ(tag):
return "London" # one copy, Tag-side only
@Public
@Operation
def dispatch(agency, sender, message):
return f"{sender.name}: {message}" # only members ever get here
@Secret
@Record
def clearance(agent):
return "top"
@Action
def status(agent):
return agent.clearance # inside: the secret resolves
bond = Character("Bond")
Agency(bond)
assert bond.colour == "navy" # published Report, read-only
assert bond.dispatch("hello") == "Bond: hello"
assert bond.status() == "top"
assert not hasattr(bond, "HQ") # Tag-side stays Tag-side
assert not hasattr(bond, "clearance") # secret from outside
send = bond.dispatch # a captured handle
del Agency[bond]
assert bond.status() == "top" # his own Action: he keeps it
try:
send("late")
except TagResolutionError:
pass # the Agency's: revoked with membershipThe rule in one line: what the Agent does is public, what the Agency keeps
is internal. @Secret hides an Agent member; @Public shows a Tag member.
Both are modifiers: they stack on @Record, @Action, @Report or
@Operation in either order.
A published member answers members only, and only sound ones. A Rogue
Agent (he left) keeps what he became, his own Actions and Records, and
loses what the Agency lent him: TagRogueAccessError. A defective Agent
(a promise broke, any promise, whoever made it) is turned away until
repaired, and the refusal names the broken promise. You never write
either check, and the second one gives you the autofix pattern: catch
the promise, repair what it names, try again.
class Sworn(Tag):
@Post
def Has_Oath(agent):
return agent.oath is not None
Agency(bond)
bond.oath = None
try:
Sworn(bond)
except Postcondition.Has_Oath:
pass # a member, but defective
try:
bond.dispatch("hello")
except Postcondition.Has_Oath: # the refusal names what is broken
bond.oath = "for Queen and country"
assert bond.dispatch("hello") == "Bond: hello"Notice the symmetry. A Report is written exactly like a Record, with the
Tag instead of the Agent as its first input; it runs once per Tag and its
value is shared by the whole Field. def hit_die(tag, inherited) extends
the Base's value, as def spells(agent, stored) extends what is stored.
Watch out. A secret resolves only while one of the Agent's own Actions or protocols is running. A handle to a secret Action captured inside and called outside fails, on purpose.
When you want a Tag to clean up when it leaves, or a role that lasts for a block of code.
class Sentry(Tag):
@Imprint
def post(agent):
agent.on_duty = True
@Rip
def stand_down(agent):
agent.on_duty = False
guard = Character("Guard")
with Scope(guard, Sentry): # applies on entry
assert guard.on_duty
assert not guard.on_duty # Ripped on exit, even on error
assert guard not in Sentry@Imprint runs after the Tag applies; @Rip runs after it leaves. They
are constructor and destructor, __enter__ and __exit__.
A role's conditions do not leave with it on their own. What the
Agent became stays (pattern 1's Rogue Agent), and so does what the role
required: a promise that outlives its Tag fails loud, never silently.
If a promise should end with the role, you say so, in the role's own
@Rip protocol, one name at a time:
class Knight(Tag):
@Post
def Has_Sword(agent):
return agent.sword is not None
@Rip
def Undub(agent):
Contract.Delete(agent, "Has_Sword") # the promise ends here, visibly
def Salute(agent):
return f"{agent.name} salutes"
lance = Character("Lance")
lance.sword = "Excalibur"
Knight(lance)
del Knight[lance] # dubbed no more
lance.sword = None
assert lance.Salute() == "Lance salutes" # what he became: stays
assert lance # what the role required: ended by youThe other way is a guard inside the promise, if agent not in Knight: return True, which lets the promise follow any membership you like; the
Contracts Guide shows both.
Watch out. Python does not promise to run finalizers at shutdown, so
del agent is best-effort. Scope is the guaranteed path.
An object's own __del__ is a Layer. It keeps running when the object
is tagged. A Tag may replace it, or wrap it with @Underlay; the Tags'
teardowns always run first, and a __del__ never stops them:
farewells = []
class Lantern:
def __del__(self):
farewells.append("wick out")
class Carried(Tag):
@Rip
def Put_Down(agent):
farewells.append("put down")
class Enchanted(Tag):
@Underlay
def __del__(agent, underlay):
farewells.append("spell fades")
underlay() # the Lantern's own, or nothing
lamp = Lantern()
Carried(lamp)
Enchanted(lamp)
del lamp
assert farewells == ["put down", "spell fades", "wick out"]At interpreter exit only the __del__ Layers run. To have teardowns run
at exit too, register the Agent with At_Exit(agent).
Put it together. This is the D&D example in miniature.
class Species(Tag):
@Record
def speed(agent):
return 30
class Elf(Species):
@Record
def spells(agent, stored):
return (stored or []) + ["Light"]
class Class(Tag):
@Report
def hit_die(tag):
return 8
class Wizard(Class):
@Report
def hit_die(tag):
return 6
@Pre
def Can_Study(agent):
return agent.level >= 1
@Record
def spells(agent, stored):
return (stored or []) + ["Magic Missile"]
@Record
def hit_points(agent, stored):
return (stored or 0) + agent.level * Wizard.hit_die
@Post
def Knows_Spells(agent):
return len(agent.spells) > 0
def Attack(agent):
return f"{agent.name} casts {agent.spells[-1]}"
class Fighter(Tag):
@Record
def spells(agent, stored):
return (stored or []) + ["Identify"]
class War_Caster(Tag):
@Pre
def Is_A_Caster(agent):
return agent in Wizard
@Action
@Underlay
def Attack(agent, underlay):
return underlay() + " behind a shield"
ari = Character("Ari", level=3)
for choice in (Elf, Wizard, Fighter, War_Caster):
choice(ari)
assert ari.spells == ["Light", "Magic Missile", "Identify"]
assert ari.hit_points == 18
assert ari.Attack() == "Ari casts Identify behind a shield"
assert ari # every promise holds
assert f"{ari:tags}" == "Elf, Wizard, Fighter, War_Caster"
print(Outline(ari))
# Character
# Species
# Elf
# Class
# Wizard
# Fighter
# War_CasterWhen you want to say something about a Tag itself: this class is
rare, that species is homebrew, this rule is deprecated. A Tag is an
object, so a Tag can be tagged. A Tag marked @Pin applies to Tags, and
the Tag it is applied to becomes its Agent: it joins a Field, it can be
gated, it can promise, it can leave.
@Pin
class Rare(Tag):
@Record
def rarity(tag): # the receiver is the Tag
return "rare"
@Action
def Describe(tag):
return f"{tag.__name__} is {tag.rarity}"
Rare(Wizard) # pin the Tag
assert Wizard in Rare # 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(ari, "rarity") # never on the Tag's Agents
for tag in Rare: # walk the rare Tags
print(tag.Describe())Notice the first parameter is tag, because the receiver is a Tag. What
a Pin gives lands on the Tag the way a Report or an Operation does: one
value shared by the whole Field, inherited by the Tag's Shapes
(War_Caster.rarity is "rare" too), and never on the Tag's Agents.
A Pin can also patch what a Tag shares. Say a Tag has an Operation that controls an engine, and the engine changed. A Pin Action of the same name replaces it, with the old one as its Underlay, and every Agent that goes through the Tag gets the new control at once:
class Engine(Tag):
@Operation
def Control(tag, level):
return f"{tag.__name__} v1 at {level}"
def Drive(agent):
return Engine.Control(agent.level) # through the Tag, at call time
kit = Character("Kit", level=2)
Engine(kit)
assert kit.Drive() == "Engine v1 at 2"
@Pin
class Firmware_2(Tag):
@Action
@Underlay
def Control(tag, underlay, level):
return underlay(level).replace("v1", "v2")
@Public # reach every Agent of the Tag, too
@Record
def firmware(tag):
return "v2"
@Rip
def Unpatch(tag, original): # the originals, handed to you
tag.Control = original.Control
Firmware_2(Engine)
assert kit.Drive() == "Engine v2 at 2" # actualized, no per-Agent work
assert kit.firmware == "v2" # published onto the Field
del Firmware_2[Engine] # un-patch: one deliberate line
assert kit.Drive() == "Engine v1 at 2"Watch out. A Pin may replace what a Tag shares (its Operations
and Reports), never what its Agents do (its Actions, Records and
conditions); TopKit refuses that at the gate, because on a class the two
live in one dictionary. if Wizard: still asks whether anyone is a
sound Wizard, not whether Wizard's own promises hold; ask those from the
Pin's side, Wizard in ~Rare. A Pin applies only to Tags, and an
ordinary Tag only to objects, so a Field is never a mix of the two. A
Flag Pin is a keyword on the Tag: "Deprecated" in Wizard. Prefer it to
Wizard.deprecated = True for the same reason you prefer an Undead Tag
to asleep = True: a value can be flipped back, membership is a state of
the architecture, with a Field to walk and a history that stays.
| Question | Spelling |
|---|---|
| Is it a Wizard now? | agent in Wizard |
| Was it ever? | isinstance(agent, Wizard) |
| Does it carry the keyword? |
"Undead" in agent, Keyword(agent, "Undead"), a Flag's words too |
| Which Tags, in order? |
Tags(agent), f"{agent:tags}"
|
| The whole shape? |
Outline(agent), f"{agent:outline}"
|
| Are its promises holding? |
if agent:, Contract.Status(agent), f"{agent:contract}"
|
| What did it look like right after Wizard? |
agent.Wizard, Wizard[agent]
|
| Question | Spelling |
|---|---|
| Who is one, and sound? |
for a in Wizard, len(Wizard), if Wizard:
|
| Who is one, and broken? |
for a in ~Wizard, if ~Wizard:
|
| Everyone? | Wizard[:] |
| Either, both, without? |
Wizard | Fighter, Wizard & Fighter, Wizard - Sworn (the Fields Guide) |
| What applies with it? |
Form(Wizard), f"{Wizard:form}"
|
| Take it away | del Wizard[agent] |
| Which Pins does it carry? |
Wizard in Rare, f"{Wizard:pins}"
|
Nothing TOP-level lives at Wizard.something. That namespace is yours: put
your Reports and Operations there.
| Do | Don't |
|---|---|
| Make a Tag for what something is: species, class, role, clearance. | Make a Tag for a passing state. Asleep is a Record, asleep = True. |
Let independent Tags pile up on one Record with stored. |
Chain Tags by inheritance just to share a list. |
Extend an Action with @Underlay. |
Call agent.Attack() from inside Attack (that recurses). |
Gate with @Pre; promise with @Post; repair through ~Tag. |
Roll your own validation after the fact. |
Mark word-like Tags @Flag and write rules as data. |
Match every Tag by name. Only Flags are words. |
Say things about a Tag with a @Pin. |
Keep a side table of Tags outside TOP. |
Clean up with @Rip, guarantee it with Scope. |
Rely on del agent for anything that matters. |
| Reset by Rip and apply again. | Reapply an active Tag hoping it resets (it does nothing). |
| Expect a Rogue Agent to keep its own Actions and lose the Agency's published ones. | Check membership by hand inside every published Operation. |
| Catch the named promise, repair what it names, retry. | Catch every failure in one handler and guess. |
Keep shared data in a Report, one copy. |
Copy shared data into every Agent's Record. |
Ask agent in Wizard. |
Ask type(agent) is Character; the type is wrapped. |
-
The Contracts Guide: gates, promises and error control,
aboard a starship; and one promise read by its name,
agent.Has_Oath. - The Fields Guide: populations, partitions and the algebra.
- The Specification: the laws, ring by ring.
-
examples/dnd_character.pyandexamples/biome.py: the long form. -
benchmarks/bench.py: what it costs. -
IMPLEMENTATION_NOTES.md: how TopKit does it.