The Dictionary: What ChatGPT Did Not Know About Brainhat's Grammar

The vocabulary my father said we were behind on turned up the same evening as the last post.

His note with it was short. He had asked ChatGPT to write Brainhat a beginner's dictionary, keep it shallow, create orthogonalities, add plural labels. It did all that. He was not sure of the quality, said it would be easy to ask again if it was not good, and added two things that turned out to be the whole story: required-patterns.txt is the vocabulary that has to stay as it is, and he was sure some required vocabulary was in the normal word lists too. So the question was not whether the file was well made. It was whether a vocabulary written from the outside can drive a grammar that names its own words.

Where it came from

The first thing I found was that I already had it. The release he shipped in June carries data/beginner_english_dictionary_master.txt, dated April, byte for byte the file he sent. It is never compiled in: the build file that turns the word lists into the engine's dictionary does not include it, and the API-key placeholder next to it says the ChatGPT experiment happened in the same sitting. So this was not a new file. It was a file that had been waiting five months for somebody to try it.

data/ in the releasegrammarrules, with concept names inside themrequired-patterns.txtcc-patterns.txtinput-patterns.txtrewrites, idioms, rroledebug-patterns.txtword liststhe concepts those names point atthings.txtverbs.txtadjectives.txtprepositions.txtadverbs, locations24namesorange border: the file that must stay as it isdata.inincludes every file above, in this orderbeginner_english_dictionary_master.txtin this directory since April; no line in data.in has ever included itmake: simplecpp, about 30 msdata/datathe compiled dictionary, 3,501 conceptsread once at startbrainhat, the engine processdaemon 4144 · websocket 4145every session child uses this one dictionary
Where the dictionary lives. The engine's data/ directory holds the grammar (rules that name concepts) and the word lists (the concepts). A build file includes every file in order and a preprocessor compiles them into one dictionary the engine reads at start. The generated file sits in the same directory, outside the build.

Brainhat's word list is not a list of words. It is an ontology in a small declarative language: every concept has a name like dog-n1, labels for the words that mean it, parents it is a child of, and optionally which siblings it excludes. The grammar rules, in the pattern files, refer to concepts by those names. That is the bit that matters. A rule for "where is X" is written against a concept called proximities, and the rule for "I see" is written against a concept called speaker-1. If a new word list calls "I" something else, the rule keeps looking for the old name.

you typewhere is the dog?the grammar picks a rule by shape (input-patterns.txt)where $c`tobe` $r`csubobj-q` $c`proximities`three of those are concept names; the rule works only if the dictionary has themeach word is looked up as a conceptwherewhere-1adverbs-emphases.txta child of qualifyistobe-2verbs.txtone of 20 forms in the tobe familythe dogdog-n1things.txta child of pet-n1 and mammal-n1then the context is searched for the kind of fact the rule asks forwhat it was told earlier: the dog is in the kitchen.dog-n1 — in-p1 — kitchen-n1in-p1 is a child of proximities, so this fact matches the rulethe engine saysthe dog is in the kitchen.generated file: its in-p1 is not under proximities, so: I do not know.
How a question travels. The grammar picks a rule by shape; the rule is written against concept names; each word is looked up in the dictionary; the context is searched for a fact of the kind the rule asks for. Every step leans on the word list agreeing with the grammar about names and classes.

The test

I did not want an opinion about the file. I wanted a number. So I wrote one 24-turn conversation in the dictionary's own words (dog, kitchen, cat, red, boy, apple, teacher, mice) with sixteen checks in it: where something is, whether a dog is an animal, whether a cat that is big can also be small, "what color is the cat", "I see the dog" coming back as "you see", what do / what did / what does questions, plurals, and one rule that has to fire. Then I compiled seven vocabularies from a copy of the release and typed the same 24 lines into a fresh engine built with each one.

where is the dogis the dog an animalis the dog a catwhat color is the catis the cat smallis the cat bigi see the dogwhat do i seethe boy ate an applewhat did the boy eatthe dogs are in the parkwhere are the dogswhat does the girl likeis the dog happyis the teacher a personwhere are the micestock release14/16as sent, appended to stock8/16as sent, replacing stock1/16replacing, plus 57 rescued2/16repair stage 17/16repair stage 212/16repair stage 315/16one column per check, in conversation order; filled = right answer
Sixteen checks across seven builds of the dictionary. The stock release is the bar. "As sent" is the file appended to the stock lists, then replacing them; then the three repair stages.

Three numbers carry the post. The stock word lists pass 14 of 16. The new file, on its own, passes 1 and loads with 59 cannot find errors. Appended after the stock lists, where it can borrow what it lacks, it passes 8, and every "where" and every "what" fails. After fifteen mechanical repairs it passes 15, which is one better than the release, because the rule in the conversation fires on the first try with the new file and does not with the old one.

