DEV Community

Saran
Saran

Posted on

I Gave My Restaurant Dashboard a Memory Using Hindsight

The best answer my floor assistant gave me was “There is no record of any events or incidents involving Table 5 for yesterday.”

It sounds like a non-answer. It was the moment I trusted the system. I had asked about a table I knew was empty of history, and instead of inventing a story, it said it had nothing. That behavior came from changing where the memory lives, and this post is about that change.

What I built

Tablewise is a live dashboard for a restaurant floor: a table map with free, occupied, reserved and cleaning states, a waiting list, reservations, a staff view and a display mode for a wall screen. It was useful on day one and forgetful on day two. Every note a host said out loud, “table 5 wobbles”, “Reddy family has a peanut allergy”, “never seat ten people at T16 without asking the kitchen”, disappeared the moment the shift ended.

So I added an assistant tab. Staff can type things to remember, and ask questions like “what happened with table 5 yesterday?” or “what should I do tonight?”. The memory behind it is Hindsight, an open-source agent memory system, and the assistant is a thin layer on top of it. You can try it live at spice-routeee.netlify.app.

The first version was an if/else statement

My first assistant was a keyword matcher. It stored notes in the browser, tagged them by table number and a handful of words, and matched questions with regular expressions. It worked for the five phrasings I had tested.

Then I typed “Hello, I have a doubt” and got back a canned list of example questions. That is what a rules engine does when the input falls outside its rules. It was also the point where I stopped patching and started looking for real agent memory: something that stores what happened, understands a question phrased differently from how the note was written, and can reason over what it recalls.

Where Hindsight sits

The browser never talks to Hindsight directly. A small serverless function holds the API key and proxies four calls:

Browser (dashboard + assistant tab)
| /api/retain /api/ask /api/insights /api/seed
v
Netlify Function (holds the Hindsight key)
| retain / recall / reflect
v
Hindsight memory bank: one bank per restaurant

The Hindsight docs describe three operations, and I use all of them: retain to store, recall to fetch relevant memories, and reflect to answer a question by reasoning over memory.

Retain: write memories with a date

Everything staff tell the assistant, and every long wait the floor logs, becomes a memory. The important decision was to put the date inside the text and also pass it as the timestamp, so “yesterday” has something to resolve against:

js
const memPayload = i => ({
content: On ${i.d} (${ago(i.d)}) at ${fm(i.tm)}: ${i.tx} +
${i.tb ? [Table T${i.tb}]: ''},
context: i.k, // incident | instruction | fact | wait
timestamp: i.d + 'T' + fm(i.tm) + ':00+05:30',
});

On the server, the note goes into the restaurant’s memory bank:

js
await client.retain(BANK_ID, content, {
context: str(b.context, 100) || 'restaurant operations',
timestamp: when(b.timestamp),
async: true,
});

When I looked at what came back from recall, the stored memories were no longer my raw sentences. They read like extracted facts with a when and a where attached: “Table 7 is noisy due to its proximity to the bar. | When: 2026-11-07 | Where: bar”. I did not write that structure. Hindsight did.

Recall plus reflect: two calls per question

Each question runs both operations in parallel. reflect produces the answer, and it also gets the live floor state as context (which tables are occupied, who is booked tonight). recall returns the raw memories so the UI can show what the answer was built from:

js
const [rec, ref] = await Promise.all([
client.recall(BANK_ID, query, { budget: 'low', maxTokens: 2048 }),
client.reflect(BANK_ID, query, { budget: 'low', context }),
]);
return {
answer: ref?.text ?? '',
memories: (rec?.results ?? []).slice(0, 6).map(r => ({ text: r.text, type: r.type })),
};

Showing the recalled memories under every answer turned out to matter more than the answer itself. A manager can see “this is what I based that on” and decide whether to believe it.

What changed in practice

Before, with the keyword memory, an off-script message got the help text. After, with Hindsight behind it, “Any recurring issues?” came back as a written answer: Table 7 is noisy because of its position next to the bar, and a party of four recently waited 18 minutes, with a note that one long wait is not yet a pattern. Underneath, the memories it used were listed.

The insights panel, which used to be a set of hard-coded rules, is now one reflect call over everything retained. It surfaced the Mehta anniversary booking at 20:30 for twelve guests and suggested briefing the team. Nobody wrote a rule for that. It connected a reservation in the live floor state with what the bank knew.

And the answer from the top of this post: asked about Table 5 yesterday, before any history for that table existed, it said it had no record. A rules engine either returns nothing or guesses. This said what it did and did not have.

The bug that took longest: a health check that passed while recall failed

My first deployment reported “connected” and still answered from the fallback. The assistant was showing a green dot while every recall call failed with Authentication failed: Invalid API key format.

The cause was my health check. It called getVersion, which does not need a key, so it passed even though the key was bad. The fix was two parts: clean the key on the server (pasted keys pick up spaces, quotes and a stray “Bearer”), and make the health endpoint make one real authenticated call:

js
const cleanKey = v =>
v.trim().replace(/^["']+|["']+$/g, '').replace(/^bearer\s+/i, '').trim();

// in /api/health
await client.getVersion();
await client.recall(BANK_ID, 'health check', { budget: 'low', maxTokens: 64 });
return { ok: true, auth: 'ok' };

A health check that does not exercise the credential you actually depend on is worse than none, because it tells you everything is fine.

Limits I hit
Retain is asynchronous. I use async: true so the serverless function stays inside its time limit. A note saved a few seconds ago may not be recallable yet. For a host typing a note and immediately asking about it, that gap is visible.
Duplicate notes inflate confidence. I saved “table 7 is noisy” twice while testing, and the assistant started calling the table “consistently” noisy. It was one complaint stored twice. I do not de-duplicate yet.
Forgetting is local only. Deleting a note in the UI removes my browser’s copy, not the memory in Hindsight.
Serverless time limits are tight. I keep reflect and recall at the low budget for that reason.
The floor feed itself is simulated in the browser. Table status, the queue and bookings come from a simulation, not a point-of-sale system.
What I would tell someone starting this
Put the date in the memory. Store it in the text and in the timestamp. Relative questions like “yesterday” depend on it.
Show what was recalled. Listing the memories under each answer builds trust faster than a better-sounding answer.
Test your health check with the credential. If it can pass while the real call fails, it is lying to you.
Keep the key on the server. A browser-only app cannot hold a secret, so a tiny proxy function is not optional.
Expect asynchronous retain, and design the UI around it. Say “saved” and mean “queued.”

I started with a keyword matcher and a handful of regular expressions, and the gap between that and a memory that restates what it knows with dates and sources was larger than I expected. If you want to try the same thing, the Hindsight quickstart gets a bank running in a few minutes, and you can try the assistant yourself at spice-routeee.netlify.app.

Top comments (0)