diff --git a/docs/2026-06-02-chateau-pilot-review.md b/docs/2026-06-02-chateau-pilot-review.md new file mode 100644 index 0000000..670e2a0 --- /dev/null +++ b/docs/2026-06-02-chateau-pilot-review.md @@ -0,0 +1,1161 @@ +--- +title: "Château du Faune — Tracker / Journal pilot review" +subtitle: "Findings from the first week of !journal, !goals, !tasks usage across four Signal rooms" +date: 2026-06-02 +author: aiolabs / Château du Faune +--- + +# Executive summary + +For the past two weeks, members of the Château du Faune have been +"going through the motions" of using `!journal`, `!tasks`, `!goals`, +`!reminder`, `!buy`, `!sidequest`, and related commands across four +Signal rooms — Operations, Animals, Gardens & Food, Events, and the +general castle room. Nothing was hooked up yet: the chats are mirrors +of what the maubot stack on the new Matrix homeserver will eventually +capture. The point was to *generate signal* — to find out which +verbs people actually reach for, how they phrase entries, where the +syntax gets in the way, and which features the protocol spec is +quietly missing. + +This document reads that signal. It is written for both the chateau +members (who will see their words quoted back and choices made about +how the bot will treat their phrasing) and for the +[aiolabs/maubot-plugins][repo] developers (who will turn the +recommendations into code and spec changes). The +[community-organizer spec][spec] is the contract; the +[`tracker/`][tracker] and [`journal/`][journal] plugins are the +reference implementation; the data we collected over the past two +weeks is the field test. + +**Top-line findings:** + +1. **The capture habit is sticking.** The bot doesn't exist yet, and + people are still using the syntax — sometimes correcting each + other on it. The most surprising data point is that Coco told + Helena *"`!journal` is the required format"* unprompted. Social + enforcement of a syntax we haven't actually shipped is the best + possible adoption signal. + +2. **The vocabulary the community reaches for is broader than the + spec.** The current spec lists `!task`, `!sidequest`, `!journal`, + `!remind`, `!add`, `!done`, `!list`, `!setup`. The chats show + people typing `!buy`, `!need`, `!goals`, `!stewards`, `!tasks`, + `!task` (singular), `!reminders` (plural), `!idea`, `!plans`, + `!priority` (inline), `!side quests`, and a few more. Some of + these are per-room shortcuts the spec already accommodates; + others want a small protocol-level addition. + +3. **The 5-level priority scheme came directly from this pilot.** + Coco proposed "1 urgent / 2 crucial / 3 important / 4 future / 5 + frequent/ongoing" in the operations chat on May 24. It is now + §3.3.1 of the spec, exactly as proposed. This loop — observe → + spec → implement → re-observe — is the one we want to keep + running. + +4. **The bot will need to be more grammar-tolerant than the spec + currently implies.** Multiple verbs per message, edited messages + that add content, `!`-words inside bullet points used as + priority markers, pinning as a "this is the current list" + signal, no-prefix journals — these are all behaviors the bot + doesn't catch today but should. + +5. **The community is already designing the bot's voice.** Alfred + got named on May 24 ("we trusted him with resource management, + safety, tool use, etc."). Photos got captioned, journals got + pinned, Cocopis explained what `!tags` are *to other members*. + The chateau is treating the bot as a collaborator before it + exists. We should lean into that — bot personality is now a + design surface. + +The rest of this report: + +- **§1** What we observed, by room and by pattern +- **§2** What the existing spec + plugins handle well +- **§3** Spec changes proposed +- **§4** Plugin changes proposed +- **§5** New ideas the data surfaced +- **§6** How this fits the larger omnixy / aiolabs ecosystem +- **§7** A phased plan +- **§8** Open questions for the chateau + +[repo]: https://git.atitlan.io/aiolabs/maubot-plugins +[spec]: ../docs/community-organizer-spec.md +[tracker]: ../tracker/ +[journal]: ../journal/ + +--- + +# 1 — What we observed + +## 1.1 Verbs in actual use + +A frequency-sorted list of every `!verb` we saw across the four rooms +(case-folded), grouped by what the spec or current shortcut config +covers: + +| Verb seen | Already in spec | Status | +|---|---|---| +| `!journal` | Yes (universal) | Heavy use across all four rooms | +| `!reminder` / `!reminders` | Spec verb is `!remind` | Mismatch — see §3.1 | +| `!tasks` / `!task` | Spec verb is `!task` | `!tasks` plural also appears | +| `!goals` | No | New — see §3.2 | +| `!stewards` | No (per-room shortcut candidate) | Used as a list/backlog, not a one-off task | +| `!buy` | Per-room shortcut, fits cleanly | Should ship pre-configured for cdf rooms | +| `!need` | No | Distinct from `!buy` in usage — see §3.2 | +| `!sidequest` / `!side quests` | Yes | Used; "side quests" with space appeared once | +| `!idea` / `!plans` | No | Speculative / design-phase items | +| `!priority` (inline) | No (priority is a tag) | Used as a bullet-level marker — see §1.4 | +| `!move` | Mentioned by Ulo as a future verb | Not in spec | + +And a usage example per verb so the design instinct is grounded in +actual phrasing rather than imagined phrasing: + +**`!journal` — past-tense daily log, multi-line is the dominant +form:** + +> `!journal` +> `- opened/closed, watered and fed hens, babies & pacas` +> `- put insulators on the rebars so they're ready to go for fence` +> `- mentally plotted potential hidden-in-plain-sight stable locations. I want opinions next` +> `- should scrape clean duck babes straw tmrw` +> `- should clean and dry very well sapphis wound again` + +Note the mix of past-tense ("opened/closed") and forward-looking +("should scrape clean tmrw") bullets in the same `!journal`. People +do not bucket their day cleanly. §3.4 addresses this. + +**`!goals` — a *list of current intentions*, often nested with +sub-categories:** + +> `!goals & notes next week for garden/kitchen crew` +> +> `GARDEN` +> `- documentation & journaling process` +> `- prepping & starting the Lady Godiva pumpkin patch.` +> `…` +> +> `KITCHEN` +> `- forage, harvest & food processing resource improvements (ex. cherries into vinegars…)` +> `- filling the shop up (& soon looking into certifying our kitchen/lab)` +> `…` + +`!goals` lists evolve over days — Coco's gardens `!goals` on May 27 +and June 2 are *the same list*, refined. This is the strongest +argument we found for **replaceable-event semantics** (§3.3). + +**`!reminder` — a future-pointed list, often with embedded +@-mentions and `!priority` markers inside bullets:** + +> `!goals this week/soon` +> `- attempt to secure alpaca deposit & arrival of the boys` +> `- !priority confirm if teeth & nails will also be done by the shearer 'sometime in july'` +> `- !priority diagnosis of sapphis skin condition be made by a vet in person.` +> `!reminder following shearing, immediately start sapphis skin infection protocol` +> `- fencing add in doors & handles. Start soon on setting up 2nd stable paddock` + +Look at that message carefully: it is *one* `!goals` entry, with +`!priority` and `!reminder` appearing **inside bullets as inline +markers**, not as new commands. This is the single most important +parser decision we have to make — see §4.1. + +**`!buy` — shopping list, sometimes added-to over days via edit:** + +> `!buy` +> `- roll out flypaper for stable(s)` +> `- door handles for electric (or from laura)` +> `- la fourche order, sesame & produce will be thursday, 28th` +> `- buy more pans & x2 induction adapters` + +**`!need` — semantically distinct from `!buy`. Means "stock is +low" rather than "purchase this":** + +> `!need puppy & cat food, hay & straw soon` + +Coco used both in the same week. The distinction matters because +`!need` items become `!buy` items only after someone agrees to act +on them, where `!buy` is already a decision. + +## 1.2 Cadence patterns — the recurring chore problem + +The operations chat opened with a chores list dominated by +*recurring* items with fuzzy intervals: + +> `pacas:` +> `- every ~3 days scoop manure` +> `- every ~3 days fluff bedding` +> `- every 2 wks to 1 month - change out whole bedding` +> `- daily fresh water` +> `- daily light green/yellow hay` +> `- training - max 1x/day - approach with treats in a bucket…` +> +> `hens & ducks:` +> `- am let out, pm close in` +> `- every ~3 days fresh water & food` +> `- every 1 week change out straw` +> `- spend time with the puppies every day` + +Coco flagged the problem herself: *"weird thing is tasks that are +every few days or variable timeframes?"* + +The spec partially anticipates this with `priority:5` ("frequent / +ongoing"), but `priority:5` is a marker, not a cadence. "Every 3 +days" and "daily" are different cadences and the bot should know. +RFC 5545 `RRULE` is the canonical answer; NIP-52 doesn't yet +codify recurrence (§11 of the spec lists this as open). A +lightweight stopgap is proposed in §3.5. + +## 1.3 Multi-verb messages + +Helena posted in the Gardens & Food room: + +> `!journal` +> `• bigger Team meet and on board of Marta what we did the last weeks…` +> `• weeding` +> `• getting fresh alpaca manure and distributing` +> `• collecting seeds for next year planting` +> +> `!task` +> `- research thing for rain tank` +> `- update online garden map` +> `- check on sprouting pumpkin seeds and plant when ready` + +This is one Matrix message but **two distinct entries** — a +`!journal` and a `!task` list. The current tracker dispatch regex +matches the first verb at start-of-message and stops. The plugin +will silently drop Helena's `!task` block. + +This is a real bug, not a theoretical one — Helena was the first +person who naturally typed in this shape, and the bot would have +lost the task list. §4.2 fixes it. + +Skye independently did the same thing on the same day: + +> `!goals !tasks` +> `- [ ] organise conversation about image ai making with pat and coco` +> `- [ ] Look into creation of the funding via our socials…` +> `- [ ] For flyers/posters the !goals is to make one for pats new events app for ariege…` + +`!goals !tasks` on one line is yet another shape: two verbs sharing +one body. Likely "this entry belongs to both lists" — but the spec +needs to say so explicitly. + +## 1.4 Inline `!priority` and `!reminder` markers + +Returning to Coco's May 27 `!goals`: + +> `- !priority confirm if teeth & nails will also be done by the shearer 'sometime in july'` +> `- !priority diagnosis of sapphis skin condition be made by a vet in person.` +> `!reminder following shearing, immediately start sapphis skin infection protocol` + +`!priority` and `!reminder` are appearing **inside body content** as +bullet-level annotations, not as commands. A naïve parser that +matches `!\w+` anywhere in a message would interpret these as new +commands and produce four entries from one message. + +The fix is the rule the journal plugin already enforces: a `!verb` +counts as a verb **only** if it is at the very start of the message +(or, with §4.2 in place, at the very start of a top-level block). +Everything else is body content with inline tags. + +But "inline tags" is itself a feature worth keeping: a bullet +starting with `!priority` is a natural way to mark *that bullet* +as urgent within a larger list. The bot can: + +1. Treat the inline `!priority` as a *tag on the bullet body* + (preserves the user's intent). +2. Leave the parent entry's priority alone (the user didn't priority + the *list*; they priority'd one item in it). + +A renderer can then surface that bullet as priority-1 within the +parent entry. This is a richer model than the spec currently +sketches, but it matches what users are actually doing. §3.3 +proposes the wire format. + +## 1.5 Pinning as state + +Across all four rooms, the dominant `!goals` / `!tasks` / `!stewards` +messages are **pinned to the room**. When the list evolves, the new +version is pinned and the old one is unpinned. Matrix has native +support for this (`m.room.pinned_events`). + +We propose treating "the latest pinned message matching `!goals` +from a given room" as the **current state of that group's working +list**. This gives every room a queryable "what's on our plate +right now" view without requiring users to remember an item id. + +§3.3 + §4.5 develop this. + +## 1.6 Implicit subjects — animals, people, places + +The animals room is full of entries like: + +> `- thinking of long-term solutions for pups barking and neighbors.` +> `- 45 min in the alpaca paddock with both pups, no electricity.` +> `- carefully choose feeding station(s) so they are isolated and don't compete with any other animals` +> +> *(and elsewhere)* +> +> `lets clean sapphis wound again soon?` +> `also just realized the shearer will come in july` + +The community has named entities — *Sapphi*, *Loki*, *Leia*, +*Tango*, *Morrigan*, *the alpacas*, *the pups*, *the hens*, *the +south paddock*, *the orchard*, *the coop* — that recur across many +entries. A useful query is "show me everything mentioning Sapphi +this month" or "what tasks involve the south paddock?". + +NIP-52 already supports `["p", ""]` for *people*. Animals +and places need a convention — see §3.6. + +## 1.7 Inline @-mentions = informal assignment + +> `- location & plans for stable, cont. @coco` +> `- closing the main property gate? guardian dogs sign @coco` +> `- pruning & landscaping @Thomasss**]` +> `- following a budget per person for food orders, picking up produce @Skye 🌟` + +People mention each other inline to mean "this bullet is yours". The +bot can extract these into structured `["p", …]` tags and a +renderer can surface a per-person "your bullets" view. §4.6. + +## 1.8 Cross-group routing ambiguity + +Skye said in the Events room: + +> *"organise a mini meeting to talk about ai image making etc with +> pat/coco …. I'm not sure which chat to put these social things in +> hehe"* + +Some items belong to multiple groups. The current 1-room ↔ +1-community mapping makes this expensive — you'd have to type the +same entry twice. Coco's answer (*"i think events would be the +fitting place"*) is the human workaround; the bot should help. +§5.2 proposes optional multi-room fan-out. + +## 1.9 No-prefix journal-shaped content + +Lots of messages look like journals without using any command: + +> `today plan for me:` +> `- working in other groups on animals and operations` + +> `Today's plan for me:` +> `- making bulk buckwheat zucchini burgers some for today and rest in freezer` + +> `journal` +> `started but did not complete: bathroom clean, mapping stables, fencing the internal 150m paddock` + +The chateau is explicitly standardizing on `!journal` (Coco +corrected Helena on this in real time). So we **should not** make +the bot fuzzy-match — they want explicit syntax. But the data +suggests an opt-in *"capture every non-command message in this +specific room"* mode could be valuable for very-low-friction rooms +(e.g. a private journal room per member). Default: off. §5.1. + +## 1.10 Capitalization, punctuation, language + +The same intent appears as `!journal`, `Journal:`, `journal`, `!journal:`, +`Today plan for me`, `today plan:`, sometimes mixed with French. + +The bot's regex needs to be: +- case-insensitive on the verb (`!Journal` == `!journal`) +- tolerant of a trailing colon (`!journal:` is fine) +- bilingual on body content (already true — body is opaque text) + +The current `_CMD_RE = re.compile(r"^!(\w+)(?:[ \t\r\n]+(.*))?$", re.DOTALL)` +in `tracker.py:52` *does* match `!Journal` because `\w` is +case-insensitive in unicode mode, and the dispatch already calls +`.lower()`. But it does **not** match `!journal:` because the colon +follows the verb with no whitespace. One-line fix in §4.3. + +## 1.11 Edits matter + +Two distinct edit patterns appeared: + +**A. Edit-to-append.** Coco edited a `!journal` post-hoc and +commented: *"just added that i also fed puppies 2x today"*. The +intent is "this is the same entry, with more content". + +**B. Edit-to-correct.** Coco scheduled a vet appointment for "June +5 @ 14h", then edited the same entry to "May 30 @ 15h" with the +note *"updated to May 30 @ 15h because they need the vaccination +within the month"*. + +The tracker's current known-quirks note (`tracker/README.md:133`) +says: + +> *"Edited messages don't re-trigger the bot. Matrix sends edits +> as a separate `m.replace` event that maubot doesn't pass to +> handlers. Type a fresh message instead of editing."* + +This is solvable — `m.replace` events *are* visible to maubot; the +handler just needs to be wired. §4.4. + +--- + +# 2 — What the existing spec and plugins handle well + +Before proposing changes, let's mark what's already right. The pilot +*validates* a lot of the design. + +## 2.1 The split between universal verbs and per-room shortcuts + +The spec says: every implementation must recognize the universal +verbs (`!task`, `!journal`, etc.); per-room shortcuts (`!buy`, +`!harvest`, …) expand to them. The data agrees: every room developed +its own dialect (animals uses `!journal` heavily, operations uses +`!buy` and `!stewards`, events uses `!goals`). A single plugin +serves all of them. + +We just need to **pre-configure** the cdf rooms with sensible +shortcuts so members don't have to `!setup add buy task #buy` on day +one. §4.7. + +## 2.2 The passive regex / multi-line story + +The journal plugin's switch to `@command.passive` with a regex was +right. Every multi-line entry we observed (and many one-line ones) +would have been silently dropped under `@command.new`. The CLAUDE.md +footgun is real and the plugin avoided it. + +## 2.3 The 5-level priority scheme + +`priority:1..5` with the labels urgent / crucial / important / future +/ frequent-ongoing came out of the pilot. The renderer plan is to +surface labels rather than numbers; the wire tag stays `priority:N` +for sort stability. The data didn't push back on this — Coco's +proposal landed exactly as written. + +## 2.4 Sidequests as a separate kind + +`!sidequest` (with a `🎯` emoji ack) gives a distinct surface for +"would be cool to do" items. Coco's *"`!side quests` lists which are +different from the rest of the tasks"* is exactly the use case. The +spec already excludes sidequests from default `!list` output, which +is the right default. + +## 2.5 The Matrix-only Phase 1 posture + +The tracker is `matrix-only` for v1 — no Nostr publishing yet. This +turns out to be the right call: the four-week dataset shows the +community is still finding its vocabulary. Publishing to a public +relay before the vocabulary stabilizes would make schema migrations +visible to anyone subscribed. Stay matrix-only until the verb set is +stable, then bridge. + +## 2.6 The journal plugin's append-only stance + +The spec's "`!done` on a journal entry is undefined behavior; +implementations SHOULD reject it" matches the data: nobody tried to +close a journal. They edit, they re-journal, they pin — they don't +close. Good call. + +--- + +# 3 — Spec changes proposed + +These are protocol-level additions to `docs/community-organizer-spec.md`. +Each is small. None breaks existing conformance — they extend, they +don't refactor. + +## 3.1 Plural aliases for universal verbs + +`!reminders` and `!tasks` should be accepted as equivalents of +`!remind` and `!task`. `!sidequests` likewise. This is a one-line +parser change; it costs nothing and removes a footgun (users will +naturally pluralize when a list is multi-item). + +> *Spec change:* in §3.1, add a row: +> *"Implementations MUST accept the bare plural form of each verb +> (`!tasks`, `!sidequests`, `!reminders`) as equivalent."* + +## 3.2 New universal verbs: `!goals` and `!need` + +Both verbs got real, repeated, distinct-from-existing usage. Both +deserve protocol-level status, not per-room shortcut status, because: + +- They had identical meaning across all four rooms. A bakery co-op + reading this spec should be able to use them without re-configuring. +- The shortcut mechanism is for *vocabulary the community invented* + (e.g. `!harvest`, `!brewery`). `!goals` and `!need` are not + vocabulary inventions — they're a category the spec missed. + +| New verb | Purpose | Underlying kind | +|---|---|---| +| `!goals ` | A list of current intentions for the author/group/period. Replaceable per `(author, room, period?)`. | 31922 with `["t", "goals"]` | +| `!need ` | A supply that is running low. Distinct from `!buy` (which is "I will purchase this"). Default lifecycle: open → `!buy` (converts to task) or `!done`. | 31922 with `["t", "need"]` | + +`!goals` deserves §3.3 (replaceable semantics) since the goals lists +visibly evolved over days in the pilot. + +## 3.3 Replaceable-event semantics for `!goals` (and stewards lists) + +NIP-52 kind 31922 is a *parameterized replaceable event* keyed by +`d`-tag. Today the tracker assigns a unique `d` per entry. For +`!goals` (and the per-room `!stewards` list pattern), we want the +opposite: the latest `!goals` from a given `(author, room)` pair +should *replace* the previous one. + +> *Spec change:* in §3.3, add: +> *"Implementations MAY designate certain verbs as replaceable per +> `(author, room, period?)`. When a verb is replaceable, the bot +> reuses the same `d`-tag for successive captures, so the relay +> retains only the latest version. Recommended replaceable verbs: +> `goals`, `stewards`."* + +The `period` qualifier lets `!goals this week` and `!goals next week` +coexist. Suggested encoding: a `period:` tag (e.g. `period:2026-w22`). +For v1 we keep it simple — no period qualifier, latest wins. + +The pinning UX hooks straight in: when a `!goals` message is the +latest replaceable entry from its author, the bot can ask Matrix to +pin it (and unpin the previous one). This makes the room's pinned +message a live mirror of the relay state. + +## 3.4 Forward + retrospective in a single `!journal` + +The data shows people blend "did X" and "will do Y tomorrow" in the +same `!journal`. The spec currently says journals are past-tense and +append-only. Rather than fight the user, we accept the blend. + +> *Spec change:* in §3.1, clarify: +> *"`!journal` is append-only; implementations MUST NOT close +> journals. Bodies MAY contain forward-looking statements; the +> classifier MAY surface these as candidate tasks at review time but +> MUST NOT auto-create derived task entries."* + +The "candidate task" idea is a v2 nicety: a weekly digest that pulls +"should…" bullets out of journals and offers them as `!task` +candidates. Not in v1. + +## 3.5 Recurrence tags (lightweight, pre-RRULE) + +For the chore-list problem (`every ~3 days scoop manure`, `daily +fresh water`), we propose a `frequency:` tag namespace that lives +inside the existing `["t", …]` mechanism. Renderers parse it. + +Suggested values: + +| Tag | Meaning | +|---|---| +| `frequency:daily` | Once per day | +| `frequency:weekly` | Once per week | +| `frequency:3d` | Every 3 days | +| `frequency:2w` | Every 2 weeks | +| `frequency:as-needed` | No fixed cadence; on demand | + +This is a stopgap. When NIP-52 or a sibling NIP defines proper +recurrence, we migrate. For now, the eink renderer can group +`frequency:daily` items into a "today" panel and `frequency:weekly` +into a "this week" panel without parsing RRULE. + +> *Spec change:* in §3.3, add: +> *"`frequency:` is reserved for recurrence hints. Recommended +> values: `daily`, `weekly`, `Nd` (every N days), `Nw` (every N weeks), +> `monthly`, `as-needed`. Implementations MAY define additional +> values."* + +## 3.6 Subject tags — animals and places, not just people + +NIP-52 has `["p", ]` for people but no convention for +non-human subjects. We propose a namespaced `t`-tag: + +| Tag | Meaning | +|---|---| +| `subject:animal/sapphi` | Animal named Sapphi | +| `subject:animal/loki` | Animal named Loki | +| `subject:place/south-paddock` | A named place on the property | +| `subject:object/electric-fence` | A named asset/object | + +A community can define its own subject vocabulary via `!setup`: + +``` +!setup subjects animal/sapphi animal/loki animal/leia +!setup subjects place/south-paddock place/orchard +``` + +When typing, members can reference subjects with a `@`-prefix as +shorthand: + +``` +!journal cleaned @sapphi's wound, fed @hens at noon +``` + +The bot expands `@sapphi` to `subject:animal/sapphi` tags and +preserves the original text in the body. Renderers can then offer +"recent entries about Sapphi" without the bot needing to do NLP. + +For *people* the existing `["p", ""]` already works; +inline `@person:name` (or just `@`-mentions resolved through the +Matrix room's member list) feeds straight into that. + +## 3.7 Inline `!verb` markers within a body are tags, not commands + +This is half a spec change, half a clarification: + +> *Spec change:* in §3.3, add: +> *"A `!verb` occurring at the start of a message (or, with multi-verb +> support, at the start of a top-level block) is a command. A `!verb` +> appearing inside body content is body content and MAY be extracted +> by the bot as an `inline:` tag on the parent entry. This +> preserves user intent without spawning ghost entries."* + +So Coco's `- !priority diagnosis of sapphis skin condition…` bullet +gets recorded as part of the parent `!goals` entry, *and* the parent +gets an `inline:priority` tag. A renderer can surface that bullet +with priority styling. + +## 3.8 Multi-line entries with sub-headers + +A small content-format convention so renderers can do something +sensible with Coco's `GARDEN` / `KITCHEN` sub-headers: + +> *Spec change:* in §10, add: +> *"Bodies MAY use Markdown-style headers (`#`, `##`) as in-body +> section markers. Renderers MAY surface them as visual groupings. +> The body remains a single content blob; sub-headers do not split +> the entry."* + +--- + +# 4 — Plugin changes proposed + +These are concrete edits to `tracker/`, `journal/`, and a small +amount of `wiki/` if we want cross-plugin discovery. Each maps to a +specific file path so the work is sized. + +## 4.1 Parse inline `!verb` markers as tags, not commands + +`tracker/tracker.py:184` — the `dispatch` handler matches a verb at +start-of-message. Today, anything else is unmatched and the message +is dropped (correct). We extend: after dispatching the top-level +verb, scan the body for inline `!verb` occurrences and add them as +`inline:` tags on the recorded entry. Implementation is a +single pass over the body before recording. + +This makes Coco's `- !priority …` bullets actually do something +useful at the entry level without inventing ghost entries. + +## 4.2 Split multi-verb messages + +`tracker/tracker.py:52` — the `_CMD_RE` regex anchors at +start-of-string and captures everything to end-of-string. We change +to a *two-pass* parse: + +1. Split the message at every line that starts with `!verb` (and + only such lines — bullets like `- !priority …` are NOT split + points). +2. Each split block becomes its own captured entry. + +Helena's `!journal … !task …` message becomes two entries. Skye's +`!goals !tasks` on a single line stays as one entry but gets *both* +tags applied. This requires a second regex for the multi-verb-one-line +case. + +The order matters: split first on newline-prefixed `!verb`s, then on +the single-line multi-verb case. + +## 4.3 Tolerate trailing colon and case on verb + +`tracker/tracker.py:52` — change the regex to allow an optional +trailing colon and explicit case-insensitive flag: + +```python +_CMD_RE = re.compile(r"^!(\w+):?(?:[ \t\r\n]+(.*))?$", re.DOTALL | re.IGNORECASE) +``` + +(The `.lower()` already happens in `dispatch`; the regex flag is for +unicode `\w` consistency. Trailing colon is the visible change.) + +Apply the same change to `journal/journal.py:34` for `_JOURNAL_RE`. + +## 4.4 Handle Matrix edits + +`tracker/tracker.py` — register a handler for `m.room.message` events +with `rel_type == "m.replace"`. The handler: + +1. Looks up the original event id in the items table (we need to + start recording it — schema change below). +2. If the original is a captured entry, treat the edit as a body + update on that entry. Bump a `last_edited_ts`. +3. Reply with a small ack ("✏️ Updated entry `42`"). + +Schema change: add `mx_event_id TEXT` column to `items`, indexed. +This is a real change to `upgrade_table` — add as `upgrade_v2`. + +`tracker/README.md:133` — strike the "Edited messages don't +re-trigger the bot" caveat. + +This is the single change that unlocks the most observed user +behavior. People *do* edit, and treating edits as no-ops loses +substantial information. + +## 4.5 Detect pin events; mark replaceable verbs as pinned + +`tracker/tracker.py` — subscribe to `m.room.pinned_events`. When a +captured entry's source Matrix message becomes pinned, mark the +entry as `pinned=true`. When a `!goals` entry is recorded, the bot +*itself* asks Matrix to pin the message (and unpin the previous +`!goals` from the same author). + +This makes the pinned-message UX a transparent mirror of the relay +state, without users needing to manually pin/unpin. + +(This requires the bot to have pin-event permission in the room. The +admin can grant it via Element. Document in the README.) + +## 4.6 Extract inline @-mentions as `p`-tags + +`tracker/tracker.py` — alongside the existing `_TAG_RE` extraction +for `#tag`, add an `_MENTION_RE` extraction for `@user`. Resolve +against the Matrix room's member list (best-effort; if it doesn't +resolve, store the literal string). + +The recorded entry gets `["p", ""]` tags for each resolved +mention, *and* keeps the `@user` substring in the body for human +readability. A renderer can then offer a "your bullets" view per +person. + +## 4.7 Ship sensible default shortcuts for the cdf rooms + +`tracker/tracker.py` — add a one-time bootstrap that, on first +`!setup` (or first capture) in a room whose name matches a known +chateau room, pre-populates shortcuts. Suggested defaults: + +| Room name | Shortcuts | +|---|---| +| Animals | `!buy task #buy`, `!chores task #priority:5`, `!vet task #vet`, `!need need` | +| Gardens | `!buy task #buy`, `!harvest task #harvest`, `!plant task #plant`, `!need need` | +| Operations | `!buy task #buy`, `!steward task #steward`, `!repair task #repair`, `!need need` | +| Events | `!host task #host`, `!ticket task #ticket` | + +These are starting points, not a final list. Communities edit them +via `!setup` once they're in. + +A safer way to ship this is a `default_shortcuts.yaml` config the +plugin reads, so changes don't need a code redeploy. + +## 4.8 New universal verbs implementation + +`tracker/tracker.py:56` — add to `UNIVERSAL_VERBS`: + +```python +UNIVERSAL_VERBS = { + "add", "task", "tasks", "sidequest", "sidequests", + "remind", "reminders", "done", "list", "setup", + "goals", "need", "needs", +} +``` + +(Plural aliases per §3.1.) + +Add `_handle_goals` and `_handle_need` handlers. `!goals` uses +replaceable-event semantics (§3.3) — for the matrix-local v1, this +means *upserting* on `(user, room, kind='goals')` rather than +inserting. One row per author per room. + +`!need` records a `kind='need'` item that stays open until +either `!done` (we got some) or another verb explicitly converts it +to a `!buy` task. + +## 4.9 Frequency tag parsing in `!list` + +`tracker/tracker.py:302` — when listing items, group by +`frequency:` tag so the chore-list renders as: + +``` +**Daily (3):** +- `5` fresh water for alpacas +- `7` fresh water for ducks +- `12` open & close hens + +**Every 3 days (2):** +- `19` scoop alpaca manure +- `23` fluff alpaca bedding + +**Weekly (1):** +- `4` change out hen straw +``` + +This is rendering, not protocol — the spec just stores tags. + +## 4.10 Replace the `tracker/README.md` quirk note + +After §4.4 lands, strike the edit caveat. Add a paragraph on the +new pin behavior, the new universal verbs, and the multi-verb +splitting. + +--- + +# 5 — New ideas the pilot surfaced + +## 5.1 Opt-in "private journal room" mode + +For a per-user *private* journal room (one bot account, one human +member), the prefix friction adds nothing — every message is +journal content by definition. We can add a per-room flag +`auto_journal: true` (default `false`) that records every plain +message as a `!journal` entry. This is *opt-in per room* and the +bot acks loudly the first time it activates so nobody is surprised +by it. + +For shared rooms — which is everywhere in the pilot — the default +stays explicit-prefix-only, matching Coco's "`!journal` is the +required format" enforcement. + +## 5.2 Multi-room fan-out + +Skye's "I'm not sure which chat to put these social things in" +suggests an entry can sometimes belong to multiple rooms. We +propose a `+#room` syntax: + +``` +!task +#events +#general organize image-AI brainstorm with pat and coco +``` + +This records the entry in *both* communities. The same entry has +two `a`-tags (one per NIP-72 community). Renderers in either room +see it. `!done` closes it in both. + +Implementation: parse `+#` tokens out of the body, resolve +to room ids the bot is also a member of, and insert one row per +target room with a shared `link_id`. v2 work, not v1. + +## 5.3 Photo / link / media in entries + +Helena's "checked feathered babies" entry was actually a photo + +caption. Thomas shares YouTube links. Coco shared a PDF on toxic +plants. The body field today is text; the bot can capture media +attachments by storing their `mxc://` URI in a `media` JSON column. +A renderer can fetch and display. + +Especially valuable for the eink panel — a photo of the daily chores +list, a graph of fed/watered checkmarks, the duck-pool-water-runoff +diagram. + +## 5.4 Calendar feed for timed entries + +Pat already saw the vet appointment on the castle calendar — *"nice +i already see it on the castle calendar"*. This integration should +be documented: where it lives, what feeds it. If it's manual, it +should be automated; if it's automated, it should be tested for +all `kind 31923` timed entries. + +The natural shape: an iCalendar feed at +`https://docs.ariege.io/calendar.ics` produced from the bot's +timed-entry table. Castle members subscribe in their calendar app +of choice. + +## 5.5 Daily / weekly digests + +The pilot showed the value of *summary*. Coco pins a current +`!goals`. Ulo posts meeting summaries. Helena writes "homework" +recaps. The bot can produce these on a cron: + +- **Morning digest** (per room): "Yesterday's journal entries (3), + Open tasks this week (12), Goals pinned (1)." +- **Weekly digest** (Sunday evening): "This week's completed tasks + (28), Open carry-over (15), Journal highlights (a sampling)." + +This is a content surface, not protocol. The data is already there. +Implementation = a cron handler + a markdown template + posting to +the room. Maubot has cron support via `asyncio` + `croniter`. + +## 5.6 Bot personality — Alfred + +The community named the bot Alfred (the butler). Replies should +match: a slightly formal, helpful, occasionally dry tone. + +Concretely: +- Current acks: `📥 Logged for @alice.` +- Alfred-flavored: `Very good, @alice. Logged.` + +A `personality` config field with `default | alfred | minimal` +lets other adopting communities pick. Keep `default` deadpan and +neutral. + +This is low-priority but it costs nothing and the data shows the +community will engage with the bot more if it feels like a +collaborator. "What should we name our assistant?" — *"Alfred would +be my go to"* — *"doiittt"* is a community taking ownership of the +tool before it exists. + +## 5.7 LLM fallback inside the chateau network + +The spec's classifier conformance level 2 is LLM fallback. The +chateau has an existing Ollama setup or can run one on the same +host as maubot. The fallback fires only when: + +- The user typed `!add` (explicit ambiguity), +- The rules classifier returned `unclassified`, +- The LLM is reachable. + +If any condition fails, the entry stays in the inbox until +human-sorted (level 0 fallback). The architecture stays +self-hostable. + +Smallest reasonable model for this is in the `llama3:1b` / +`phi3:mini` size range — runs comfortably on the maubot host. + +## 5.8 Wiki / docs lookup inside the tracker context + +The wiki plugin already exists. Inside a tracker reply, if an entry +mentions e.g. *"recycling"*, the bot could surface a `[?]` hint: +"Related: `docs.ariege.io/spaces/waste-recycling`". One-line link, +ignorable. + +This makes the chateau's docs site organically discoverable from +chat without anyone needing to remember URLs. Implementation: +inside the tracker's record path, fire a quick keyword query at the +wiki plugin's index and append top-1 result if confidence is high. + +--- + +# 6 — How this fits the larger omnixy / aiolabs ecosystem + +The Château pilot is one community using one piece of the stack. The +broader picture: + +## 6.1 The protocol survives the implementation + +The community-organizer spec is CC0 and references only open +standards (RFC 5545 VTODO, NIP-52, NIP-72, ActivityStreams). If the +maubot plugin disappears tomorrow, the events on the relay are still +readable by any NIP-52 client. The chateau's organizational memory +is not coupled to maubot. That's the design property we're +protecting. + +## 6.2 Nostr bridge (Phase 2) + +Today the tracker is matrix-only. When the vocabulary stabilizes +(probably 2-4 more weeks of pilot), Phase 2 bridges to Nostr: + +- Bot writes captured entries to a community Nostr relay. +- The relay can be private (auth-gated) or public per the + `publish` posture config. +- Renderers (the eink panel, a future webapp dashboard) subscribe + to the relay and surface the data. + +This is the unlock for the *inky-impression* panel in the foyer — +the data shape it consumes is exactly the events the bot will be +producing. + +## 6.3 Identity — the signer abstraction + +Today everything is signed by the bot. The spec's §7.2 / §12 +develop the per-user-signing endgame: a sidecar +`nsecbunkerd` on the same host, the bot holding only scoped NIP-46 +connection tokens. + +At chateau scale (10-20 members), this is overkill for v1. We run +v1 (trust the bot, `author` tag carries the human MXID) for as long +as the community is small and physical-trust-bounded. We migrate to +per-user signing when: + +- More than one community is using the same bot account, *or* +- An external party (visitor, woofer, neighbor) needs to write + entries without being a fully-trusted member, *or* +- LNbits #18 (the bunker integration) ships and we want a single + identity story across the stack. + +The signer abstraction is *already* in the spec, so the migration +is design-for-it-now, do-it-later. + +## 6.4 LNbits / Lightning + +Two Lightning hooks the pilot suggests: + +**Marketplace / payable buy items.** A `!buy` item could optionally +carry a price and produce a Lightning invoice via LNbits when +clicked through. "Roll-out flypaper — €12 — pay" with the invoice +generated against the chateau's funding wallet. Useful for outside +contributors who want to chip in a specific item. + +**Visa funding stream.** Coco's alpaca funding stream (€23k over +time) is exactly the kind of recurring-pledge use case LNbits has. +This is a partnership channel between the tracker and the LNbits +extension at the marketplace level, not v1. + +## 6.5 Events app + +Coco's events-app side-project is in the pilot data: +*"developing the app & biz plan for tech company of an +ariege-wide calendar of events app"*. The tracker's NIP-52 31923 +events are the right shape to feed that calendar. The webapp +consumes NIP-52 already (per the bunker spec discussion). One data +shape, two surfaces: the bot records, the events app displays. + +## 6.6 Continuwuity homeserver + +The Matrix homeserver running this is Continuwuity, deployed via +`deploy/server-deploy/modules/services/matrix.nix`. The maubot +daemon registers via the admin-room token flow (see +`~/dev/CLAUDE.md` Matrix-homeserver section). All bot accounts in +this report (`@journalbot`, future `@trackerbot`, `@wikibot`, etc.) +are regular Matrix users on that homeserver. + +The bot-as-user pattern means a chateau member can DM the bot +privately (e.g. for personal `!journal` entries the public room +shouldn't see). The current journal plugin already supports this — +just invite the bot to a 1-1 room. + +## 6.7 Wiki cross-cutting + +The wiki plugin (`!ask`, `!doc`) reads `docs.ariege.io`. The +chateau's documentation should grow alongside the tracker — when a +new community-life protocol gets agreed on at a meeting (e.g. +"who feeds the cat"), it should land in `docs.ariege.io/spaces/...` +and be `!ask`-able. The pilot showed several decisions that would +benefit from durable documentation: the chickens' opening time, the +puppy training schedule, the vet's number. + +This argues for a small `tracker → wiki` integration: when a +`!journal` entry contains a *decision* (heuristically: contains +"we decided" / "rule" / "from now on"), the bot pings the room with +"This looks like a durable decision — would you like to file it +as a docs page?". One-click → wiki page draft. + +--- + +# 7 — Phased plan + +A 5-phase rollout that keeps each step small, deployable, and +reversible. + +## Phase 0 — Verb normalization and edit handling (next 1-2 weeks) + +Small parser / dispatch changes. No schema migrations beyond +`mx_event_id`. + +- §4.1 inline `!verb` markers as tags +- §4.2 multi-verb-per-message splitter +- §4.3 trailing-colon + case +- §4.4 edit handling (schema v2: add `mx_event_id`) +- §4.8 new universal verbs (`!goals`, `!need`, plural aliases) +- §4.10 README updates + +After Phase 0, the bot won't drop any of the entries we observed. + +## Phase 1 — Replaceable goals and pinning (2-4 weeks) + +- §3.3 replaceable-event semantics in spec +- §4.5 pin/unpin behavior on `!goals` recording +- §4.7 default shortcuts for cdf rooms +- §4.9 `!list` grouping by frequency + +After Phase 1, `!goals` becomes a living list per author and the +chore-recurrence model is at least visible to renderers. + +## Phase 2 — Subjects, mentions, recurrence (4-6 weeks) + +- §3.5 `frequency:` tag in spec +- §3.6 subject tags (animals, places) +- §4.6 inline `@`-mention extraction +- §3.7, §3.8 grammar clarifications in spec +- §5.5 daily/weekly digests + +After Phase 2, the eink panel can render meaningful views without +needing protocol changes. + +## Phase 3 — Nostr bridge (6-10 weeks) + +- Wire the tracker's local SQLite to a Nostr publisher. +- Subscribe to events tagged with the bot's communities for + bidirectional sync. +- Eink panel goes live against the relay. + +## Phase 4 — Per-user signing and identity (when LNbits #18 ships) + +- Operator-IdP + sidecar bunker per spec §12. +- Chat-handle ↔ pubkey binding via LNbits. +- Bot acquires scoped NIP-46 tokens per member. +- `BotSigner` becomes the fallback only. + +--- + +# 8 — Open questions for the Château + +Things the team should weigh in on before we lock the next phase: + +1. **Verb names.** Is `!goals` the right word, or `!intentions` / + `!focus` / `!agenda`? Same for `!need` vs `!stock` vs `!low`. The + bot will accept what the community uses — but the spec name will + stick. + +2. **Default shortcuts per room.** §4.7 lists candidates. Anything + missing? Anything wrong? Specifically: does the Animals room + want `!vet` and `!med` as separate kinds, or one `!health`? + +3. **Subject vocabulary.** Which animals, places, and assets are + worth naming as subjects? Suggested first pass: **animals** — + Sapphi, Loki, Leia, Tango, Morrigan, plus group names + (alpacas, hens, ducks, pups, hatchlings). **Places** — orchard, + south-paddock, coop, garage, kitchen, lab, dependence. **Assets** — + tractor, electric-fence, van, irrigation. + +4. **Calendar integration.** Today *something* turns dated journal + entries into castle-calendar items. Who owns that pipeline? + Should the bot own it? Should we publish an `.ics` feed off the + maubot host? + +5. **Photo / media capture.** Does it matter that journal entries + capture the photos attached to them, or is text enough for v1? + (Materially affects storage and the eink panel.) + +6. **The "Alfred" persona.** Yes / no / opt-in per room? + +7. **The private-journal room mode.** Would members value a 1-1 DM + with the bot where every message is auto-journaled, or does the + explicit `!journal` everywhere feel cleaner? + +8. **LLM fallback.** Comfortable running a local model on the same + host that classifies `!add` entries? Or stay rules-only? + +9. **Cross-room fan-out.** Do members value `+#room1 +#room2` + syntax, or is "I'll just repost in the other room" fine? + +--- + +# Appendix A — Glossary + +- **Verb** — A `!`-prefixed word at the start of a message that the + bot recognizes as a command (e.g. `!journal`, `!task`). +- **Tag** — A `#`-prefixed word inside a message that gets attached + to the entry as metadata (e.g. `#buy`, `#priority:1`). +- **Subject** — A `@`-prefixed word that names an entity (animal, + place, asset) the entry is about. +- **Shortcut** — A per-room alias that expands to a universal verb + + tags (e.g. `!buy` expands to `!task #buy`). +- **Replaceable event** — A NIP-52 event keyed by a `d`-tag, where + newer events overwrite older ones with the same key. Used for + living lists like `!goals`. +- **Inbox** — Where `!add` entries land when the classifier can't + determine a kind. Cleared via `!sort` or `!reclassify`. +- **Bot** — The maubot plugin instance attached to a Matrix client + account (e.g. `@journalbot:ariege.io`). +- **Renderer** — Any output surface (eink panel, webapp dashboard, + mobile notification) that consumes captured events. + +--- + +# Appendix B — File map + +Concrete files this report's proposals would change: + +| File | Change | +|---|---| +| `docs/community-organizer-spec.md` | §3.1 plural aliases, §3.2 new verbs, §3.3 replaceable, §3.5 frequency, §3.6 subjects, §3.7 inline markers, §3.8 sub-headers | +| `tracker/tracker.py:52` | Regex updates (case-insensitive, trailing colon) | +| `tracker/tracker.py:56` | `UNIVERSAL_VERBS` additions | +| `tracker/tracker.py:184` | Dispatch — multi-verb + inline tag extraction | +| `tracker/tracker.py` | New `_handle_goals`, `_handle_need`, `_handle_edit` | +| `tracker/tracker.py` (upgrade table) | `upgrade_v2` — `mx_event_id`, `link_id`, `pinned` columns | +| `tracker/classify.py` | Optional — LLM fallback wiring | +| `tracker/README.md:133` | Strike the edit caveat, document new behaviors | +| `journal/journal.py:34` | Regex update consistent with tracker | +| `wiki/wiki.py` | Optional `!ask`-from-tracker integration | + +--- + +*End of report.* diff --git a/docs/2026-06-02-chateau-pilot-review.pdf b/docs/2026-06-02-chateau-pilot-review.pdf new file mode 100644 index 0000000..b974d22 Binary files /dev/null and b/docs/2026-06-02-chateau-pilot-review.pdf differ