maubot-plugins/docs/2026-06-02-chateau-pilot-review.md
2026-09-20 17:30:51 +02:00

1161 lines
45 KiB
Markdown

---
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", "<pubkey>"]` 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 <text>` | A list of current intentions for the author/group/period. Replaceable per `(author, room, period?)`. | 31922 with `["t", "goals"]` |
| `!need <text>` | 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:<spec>` 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", <pubkey>]` 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", "<mxid-or-pubkey>"]` 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:<verb>` 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:<verb>` 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", "<mxid>"]` 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 `+#<roomname>` 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.*