Pinakes Guides
How to model a character-driven fictional universe with AT Protocol Lexicons, using a real universe as the worked example.
Getting started and the command pages are the tour: how to install Pinakes and what each command does. These guides are the working detail — what the compiler actually reads, what it actually emits, and what it deliberately leaves alone.
| Guide | What it covers |
|---|---|
| Record types | The eight record types compile emits, the Lexicon documents it generates for them, and how identity works |
| Prose to records | The frontmatter contract, the resolution rules, and what never leaves the repo |
| Continuity and drift | The lint rules, Lexicon validation, the CI drift gate, and the judgment passes that stay outside CI |
| Prose triage | prose-check: the countable half of the AI-tells pass, why its counts are inputs rather than verdicts, and why it is deliberately not a gate |
The worked example
Section titled “The worked example”Every example in these guides is taken from Supper Club Secrets, a six-book cozy-mystery series and the universe Pinakes was built against. Book 1, The Case of the Missing Hot Sauce, is 25 chapters across four meals, and compiles to 149 records:
25 records -> records/book1/scenes.json 97 records -> records/book1/character_state_events.json 3 records -> records/book1/custody_events.json 14 records -> records/series/places.json 13 records -> records/series/character_profiles.json 1 records -> records/series/items.jsonIts NSID root is com.supperclubsecrets, so its scene records are
com.supperclubsecrets.scene and its Lexicon documents land in
records/lexicons/com.supperclubsecrets.*.json.
Where these guides describe something the code does not do yet, they say so rather than describing the intent. The known gaps section below collects those in one place.
Why character-driven fiction is the hard case
Section titled “Why character-driven fiction is the hard case”Plot-driven continuity is mostly bookkeeping: who was where, what happened when, which object is in whose hands. A linter can check that, and Pinakes does.
Character-driven fiction adds a harder axis. What has to stay continuous is not just position but state — how guarded a character is, what they know and are not saying, which version of themselves they are performing for whom. That state changes scene to scene, it is what readers actually follow, and it is the first thing to break when a book is revised late.
So the central modelling decision in Pinakes is that a character’s register is a
time series, not a field. A profile record says who a character is; a stream
of stateEvent records says where they stood at each point in story time. Book 1
has 13 profiles and 97 state events — the ratio is the point.
That shape is also why AT Protocol is a natural fit rather than a novelty. A character whose history is an append-only stream of dated events, keyed to a stable identity, is exactly what a PDS repository holds.
The Golden Rule
Section titled “The Golden Rule”Read Record types and it is easy to start thinking of the records as the model and the prose as input to it. It is the other way round.
Prose is the source of truth. If a finished chapter contradicts the codex,
the chapter is right and the codex is what gets updated. Records are a
projection taken downstream of the writing; nothing in records/ ever edits
anything in stories/. records/ is build output and can be deleted at any
time.
Practically, this means the frontmatter contract in Prose to records is a description the author keeps accurate, not a schema the author writes to first.
Known gaps
Section titled “Known gaps”Things these guides describe as absent, gathered here so they are easy to find. None of them block the pipeline; all of them are places where the documented model and the shipped code disagree.
- Accounts are carried, not resolved. A character’s
didandhandleare checked for syntax and uniqueness and carried onto its profile, but nothing in the CLI mints a DID or confirms that the handle resolves to it, since that needs the network. See Record types.
Closed since these guides were written
Section titled “Closed since these guides were written”- A character’s account lives on its registry entry and reaches its
profile record.
didin codex frontmatter used to be ignored, so a consumer that needed it had to re-read the codex, andhandlewas carried from the codex unchecked. Since 0.9.1 both live on the character’sentities.yamlentry and are carried ontocharacter.profile. An invalid one (invalid-did,invalid-handle), one two characters share (duplicate-did,duplicate-handle) and one still in codex frontmatter (did-in-codex,handle-in-codex) each fail bothlintandcompile. A universe with either field in its codex files has to move it to upgrade. - A custom rule that would never run fails
lint. Each of these used to load nothing, or load a rule that checked nothing, and reportOK — all checks passed cleanly. Now each is an error:paths.rulesset to a directory (rulesrather thanrules/*.yaml), a glob that matches no files, a rule file with invalid YAML, the wrong shape, or an invalid regex, astateEventrule on a field other thanregisteror usingrequired, and a rule with neither apatternnorrequired: true.pinakes initstill creates norules/directory, since custom rules are optional. stateEventrules can express a register vocabulary. They used to test each annotation with its parenthetical note included and check only the term left of an arrow, so the example rule on thepinakes lintpage reported 86 false positives on Book 1 and missed the three real off-vocabulary values. It now reports exactly those three; see Continuity and drift.missing-dateis its own configurable rule. It used to be hardcoded toerrorand evaluated insidenon-sequential-dates, so turning the ordering check off silently stopped reporting undated chapters.- A registry entry that fails validation fails
lint. It used to be skipped with only a console warning. It is now aninvalid-registry-entryerror; see Continuity and drift. itemandcustodyEventnow exist. Both are compiled and validated; see Record types. A universe carrying hand-writtenitems.jsonorcustody_events.jsonfrom before this should delete them and letcompileproduce them. Items now land inrecords/series/;compileremoves a leftoverrecords/<book>/items.jsonas stale (see below).compileremoves stale record files. A record file it no longer produces — a deleted or renamed book, a record type a book stopped producing, Lexicon documents for an old NSID — is deleted and reported asremoved stale. Only pinakes’ own file names are candidates; see Continuity and drift.config.tspath defaults now match the documentedcodex/andrecords/layout.pinakes --versionreads the version frompackage.json, so it cannot drift from the package again.