# Coffee Compass — Vision & Build PRD

*A build-it-yourself product brief for an AI-assisted Fellow Aiden brew-profile companion.*

**Scope.** This document is a build specification, not a supported software distribution. It provides the product model, requirements, sequencing, and implementation lessons needed to create your own version. Your resulting application, technology choices, security posture, deployment, and maintenance remain yours.

**Version:** 1.1 — complete. Covers the everyday tool, two-way device sync, profile
materialization, self-grounding on your own profiles, and the Brew Log. Built and running.
**Audience:** a Fellow Aiden owner who wants to build their own version, with their own AI
coding agent, on whatever stack they prefer.

## How to use this document

This is a product requirements + vision brief, not a codebase. Hand it to a capable AI
coding agent (or read it and build by hand). It describes *what* the tool does and *how the
pieces fit*, plus the hard-won facts about the Fellow Aiden integration that will save you
days. It deliberately avoids prescribing a language, framework, database, or model: those
are yours to choose. Where a decision matters, the brief explains the reasoning so you can
adapt it to your needs rather than copy ours.

There is no repository and no shared code by design. The goal is for you to build something
that fits *your* taste, *your* coffees, and *your* setup, grounded in *your* data, rather
than inherit assumptions baked into someone else's build.

## Why a PRD instead of a repository

This is a deliberate choice, not laziness about packaging.

Software is trending toward disposable and single-use. A great deal of what gets published as
a repo is something almost no one else will actually run, because running someone else's build
means trusting it. And trust is the real obstacle here: this tool holds your account
credentials, talks to a reverse-engineered API, and can create and delete things on a physical
device. Reasonable people are skeptical of letting an unfamiliar codebase touch any of that. No
one wants a stranger's project taking down their home server, burning their API access, or
quietly opening a backdoor.

A PRD sidesteps all of it. You hand this brief to an agent you already trust, in an environment
you control, and it builds something *you* can read and own end to end. You adapt the data
model to your coffees, swap the model for whatever you run, and decide your own safety posture.
You get the design and the hard-won integration knowledge without importing anyone's
dependencies, assumptions, or risk. The thing you end up with is yours, not a fork of ours.

If you build something good from this, that is the entire point. There is nothing to star and
no one keeping score.

## The problem

The Fellow Aiden brews wonderfully once a profile is dialed in, but dialing in is a craft,
and the results are fragile:

- **Profiles live on the device and the device holds only 14.** Your dialed-in recipes get
  evicted to make room. There is no durable, personal home for them.
- **Tuning is trial and error.** People ask an AI for settings, type them in by hand, brew,
  adjust, repeat, with nothing remembering *why* a change was made or what your palate
  actually prefers.
- **Knowledge is scattered.** The coffee's origin and roaster notes, the brewing principles
  that should inform temperature and pulses, and your own taste history rarely sit in one
  place where they can inform the next cup.

## The vision

A personal companion that:

1. Keeps a **library of the coffees you brew**, with the details that matter for brewing.
2. **Generates and refines Aiden brew profiles** with an AI model, grounded in real brewing
   principles and in a model of your taste that *learns from your feedback*.
3. Acts as the **durable home for your profiles**, beyond the device's 14-slot limit, and
   reconciles cleanly with the machine in both directions.
4. Stays **personal and private**: your data, your palate, your device. No accounts to
   create for anyone else, no telemetry, no publishing.

It is explicitly a single-user, personal tool. That keeps the design honest and small.

## Core capabilities

Each capability below is described as an outcome, not an implementation.

### 1. Coffee library
Track the coffees you brew: name, roaster, origin, process, variety, roast level, flavor
notes, a rating, and free notes. Support adding a coffee by pasting a product URL or a block
of text and letting the AI extract the structured fields (never fabricating; leaving blanks
when a fact is not present). Warn when a coffee name already exists.

### 2. AI profile generation, grounded
Generate a complete Aiden brew profile for a coffee. Two inputs make the output good rather
than generic:

- **A brewing-principles reference** folded into the model's context: vetted, source-tagged
  guidance on how bloom, temperature, pulse count, and grind interact with roast level and
  origin. This keeps the model reasoning from real craft, not vibes.