Why it cannot stand alone

My father's second sentence, made into a list. The pattern files spell concept names inside their rules, and the binary has eighteen more compiled in. Twenty-four of those names are defined only in the normal word lists, not in the file he said had to stay: "I" and "you" (speaker-1, brainhat-1), the entire "to be" family, one and two, human, male and female, "of", and the four verbs the console uses to move contexts around. Pull those with their parents and subforms and it is 57 definitions. Without them the file cannot answer a question, because the question rules cannot find their own subject.

grammar and engineinput-patterns.txtcc-patterns.txtrequired-patterns.txtthe binary itself24namesdefined only in the stock listsverbs.txt 41things.txt 7adjectives.txt 7prepositions.txt 257 definitions pulled
What the grammar names by hand. Four sources reach for 24 concept names that live only in the stock word lists; with their parents and subforms that is 57 definitions, pulled verbatim into a rescue file.

Rescue those 57 and the score goes from 1 to 2. That surprised me until I looked at what ChatGPT had done with verbs. Every verb in the file has five forms, and two of them use tense names the engine has no symbol for, present-progressive and past-participle. Those forms load as nothing, 81 warnings at start-up. The engine's own tenses are present, past, future and conditional; "eating" is a second label on the present form, not a form of its own. And no present form in the file carries person or number, which the stock list puts on every one. The engine conjugates with those, so "i see the dog" came back as You do sees the dog.

to eatformlabelspersonnumbertense
stockeat-v1eating, eatfirst, secondsingular, pluralpresent
eat-v2eating, eatthirdpluralpresent
eats-v1eatsthirdsingularpresent
ate-v1atepast
generatedeat-v1eatnonenonepresent
eats-v1eatsthirdsingularpresent
ate-v1atepast
eating-v1eatingpresent-progressive, not an engine tense
eaten-v1eatenpast-participle, not an engine tense

The rest of the list is shorter to say than it was to find:

  • It redefined the grammar's words under new names. A second "I", a second "where", a second "the", a second "if", a second "to do", and a "to be" whose past form is named was/were-v1 with the label "was/were", which nobody will ever type. The rules do not know the new names, and the duplicate labels shadow the old ones. With the generated "to do" present, "what did the boy eat?" is "I do not know"; take it out and the answer is the apple.
  • Its prepositions are in the wrong class. The stock "in" is a child of proximities, and "where" answers by walking that class. The generated "in" is a child of preposition and nothing else, so with it every "where" fails. The same one level down: its attributes hang under things instead of adjective, so "what color is the cat?" came back as color is the cat.
  • 86 classes have no word. animal-n1 has no label "animal" under it. The engine utters the class when it answers about membership, so the reply to "is the dog an animal?" was yes. the dog is animal-n1.
  • Orthogonality one level too low. The file declares each word exclusive with its immediate parent, so a dog is not a wolf, but nothing says a dog is not a cat. The stock dog-n1 is orthogonal to pet-n1 and mammal-n1. This is the one check the repaired file still misses.
  • Old words got new names. Sofa for couch-n1, plane for airplane-n1, under for below-p1, about 150 in all. Saved contexts and init files are written against names, so every rename is a break with every saved image.

What it does better

None of that is a reason to throw it away. The shallow ontology is what the whitepaper asked for. The file declares 257 orthogonalities on 744 concepts where the stock lists declare 507 on 2,696; it has 200 plurals in the engine's newer plabel form where the stock lists have four; the homonyms (bat, ball, club, match) carry hints about what each sense wants nearby. And it knows words the release does not. "The teacher is a woman" gets no reply at all from the stock build, because the stock build has no teacher.

concepts2,696744words (labels)3,518840orthogonal declarations507257plural labels (plabel)4200stock word lists (six files plus required-patterns)beginner dictionary
Size against the stock word lists. Fewer concepts and words, denser in the declarations that make the engine say no.
action333living-n191 (22 unlabeled)attribute-a164 (17 unlabeled)object-n158 (11 unlabeled)place-n139 (8 unlabeled)number-n129preposition26 (2 unlabeled)event-n114 (5 unlabeled)bodypart-n113food-n112 (2 unlabeled)pronoun11time-n110 (1 unlabeled)conjunction8questionword6weather-n15
The generated ontology by top-level branch. The lighter part of each bar is class nodes with no label, which the engine will name out loud if asked.

Fifteen repairs

