Skip to content

Mikhail-Za/tastebud-memory

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

8 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ‘… tastebud-memory

Compositional project fingerprints for AI agent memory. Give every project a deterministic high-dimensional identity vector. Every day's work becomes a weighted blend of those vectors. One "taste" (a dot product) decomposes any day back into its exact ingredients, enumerates every day a project ever touched, and detects ingredients nobody has named yet.

Zero dependencies. Two JSON files. ~600 lines of Node.

What it looks like

$ node tastebud.mjs decode 2026-01-12        # un-mix one day from its 4096-dim vector alone
2026-01-12 - Dosing pump driver; recipe-site deploy; soil sensors moved to bed 2
recovered from vector alone (vs actual):
  aquarium-controller          est=0.502  actual=0.500
  recipe-site                  est=0.296  actual=0.300
  garden-sensors               est=0.191  actual=0.200

$ node tastebud.mjs gaps                     # what's been worked on but never documented?
workstreams in logs with NO project file:
  aquarium-controller            4 day(s)  mass=2.150  first=2026-01-10
  home-lab                       3 day(s)  mass=1.550  first=2026-01-05
  sourdough-lab                  2 day(s)  mass=0.900  first=2026-01-15  [NOT EVEN IN CODEBOOK]

$ node tastebud.mjs tasteslike sourdough-lab # the unknown ingredient: what is it close to?
sourdough-lab  [UNKNOWN INGREDIENT - not in codebook]
keeps company with (rarity-weighted co-occurrence):
  recipe-site                  0.463
  aquarium-controller          0.201

$ node tastebud.mjs unknowns                 # triage: which unknowns are RIPE to decide on?
unknowns: 1 total  (decide 1, maturing 0)
  sourdough-lab                open 155d   2 day(s)  mass=0.900  [OVERDUE  ]  -> alias->recipe-site

Those last two are the headline feature. sourdough-lab doesn't exist anywhere as a project: the nightly tagger invented the slug because nothing in the codebook fit, and the system flagged it and placed it next to its nearest relative. Your agent notices new workstreams forming before you've named them and then helps you decide what to do about each one. The -> alias->recipe-site is a recommendation, not a decision: sourdough-lab keeps company almost entirely with recipe-site, so the system reads it as the same work and suggests folding it in. You stay in charge: you could mint it as its own project instead, dismiss it, or wait.

The pipeline

daily log (markdown)
     β”‚  nightly LLM tagger (rules + your codebook; invents slugs when nothing fits)
     β–Ό
composition row   {"date":"2026-01-12","major":[{"slug":"aquarium-controller","w":0.5},...]}
     β”‚  deterministic: slug string β†’ seeded Β±1 hypervector, day = weighted sum
     β–Ό
fingerprint (4096-dim)
     β”‚
     β”œβ”€ decode / where / first / cooccur / window / diff / gaps   (exact membership queries)
     β”œβ”€ similar / drift / tasteslike / backtest                   (vector-layer extras)
     β”œβ”€ unknowns / mint / alias / dismiss / watch / autofile      (ripen-then-decide triage loop)
     └─ MCP server β†’ your agent tastes before it reads

The idea (and where it came from)

This started as a question about hexadecimal colors: what if every project had a unique color, and a day's work blended them into a new color you could un-mix? The problem is that color can't do that. Three channels can't carry the membership of 50 projects, which is why mixed paint can't be un-mixed.

The metaphor that actually works is a chef's palate. A trained chef tastes an unfamiliar dish and names every ingredient in it, estimates the proportions, and (the key move) notices when there's something in the dish he doesn't recognize. That's what this does, with ~30-year-old math: hyperdimensional computing (Kanerva) / vector symbolic architectures.

  • Each project slug deterministically seeds a 4,096-dimension Β±1 vector. Random high-dimensional vectors are nearly orthogonal, so they don't interfere.
  • A day's fingerprint is the weighted sum of its projects' vectors.
  • Decoding is a dot product against the codebook: projects in the blend score close to their weight, and absent projects score close to zero. Mixing is reversible, the thing color couldn't do.
  • The residual, the mass the codebook can't explain, is the unknown ingredient detector. When your agent starts working on something that has no name yet, this notices before you do.