- **A taste model**: a small set of preference dimensions (sweetness, body, acidity,
  brightness, fruit, chocolate, nutty, floral, fermentation, plus bitterness and astringency
  *tolerances*) on a 0–10 scale, supplied to the model so the profile leans toward what you
  like.

The model returns a structured profile (the exact field set is in "The Aiden profile" below)
plus a short rationale. Validate every value against the device's allowed ranges before
trusting it.

### 3. The iteration loop
Refining matters more than first-shot generation. Support:

- **Current-profile-aware refinement**: when a profile already exists, the model adjusts it
  rather than starting over.
- **A "blind take"**: generate a profile *without* showing the model your existing recipe,
  so it reasons independently. This is a good way to check whether the AI is genuinely
  reasoning or merely echoing your seed data. (We found ours was echoing until we built this.)
- **Inline editing** of any field, with a **"final" lock** that marks a profile as settled.
- **Conversational feedback** that nudges the current profile ("a touch sweeter", "less
  astringent").

### 4. Brew logging and taste learning
After brewing, log the result with structured flags (too thin, too bitter, too astringent,
too bold, perfect, etc.) and an optional rating. Each logged brew **nudges the taste model**
by a small, bounded amount and **records the evidence** (the "why") so the learning is
transparent and correctable. Keep this visible on a "taste model" page where the user can see
every dimension, the evidence behind it, and edit any value directly. Small bounded nudges
(for example ±0.25 on the 0–10 scale, clamped) beat large swings; the point is gentle drift
toward truth, auditable at every step.

### 5. Push a profile to the Aiden + share
Send a generated profile to the device so it appears in the official app, ready to brew.
Optionally generate a shareable link and QR code for it. (See "Fellow integration reality".)

### 6. Two-way sync and a durable archive
Pull every profile down from the account into a **durable archive**, and manage which of your
custom profiles are on the device. This is where the 14-slot problem gets solved and where
your library becomes the source of truth. The full model is in "Lifecycle and sync".

## The Aiden profile (data contract)

A profile the device accepts has exactly these fields. **Temperatures are in Celsius.** This
is the single most important and least obvious fact: the official app shows Fahrenheit, but
the API speaks Celsius, on a half-degree grid. If you send Fahrenheit values they will be
rejected (they fall outside the allowed range).

| Field | Type | Allowed values |
|---|---|---|
| `profileType` | int | `0` for a standard custom profile |
| `title` | string | up to 50 characters |
| `ratio` | number | 14 to 20, step 0.5 (coffee-to-water; advisory for some users) |
| `bloomEnabled` | bool | |
| `bloomRatio` | number | one of {1, 1.5, 2, 2.5, 3} |
| `bloomDuration` | int | 1 to 120 (seconds) |
| `bloomTemperature` | number | **50 to 99 °C, step 0.5** |
| `ssPulsesEnabled` | bool | single-serve |
| `ssPulsesNumber` | int | 1 to 10 |
| `ssPulsesInterval` | int | 5 to 60 (seconds) |
| `ssPulseTemperatures` | number[] | each **50–99 °C step 0.5**; array length equals `ssPulsesNumber` |
| `batchPulsesEnabled` | bool | |
| `batchPulsesNumber` | int | 1 to 10 |
| `batchPulsesInterval` | int | 5 to 60 (seconds) |
| `batchPulseTemperatures` | number[] | each **50–99 °C step 0.5**; array length equals `batchPulsesNumber` |

Conversion: `C = (F − 32) × 5/9`, then snap to the nearest 0.5 and clamp to [50, 99]. Example:
195 °F becomes 90.5 °C; 198 °F becomes 92.0 °C.

A note on the coffee-to-water ratio: keep it in the contract because most people want it, but
be aware some users (including ours) ignore it and dose grounds by hand, because the machine
will not let them dose as much coffee as they want. Treat it as advisory, not a bug.

## Fellow integration reality (the part worth reading twice)

There is no official public API. There is a cloud API the mobile app uses, and the community
has reverse-engineered it. You can talk to it directly or use a community client library. The
behavior below is what we learned the hard way; expect it to drift as Fellow updates things.

**Authentication.** Log in with the account email and password and you get a bearer token
(plus a refresh token). Treat these as secrets: keep credentials in environment configuration,
never in code, and **do not log the auth response** — some community clients print it at debug
level, tokens and all. Silence that.

**Endpoints (shape, not a contract).** Roughly: a login endpoint; a "devices" endpoint that
returns your brewers; per-device "profiles" endpoints to list, create, and delete; and a
per-profile "share" endpoint that returns a shareable link. The base is an AWS API Gateway
host that community clients already know; rather than hardcode a URL that may rotate, discover
it from a current community client.

**Two traps that will break a naive client (these cost us real time):**

1. **The device object no longer embeds the profile list.** Older clients assume the device
   record contains `profiles` and `schedules` arrays and crash on construction when those keys
   are absent. Fetch the profile list from the dedicated profiles endpoint instead, and read
   only the brewer id from the device record.
2. **Delete validates against that same list.** A delete-by-id call checks the id against the
   client's *internal* profile list before issuing the delete. If you left that list empty
   (because of trap 1), every delete fails with "profile does not exist." Populate the profile
   list from the endpoint and deletes work.

**Sharing is server-side, and the QR is just the link.** When you request a share for a
profile, the cloud returns a single thing: a `brew.link` URL (we confirmed the response payload
is literally `{ "link": "https://brew.link/p/…" }`, nothing more). There is no separate QR image
from the API. The QR code you see is simply that URL encoded as a square: the `brew.link` page
and the Fellow app's share screen render one, and you can equally render your own from the link
to show in your tool or print on a coffee bag. Scanning it opens the profile in the Brew with
Aiden app. You create the profile on the account first, then request its link; you cannot
construct a `brew.link` URL offline from settings.

**The device holds 14 profiles; the cloud holds more.** This asymmetry is the whole reason the
archive and the managed-sync model exist.

**Profiles get a server-assigned id** when created (and a *new* id if you delete and recreate).
Do not treat that id as stable identity (see "Lifecycle and sync").

## Conceptual data model

Described as entities and the fields that carry meaning. Use whatever storage you like.

- **Coffee**: identity + metadata (name, roaster, origin, process, variety, roast, flavor
  notes, rating, favorite/buy-again/subscribe flags, free notes).
- **Brew Log**: one chronological timeline per coffee, two entry types — a **note** (a tasting
  impression you write) and a **dial-in** event (auto-recorded when conversation feedback
  produces a new profile: "you said X → profile vN"). Recent entries are fed to the model as
  grounding, so your impressions and dial-in history carry into the next regen. This is the
  passive memory; the conversation is the live steering.
- **Heuristics**: per-user trigger → adjustment rules (e.g. "too thin → raise Pulse 2 by 1°F;
  then tighten spacing"), fed into every generation so feedback nudges reflect the user's
  own proven moves, not generic advice. Plus **structural tendencies** mined from the user's
  real profiles (their typical bloom temps by roast, bloom-ratio and pulse-count habits) stated
  as explicit guidance — so the model generalizes to how the user actually brews instead of a generic middle.
- **Profile (generated)**: an immutable, versioned set of brew settings attached to a coffee,
  with the AI's rationale and a confidence band, and a "final" flag.
- **Taste model**: one per user; the preference dimensions and tolerances on 0–10.
- **Taste evidence**: append-only rows explaining each nudge (which brew, which flag, which
  direction), so the model is auditable.
- **Brew session + feedback**: what you brewed and how it turned out (flags + rating + notes).
- **Archive snapshot**: append-only, insert-on-change snapshots of *every* profile on the
  account (your customs, the vendor's built-ins, any seasonal "drops"), each with full settings
  and a content hash. This is the memorialization layer; it never deletes. Latest state for a
  profile is its most recent snapshot.
- **Managed custom**: the working record for each of *your* custom profiles, with the lifecycle
  fields below.

## Lifecycle and sync (the heart of the design)

This is the most reusable idea in the whole project, and it is fully stack-agnostic. Treat the
relationship between your library and the device as **desired state plus an explicit apply**,
the same shape as infrastructure tooling's "plan / apply".

**The coffee is the unit; everything is one set in three states.** Resist the temptation to
model "your coffees" and "the device profiles" as two separate worlds — users don't think that
way. A coffee is either **On device**, in the **Queue** (staged to move), or in the **Archive**
(off device, kept). "Not on the device" simply *is* "archived." The device view should show your
whole coffee set, bucketed by state — not a separate list of device-only profiles. Link each
coffee to its device profile with a stable id (not name-matching — coffees collide on origin
words like "Peru"); a coffee with no device profile yet is just an archived coffee you haven't
dialed for.

**Two independent axes drive the buckets:**

- **Desired (you choose):** `active` means "I want this on the Aiden". Because the device holds
  14, active is hard-capped at 14. The opposite is `archived`: kept locally, not on the device.
  "Memorialized" and "deleted from the device" are the same end state, and nothing is ever lost
  locally.
- **Observed (sync sees):** `on_device`, true or false, learned by reading the device.

These are different things and should not be collapsed into one checkbox. One is a wish, the
other is a fact.

**The queue is the difference between them, derived live (never stored):**

| | on device | not on device |
|---|---|---|
| **active** | On device — in sync | **Queue: ADD** — push on next sync |
| **archived** | **Queue: REMOVE** — delete on next sync | Archive — off device, kept |

Elevating a coffee from Archive makes it `active` → it shows as a queued ADD. Archiving an
on-device coffee makes it `archived` → a queued REMOVE; after apply it drops into the Archive.
A coffee with no device profile at all is in the Archive too — to elevate it you dial a profile
first (there's nothing to load otherwise).

**Apply is manual and ordered.** When the user syncs, process **REMOVES before ADDS**. Even
though the desired set is capped at 14, the device may currently hold 14; removing first frees
a slot so an add never transiently pushes the device to 15 and gets rejected. So the real-world
swap is explicit: archive one (queues a remove), sync, the remove runs first, then the add.

**Two stable links keep identity straight.** (1) Each device profile points at its **coffee**
by a stable id, so the device view can bucket the one coffee set — never match coffee↔profile by
name (origin words like "Peru" collide and mis-link). (2) The **server-assigned** profile id is
just a pointer to whatever is currently on the device: set it on push, cleared on remove. Because
delete-then-recreate yields a new server id, keying identity on the server id would spawn
duplicates; keying on your local ids (and relinking when a recreated profile reappears) does not.
Expect a few device profiles that aren't in your coffee history yet — give them coffee cards so
every profile has a home.

**Safety boundaries:**

- **Apply mutates the device only on an explicit manual action.** Confirm before deletes.
- **A background poller, if you add one, observes only.** It can notice that you deleted a
  profile directly on the machine, or that a new one appeared, and notify you. It never pushes
  or deletes on its own.
- **The archive never deletes.** A conflict (the device changed under you) can never cost data,
  because the archive already captured the device's truth. Surface conflicts for manual
  resolution rather than overwriting.

## Suggested build order

We built it in this order and it worked well, because each phase ships value and de-risks the
next:

1. **Library + AI generation + iteration loop + brew logging + taste learning.** The everyday
   tool, before any device integration.
2. **Push a profile to the device + share link/QR.** First contact with the Fellow API. Expect
   the Celsius conversion and the two client traps here.
3. **Pull + archive (memorialization).** Read-only with respect to the device. This alone
   solves the 14-slot fear: your library outlives any eviction.
4. **Managed customs: active/archive, the derived queue, manual apply** (with the remove-before-
   add ordering and the 14-cap guard). The two-way sync.
5. **A "generate → queue" bridge**, so a freshly generated profile flows into the queue rather
   than being pushed immediately. One path, not two.
6. *(Planned)* **Grounding the AI in your real, dialed-in profiles**, so refinement reasons from
   what is actually on your machine, not from seed approximations. The "refinement tour" section
   will document this once complete.

## Roles you will need to choose

To keep this stack-agnostic, here are the *roles*, not our picks:

- A **web UI** (any framework) for the library, profile cards, taste model, and the device page.
- A **datastore** for the entities above (any relational store is plenty).
- An **LLM** for extraction, generation, and refinement. A capable local or hosted model both
  work; ground it well and validate its output against the data contract.
- A **Fellow cloud client** (community library or your own thin HTTP client) that handles the
  auth, the profile endpoints, and the two traps above.
- A **QR generator** if you want printable share codes.

## Principles that kept the build honest

- **Personal, private, no publishing.** Your data and palate stay yours.
- **The machine is never mutated silently.** Manual apply, confirmed destructive actions.
- **Memorialize, never lose.** The archive is append-only.
- **Make the AI's reasoning auditable.** Show the evidence behind taste nudges; offer a blind
  take to expose echoing.
- **Validate everything against the device contract** before sending it.
- **Ground the AI in real principles and real data**, not vibes.

## Prior art and resources

You do not have to reverse-engineer the Fellow cloud API from scratch. The community has already
done much of it:

- **A reverse-engineered Fellow Aiden client.** A well-known community Python library handles the
  authentication, the device and profile endpoints, and the share link (search "fellow aiden" on
  PyPI or GitHub; the popular one also ships a small "brew studio" with AI-generated recipes). It
  is unofficial and community-maintained, so it can lag behind Fellow's changes: the two traps in
  "Fellow integration reality" above are exactly that kind of lag. Even if you write your own
  client, read this one as a reference for the API shape.
- **An MCP server for the Aiden** (`github.com/ravenintheforrest/aiden-mcp`), plus agent-skill
  wrappers of the community client, for driving the brewer by natural language from an AI
  assistant. If natural-language device control is your goal, start from one of these rather than
  the raw API.

On referencing these at all: they are public community work, not our build. Pointing you to them
is the opposite of exposing a private codebase; it is the fastest way to stand on what already
exists and then go your own way. If you would rather not depend on a community client, the API
shapes and the gotchas documented above are enough to write a thin client of your own.

## The refinement tour (calibrating the AI to your palate)

*Covers the calibration method, the baseline, the validated before/after of grounding, and a
stricter "names withheld" integrity check.*

The roaster's published profile is generic by necessity. The whole value of a personal
tool is dialing in to *your* taste, and if you are an established brewer you already hold
the answer key: your own dialed-in profiles. This section is a repeatable way to teach the
model your own approach and verify it, useful even if you do not do this kind of work for a living.

**Two stages, on purpose:**

1. **Known / named calibration (names can bleed in).** Generate a profile while giving the
   model the coffee's name and attributes. Be aware that the name, and any reference
   profiles you feed the model, *can bleed into its reasoning*: it may lean on what it
   already "knows" about a well-known coffee rather than reason purely from character. That
   is fine for a first pass; it tends to produce a sensible, close, but generic result. You
   compare that result against your real device settings to see the gap.
2. **Blind refinement (force independent reasoning).** Withhold the dialed-in recipe (and,
   for a stricter test, the name too), leaving only character: origin, process, roast,
   tasting notes, plus your taste model. This removes the bleed and tests whether the model
   has actually learned *your* taste rather than recognized a coffee. The gap closing here is
   the real proof.

**Baseline finding (stage 1, recipe withheld, name given), against three dialed-in
profiles:**

- **The profile structure was already correct**: the model reliably produced an Aiden-shaped
  profile (a sensible ratio, a ~2.5 bloom, a descending multi-pulse temperature curve in the
  brewing range). On one coffee the batch pulse curve matched the user's exactly.
- **Two systematic gaps emerged.** Bloom temperature **ran hot**: the model defaulted to the
  middle of the range while the user blooms cooler, and cooler still on darker or natural
  coffees. And it **missed per-coffee intensity**: where the user had tuned one coffee to a
  hotter peak and an extra pulse, the model produced its generic curve instead.
- In short, the model produced a competent **house profile**, not yet the user's own style.

**What grounding changed (validated).** We then supplied the user's real dialed-in profiles
as worked examples in the generation context, excluding the coffee being profiled so a blind
take stays honest, and re-ran the same three coffees:

- **The bloom-runs-hot bias corrected.** Bloom temperatures dropped into the user's real
  range. One coffee's bloom went from 7°F too hot to within 4°F and its bloom time started
  matching exactly; another landed precisely on the user's value.
- **Per-coffee intensity was captured.** The coffee the user had tuned to a hotter, longer
  five-pulse batch (which the baseline rendered as a generic four-pulse) came back as a
  five-pulse batch. The model had no way to know that before; it learned it from the examples.
- **A new nuance appeared, worth stating plainly.** Grounding teaches the *central tendency*
  of how the user brews, so a coffee that bucks the trend (one the user actually blooms warmer
  than their average) got pulled slightly toward the mean and overcorrected cooler. The lesson:
  grounding produces a markedly better *starting point*, not a perfect clone. The iteration
  loop (nudge it back via feedback) closes the last gap on atypical coffees.

So the arc is: the profile structure was free; calibration against your own profiles fixes the
systematic bias and captures your per-coffee patterns; and the feedback loop handles the
outliers. That is the whole point of a personal tool, and it only works because *your* dialed-in
profiles are the answer key.

**Hard mode: names withheld (the integrity check).** To confirm the model was reasoning from
character and the user's own taste rather than recognizing a known coffee, we re-ran with the coffee
name and roaster removed entirely (only origin, process, roast, and tasting notes given), the
same coffee still excluded from the examples, and the seed library reference turned off so the
*only* profile signal was the user's own dialed-in profiles. It held up: outputs still landed in
the user's real neighborhood (bloom within a few degrees, correct structure). On the
character-rich coffee it actually got *closer* with no name at all. Two honest caveats surfaced:
where character was thin (a vague blend), the model leaned hardest on the user's central tendency
and drifted from that coffee's atypical setting; and where the name had been carrying a real
decision (one coffee's extra pulse), removing it lost that detail. The practical guidance:
*give the name in normal use* — identity is legitimate signal — and let grounding plus character
do the rest. The nameless run is a validation tool, not the recommended mode.

**Second case study: low-feedback coffees vs the user's curated profiles.** We later ran the
same blind comparison on six coffees the user had only lightly refined (and for which the user
supplied a curated "best-known" profile each, with a self-assigned confidence tag). The result
sharpened the picture: on a *conventional* coffee the grounded model nailed the bloom and came
within a degree or two on pulses; but on coffees where the user makes *unusual* choices it
diverged predictably — it stayed near its ~188-190°F bloom comfort zone where the user blooms
much cooler (176-184°F), it preferred four batch pulses where the user runs five, and it could
not reproduce an idiosyncratic *ascending* pulse ramp the user had dialed by hand. The lesson:
grounding reliably lands the well-trodden coffees, but a user's hard-won, atypical choices on
lightly-explored coffees are theirs — the model gives a sensible generic, not a clone. This is
exactly why the user's own stored profiles (and a confidence tag noting how much a profile was
refined) matter: trust the user's value over the model's where the two disagree on a
low-confidence, idiosyncratic coffee.

The takeaway for a builder: do not judge the AI only on first-shot generation. Build the
calibration loop (generate, compare to your real settings, feed your reals back as examples,
re-compare). The structure comes for free; *your palate* is what you are teaching.

**Scaling the examples.** Feed the model the dialed-in profiles *most similar* to the coffee
being profiled first (rank by shared origin, process, roast, and tasting-note terms), and only
truncate the list once the library is large. Two cautions learned the hard way: keep the cap
generous, because a coffee with no close precedent still benefits from the full breadth of your
experience, and do not over-read a single generation, because a model run at a low temperature still
wobbles a few degrees run to run. You are aiming the starting point into the right neighborhood,
not hitting an exact number on the first try.

## Getting smarter over time (without fine-tuning)

The goal is to stop reaching for an outside model to get good profiles. The path is layered,
and none of it is weight training:

1. **Grounding self-improves as the library grows (passive, biggest lever).** Every coffee the
   user dials adds a real worked example; similarity-ranking surfaces the closest ones. More of
   the user's actual profiles = less drift to the generic middle.
2. **Encode measured tendencies as explicit guidance (active).** Mine the user's real profiles
   for their structural habits (bloom temp by roast, bloom-ratio and pulse-count preferences)
   and state them as rules in the principles. This corrects systematic biases the examples alone
   don't (the model averaging to a median instead of, say, cooling the bloom for sweet-protection).
3. **The Brew Log feedback loop (compounding, per-coffee).** Tasting notes and dial-in events
   accumulate and ground future regens; structured brew feedback nudges the taste model.

A measured caveat: idiosyncratic, hand-built structures (an unusual ascending pulse ramp) the
model will not invent — those stay the user's stored values. Trust the user over the model where
they disagree on a low-confidence, atypical coffee. Fine-tuning is not worth it at personal scale;
in-context grounding plus encoded tendencies captures the value with none of the cost.

## Working with your AI agent (starter prompts)

This brief is meant to be *handed to* an AI coding agent, not read like a manual. Below is a
sequence of starter prompts that mirrors the suggested build order. Paste the whole brief into
your agent first, then drive it phase by phase. Adapt the bracketed parts to your stack and
your coffees. These are a starting point, not a script.

**0. Orient the agent and choose your stack.**
> I'm giving you a product brief for a personal Fellow Aiden brew-profile tool. I want to build
> my own version for myself. My environment is [language / framework / database / model]. Read
> the whole brief, then propose an architecture and a phase-1 plan that follows the suggested
> build order. Flag anywhere my stack changes the brief's assumptions, and ask me anything you
> need before you write code.

**1. The everyday tool first (library, generation, iteration, logging).**
> Build phase 1 from the brief, with no device integration yet: the coffee library and the
> conceptual data model, the add-a-coffee flow (paste a product URL or text and extract the
> structured fields, never fabricating, leaving blanks when a fact is absent), AI profile
> generation, the iteration loop, and brew logging. Generate the schema and the core screens.

**2. Ground the model in real principles and a taste model.**
> Wire in two grounding inputs from the brief: a brewing-principles reference, and a taste model
> with the preference dimensions and tolerances on a 0–10 scale. Generate a complete profile plus
> a short rationale, and validate every value against the device data contract (Celsius, the
> half-degree grid, the allowed ranges) before trusting the output.

**3. Teach it what I actually like, from my own dialed-in profiles.**
> Here are [N] of my dialed-in profiles [paste them]. Build my taste model from them and mine my
> structural tendencies — typical bloom temperature by roast level, bloom-ratio and pulse-count
> habits — and state those as explicit guidance in the generation context. When you generate for
> a coffee, feed the most *similar* of my profiles first (rank by shared origin, process, roast,
> and tasting-note terms).

**4. Prove it learned, the blind way.**
> Add a "blind take": generate a profile without showing the model my existing recipe, then show
> me the gap against my real settings. Then run a leave-one-out across all my profiles — hide each
> coffee's real recipe, predict it from character plus my taste model and my *other* profiles, and
> report how close it lands. I want to see whether it's actually reasoning from my taste and my real profiles, or just echoing my seed
> data.

**5. Connect to the Aiden (mind the traps).**
> Implement the Fellow cloud client per the "Fellow integration reality" section. Watch the two
> traps: fetch the profile list from the dedicated profiles endpoint (the device object no longer
> embeds it), and populate the client's internal profile list before any delete or deletes fail.
> Keep credentials in environment config, never in code, and do not log the auth response. Push a
> profile to the device and request its share link / QR.

**6. Solve the 14-slot problem, then sync both ways.**
> Implement pull + an append-only archive first (read-only with respect to the device — this alone
> outlives any eviction). Then build the managed sync from the brief: desired state (active vs
> archived, capped at 14) versus observed state (on-device or not), the queue derived live as the
> difference, and a manual, confirmed apply that processes removes before adds.

**A few cautions worth pasting when relevant:**
> Don't over-read a single generation — even a low-temperature run wobbles a few degrees run to
> run; you're aiming the starting point into the right neighborhood, not hitting an exact number.
> And where the model and one of my hard-won, atypical profiles disagree on a low-confidence
> coffee, trust my stored value over the model's.

---

*This brief documents the project through two-way device sync, the calibration method, grounding,
the Brew Log, and how the model improves over time.*
