Commit d9ffa76
authored
feat(agent): let Stacky edit the family's lists, and answer what changed (#61)
* feat(memory): say out loud what an edit did to a family's list
A family's Road-Trip list grew from thirteen items to twenty-seven and never
had a single one ticked off. Nothing was malformed; things went quiet. Each
re-post of the list was re-extracted with fresh wording ("Alternative Dachbox"
came back as suchen, recherchieren, pruefen, besorgen), and because dedup
matches on exact text, every variant landed as a new item.
`stack.list_doc` reads a list page and reports what one edit actually did:
ticked off, reopened, added, moved between headings, reworded, and -- the only
category that means something was destroyed -- removed, always named in full.
A count is not checkable by a family member; "8 items became 7" tells nobody
which one went.
Pure: no git, no Matrix, no I/O. It is what the write path will call to turn an
opaque rewrite into a reviewable one, what a CLI verb would call, and what the
curator needs so the classifier stops replacing the family's words with its own.
Also records the findings and plan in docs/design/brain/write-layer.md so the
reasoning behind all of this outlives the session that produced it.
* feat(memory): stop a re-posted list from growing every time it is posted
A family kept one list for a camping trip and re-posted the whole thing
whenever something changed, because that was the only way to say "this is the
current state". Thirteen items became twenty-seven, and not one was ever
ticked off.
Nothing was broken in the merge. The classifier simply never saw the list it
was adding to. Reading each post blind, it re-worded the items every pass --
"Alternative Dachbox" came back as suchen, then recherchieren, then pruefen,
then besorgen -- and because the merge matches on exact text, every variant
landed as a new entry.
So show it the list. A note captured into a topic room now carries that
topic's current todos into the classify prompt, framed as already recorded,
with the point spelled out: an item is not new because it is phrased
differently, and marking something finished is not this field's job.
Verified on the rig against the real list: posting it, posting it again, and
posting it a third time with "-> CHECK" markers all leave the list at fourteen
items. Before this, the third one added "Fenstertasche pruefen" -- the model
understood the marker and had nowhere to put it.
* feat(memory): let a whole page be rewritten, and report what that changed
Reading the vault is fs-shaped: read_file on a path, and it works because
every model is trained on it. Writing had no counterpart, so it grew domain
verbs instead, and a model that can describe the right list perfectly still
cannot perform twenty string-matched calls in a row to produce it.
`stack memory write <path> --by <person>` is the counterpart. One page in, one
page out, attributed. What comes back is not "ok": for a list page it names
what the edit did, and always names in full anything it removed.
$ stack memory write family/camping/todos.md --by marge
Wrote family/camping/todos.md (by marge)
ticked off 2: Heringe mitbringen, Kochloeffel mitbringen
$ stack memory write family/camping/todos.md --by bart
Wrote family/camping/todos.md (by bart)
REMOVED 1: Campingstuehle mitbringen
That report is also the reason a primitive write is safe to hand to a model:
a rewrite that quietly loses six items stops being quiet. Content arrives via
the agent's own data directory rather than argv, because a markdown document
does not survive shlex.
* feat(agent): let Stacky change a list by rewriting the page
Stacky could read the family's vault and change nothing in it. Changing a list
meant one CLI call per item, matched by substring. Asked to tidy one, it
produced the correct final document in chat, grouped and split exactly as
asked, then failed to perform the twenty calls that would have made it real,
and said it had.
So `write_file` on a vault page now routes to `stack memory write`. Stacky
reads the page, rewrites it whole, and reads back what actually changed
("ticked off 2: ...", or "REMOVED 1: ..."). Ticking off is `- [x]`, splitting a
list in two is a heading. Ordinary markdown, which is what these models are
good at.
`edit_file` on a vault page is refused with instructions rather than
translated: a family list is fifteen lines, whole-document reasoning is the
point, and patching a read-only mount would be the same write with extra ways
to be wrong.
SKILL.md changes with it, or it would keep teaching the verbs this replaces.
It now carries the rules that matter: write the page back in full, keep the
family's words, tick off rather than delete, relay what the write reports, and
never claim a change without having read that report.
* fix(agent): make Stacky's edits to a list actually reach it
Rewriting a page died in the tool loop instead of saving. Every edit the
agent made to a family list was lost, and it had no way to tell.
Editing a page through apply_patch now points at the way that works
rather than failing against a read-only mount.
* fix(agent): stop Stacky ticking off items nobody asked about
Asked to reorganise a list, it would strike two unrelated items and
describe a change it never made. It now edits the page instead, and says
so when it cannot.
* feat(agent): let Stacky tick one item off without rewriting the list
Changing a list meant replacing the whole page, so an edit made from a
copy that was a few seconds old quietly undid whatever someone else had
just added. Narrow changes now touch only the lines they name, and if
the list moved on in the meantime Stacky is told which line no longer
fits instead of overwriting the change.
* feat(memory): say in the history what each change actually did
Every edit was recorded as "updated todos.md", whoever made it and
whatever it changed, so scrolling the history answered nothing. A change
now names itself: "marge ticked off 1: Wetter checken in camping". Pages
that are not lists say how much text moved, and an edit that lost items
still names every one of them.
* feat(memory): ask what changed in the family's memory, and when
The vault has always recorded every version and who wrote it, and nothing
read it back. Now "what's new this week", "who changed Homer's page" and
"when did this land on the list" are one question each, for the family
and for Stacky.
* feat(agent): let Stacky answer "what has Homer been up to lately"
Asked that, Stacky searched four times and guessed from whatever his page
says now. Questions about change have their own answer: what was added,
by whom, when. Housekeeping by the bots stays out of it, so the answer is
what the family did.1 parent e9f7f7e commit d9ffa76
23 files changed
Lines changed: 2946 additions & 34 deletions
File tree
- docs/design/brain
- lib/stack
- stacklets
- agent
- runtime
- workspace/skills/family-memory
- core
- docs/bot
- memory
- cli
- tests
- framework
- stacklets
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
327 | 327 | | |
328 | 328 | | |
329 | 329 | | |
330 | | - | |
| 330 | + | |
331 | 331 | | |
332 | 332 | | |
333 | 333 | | |
| |||
342 | 342 | | |
343 | 343 | | |
344 | 344 | | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
345 | 353 | | |
346 | 354 | | |
347 | 355 | | |
348 | 356 | | |
349 | 357 | | |
350 | 358 | | |
351 | 359 | | |
| 360 | + | |
352 | 361 | | |
353 | 362 | | |
354 | | - | |
| 363 | + | |
355 | 364 | | |
356 | 365 | | |
357 | 366 | | |
| |||
0 commit comments