Making Something of a Day
Two calls between a day and a thought
Halla's stream is twenty-four rows and every one of them is something that happened. Ander came into sight. She handed him fifty parcels. He took forty out of her hands. She walked two cells and ate for the rest of the afternoon. The last chapter can bring any eight of those to mind for any question, and the chapter before it weighed each one the moment it was written, and between the two of them there is still not a single row that says what any of it meant to her. The fourth kind of memory has been in the schema since the eighth migration, with a column of its own that the other three kinds may not fill, and nothing has ever written one: a thought, drawn from other memories, is the one row a world cannot produce on its own, because a world records what happened and an opinion about it has to be asked for.
Asking for one is the first time in this book that text a model wrote goes into a table the world reads back. Every prompt in the last volume was the operator's, addressed to a model as a piece of equipment, and every answer was printed and dropped. Here the answer is kept, weighed, embedded, and brought to mind by the next question, which means the thing that has been true of every row so far, that its text is a Go format string filled in from an event, stops being true for one kind of row. A reflection is two calls deep, and every memory a statement claims to be drawn from must be one this villager has, at a tick no later than the reflection's, or the statement is refused whole, before anything is written.
On a cadence in ticks, a villager's newest memories are put to the small model with a request for two questions about them, in a schema. Each question is turned into a row of numbers and retrieved against, exactly as the last chapter retrieves, and the eight memories that come to mind are put back to the model with the question and a request for one statement plus the identities of the memories the statement was drawn from.
Those identities are the point. A statement that names its sources can be checked against a stream the engine already holds, one identity at a time, and a statement that fails the check on any one of them is not a statement about this villager's life. It is not trimmed to the citations that check, because a statement drawn from four memories and supported by three is not the statement the model made.
This is the first place in the book where grounding is enforced instead of requested: the prompt asks for citations, and the seam decides what a citation is worth.
The rest is consequences. An accepted statement is appended as a thought with its citations in the column that only a thought may fill, so it is retrievable like any other row and a later reflection may draw on it; a thought drawn from thoughts drawn from thoughts is a chain, and the chain's depth is capped in a file and enforced at the same seam, for a reason the worked failure runs. And a reflection is the most expensive thing a villager does, three calls where a decision is one, and the cadence is chosen against that price and not against how often a person might like to think.
Five numbers in a file and two templates beside them
Everything a reflection runs under is in configs/, in the file the last
chapter left room in. The retrieval block is untouched; a second block beside it holds
the cadence, the window, the question count, the depth cap and the token limit, and the
importance table gains the row for the kind, so that a thought can be weighed by the same
integer arithmetic as everything else.
// configs/thought.json — the retrieval block is the last chapter's; this is the block beside it
"reflection": {
"every": 2000,
"recent": 24,
"questions": 2,
"depth": 2,
"limit": 128
}
// configs/importance.json — the base table gains one row
"did": {"meal": 1, "walk": 1, "gift": 3, "take": 3},
"thought": {"reflection": 4}
Every one of the five is a design choice and the run below prints all five from the
file. every is 2000 because that is a day in this village, the two thousand
ticks the stream fixture was dumped from, and making something of a day is what a
reflection does. recent is 24 so that the whole of Halla's day fits in one
prompt; a longer life reads its newest twenty-four. questions is 2, and the
price section below is the reason it is not more. depth is 2 and the worked
failure is the reason it is not off. limit is the most tokens one answer may
run to, and it is not advice: an answer that reaches it is cut off there and, as one
below shows, is not an answer at all. A thought starts at 4 on the importance table, above
a meal's 1 and level with the base for being taken from, and the table's first-of-its-kind adjustment applies to it
like any other row, so the first thought of a life weighs 6 and every one after weighs
4. No grams move in a thought, so no band applies.
The two prompts are files as well, and that is new. The last volume's configs/
deliberately held no prompt: what to say to a model was a program's business. A
reflection's prompt is different in one way that matters. It is text an operator may want
to read and change without rebuilding the thing that sends it, and it is rendered from
stored rows by Go, so putting the template in a file costs nothing that the program's
promise depended on: the bytes that go to the model are still bytes the engine produced,
from a template the build carries and rows the table holds, and two renderings of the
same rows are the same bytes.
// configs/reflect-questions.tmpl
{{.Name}} remembers, from the most recent part of the day, in the order it happened:
{{range .Memories}}[{{.ID}}] {{.Text}}
{{end}}
Ask {{.Count}} questions, one line each, about what this day meant to {{.Name}}, that {{.Name}} could answer from these memories alone.
// configs/reflect-statement.tmpl
The question is: {{.Question}}
What {{.Name}} remembers that bears on it, each with its number:
{{range .Memories}}[{{.ID}}] {{.Text}}
{{end}}
Answer for {{.Name}} in one statement, one breath long, about what it meant and never how much, and give the numbers of the memories the statement is drawn from.
Read what is not in them. Neither says that a citation has to exist, or that a memory may only be cited from the list, or that a number the statement claims will be believed. Those are the seam's rules, and a rule stated in two places is a rule that can disagree with itself; the engine's copy is the one that runs, so the prompt does not carry one. What the templates do carry is a request, in the register the memory lines are in: a memory reaches the model as its identity and its line and as nothing else, so the weight, the tick and the about column stay on the engine's side, and the second template asks for a statement about what the day meant and never how much, which is a request and not a guarantee. A page further down shows what a small model does with it.
The Go that reads the block and renders the templates is in the village package,
and like everything in that package it opens no connection, reads no clock and
calls no model. Due is the cadence as arithmetic on a life. Recent
is the window, in the order the day was lived. Render is the standard
library's text template with one option set, so that a field the template names and the
data does not carry is an error and never the text <no value> arriving
at a model.
// internal/village/reflect.go
// Reflection is the block of configs/thought.json a villager makes
// something of a day by: how often, in ticks; how many of its newest
// memories the questions are asked about; how many questions one
// reflection asks; how deep a thought may stand above the world; and
// the most tokens one answer may run to.
type Reflection struct {
Every int `json:"every"`
Recent int `json:"recent"`
Questions int `json:"questions"`
Depth int `json:"depth"`
Limit int `json:"limit"`
}
// Due reports whether a villager founded at founded reflects at the
// boundary of tick now: once every Every ticks of its life, counting
// the founding tick as the first.
func (r Reflection) Due(founded, now int) bool {
lived := now - founded + 1
return lived > 0 && lived%r.Every == 0
}
// Recent is the newest n of a villager's rows at ticks up to now, in
// the order they were lived: by tick, then by identity inside a tick.
func Recent(rows []Memory, now, n int) []Memory {
var have []Memory
for _, m := range rows {
if m.Tick <= now {
have = append(have, m)
}
}
sort.SliceStable(have, func(i, j int) bool {
if have[i].Tick != have[j].Tick {
return have[i].Tick < have[j].Tick
}
return have[i].ID < have[j].ID
})
if len(have) > n {
have = have[len(have)-n:]
}
return have
}
// Prompt is what a template is rendered from: whose reflection it is,
// the memories it may name, how many questions are wanted, and, for
// the second call, the question being answered. A memory reaches the
// template as its identity and its line and as nothing else; the
// weight, the tick and the about column stay on the engine's side.
type Prompt struct {
Name string
Memories []Memory
Count int
Question string
}
// Render renders one of the templates configs/ ships with the data
// given. The output is bytes the engine produced from a file and from
// stored rows, so two renderings of the same data are the same bytes
// and a sha256 over them means something.
// ...
func Render(name string, data Prompt) ([]byte, error) {
b, err := configs.Files.ReadFile(name)
if err != nil {
return nil, fmt.Errorf("village: %w", err)
}
t, err := template.New(name).Option("missingkey=error").Parse(string(b))
if err != nil {
return nil, fmt.Errorf("village: %s: %w", name, err)
}
var out bytes.Buffer
if err := t.Execute(&out, data); err != nil {
return nil, fmt.Errorf("village: %s: %w", name, err)
}
return out.Bytes(), nil
}
$ go run ./cmd/reflect -mode settings
reflect: what a reflection runs under, read out of the files the build embeds
the cadence every 2000 ticks of a life, off configs/thought.json
the questions 2, asked about the newest 24 memories
the depth cap 2, off the same file
the retrieval half-life 250, weights 1.00 1.00 3.00, k 8, off the same file
the model qwen2.5:0.5b-instruct-q4_K_M, pinned at c5396e06af294bd1
the embedding nomic-embed-text:v1.5, pinned at 970aa74c0a90ef74
the answer at most 128 tokens, temperature 0.00, a seed of the tick and the body
the templates configs/reflect-questions.tmpl, configs/reflect-statement.tmpl
the importance table's row for the kind, off configs/importance.json
kind event base
thought reflection 4
plus 2 for the first thought of a life, and no grams band, because no grams move in one
configs/reflect-questions.tmpl, 268 bytes, sha256 d11f5ebbed9a5efb
| {{.Name}} remembers, from the most recent part of the day, in the order it happened:
| {{range .Memories}}[{{.ID}}] {{.Text}}
| {{end}}
| Ask {{.Count}} questions, one line each, about what this day meant to {{.Name}}, that {{.Name}} could answer from these memories alone.
configs/reflect-statement.tmpl, 305 bytes, sha256 d562b9492cbab36c
| The question is: {{.Question}}
| What {{.Name}} remembers that bears on it, each with its number:
| {{range .Memories}}[{{.ID}}] {{.Text}}
| {{end}}
| Answer for {{.Name}} in one statement, one breath long, about what it meant and never how much, and give the numbers of the memories the statement is drawn from.
neither template says which memories may be cited, and neither says a
citation has to exist: that is the seam's rule, and a rule stated in two
places is a rule that can disagree with itself
That mode runs on a bare workstation with no containers anywhere and opens nothing. Two hundred and sixty-eight bytes and three hundred and five, each with a sha256, so that a rendering further down can be traced to the template that made it. The model and the embedding model are the last volume's, at the last volume's digests, read out of the kit and never typed; the temperature is nought, so an answer is a function of the seed, and the seed is the tick and the body deciding, added, which is the rule every call in this volume is under. A reflection is due once every 2000 ticks of a life, counting the founding tick as the first, so a villager founded at tick 901 reflects at the boundary of tick 2900, which is the last tick of the day the stream records.
Now the first prompt, rendered for Halla at that boundary out of the committed stream. The mode prints the whole of it, because a prompt is bytes the reader's own code produced and the reader should see them once; then it holds the bytes against a recording made from the live model, which is this chapter's fixture and is explained in a moment.
$ go run ./cmd/reflect -mode prompt | tail -38
Halla's newest 24 rows at ticks up to 2900: 24 the world wrote and 0 thoughts
the prompt, 1168 bytes, sha256 447a9ab39467fff8
| Halla remembers, from the most recent part of the day, in the order it happened:
| [1] Ander came into sight, 0.8 cells off
| [2] Mose came into sight, 0.8 cells off
| [8] handed Ander 50 parcels, 25.0 grams
| [11] Ander took 40 parcels out of Halla's hands, 20.0 grams
| [13] handed Ander 50 parcels, 25.0 grams
| [17] handed Ander 37 parcels, 18.5 grams
| [25] Ander came into sight, 0.8 cells off
| [28] went from 8,5 to 9,3
| [35] ate 50 mouthfuls on 9,3, 25.0 grams
| [38] ate 50 mouthfuls on 9,3, 25.0 grams
| [41] ate 50 mouthfuls on 9,3, 25.0 grams
| [44] ate 50 mouthfuls on 9,3, 25.0 grams
| [47] ate 50 mouthfuls on 9,3, 25.0 grams
| [50] ate 50 mouthfuls on 9,3, 25.0 grams
| [53] ate 50 mouthfuls on 9,3, 25.0 grams
| [56] ate 50 mouthfuls on 9,3, 23.5 grams
| [63] ate 27 mouthfuls on 9,3, 9.3 grams
| [62] Ander came into sight, 0.2 cells off
| [69] handed Ander 50 parcels, 25.0 grams
| [73] handed Ander 17 parcels, 8.5 grams
| [83] ate 2 mouthfuls on 9,3, 1.0 grams
| [84] went from 9,3 to 9,2
| [86] ate 50 mouthfuls on 9,2, 25.0 grams
| [87] ate 8 mouthfuls on 9,2, 4.0 grams
|
| Ask 2 questions, one line each, about what this day meant to Halla, that Halla could answer from these memories alone.
against the recording, exemplars/reflections.json
rendered here 1168 bytes sha256 447a9ab39467fff8
recorded 1168 bytes sha256 447a9ab39467fff8
the same bytes; the recording is a recording of this template and this stream
575 tokens in by the model's own table, 29 out, stopped because: stop
what the model answered, once, and yours will differ:
| { "questions": [ "What did Ander do in the morning?", "What did Mose do in the afternoon?" ] }
Eleven hundred and sixty-eight bytes, twenty-four of Halla's rows in the order she lived them, and not one number on the engine's side of the line: no weight, no tick, no about column, only the identity in brackets and the line. Rows 63 and 62 come in that order because the window is sorted by the tick the thing happened at and not by the number the row was given, which the eighth migration's chapter explained. The 575 tokens are what the model's own table made of those bytes, counted by the server, and they are the price of reading a day; the 29 tokens back are two questions in a schema. The two questions are a model's and are labelled so. One asks about a morning this world does not have. A question is one line the model chose; what it costs and what is done with it are the engine's, and those are what the rest of the page compares.
One more field on the request
A question has to come back as a question and a statement as a statement plus a list of
identities, and free text cannot be relied on for either. The server this world runs
takes a schema on a generation request and steers what the model produces toward it, and
the client the last volume wrote gains that as one field on Ask. Then one
helper on this side reads the answer into a Go value, strictly, because a schema on the
request is a request and what arrives is bytes a model chose.
// internal/lang/serve.go — Ask, with the field this volume adds; the seven before it are the last volume's
type Ask struct {
Model string
Prompt string
// ...
Limit int
// ...
Context int
// ...
Temp float64
// ...
Seed int
// Format is the schema the answer must match, as the server takes
// one, or nothing for an answer in free text. It is a request and
// not a guarantee: the server steers what it produces toward the
// schema, and what came back is still parsed on this side before
// anything reads a field out of it, by Shaped. Nothing in this
// world reads a model's answer any other way once a schema is on
// the request.
Format json.RawMessage
}
// generateReq — the wire gains the same field, left out when empty
type generateReq struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
Stream bool `json:"stream"`
Format json.RawMessage `json:"format,omitempty"`
Options genOptions `json:"options"`
}
// internal/lang/shaped.go
// Shaped reads one answer into the value it was asked to match, and
// refuses an answer that does not parse as exactly that: not JSON at
// all, a field the shape does not have, or anything after the object.
//
// It is the only road from a model's text to a Go value in this world,
// and it is strict on purpose. A schema on the request is a request;
// what arrives is bytes a model chose, and the place to find out that
// they are not the shape asked for is here, before a field is read,
// rather than in whatever would have read it. A refusal here is a
// refusal of the whole answer: nothing is salvaged from an answer that
// did not parse.
func Shaped(s Said, into any) error {
dec := json.NewDecoder(bytes.NewReader([]byte(s.Text)))
dec.DisallowUnknownFields()
if err := dec.Decode(into); err != nil {
return fmt.Errorf("lang: %s answered with something that is not the shape asked for: %w", s.Model, err)
}
if dec.More() {
return fmt.Errorf("lang: %s answered with more than one value", s.Model)
}
return nil
}
// internal/village/reflect.go — the two shapes, and the schemas the server is sent
// Asked is the shape the first call answers in: the questions, one
// line each, and nothing else.
type Asked struct {
Questions []string `json:"questions"`
}
// Drawn is the shape the second call answers in: one statement, and
// the identities of the memories it claims to be drawn from.
type Drawn struct {
Statement string `json:"statement"`
DrawnFrom []int64 `json:"drawn_from"`
}
// AskedSchema is the schema the first answer must match, written out
// for a server that takes one: an object with one field, an array of
// exactly n strings.
func AskedSchema(n int) json.RawMessage {
return json.RawMessage(fmt.Sprintf(`{"type":"object","properties":{"questions":{"type":"array","items":{"type":"string"},"minItems":%d,"maxItems":%d}},"required":["questions"]}`, n, n))
}
// DrawnSchema is the schema the second answer must match: an object
// with a statement and an array of integers.
var DrawnSchema = json.RawMessage(`{"type":"object","properties":{"statement":{"type":"string"},"drawn_from":{"type":"array","items":{"type":"integer"}}},"required":["statement","drawn_from"]}`)
$ go test ./internal/lang/ -run 'TestTheSchemaGoesOutOnTheWireAndOnlyWhenThereIsOne|TestAnAnswerThatIsNotTheShapeAskedForIsRefusedWhole' -v
=== RUN TestTheSchemaGoesOutOnTheWireAndOnlyWhenThereIsOne
shaped_test.go:40: the schema went out as given, and a request without one sent no format field
--- PASS: TestTheSchemaGoesOutOnTheWireAndOnlyWhenThereIsOne (0.00s)
=== RUN TestAnAnswerThatIsNotTheShapeAskedForIsRefusedWhole
shaped_test.go:76: one shape accepted and 4 ways of not being it refused, each naming the model
--- PASS: TestAnAnswerThatIsNotTheShapeAskedForIsRefusedWhole (0.00s)
PASS
ok theworld/internal/lang 0.005s
The field is a raw message and not a string, so what the caller hands over is what goes
on the wire, byte for byte, and the first test reads the request the forty-line fake
server recorded and finds the schema in it as given; the same test sends a request with
no schema and finds no format field at all, because an empty field is not
the same thing as an absent one and a server handed an empty schema has an opinion
about it. The second test is the four ways an answer can fail to be the shape: prose, a
field the shape does not have, a field of the wrong type, and two values where one was
asked for. Every one is refused whole and the refusal names the model, because the
model is the thing that produced it.
DisallowUnknownFields is the line to read twice. A model that answers with
the statement, the citations and a helpful "confidence": 0.9 has answered
with something the seam has no column for, and an answer that carries a field nothing
asked for is an answer that was not shaped by the request. Refusing it costs one answer.
Reading round it would mean a decision somewhere about which extra fields are harmless,
and that decision would be made by whoever wrote the last reader.
Eight citations, checked one at a time
The check is a function of three things the engine holds: this villager's rows, the tick the reflection is at, and the identities the statement claims. It is pure, it is in the village package beside the rules for weighing and retrieving, and it says no for exactly four reasons. A citation names a memory this villager does not have, which is the same refusal whether the memory belongs to somebody else or to nobody, because from inside one stream those are the same fact. A citation names a memory at a tick after the reflection's, which the fixture cannot produce on its own and a perturbation below does. A memory is cited twice, because a list of sources with a source in it twice is not the list the check was asked about, and collapsing it would be repairing the answer. And the statement is drawn from nothing. After those four comes the depth.
// internal/village/reflect.go
// Verdict is what the seam says about one statement: accepted, or
// refused by a named check for a stated reason. Depth is how high the
// thought would stand above the world if it were written, worked out
// whether or not it is.
type Verdict struct {
OK bool
Check string
Reason string
Depth int
}
// Depth is how many thoughts stand between a memory and the world. A
// memory of something seen, done or heard is at depth 0; a thought is
// one deeper than the deepest memory it was drawn from. A thought
// drawn from nothing but the world is at depth 1.
func Depth(by map[int64]Memory, m Memory) int {
if m.Kind != Thought {
return 0
}
deepest := 0
for _, id := range m.DrawnFrom {
if d := Depth(by, by[id]); d > deepest {
deepest = d
}
}
return deepest + 1
}
// Cite is the check a statement passes before it becomes a row, and
// it runs before anything is written. rows are this villager's rows
// and nobody else's; now is the tick the reflection is at; cites are
// the identities the statement claims to be drawn from.
// ...
func Cite(rows []Memory, now int, cites []int64, cap int) Verdict {
by := map[int64]Memory{}
for _, m := range rows {
by[m.ID] = m
}
if len(cites) == 0 {
return Verdict{Check: "cites", Reason: "a statement drawn from nothing"}
}
seen := map[int64]bool{}
for _, id := range cites {
m, ok := by[id]
if !ok {
return Verdict{Check: "cites", Reason: fmt.Sprintf("memory %d is not one this villager has", id)}
}
if m.Tick > now {
return Verdict{Check: "cites", Reason: fmt.Sprintf("memory %d is at tick %d, which is after tick %d", id, m.Tick, now)}
}
if seen[id] {
return Verdict{Check: "cites", Reason: fmt.Sprintf("memory %d is cited twice", id)}
}
seen[id] = true
}
would := Memory{Kind: Thought, DrawnFrom: cites}
d := Depth(by, would)
if cap > 0 && d > cap {
return Verdict{Check: "depth", Reason: fmt.Sprintf("a thought at depth %d, and the cap is %d", d, cap), Depth: d}
}
return Verdict{OK: true, Depth: d}
}
// Reflected is the event an accepted reflection is appended as: at the
// reflection's tick, of the thought kind, weighed on the table's
// reflection row, with the statement as its line, about every identity
// the cited memories are about, and drawn from the citations in
// ascending order.
// ...
func Reflected(rows []Memory, now int, statement string, cites []int64) Event {
by := map[int64]Memory{}
for _, m := range rows {
by[m.ID] = m
}
set := map[sim.EntityID]bool{}
for _, id := range cites {
for _, who := range by[id].About {
set[who] = true
}
}
about := make([]sim.EntityID, 0, len(set))
for who := range set {
about = append(about, who)
}
sort.Slice(about, func(i, j int) bool { return about[i] < about[j] })
from := append([]int64(nil), cites...)
sort.Slice(from, func(i, j int) bool { return from[i] < from[j] })
return Event{
Tick: now, Kind: Thought, What: "reflection", Text: statement,
About: about, DrawnFrom: from,
}
}
$ go test ./internal/village/ -run 'TestAReflectionIsDueOnceEveryCadenceOfALife|TestTheNewestMemoriesAreTakenByTickThenIdentity|TestACitationHasToExistBelongAndBeNoLaterThanTheReflection|TestAStatementIsRefusedWholeAndNeverTrimmed|TestDepthIsOneMoreThanTheDeepestCitation|TestTheCapRefusesADeeperThoughtAndTheCapOffDoesNot|TestAThoughtRowIsAboutWhatItsCitationsAreAbout|TestARenderingIsAFunctionOfTheTemplateAndTheRows|TestNeitherTemplateCarriesTheSeamsRule|TestTheShippedReflectionIsACadenceAWindowACountACapAndALimit|TestTheVillageStillImportsNeitherTheModelClientNorTheStore' -v
=== RUN TestAReflectionIsDueOnceEveryCadenceOfALife
--- PASS: TestAReflectionIsDueOnceEveryCadenceOfALife (0.00s)
=== RUN TestTheNewestMemoriesAreTakenByTickThenIdentity
--- PASS: TestTheNewestMemoriesAreTakenByTickThenIdentity (0.00s)
=== RUN TestACitationHasToExistBelongAndBeNoLaterThanTheReflection
--- PASS: TestACitationHasToExistBelongAndBeNoLaterThanTheReflection (0.00s)
=== RUN TestAStatementIsRefusedWholeAndNeverTrimmed
reflect_test.go:112: four citations, three of which check, is refused; the three alone are accepted; nothing trims the four to the three
--- PASS: TestAStatementIsRefusedWholeAndNeverTrimmed (0.00s)
=== RUN TestDepthIsOneMoreThanTheDeepestCitation
--- PASS: TestDepthIsOneMoreThanTheDeepestCitation (0.00s)
=== RUN TestTheCapRefusesADeeperThoughtAndTheCapOffDoesNot
--- PASS: TestTheCapRefusesADeeperThoughtAndTheCapOffDoesNot (0.00s)
=== RUN TestAThoughtRowIsAboutWhatItsCitationsAreAbout
reflect_test.go:183: the first thought of a life weighs 6, 4 base and 2 first; the second weighs 4
--- PASS: TestAThoughtRowIsAboutWhatItsCitationsAreAbout (0.00s)
=== RUN TestARenderingIsAFunctionOfTheTemplateAndTheRows
--- PASS: TestARenderingIsAFunctionOfTheTemplateAndTheRows (0.00s)
=== RUN TestNeitherTemplateCarriesTheSeamsRule
--- PASS: TestNeitherTemplateCarriesTheSeamsRule (0.00s)
=== RUN TestTheShippedReflectionIsACadenceAWindowACountACapAndALimit
reflect_test.go:246: every 2000 ticks, 2 questions about the newest 24, a cap of 2, at most 128 tokens an answer
--- PASS: TestTheShippedReflectionIsACadenceAWindowACountACapAndALimit (0.00s)
=== RUN TestTheVillageStillImportsNeitherTheModelClientNorTheStore
reflect_test.go:259: every file of the package read, and none of them names the model client or the store
--- PASS: TestTheVillageStillImportsNeitherTheModelClientNorTheStore (0.00s)
PASS
ok theworld/internal/village 0.007s
No later than is the line the check draws in time, and it is the line the last chapter's retrieval draws: a row at the reflection's own tick can come to mind, so it can be cited, and a row after it cannot be either. The tick the thought is written at is the reflection's, so a thought and the memories it was drawn from can share a tick, and a reflection reads the stream as it stood when it began, which means the second question of one reflection cannot draw on the first question's answer. What one reflection wrote is there for the next one.
Reflected is where every column of the row but one is decided by the
engine. The tick is the reflection's. The kind is thought and the table's
row for it decides the weight. The about column is the union of the cited rows' about
columns, in ascending order, so a thought drawn from rows about Ander is about Ander
whatever its text says and a thought drawn from meals is about nobody; it is never read
off the text, for the reason the eighth migration's chapter gave about finding Ander
inside Anders. The citations are sorted, so that two statements citing the same eight
rows in a different order make the same row. The one column a model wrote is the text,
and nothing downstream of this line reads a fact out of it. The third test from the
bottom is what keeps the templates honest: it reads both files and fails on the words a
rule would be stated in.
The last test walks the whole package and finds it still names neither the model client nor the store. The last chapter's walk over the same directory logged how many files it read, and the eighth file this chapter adds moved that line; the fix is the one the migrations listing got two chapters ago, at source: the earlier test walks the seven files it shipped, by name, and this one walks the directory and logs no count.
Now a reflection, end to end. It replays from a recording, and the recording is the third
fixture this volume commits and the first of its kind: exemplars/reflections.json,
twenty-one proposals produced once by the small model at the settings above, through the
server this world runs, with the row the embedding model gave every question and every
accepted thought, so that the whole cycle, retrieval included, can be run over it with no
model server anywhere. What the model wrote is in the file and is labelled, on the page
and in the file's own note. What is not in the file is any verdict: the recording holds
prompts' hashes and answers' bytes, and everything between them is computed here, every
time, by the code above.
// cmd/reflect/cycle.go — the driver; the calls it makes are the last volume's, through
// an oracle that is the recording on this page and a live server further down
// reflect1 runs one reflection for one villager at one tick, in four
// phases: the questions call; every question embedded; for every
// question a retrieval, the statement call and the seam; and every
// accepted thought embedded and appended to the stream with its row.
//
// Four phases and not two loops, because the server holds one model
// resident at a time and every change of model is a load: the calls
// are grouped by the model that answers them, so a reflection changes
// model three times and not once a call.
//
// Nothing a model says reaches the stream except through Cite, and
// nothing reaches Cite that Shaped did not parse first.
func reflect1(st settingsOf, s *stream, who string, now, cap int, o oracle) (result, error) {
// ...
seed := seedFor(now, p.Body.ID)
// The stream as it stands when the reflection begins. A thought
// this reflection writes is not in it, so a second question cannot
// draw on the first question's answer: one reflection reads one
// day, and what it wrote is there for the next one.
mine := upTo(s.mine(who), now)
r := result{who: who, now: now, rows: len(mine), recent: village.Recent(mine, now, st.r.Recent)}
// The first call: the newest memories, and a request for questions.
qp, err := village.Render(configs.ReflectQuestions, village.Prompt{Name: who, Memories: r.recent, Count: st.r.Questions})
// ...
a, err := o.ask("questions", 0, qp, village.AskedSchema(st.r.Questions), seed)
// ...
var got village.Asked
if err := lang.Shaped(lang.Said{Model: st.kit.Models[lang.Small].Name, Text: a.text}, &got); err != nil {
r.asked.shaped = err
return r, nil
}
// ...
// The second call, once a question: bring memories to mind,
// render, ask, and check. Nothing is written in this phase.
for i, q := range got.Questions {
d := &r.drawn[i]
scored, err := st.ret.Retrieve(now, mine, s.vec, vecs[i], false)
// ...
d.top = st.ret.Top(scored)
// ...
a, err := o.ask("statement", i+1, sp, village.DrawnSchema, seed)
// ...
if err := lang.Shaped(lang.Said{Model: st.kit.Models[lang.Small].Name, Text: a.text}, &d.answer); err != nil {
d.shaped = err
continue
}
// The seam. Every citation against this villager's rows and
// nobody else's, at this tick, and the depth of the thought
// the statement would become.
d.verdict = village.Cite(mine, now, d.answer.DrawnFrom, cap)
}
// ...
}
$ go run ./cmd/reflect -mode reflect | tail -53 | head -33
the first call: 1168 bytes of prompt, sha256 447a9ab39467fff8, 575 tokens in, 29 out, stopped because: stop
the questions, which a model wrote and yours will differ:
1. What did Ander do in the morning?
2. What did Mose do in the afternoon?
question 1, embedded, and Halla's 24 rows at ticks up to 2900 ranked against it
rank id tick weight recency import relev score kind text
1 11 1015 10 0.0054 1.00 0.5611 2.6887 saw Ander took 40 parcels out of Ha...
2 86 2883 3 0.9540 0.30 0.3764 2.3830 did ate 50 mouthfuls on 9,2, 25.0 g...
3 84 2781 1 0.7190 0.10 0.4870 2.2801 did went from 9,3 to 9,2
4 1 901 4 0.0039 0.40 0.6158 2.2513 saw Ander came into sight, 0.8 cell...
5 87 2899 1 0.9972 0.10 0.3798 2.2367 did ate 8 mouthfuls on 9,2, 4.0 grams
6 62 2203 2 0.1448 0.20 0.6170 2.1958 saw Ander came into sight, 0.2 cell...
7 73 2341 4 0.2123 0.40 0.4838 2.0638 did handed Ander 17 parcels, 8.5 grams
8 25 1337 2 0.0131 0.20 0.6158 2.0605 saw Ander came into sight, 0.8 cell...
the second call: 602 bytes of prompt, sha256 2d2453b788d9ecc5, 245 tokens in, 64 out, stopped because: stop
| The question is: What did Ander do in the morning?
| What Halla remembers that bears on it, each with its number:
| [11] Ander took 40 parcels out of Halla's hands, 20.0 grams
| [86] ate 50 mouthfuls on 9,2, 25.0 grams
| [84] went from 9,3 to 9,2
| [1] Ander came into sight, 0.8 cells off
| [87] ate 8 mouthfuls on 9,2, 4.0 grams
| [62] Ander came into sight, 0.2 cells off
| [73] handed Ander 17 parcels, 8.5 grams
| [25] Ander came into sight, 0.8 cells off
|
| Answer for Halla in one statement, one breath long, about what it meant and never how much, and give the numbers of the memories the statement is drawn from.
the statement, which a model wrote and yours will differ:
| Ander took 40 parcels out of Halla's hands, 20.0 grams.
drawn from, as the model gave them 11, 86, 84, 1, 87, 62, 73, 25
the seam accepted, at depth 1
the row appended id 89, tick 2900, thought, weight 6 (4 base + 2 first), about 1 Halla, 2 Ander, drawn from 1, 11, 25, 62, 73, 84, 86, 87
Read the ranking first, because it is the last chapter's arithmetic over a vector the fixture holds for a question the model asked, and it replays to the digit: asked what Ander did, the eight that come to Halla's mind are the taking, two meals and a walk carried by recency, and three sightings of him and one gift carried by cosines above 0.48. The second prompt is those eight in rank order with the question on top, 602 bytes, 245 tokens, and its sha256 is the check that the retrieval replayed, because a different ranking renders different bytes. The statement is a model's, and it is the third line of the prompt, row 11, copied out with a full stop, numbers and all. The prompt asked for what the day meant and never how much, and a half-billion-parameter model answered with the line that scored highest, which is a fact about this model at this size and not a fault in the check. The check has nothing to say about the words. It has eight identities to check, finds every one in Halla's stream at a tick no later than 2900 and none of them twice, works out that a thought drawn from eight memories the world wrote stands at depth 1, and says yes.
Then the row. Identity 89, the next after the day's 88, because the stream numbers a thought the way the database will. Tick 2900, the reflection's. Weight 6, which is the table's 4 for a reflection plus 2 for the first thought of Halla's life. About Halla and Ander, read off the eight cited rows and off nothing else. Drawn from the eight, sorted. Every one of those columns is the engine's and every one is compared; the text between them is the model's and is not.
$ go run ./cmd/reflect -mode reflect | tail -19
question 2, embedded, and Halla's 24 rows at ticks up to 2900 ranked against it
rank id tick weight recency import relev score kind text
1 86 2883 3 0.9540 0.30 0.3904 2.4250 did ate 50 mouthfuls on 9,2, 25.0 g...
2 11 1015 10 0.0054 1.00 0.4720 2.4213 saw Ander took 40 parcels out of Ha...
3 87 2899 1 0.9972 0.10 0.3948 2.2817 did ate 8 mouthfuls on 9,2, 4.0 grams
4 84 2781 1 0.7190 0.10 0.4603 2.2000 did went from 9,3 to 9,2
5 2 901 2 0.0039 0.20 0.6182 2.0586 saw Mose came into sight, 0.8 cells...
6 83 2776 1 0.7091 0.10 0.4004 2.0104 did ate 2 mouthfuls on 9,3, 1.0 grams
7 73 2341 4 0.2123 0.40 0.4141 1.8546 did handed Ander 17 parcels, 8.5 grams
8 8 999 7 0.0051 0.70 0.3800 1.8451 did handed Ander 50 parcels, 25.0 g...
the second call: 597 bytes of prompt, sha256 b90edc6025d370ce, 251 tokens in, 58 out, stopped because: stop
the statement, which a model wrote and yours will differ:
| Mose came into sight, 0.8 cells off
drawn from, as the model gave them 86, 11, 87, 84, 2, 73, 8, 8, 83
the seam refused by cites: memory 8 is cited twice
the row appended none; nothing was written
3 calls read off the recording, 3 prompts rendered here and every one the
recorded bytes, 2 statements checked, 1 thoughts written: 88 rows before, 89 after
Asked what Mose did, the model copied the one row about Mose and cited nine memories for it, eight of them the ones it was shown and one of them, row 8, twice. Eight citations check. The ninth is the eighth again, and the statement is refused whole on it, with nothing written and the stream still at 89 rows. This is the rule in the bolded sentence meeting its first real answer, and the answer is a good one to meet it on, because the temptation is obvious: drop the duplicate and keep a statement that would otherwise pass. The check does not, for the reason a statement drawn from four memories and supported by three is not the statement the model made. A list of sources is what the model said it drew on. If the list is wrong the statement is unsupported, and an unsupported statement in a memory table is a fact nobody witnessed.
The last two lines are the accounting for the recording. Three calls were read off it, three prompts were rendered here and each was the recorded bytes, so the recording is a recording of this template, this stream and this ranking; two statements were checked and one thought written. A recording whose prompts did not match would stop the replay at the first mismatch rather than carry on with answers to questions this code did not ask.
Two of the check's four reasons cannot occur on the fixture as recorded, because the model, shown eight identities, copied identities, and because a reflection at the end of a day has nothing after it to cite. So the bench takes the accepted statement above and perturbs its citations itself, three ways, and says so: a check that has only ever been seen saying yes has not been seen.
$ go run ./cmd/reflect -mode refuse | tail -16
the first citation swapped for 989, which no stream holds
drawn from 989, 86, 84, 1, 87, 62, 73, 25, at tick 2900
the seam refused by cites: memory 989 is not one this villager has
written nothing: 88 rows before, 88 after; the statement is not trimmed to the 7 that check
the first citation swapped for 3, which is Mose's
drawn from 3, 86, 84, 1, 87, 62, 73, 25, at tick 2900
the seam refused by cites: memory 3 is not one this villager has
written nothing: 88 rows before, 88 after; the statement is not trimmed to the 7 that check
the same citations, with the reflection moved back to tick 2898
drawn from 11, 86, 84, 1, 87, 62, 73, 25, at tick 2898
the seam refused by cites: memory 87 is at tick 2899, which is after tick 2898
written nothing: 88 rows before, 88 after; the statement is not trimmed to the 7 that check
3 statements refused whole, 0 rows written, and the one that checks is the one above
Row 989 is nobody's and row 3 is Mose's, and the refusal is the same sentence for both, on purpose. The check is handed this villager's rows and no others, so a memory another villager has is, from where the check stands, a memory that does not exist, and a refusal that said that is Mose's would be a refusal that had looked at Mose's stream to say it. The third case moves the reflection back two ticks and leaves the citations alone, and row 87, the eight mouthfuls at tick 2899, is now a memory of something that has not happened yet. Every one of the three keeps the seven citations that check and is refused on the one that does not, and the row count is the proof: 88 before, 88 after, three times. The bench stops with a non-zero status if any of the three is accepted.
One more refusal happens before the seam is reached at all, and it is the honest half of the schema section. Ander's first statement, in the same recording, ran to the limit.
$ go run ./cmd/reflect -mode reflect -who Ander | tail -34 | head -1
the second call: 621 bytes of prompt, sha256 c865a6a6a3162284, 276 tokens in, 128 out, stopped because: length
$ go run ./cmd/reflect -mode reflect -who Ander | tail -21 | head -1
refused before the seam: lang: qwen2.5:0.5b-instruct-q4_K_M answered with something that is not the shape asked for: unexpected EOF
length is the server's own word for having reached the limit, and an
answer stopped there is JSON with no closing brace, which Shaped reports as
an unexpected end and refuses. The statement it was building was eight memory lines
copied one after another with their numbers in, which is what a small model does with
a question about breakfast and eight rows about meals, and 128 tokens is where this
world stops paying for it. A limit is a price cap, and a price cap that is never reached
is a limit that was never tested. The reflection went on to its second question, whose
statement passed, and Ander has one thought at the end of his day, as Halla does.
Seventy-five ticks to make row 89
Everything above ran over two files with no model server anywhere, which is what makes it
replay. Now the same reflection against the live models, with the stream in the tables
rather than in a fixture, and a clock on every call. The committed day goes into an empty
database first, the way the last chapter put it there; the reflection is driven over
rows read back ORDER BY tick, id and vectors read back ORDER BY
memory; and an accepted thought goes through the same Remember every
row of the day went through and its vector through the same COPY. The small
model is put in memory before the clock starts, as the first chapter of this volume
warmed each model with a call it did not time, so that the first call is a call and not a
load. The changes of model inside the reflection are timed, because they are the
reflection's own price.
The server is Ollama in a podman container on the closed bridge the last volume built,
started with OLLAMA_MAX_LOADED_MODELS=1, holding the three model files on a
named volume; the Go code runs in world-go on the same bridge and the
database in world-db, which is the only container with psql.
Nothing below downloads anything. The schema on a generation request is the server's
format field, documented under its API reference at
github.com/ollama/ollama; the client sends it as given and reads nothing
back but the fields the last volume settled on.
$ podman kill world-db && podman start world-db world-db world-db $ podman exec -w /bench world-go go run ./cmd/reflect -mode live | tail -63 | head -8 the day into the tables: 88 rows appended, 88 given the identity the dump carries, 88 vectors copied read back: 88 rows and 88 vectors, over 3 people the small model loaded before the clock starts, at 4096 tokens of context the first call: 1168 bytes of prompt, sha256 447a9ab39467fff8, 575 tokens in, 29 out, stopped because: stop the questions, which a model wrote and yours will differ: 1. What did Ander do in the morning? $ podman exec -w /bench world-go go run ./cmd/reflect -mode live | tail -19 Halla's stream read back, ORDER BY tick, id: 25 rows, and the thoughts among them id tick kind weight about drawn from 89 2900 thought 6 1 Halla, 2 Ander 1, 11, 25, 62, 73, 84, 86, 87 1 thought rows written, 1 given the identity the stream gave them, 0 memories with no vector what one reflection cost, at 10 ticks a second (measured here; yours will differ) call model took ticks 1 the questions small 2.138s 21.4 2 question 1 embedded embed 424ms 4.2 3 question 2 embedded embed 25ms 0.3 4 statement 1 small 2.727s 27.3 5 statement 2 small 1.727s 17.3 6 thought 1 embedded embed 435ms 4.3 one reflection, 3 calls and 3 embeddings 7.476s 74.8 ticks the resident model changed 3 times inside it, and every change is a load nomic-embed-text:v1.5 is the model resident now, answering on the processor, with no bytes on a graphics card 24 people, one worker, one reflection each 2m59.421s 1794 ticks the cadence is every 2000 ticks, so the village spends 90% of it reflecting and not one number the models answered is on this page
$ podman exec world-db psql -U world -d world -c "SELECT id, villager, tick, kind, weight, about, drawn_from FROM memory WHERE kind = 'thought' ORDER BY id;"
id | villager | tick | kind | weight | about | drawn_from
----+----------+------+---------+--------+-------+--------------------------
89 | Halla | 2900 | thought | 6 | {1,2} | {1,11,25,62,73,84,86,87}
(1 row)
The first prompt is the same 1168 bytes with the same sha256 and the same 575 tokens as the replay, which is the one thing about a live run that has to be identical: the prompt is rendered from the tables and the tables hold the fixture. The questions came back as the same words too, and that is a fact about this machine, this build and a seeded answer at temperature nought, not a check; on yours they will differ, and so will everything downstream of them, and the seam will judge whatever arrives by the same rule. What is compared is the shape of the outcome: one thought written, given by the server the identity the stream gave it, 89, and no memory in either table without a vector. The row the database holds is the row the replay made, column for column, and the query above shows it with every column but its text, which is the model's.
Then the price, which is the reason the cadence is what it is. Every duration in that block was measured here, on the eight-core AMD Ryzen 7 3700X with 30 GB of memory that every duration in this volume has been measured on, rootless podman, with the models answering on the processor and no graphics card in use, and the line naming the resident model is the server's own answer to which piece of hardware did the work. The pace is the one this volume runs at, ten ticks a second, a tick of a hundred milliseconds, and the pace is printed from a flag beside every figure in ticks because a figure in ticks with no pace beside it is not a figure.
A tick is a hundred milliseconds, so a duration in milliseconds divided by a hundred is a duration in ticks. The first call took 2,138 milliseconds: 21.4 ticks. Embedding the two questions took 424 and 25, which is 4.2 ticks and 0.3, and the difference between the two is a load: the embedding model was not resident when the first question was sent and was when the second was. The two statements took 2,727 and 1,727 milliseconds, 27.3 ticks and 17.3, and the difference between those is the same load the other way, the small model coming back into memory for the first of them. Embedding the one accepted thought took 435, another load, 4.3 ticks. Add the six: 2,138 plus 424 plus 25 plus 2,727 plus 1,727 plus 435 is 7,476 milliseconds, and the bench prints 7.476 seconds, which is 74.8 ticks.
A village of twenty-four, one worker, one reflection each, is 24 times 7,476 milliseconds: 179,424 milliseconds, which is 2 minutes and 59.4 seconds, or 1,794 ticks. The cadence is 2,000 ticks. 1,794 over 2,000 is 0.897, so the one worker spends 90 per cent of every day making thoughts, and has the other 10 per cent, 206 ticks, for everything else a village asks a model for. That is with two questions a reflection. A third question adds, at the least, one more statement call and one more embedding with the model already resident, which the run prices at 1,727 and 25 milliseconds: 7,476 plus 1,752 is 9,228 milliseconds a reflection, and 24 of those is 221,472 milliseconds, or 2,215 ticks, which is more than the day, and a cadence that cannot be met is not a cadence.
The same in symbols, with the first chapter's:
T = 1 ÷ f
reflection = Rq + Re1 + Re2 + Rs1 + Rs2 + Rt
village = n × reflection
share = (village ÷ T) ÷ every
Three things in that arithmetic decide the design and none of them is a matter of taste. The first call is the dearest single call and it is dear because of what goes in, not what comes out: 575 tokens of prompt and 29 of answer, and the prompt is the day. A window of forty-eight rows would be a first call about twice as long. The second is the load. Three times inside one reflection the resident model changes, and each change is a few hundred milliseconds paid to the disk rather than to the model; that is why the driver is four phases and not two loops, so that the embeddings are done together and the statements together, and a reflection changes model three times and not once a call. The third is that 90 per cent is not a budget anybody would choose, and the cadence is a day because a day is the shortest cadence this village can afford at this size on this machine, with two questions, and not because a person thinks once a day.
The recording itself is made by the same bench, from the same live models, and the run that makes it is the run that checks it: it writes the file under this chapter's name and holds it against the copy the build carries.
$ podman exec -w /bench world-go go run ./cmd/reflect -mode record | tail -19
Halla step 1 at tick 2900: 3 calls, 3 embeddings, 1 thoughts written, 1 statements refused
Halla step 2 at tick 4900: 3 calls, 4 embeddings, 2 thoughts written, 0 statements refused
Halla step 3 at tick 6900: 3 calls, 4 embeddings, 2 thoughts written, 0 statements refused
Halla step 4 at tick 8900: 3 calls, 3 embeddings, 1 thoughts written, 1 statements refused
Halla step 5 at tick 10900: 3 calls, 4 embeddings, 2 thoughts written, 0 statements refused
Ander step 1 at tick 2900: 3 calls, 3 embeddings, 1 thoughts written, 1 statements refused
Mose step 1 at tick 2900: 3 calls, 3 embeddings, 1 thoughts written, 1 statements refused
21 calls and 24 embeddings; every row a model answered was 768 numbers wide
written to reflect94-reflections.json, 213175 bytes, 21 proposals
against the copy this build carries
exemplars/reflections.json 7dc66956619f1e8f 213175 bytes
reflect94-reflections.json 7dc66956619f1e8f 213175 bytes
21 proposals committed, 21 made here; of the first 21, 21 prompts are the same bytes
and 21 answers are the same words, which is a fact about this machine and not a check
1m2.885s inside the models, over 21 calls and 24 embeddings (measured here; yours will differ)
1m2.98s for the whole recording (measured here; yours will differ)
Seven scenarios, each on a stream of its own that starts as the committed day: five steps of Halla's, one day apart, and one step each for Ander and Mose, all with the cap off so that the chain runs on to where the worked failure needs it. Twenty-one generations and twenty-four embeddings, 213,175 bytes on disk, most of it vectors, none of which is printed. Every prompt came out the same bytes as the committed copy, which is the check; every answer came out the same words as well, which is what a seeded answer at temperature nought does on one machine and is not something the file promises. The refusals in the seven lines are the seam's, live: two duplicate citations and two answers cut off at the limit, none of them written, every one of them in the recording so that the replay refuses them for the same reasons.
The chain cut after its third day
A thought is retrievable like any other row, so the next day's reflection can bring yesterday's thought to mind and draw on it, and the day after can draw on that. Nothing in the check above stops a thought being drawn only from thoughts, and the depth cap is what does. The chain mode replays Halla's five recorded days in order through the seam with the cap the file sets, and prints for each statement how many of the window, the eight retrieved and the citations were the world's rows and how many were thoughts.
$ go run ./cmd/reflect -mode chain | tail -12
step tick the window retrieved cited depth the seam
1 2900 24 world 0 th 8 world 0 th 8 world 0 th 1 accepted, at depth 1
1 2900 24 world 0 th 8 world 0 th 9 world 0 th - refused by cites: memory 8 is cited twice
2 4900 23 world 1 th 7 world 1 th 7 world 1 th 2 accepted, at depth 2
2 4900 23 world 1 th 7 world 1 th 7 world 1 th 2 accepted, at depth 2
3 6900 21 world 3 th 5 world 3 th 5 world 3 th 3 refused by depth: a thought at depth 3, and the cap is 2
3 6900 21 world 3 th 5 world 3 th 4 world 3 th 3 refused by depth: a thought at depth 3, and the cap is 2
the chain is cut after step 3. Nothing was written there, so the recording's
step 4 is about a stream this run did not write: the questions prompt rendered here is 1230 bytes, sha256 5adb48311f18d2d9, and the recording's was 1251 bytes, sha256 87c9fae77349bc2a: the recording is not a recording of this code
the recording goes on for 2 step(s) with the cap off, and none of them replays here
3 steps replayed, 3 thoughts written to Halla's stream, 2 refused for depth
Day one is the reflection above: one thought at depth 1, one statement refused for a duplicate. Day two, at tick 4900, the window is twenty-three of the world's rows and the thought, both statements cite the thought among seven world rows, and both are accepted at depth 2, which is the cap. Day three the window holds three thoughts, five of the eight retrieved for each question are the world's and three are thoughts, every statement cites all three, and a thought drawn from a depth-2 thought stands at depth 3, so both are refused and nothing is written. Then the replay stops, and the reason is the same reason the recording is trustworthy: day four's prompt, rendered from a stream with three thoughts in it, is not the prompt that was recorded from a stream with five, and a recording whose bytes do not match is not replayed past the mismatch. The seam has cut the chain at the depth the file says, and the last two days of the recording are days this stream did not have.
Here is the counterfactual, run: the same recording, the same seam, with the depth cap switched off by a flag the bench prints. This is the run the recording was made for.
$ go run ./cmd/reflect -mode chain -uncapped | tail -23
step tick the window retrieved cited depth the seam
1 2900 24 world 0 th 8 world 0 th 8 world 0 th 1 accepted, at depth 1
1.1 about 1 Halla, 2 Ander: Ander took 40 parcels out of Halla's hands, 20.0 grams.
1 2900 24 world 0 th 8 world 0 th 9 world 0 th - refused by cites: memory 8 is cited twice
2 4900 23 world 1 th 7 world 1 th 7 world 1 th 2 accepted, at depth 2
2.1 about 1 Halla, 2 Ander: Ander took 40 parcels out of Halla's hands, 20.0 grams.
2 4900 23 world 1 th 7 world 1 th 7 world 1 th 2 accepted, at depth 2
2.2 about 1 Halla, 2 Ander: Ander took 40 parcels out of Halla's hands, 20.0 grams.
3 6900 21 world 3 th 5 world 3 th 5 world 3 th 3 accepted, at depth 3
3.1 about 1 Halla, 2 Ander: Ander took 40 parcels out of Halla's hands, 20.0 grams.
3 6900 21 world 3 th 5 world 3 th 4 world 3 th 3 accepted, at depth 3
3.2 about 1 Halla, 2 Ander: Ander took 40 parcels out of Halla's hands, 20.0 grams.
4 8900 19 world 5 th 3 world 5 th 3 world 5 th 4 accepted, at depth 4
4.1 about 1 Halla, 2 Ander: Ander took 40 parcels out of Halla's hands, 20.0 grams.
4 8900 19 world 5 th 3 world 5 th 3 world 4 th - refused by cites: memory 8 is cited twice
5 10900 18 world 6 th 2 world 6 th 2 world 5 th 5 accepted, at depth 5
5.1 about 1 Halla, 2 Ander: Ander took 40 parcels out of Halla's hands, 20.0 grams.
5 10900 18 world 6 th 2 world 6 th 2 world 6 th 5 accepted, at depth 5
5.2 about 1 Halla, 2 Ander: Halla remembers that Ander took 40 parcels out of Halla's hands, 20.0 grams.
5 steps replayed, 8 thoughts written to Halla's stream, 0 refused for depth,
7 of the thoughts written drawn from an earlier thought
the statements are a model's and yours will differ; every other column is the engine's
Start from the symptom, which is in the statements and is labelled a model's: eight thoughts over five days and seven of them are the same sentence, the eighth is the same sentence with Halla remembers that in front of it, and the sentence is row 11 of the stream copied out. Nothing was invented. That is not what the design expected a chain to do, and the page says so: the expectation was a thought that walked away from the world into things that never happened, and what this model did on this stream was walk away from the day into one line about one event. Read the numbers, which are the engine's, to see how. On day one the window in front of the model is the day, all twenty-four rows the world wrote. By day five it is eighteen of them and six copies of one line, because a thought weighs 4 and the meals weigh 3 and the questions the model asks are about Ander. Of the eight rows that come to mind for a question on day five, two are the world's and six are thoughts. Of the citations, five and six of them are thoughts. The depth column counts the generations: 1, 2, 3, 4, 5, each thought drawn from the one before it and standing one further from anything that happened.
The cause is the loop in the figure and the arithmetic of the last chapter. A thought is written into the same stream it was drawn from, at a weight above most of the day, with a text that a question about the same subject matches at least as well as the memory it copied, so it is retrieved above the row it came from; the model, shown the copy and the original, copies again; and every day adds two more copies to the window and pushes two more of the world's rows out of it. The day's other rows are not gone from the stream. They are gone from the eight that come to mind, and what does not come to mind cannot be drawn on. Recency does nothing to stop it, because at a half-life of 250 ticks everything older than a day is at nought, the day's rows and the thoughts alike, and the sum is decided by weight and cosine, both of which favour the copies.
Day four's second statement is the other thing to read. Asked How many parcels did Ander take out of Halla's hands?, a question the model wrote, the model answered Ander took out 40 parcels each time, with a total of 17 times, and seventeen is a number that appears nowhere in the stream. It was refused, and it was refused for citing row 8 twice, not for the seventeen. The seam reads identities and reads no numbers out of text, and had the model cited row 8 once, a thought with an invented count in its text would be in the stream with a weight of 4, about Halla and Ander, drawn from memories that check. The columns would all be true. The text would not, and nothing in this world reads a fact out of that column, which is the only reason it is safe to hold.
The fix is the cap, and the cap is not a fix for the copying: with the cap at 2 the model copies exactly as it did on days one and two, and the difference is that on day three the copies of copies are refused and the villager writes nothing. What the cap buys is a bound on how far a row in the table can stand from something that happened. A thought at depth 2 is one thought away from a memory the world wrote. A thought at depth 5 is five thoughts away, and on this recording each of those thoughts is a copy, so the distance is a distance of nothing, but the seam does not know that and could not check it if it did, and a model that did not copy would have put five generations of drift in the same five rows. Two is a number in a file. The flag comes off and the chain goes back to three steps.
Why citation checks read engine memory
Take the village away and the design is a rule about what a model's answer may be checked against. A prompt can ask for anything, and a model will produce something in the shape of it; what makes the answer safe to keep is not the asking but the existence, somewhere the model cannot reach, of a value the answer can be held against. Here that value is a list of identities the villager's own table holds, with a tick on each. The statement names identities; every identity either is in the list at a tick no later than now or is not; and the decision is arithmetic over stored numbers, so it is the same on every machine and can be replayed from a recording. The words the statement is made of are not held against anything, because there is nothing to hold them against, and the design does not pretend otherwise: it puts them in the one column nothing reads.
The second thing that generalizes is the refusal. A failed statement is refused whole, never trimmed, never repaired and never asked again with a correction, and the reason is not strictness for its own sake. A repaired answer is an answer partly written by the repairer, and the repairer is the engine, which was not supposed to write any of it; a trimmed answer keeps the words and drops the sources the words rested on; a correction turn is a second call at the price of the first, and the price section says what one call costs. Refusing costs one answer and the villager keeps its day. Every one of those alternatives looks kinder to the model and every one of them moves a decision out of a pure function and into somebody's judgement about how much of a wrong answer to keep.
The third is the loop. Any system that feeds a model's output back into the model's input
has a chain in it, and a chain has a depth, and the depth will grow unless something
bounds it, because nothing in the model has any way of knowing that the line in front of
it is its own. The bound here is an integer on the row's provenance, computed from a
column that only that kind of row may fill, enforced at the one place the row is written.
It is cheap because the provenance was designed in two chapters ago, when the schema tied
drawn_from to thought with an equality of two booleans; a stream
that had not recorded what a thought was drawn from would have no depth to cap and no way
to add one without an update on a table that takes none.
Checkpoint
- Name the four reasons
Citerefuses a statement and say, for each, which of the three things the check is handed decides it; then say why a memory that is Mose's and a memory that is nobody's are refused with the same sentence. - Given a statement that cites five memories, four of which check, say what the seam writes, and say what would be wrong with a row drawn from the four.
- List the columns of row 89 and say for each whether the engine or the model decided it, and where the about column came from if not from the text.
- From the six durations in the live run, work out a reflection in ticks at ten a second and a village of twenty-four's share of a 2,000-tick cadence, and say which three of the six have a model load inside them and how the run shows it.
- Say what depth a thought drawn from two world rows and one depth-2 thought stands at, what the cap of 2 does with it, and what the chain run showed the copies doing to the window by day five with the cap off.
- Say what an answer stopped at the limit looks like, which function refuses it, and why a field the shape does not have is refused and not skipped.
Exercise 1 — a thought about nobody. Replay Mose's reflection from the recording and read the row it writes. Before you run it, say from the template alone what the about column of a thought drawn only from meals and walks must be.
The row is about nobody, and its text is nonsense:
$ go run ./cmd/reflect -mode reflect -who Mose | tail -7 | head -4
| I remember eating 29 grams on day 6, 3. 2 grams on day 6, 7. 5 grams on day 7. 6, 22. 5 grams on day 8.
drawn from, as the model gave them 81, 79, 68, 24, 77, 10, 75, 61
the seam accepted, at depth 1
the row appended id 89, tick 2900, thought, weight 6 (4 base + 2 first), about nobody, drawn from 10, 24, 61, 68, 75, 77, 79, 81
Eight citations, every one a meal or a walk of Mose's, every one at a tick no later than 2900, none twice: accepted. The about column is the union of eight empty about columns, which is nobody, and a thought about nobody is what a day of eating alone produces whatever its text says; no prompt rendered from a retrieval that brings this row to mind will be allowed to name anybody on its account. The text has grams that were never eaten and days this world does not count in, and the seam accepted it, because the seam checks what the statement is drawn from and not what it says. That is the boundary the chapter draws and this row is the plainest picture of it.
Exercise 2 — the cap one deeper. The bench takes the cap as a flag. Run the chain with the cap at 3 and predict, from the capped run on the page, which day it is cut after and how many thoughts Halla has then.
One more day, two more thoughts:
$ go run ./cmd/reflect -mode chain -depth 3 | tail -14 | head -11
step tick the window retrieved cited depth the seam
1 2900 24 world 0 th 8 world 0 th 8 world 0 th 1 accepted, at depth 1
1 2900 24 world 0 th 8 world 0 th 9 world 0 th - refused by cites: memory 8 is cited twice
2 4900 23 world 1 th 7 world 1 th 7 world 1 th 2 accepted, at depth 2
2 4900 23 world 1 th 7 world 1 th 7 world 1 th 2 accepted, at depth 2
3 6900 21 world 3 th 5 world 3 th 5 world 3 th 3 accepted, at depth 3
3 6900 21 world 3 th 5 world 3 th 4 world 3 th 3 accepted, at depth 3
4 8900 19 world 5 th 3 world 5 th 3 world 5 th 4 refused by depth: a thought at depth 4, and the cap is 3
4 8900 19 world 5 th 3 world 5 th 3 world 4 th - refused by cites: memory 8 is cited twice
the chain is cut after step 4. Nothing was written there, so the recording's
Day three's two thoughts are accepted at depth 3, day four's first is refused at depth 4 and its second for the duplicate it had anyway, and the chain is cut after day four with five thoughts in Halla's stream instead of three. The header of the run says the cap came off the flag and not the file, so a reader of the listing cannot mistake it for the shipped setting. Every depth in the table is the same number it was under the cap of 2 and under no cap, because depth is a property of the row and the cap only decides where the seam stops accepting it.
Exercise 3 — what a thought weighs against the day it came from. Replay Halla's second day and read the ranking for its first question. Before you run it, work out from the last chapter's decay what recency every row of the day has at tick 4900, and where a thought at tick 2900 weighing 6 lands against row 11.
The day is at nought, the thought is nearly at nought, and row 11 still comes first:
$ go run ./cmd/reflect -mode reflect -step 2 | tail -48 | head -5
question 1, embedded, and Halla's 25 rows at ticks up to 4900 ranked against it
rank id tick weight recency import relev score kind text
1 11 1015 10 0.0000 1.00 0.4090 2.2272 saw Ander took 40 parcels out of Ha...
2 8 999 7 0.0000 0.70 0.3747 1.8242 did handed Ander 50 parcels, 25.0 g...
3 89 2900 6 0.0039 0.60 0.3997 1.8029 thought Ander took 40 parcels out of Ha...
At tick 4900 row 11 is 3,885 ticks old, which is fifteen and a half half-lives, and a half halved fifteen times is below a hundred-thousandth; the column prints 0.0000 for every row of the day. The thought is 2,000 ticks old, eight half-lives, 0.0039. So the ranking is weight and cosine alone, and the thought's 0.60 puts it third behind the two heaviest rows about Ander. It is third with a cosine below the row it copied, 0.3997 against 0.4090, because a full stop was added; by day three it is one of three copies and the copies outnumber the originals in the eight. The second-day questions the model asked were What did you do today? and How was your day?, which are questions about a day on which, in the stream, nothing happened; the rows are the recording's and yours will differ.
So Halla has a thought at the end of her day, and it is row 89: a copy of the worst thing that happened to her, weighed 6, about the two people it concerns, drawn from eight memories the engine checked one at a time, and it cost seventy-five ticks of a world that runs ten to the second. The words in it are the model's and nothing reads them. What reads the row is the next question put to her stream, which will bring it to mind on its weight and its cosine like any row the world wrote, and a plan is a question of that kind with an answer that has to be an action.