Add a seed
Gist
A seed is a short markdown note holding one piece of knowledge, with its source. It contains fenced blocks that a machine can read. Think of a seed as the thing a rule grows from. There are two kinds:
- Global seeds (
global/seeds/**/*.md): concepts, links between them, verb classes, relation properties and values.global/build.rscompiles them into Rust tables. A wrong seed breaks the build. - Grammar seeds (
en/seeds/<section>/*.md):```ruleblocks for English, run by the deterministic engineen::expert. A unit test checks that every rule parses.
When to use
- A reader prints a gap: "no class for verb X", "where do they go when STATE", "state S: unknown whether trouble or relief". A gap means level 1 is missing knowledge. Add a global seed. Do not teach the reader to memorize the answer.
- You know a general fact that many tasks need, such as an inverse relation, a cause-effect link, or what a verb does to a quantity.
- You want the tagger or parser output checked against a grammar fact (grammar seed).
Domain-specific depth, such as legal terms or chemistry at expert level, belongs in a domain module, not in the global level. See Adapting Mova to your own domain.
Global seed file layout
Write prose for humans first, then the blocks. The build reads only the blocks. Files are found
recursively and sorted by path. The sections already in the repo are self/, shortcuts/,
values/ and wikidata/.
# Short title
**Gist.** One to three sentences: the knowledge.
**Conditions and exceptions.** Where it does not hold.
**Examples.** Two to four short English examples.
**Sources.** Book and page, paper id, dataset.
```relation
...
## Block syntax (exactly what `global/build.rs` parses)
Each non-empty line inside a block is one entry. Leading and trailing spaces are trimmed.
| block | line format | compiled into | notes |
| --- | --- | --- | --- |
| ```` ```concept ```` | `name: word word word` | `CONCEPTS` | the words trigger the concept in text (`global::concepts_in`) |
| ```` ```category ```` | `name: word word word` | `CONCEPTS` with `category: true` | explains steps (time units, measures, countable things) but never starts a consequence chain |
| ```` ```link ```` | `from -> to : why` | `LINKS` | both ends must be declared concepts or verb classes; `why` is the explanation that gets printed |
| ```` ```verbs ```` | `class effect lemma lemma ...` | `fn verb_class(lemma) -> Option<(&str, char)>` | `effect` is one of `=` `+` `-` `±` (what the verb does to the holder's quantity); the class name also becomes a concept |
| ```` ```relation ```` | `name: key=value key=value` | `RELATIONS`, `global::relation(name, key)` | values contain no spaces |
| ```` ```principle ```` | `id:`, `name:`, `gist:`, `against:`, `towards:`, optional `rule:` (one per line) | `PRINCIPLES` | `against`/`towards` are space-separated words, `_` stands for a space; `rule` names a predicate over the story world (`world::values`) |
These relation keys are currently read by code (`world/src/babi.rs`, `math/src/qread.rs`):
| key | meaning | example |
| --- | --- | --- |
| `inverse` | the reverse relation | `north_of: inverse=south_of` |
| `same` | an alias | `fit_inside: same=smaller_than` |
| `vec` | a direction on the plane, `x,y` | `east_of: vec=1,0` |
| `step` | the step letter in path answers | `north_of: step=n` |
| `transitive` | chains compose | `bigger_than: transitive=yes` |
| `order` | position in time | `morning: order=1` |
| `go` | where an agent in this state goes | `tired: go=bedroom` |
Any other key compiles without error, but no code reads it until you write that code.
## Steps: global seed
1. Find the gap. Run the reader on a tiny story (see [story-worlds.md](story-worlds.md)):
```sh
cargo run --release --manifest-path world/Cargo.toml -- \
babi-probe "Sumit is sleepy." "Where will sumit go?"
# ? Where will sumit go? → ("?", "gap: where they go when sleepy")
The gap: marker shows the missing knowledge.
2. Check whether the knowledge already exists: grep -rn "sleepy" global/seeds.
3. Add it. Use an existing file when the topic matches, or create a new .md file. Here, the
motivations seed (State → where they go) already holds
hungry: go=kitchen and similar lines:
sleepy: go=bedroom
-
Rebuild and run the tests.
build.rsreruns whenever anything underseeds/changes:cargo test --release --manifest-path global/Cargo.toml -
Rebuild the consumer and probe again.
worldandmathdepend onglobalby path:cargo run --release --manifest-path world/Cargo.toml -- \ babi-probe "Sumit is sleepy." "Where will sumit go?" # ? Where will sumit go? → ("bedroom", "sleepy → bedroom (first level)") -
Measure the whole benchmark before and after, twice each (see "Measure" below).
Example: a new verb class with a link
# Heating
**Gist.** Heating makes a thing hot.
**Sources.** Any school physics textbook.
```verbs
heat + heat warm boil fry
hot: hot boiling
heat -> hot : heating makes a thing hot
A unit test pins the behaviour, including a negative control:
```rust
#[test]
fn heat_class() {
assert_eq!(global::verb_class("boil"), Some(("heat", '+')));
let p = global::path(global::concept("heat").unwrap(), global::concept("hot").unwrap()).unwrap();
assert_eq!(p[0].why, "heating makes a thing hot");
assert_eq!(global::verb_class("freeze"), None); // must stay unknown
}
Build gates (what makes the build fail)
cargo build panics, naming the seed file, when:
- a link points to an undeclared concept (
hot -> burn_riskwith noburn_riskconcept); - a concept or category name is declared twice;
- the same
from -> tolink appears twice; - a relation name repeats, a relation line has no
:, or akey=valueitem has no=; - a
linkline has no: whyor no->, or aconceptline has no:; - a verb line has no effect, or its effect is not one of
= + - ±; - a verb class has the same name as a concept;
- a principle lacks
id:,name:,gist:,against:ortowards:, or itsidrepeats.
The gates are tested to really fail. Adding hot -> burn_risk : ... without declaring burn_risk
stops the build with "concept «burn_risk» not declared".
Steps: grammar seed (en/seeds)
-
Pick a section:
morph,nominal,verbal,lexiconorerrors. Grep first, because more than 400 rules already exist (grep -rn "much" en/seeds). -
Write the prose (Gist, Conditions, Examples, How it looks in UD, Sources) and a rule block:
rule: en.verbal.double-modal what: two modal auxiliaries on one verb (might could go) - standard English allows one match: v[upos=VERB|AUX]; m1[xpos=MD, rel=aux, head=v]; m2[xpos=MD, rel=aux, head=v, after=m1] require: not m2[] severity: warn source: CGEL ch. 3 (modals have no non-finite forms)Fields:
rule(unique id),match(nodesname[cond, cond]separated by;),require(clauses separated by;, alternatives within a clause separated byor; each item isv[...],not v[...],exists c[...]ornone c[...]), optionalunless,what,severity(defaultwarn),source. Conditions:upos=,xpos=,lemma=,form=,rel=,rel~(base relation),feats.X=,feats.X,!feats.X,head=v|0,before=,after=,next=,prev=,suffix=,prefix=,lemma.suffix=,lemma.prefix=,feats.X=@node,lemma=@form. Use!=and!~to negate. Separate alternative values with|. -
Check that it fires where it should and stays silent elsewhere. Use a tiny CoNLL-U file with one bad sentence and one good sentence:
EN_EXPERT_LIST=1 cargo run --release --manifest-path en/Cargo.toml -- \ expert-check en/seeds/verbal tests/double-modal.conllu # en.verbal.double-modal | warn | 1 | 1 | ... | dm1: go / might / couldOn a parser's output with a gold file,
expert-check <dir> --gold gold.conllu pred.conlluadds a precision column: how many violations sit on tokens where the prediction is really wrong. -
Run the gate test. It requires that every rule in
en/seedsparses and that rule ids are unique:cargo test --release --manifest-path en/Cargo.toml --lib seeds_all_parse -
Run the rule over a gold treebank such as UD EWT. A rule that fires often on gold data is either wrong or is catching errors in the gold data. Read the hits before you trust the rule.
Measure
-
Verb classes are features of the word-problem solver and the story readers. Widening a class can lower accuracy. Treat every level-1 change as an experiment. Measure the benchmark before and after, two identical runs each. If a class extension hurts, add the words as a plain
conceptfor explanations instead. -
Ablation without editing files:
GLOBAL_EXCLUDE=substr,substrdrops every seed whose path contains one of the substrings at build time. Compare runs with and without a seed to see what it is worth:GLOBAL_EXCLUDE=motivations cargo run --release --manifest-path world/Cargo.toml -- \ babi-probe "Sumit is tired." "Where will sumit go?"
Pitfalls
- Unknown block names are silently ignored.
```relations(with an extras) or```verbcompiles to nothing. After adding a seed, check that a test or a probe sees it. - The first verb class wins. When a lemma appears in two
verbslines, the line read first wins, in path order of the files. No error is raised. - Verb lemmas match exactly. Multi-word verbs are written with
_(pick_up). Principle words also use_for spaces. - An unclosed fence at the end of a file drops that block.
expert-checkprints rules that fail to parse (asNOT PARSED) but does not fail. Use theseeds_all_parsetest as the gate.- Readers that parse text (
world,math) use the English model shipped inen/models/en.bin; to use your own, see Retrain the English tagger and parser. - Do not put culture-specific or trick logic into level 1. Level 1 holds common human knowledge. Alternatives belong in a domain or an auxiliary module.
This page in Markdown → https://md.mova.elamur.ai/free/en/docs/train/add-a-seed/