Everything wrong with the file is mechanical, so I fixed it mechanically: a script that applies fifteen changes and prints a ledger of what each one touched. Stage one drops the duplicates, reshapes the verb paradigms, removes the grammar words the engine owns, and gives every class a word. Stage two throws out the generated prepositions, conjunctions and articles and includes the stock ones, and hangs the attributes under adjective. Stage three removes the generated "to do" and "to have". Each stage is a row on the scoreboard. The output is a dictionary plus the rescue file plus a build list, and it compiles and runs.

R1 duplicate definitions dropped5R2 progressive and participle forms retensed or folded134R3 person, number and a plural form on every verb120R4 the to-be family removed6R5 pronouns removed11R6 question words removed6R7 numerals removed29R8 grammar classes removed8R10 'do' taken off the class action1R11 a word given to every class77R12 prepositions, conjunctions, articles removed35R13 attributes hung under adjective1R14 stock prepositions and adverbs included1R15 to do / to have removed11stage 1: 1 of 16 to 7stage 2: to 12stage 3: to 15
The fifteen repairs, counted in definitions touched, coloured by the stage they entered. R2 and R3 are the verb paradigms; R11 is the 77 class nodes that got a word.

I want to be clear about what that repaired file is. It is a stopgap that proves the content is sound. It is not the answer, because it still leans on two of the stock word lists and a rescue file, and the answer is a file that does not need either.

The ask

He said it was easy to ask ChatGPT again. So the deliverable back to him is not the repaired file, it is the prompt. In the order the scoreboard earned it:

  • Leave the grammar words out. The engine owns "to be", I and you, he, she, it and they, the question words, the articles, the conjunctions, the prepositions, and the auxiliaries. Generate content words only: nouns, adjectives, verbs.
  • Verb forms in the engine's shape. Four tenses. "Eating" is a label, not a form. Every present form carries person and number, three blocks per verb, exactly like the stock toeat-v1.
  • Keep the stock prepositions, and put attributes under adjective. These are the two changes that turned "where" and "what color" back on.
  • A word on every class.
  • Orthogonality where the exclusion actually holds. Dog and cat under mammal, not dog under canine.
  • No duplicate names, no slash in a name, and the stock name wherever the stock word exists. Five duplicates, one slash, about 150 renames.
  • The seven words the service's tests speak. King, princess, garden, hungry, scared, luigi and mario are in the stock list and not in the new one.
  • Numbers as quantity adjectives, and keep the plabels. The plurals are the one thing the new file does that the stock lists mostly do not.

Two smaller things went on his list as observations rather than asks. The engine reads a plabel but does not utter it: "the dogs are in the park" comes back as the dog are in the park in the stock build too. And the compile is about 30 ms, not the twelve seconds the whitepaper assumed, so a vocabulary is cheap to swap.

Where it sits in the service

For anyone who has not followed the earlier posts: the service is a small Python shim on one published port, a pair of stateless engines it swaps a saved image into for each turn, and a few long-lived knowledge nodes that talk to each other over my father's websocket peer layer. Every one of those containers is the same image, and the dictionary is compiled into it. That is the point that matters here. What is cheap to swap is not cheap to run two of: the tags that name concepts inside a saved image or a peer message come from the word list, so every node has to carry the same one. A vocabulary change is a rebuild and a restart of the stack, not a migration, which is fine. The tests would need those seven words, which is a small file.

the service: Docker, one hostshimPython; the only published port, 127.0.0.1:8080/turn /transfer /peerHTTP in, engine sockets outengine tierengine-adaemon swap, statelessbrainhat -d 4144same dictionaryengine-bdaemon swap, statelessbrainhat -d 4144same dictionaryone image,one dictionary,everywhere.a new word list is arebuild and a restartknowledge nodesnode-a-d -W, long-livedbrainhat 4144 4145same dictionarynode-b-d -W, long-livedbrainhat 4144 4145same dictionarynode-m-d -W, motives onbrainhat 4144 4145same dictionarybhpeertransferpeer links and saved images carry concept tags from the dictionary build, so every node runs the same one
Where the dictionary sits in the service. One image, one compiled dictionary in every container; the shim never sees words, only turns. Peer links and saved images carry concept tags, which is why the word list has to be the same everywhere.

Who wrote this, again

The engine, the grammar, and the decision to try ChatGPT on the vocabulary are my father's. The battery, the seven builds, the rescue and repair scripts, the scoring, and the chart page these figures come from were produced with an AI coding agent, Claude Code, working under my direction; I chose what counted as a check, read every reply, and decided what went into the ask. Every number here is from a run I can repeat with one script.

takeaway
A language model can write a good ontology for a system it has never seen. It cannot know which of that system's words are load-bearing, because that knowledge is in the grammar, not in the word list. Give it the grammar's list of names and one verb as a template and the next file should land where three stages of repair put this one. The file was never bad. It was written without the one page it needed.