Most web browsers only allow for 10 megs (though some are as low as 5) of on-disk storage per origin. This not only has to contain the current game state, but also all previous game states in the game history. This is a particular problem with very large and/or complicated Twine games.
In an attempt to minimize this issue, LibEcho uses a defaults and delta Persistence system. "Persistent Objects", be they characters within the game and their statistics and inventory, articles of clothing and their state, etc etc, are all defined at initialization time, outside of SugarCube's game history stack. Later, when an object's state is changed, only the differences from the object's initially defined default state are stored within SugarCube's game history stack. This greatly reduces the game's localstorage footprint (versus storing every object's full state within every game history moment), while still allowing the flexibility for almost all objects to be modified and tracked in the story history.
All persistent objects must ultimately derive from the PersistentObject class.
Each PersistentObject has a readonly "id" field (hereafter referred to as the "Persistence ID"), a simple alphanumeric-underscore string (whitespace, punctuation, and special symbols are not allowed).
A PersistentObject may be the "child" of another PersistentObject. This is tracked in the id field via a dot notation. For example, each Person object contains a GeneralInventory object that tracks what the Person is carrying. If the game contains a Person with the id of "joe", then the id of Joe's GeneralInventory might be "joe.inventory". Likewise, the actual contents of that inventory might be stored as "joe.inventory.contents".
You won't generally need to worry too much about this id hierarchy, though. LibEcho takes care of most of it for you. The vast majority of the time, you'll only need to know the ids of toplevel objects that you have explicitly created: "joe", "cocktail_dress", etc.