Why bother? (what semantic search can't do)

We baselined an agent's real memory (92 days, ~400 files) with embedding search before building this. Semantic search was good at date lookups and well-named topics, and structurally incapable of:

  • Enumeration: "list ALL days that touched project X" (search returns representatives, never the complete set)
  • Origin under aliasing: "when did X start, including under its old name?" (recency and volume bury the origin; this was a total miss in our baseline)
  • Temporal set-difference: "active in March, dead by June"
  • Absence: "which workstreams never got documentation?" (you can't embed a negation)

One concrete failure from that baseline, because it's instructive. A business project had been renamed early in its life, and we asked search "when did this start, including under its earlier name?" with three increasingly charitable query phrasings. Every one returned the later, high-volume era of the project; the actual origin date and rename event never surfaced, because similarity ranking structurally favors where the bulk of the writing is, and origins are by definition thin. tastebud.mjs first <slug> answers it exactly, because the codebook records the alias and the composition table records the date. Absence queries fail even worse: asked "which workstreams never got a project file," embedding search matched the literal phrase "project file" to documentation about the memory system itself. It cannot reason about what isn't there.

Fingerprints answer all four exactly, in milliseconds, because composition is recorded, not inferred. Tastebud complements semantic search (meaning); it doesn't replace it (membership).

The unknown-ingredient loop (detect, ripen, decide, reverse)

Detecting an unnamed workstream is only half the job. The other half is deciding what it is, without being nagged about every fleeting one-off and without making a single irreversible mistake. Tastebud closes that loop with a full triage + reversible-decision system. The chef keeps tasting; you only get asked once a dish is worth a verdict.

Detection is continuous but silent. Every night the sweeper notices new unknown slugs and logs them. No pings. A brand-new slug seen once is not a reason to interrupt you.

Mature before asking. An unknown is not surfaced for a decision until it ripens: it has to recur across days, accumulate real major weight, or age past about a week. Thin one-offs sit quietly in a Maturing bucket and never nag. A workstream that shows up for one afternoon and vanishes was noise; a workstream that keeps coming back has earned a question. All the thresholds are env-tunable (TASTEBUD_RIPE_DAYS, TASTEBUD_RIPE_MASS, TASTEBUD_RIPE_AGE_DAYS, and more).

Auto-file the obvious. If an unknown already has a project file on disk (config projectsDir), the nightly sweeper files it into the codebook automatically. A file means a human already judged it a real project, so there is nothing to ask. Logged, not pinged.

Weekly digest decides only ripe items. Once a week a review surfaces only the ripe unknowns, each with a recommendation (mint / alias / dismiss / watch) plus the context behind it, pushed through the config notifyCommand. Everything still maturing stays out of your way.

Every decision is reversible. Triage state lives in a persistent ledger (<dataDir>/unknowns-ledger.json). Nothing you decide is permanent:

  • dismiss means "not now," not "never." A dismissed one-off revives if it starts growing again, and comes back recommended as something to watch.
  • mint writes the slug into the codebook, and is fully undoable with mint --undo.
  • watch parks it on a watchlist: out of the decide queue, still in view.
  • alias folds a name into an existing project and resolves it. The engine treats codebook aliases as known, so the unknown leaves the list for good (until you remove the alias).
$ node tastebud.mjs unknowns                 # what is ripe to decide on right now?
unknowns: 1 total  (decide 1, maturing 0)
  sourdough-lab                open 155d   2 day(s)  mass=0.900  [OVERDUE  ]  -> alias->recipe-site

$ node tastebud.mjs alias sourdough-lab recipe-site   # fold it in; it now resolves
$ node tastebud.mjs watch sourdough-lab "keep an eye on it"   # or defer the call
$ node tastebud.mjs mint sourdough-lab --class product        # or make it its own project
$ node tastebud.mjs mint sourdough-lab --undo                 # changed your mind: undo it

The recommendation is a hint, never a decision. Here sourdough-lab co-occurs almost entirely with recipe-site, so the system suggests aliasing it; a human who knows it is a distinct effort can mint it instead. The point of the loop is that you make the call, late enough to be sure, and can always take it back.

Quickstart (60 seconds)

git clone https://github.com/Mikhail-Za/tastebud-memory && cd tastebud-memory
node tastebud.mjs check                      # validate the sample data
node tastebud.mjs decode 2026-01-12          # un-mix one day from its vector alone
node tastebud.mjs where aquarium-controller  # every day that project ever touched
node tastebud.mjs gaps                       # workstreams with no documentation
node tastebud.mjs tasteslike sourdough-lab   # an UNKNOWN ingredient: what is it close to?
node tastebud.mjs backtest aquarium-controller  # would the detector have caught this project emerging?
node tastebud.mjs unknowns                   # the triage queue: which unknowns are ripe to decide?
node tastebud.mjs color 2026-01-12           # the original metaphor, as garnish

The unknown-ingredient loop adds its own small command set (all reversible, all snapshot before they write):

node tastebud.mjs unknowns --write           # write unknowns-report.md (Decide / Watching / Maturing)
node tastebud.mjs alias sourdough-lab recipe-site   # fold a name into a project; it now resolves
node tastebud.mjs mint sourdough-lab --class product  # promote an unknown to its own codebook slug
node tastebud.mjs mint sourdough-lab --undo  # reverse a mint
node tastebud.mjs dismiss sourdough-lab "one-off"   # "not now"; revives if it grows again
node tastebud.mjs watch sourdough-lab        # park on the watchlist, out of the decide queue
node tastebud.mjs decisions                  # the persistent decision ledger, grouped by status
node tastebud.mjs digest                     # the daily report: ripe decide queue + recent decisions (push via notifyCommand)
node tastebud.mjs autofile                   # dry-run filing unknowns that already have a project file

The sample data is a fictional fortnight that includes an emerging project and an unknown ingredient, so every command above demonstrates something real.

Using it on your own memory

  1. Codebook: list your projects in codebook.json (slug + aliases + has_file). Slugs are permanent (they seed the vectors); add aliases instead of renaming.
  2. Backfill: have a strong LLM read each daily log and emit {major:[{slug,w}], minor:[], new:[]} per day (rules in examples/nightly-prompt.md). Verify a sample before trusting it. See docs/methodology.md for the gate/backtest protocol we used.
  3. Nightly: schedule your platform's LLM to tag yesterday into inbox/<date>.json (prompt template provided), and run node tagger.mjs nightly --write an hour later as the deterministic sweeper. If the primary fails, a local model takes over and you get an alert with the reason (notifyCommand in config can point at Slack, ntfy, a webhook, anything). The same nightly pass logs new unknown slugs silently and auto-files any that already have a project file (config projectsDir); it does not interrupt you for either.
  4. Weekly decision loop: once a week run node tastebud.mjs unknowns --write and review only the ripe unknowns, each with a recommendation. Decide with mint / alias / dismiss / watch; every call is reversible and snapshots before it writes. See docs/production-pattern.md for the wiring.
  5. Agent integration: register mcp-server/server.mjs (standard MCP, stdio) so your agent can taste before reading: decode a day in milliseconds, then fetch only the logs that matter.

What this is not

  • Not a summarizer. A fingerprint is an index, not the content. The chef recovers the ingredient list, not the recipe steps. Taste, identify, then fetch.
  • Not magic capacity. A bundle reliably holds a few dozen constituents at D=4096. Plenty for a day, wrong for a year; aggregate windows instead.
  • Not a replacement for the codebook. Decoding requires reference vectors, like a palate requires training. Unknown ingredients are detected as unexplained mass, then triaged.

Honest engineering notes

The full methodology, including the kill-gates this project had to pass before going live, is in docs/methodology.md. Highlights: an adversarial verification pass re-derived 31 of 92 days blind and found 2 real tagging errors (93.5% faithful); backtests flagged emerging projects within 0-2 days; and our honest finding that the composition table does most of the query work. The vector layer earns its keep on decode, drift, similarity, and fixed-size encoding, not on basic lookups. Production wiring (primary LLM cron + deterministic sweeper + alerted local fallback) is in docs/production-pattern.md.

License

MIT. Built by Mikhail Zaidi with Claude (Anthropic), June 2026.

About

A chef's palate for AI agent memory; Un-mix any day's work into its exact projects, and detect workstreams nobody has named yet. Hyperdimensional fingerprints, zero dependencies.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors