c8444813e2
Behavioural memory, narrowed on purpose. internal/memory/behavior.go builds a profile out of self-facts — distinct days per weekday, median time of day — and reads it back in RU; router.ParseHabitQuery finds the weekday deterministically; a `habits` query source answers the question. Three things the plan doc asks for are deliberately absent, and the doc now records why: - The profile is COUNTED, not LLM-generated. A 1.7B asked to summarise a year of habits writes fluent claims about the owner's life that no row supports, and a wrong claim about him is the most expensive kind of wrong maven can be. - No cached profile fact, so no "update on fact write" machinery. It is recomputed on the question; a cache that can disagree with its own rows is two truths. - No proactive daily plan nudge. A dispatcher proposal at 08:00 every day is the definition of a nag. The path from "she noticed a pattern" to "she acts on it" already exists in internal/pattern with the proposal queue on /routines, and it goes through him. A one-off is not a habit: an activity needs two distinct days before she will call it usual, and until then she says she does not know yet. Only self-facts count — env rows are the world, config rows are her own tuning state. The typical time is a median so one 03:00 outlier cannot move a morning habit into the night. An unrecognised fact key is read back verbatim rather than glossed into something she made up. The source sits before "calendar" in querySources, and its matcher requires a habit marker, so "что я делаю в среду?" still reaches the calendar — answering a question about this coming Wednesday with a statistical average would be answering a different question. Verified: make build and make test both exit 0.
63 lines
4.5 KiB
Markdown
63 lines
4.5 KiB
Markdown
# Plan: Behavioral Memory — How I Do Stuff
|
|
|
|
**Goal:** Maven builds a rich behavioral model of the user over time: habits, routines, preferences, recurring tasks, deadlines, and commitments. She uses this model to proactively propose plans, surface reminders, and adjust her behavior — all grounded in the existing fact/event/note stores.
|
|
|
|
**Done when:**
|
|
- `internal/memory/behavior.go` — behavioral model builder reads facts, events, notes, nudges, reminders, tools, calendar events
|
|
- Model is exposed as a structured profile: `{"routines": [...], "preferences": {...}, "recurring_tasks": [...], "typical_schedule": {...}}`
|
|
- LLM generates this profile periodically (daily) and stores it as a note/fact
|
|
- Proactive loop uses the profile to propose daily plans: "сегодня ты обычно делаешь X, Y, Z. напомнить?"
|
|
- Profile is queryable via voice: "что я обычно делаю по вторникам?"
|
|
- Profile updates on fact write — not just periodic — so a new "walk" fact immediately adjusts the walking schedule
|
|
|
|
**Scope:**
|
|
- `internal/memory/behavior.go` — behavior builder
|
|
- `internal/memory/profile.go` — profile data structures (routines, preferences, schedule, commitments)
|
|
- Reuses `internal/llm.Client` for profile generation
|
|
- Reuses `internal/pattern/detector.go` for interval detection on behavioral data
|
|
- Extends `internal/router/intent.go` — `IntentQuery` extended with behavioral sub-queries
|
|
- Reuses `internal/delivery.Dispatcher` for surfacing proposals as nudges
|
|
|
|
**Steps:**
|
|
1. Design profile schema: `BehaviorProfile` struct with `Routines []Routine`, `Preferences map[string]string`, `RecurringTasks []Task`, `WeeklySchedule map[string][]Activity`
|
|
2. Create `internal/memory/behavior.go` — `Builder` that reads `store.RecentFacts(1000)`, `store.EventsFor` (all action+object combos), `store.RecentNotes(500)`, `store.ListReminders`
|
|
3. Implement profile generation — prompt for `llm.Client` that takes raw facts and outputs a structured JSON profile; stores result as a fact (`kind=config, key=behavior_profile`)
|
|
4. Create `internal/memory/planner.go` — reads the profile each morning (via routine cron `"0 8 * * *"`) and proposes a daily plan through `dispatcher.DispatchNudge`
|
|
5. Add real-time updates — when a fact is written via `WriteFact`, the behavior builder incrementally updates the relevant profile section (append-only, no full rebuild)
|
|
6. Wire voice query — `"что я обычно делаю?"` routes to `IntentQuery` → behavior profile lookup → LLM-phrased answer
|
|
7. Add IPC read method `MethodGetBehaviorProfile` so mavweb can display it on `/dash`
|
|
8. Test with synthetic fact history — verify weekly schedule is correctly inferred
|
|
|
|
---
|
|
|
|
## Status (2026-08-01) — partially shipped, deliberately narrowed
|
|
|
|
Shipped on `overnight/behavior-profile`:
|
|
|
|
- `internal/memory/behavior.go` — `BuildProfile` counts habits per weekday out of
|
|
self-facts: distinct-day counts (`MinHabitDays = 2`), a median time-of-day, and
|
|
`FormatWeekdayRU` / `FormatOverallRU` for the spoken answer.
|
|
- `internal/router/habit.go` — `ParseHabitQuery`, which requires a habit marker
|
|
("обычно", "каждую", "привычки", …) and parses the weekday deterministically.
|
|
- `cmd/mavend/actions_query.go` — a `habits` query source, so "что я обычно делаю
|
|
по вторникам?" is answered.
|
|
|
|
**Not shipped, and not to be shipped as written:**
|
|
|
|
- *Step 3, LLM-generated profile stored as a fact.* The profile is COUNTED, not
|
|
generated. A 1.7B asked to summarise a year of habits produces fluent claims
|
|
about the owner's life that no row supports, and a wrong claim about him is the
|
|
most expensive kind of wrong maven can be. Counting is verifiable and cheap.
|
|
- *Step 5, incremental updates on fact write.* There is no cache to keep fresh —
|
|
the profile is recomputed on the question, so a new fact is already in the next
|
|
answer. A cached profile that can disagree with its own rows is two truths.
|
|
- *Step 4, proactive daily plan proposals via the dispatcher.* Maven is not a nag,
|
|
and a nudge at 08:00 every day proposing the day is the definition of one. The
|
|
sanctioned path from "she noticed a pattern" to "she acts on it" already exists:
|
|
`internal/pattern/detector.go` proposes a routine, and the owner accepts it on
|
|
`/routines`. It goes through him.
|
|
|
|
Still open, if wanted later: `MethodGetBehaviorProfile` + a `/dash` panel (step 7).
|
|
The counted profile needs no new IPC method to be *asked* about — the query source
|
|
reads `RecentFacts` over the existing surface — so this is a display concern only.
|