Inspiration
Every group trip starts the same way. Someone makes a Google Doc. Someone else drops a list of cities in the group chat. Three people ignore it. One person vetoes Barcelona because they went last year. Someone wants "somewhere with good food" and someone else wants "somewhere affordable" and those two things mean completely different places to completely different people.
The real problem isn't that groups can't agree. It's that nobody knows what anyone actually likes — not in a way that's actionable. People are bad at articulating taste on demand. But they reveal it constantly in conversation: the artists they mention, the places they keep bringing up, the restaurants they say they loved or hated, the vibes they chase.
That's the insight behind Palatable. Your group chat already contains the signal. The question is what you do with it.
The bigger vision: a group chat app where taste is a first-class citizen. Not a plugin on top of WhatsApp or iMessage — a standalone chat app where every conversation quietly builds a taste profile for the group. Every artist someone mentions, every restaurant they loved, every place they vetoed — it accumulates in the background without anyone filling out a form or answering preference questions. When the group is ready to plan anything, Palatable already knows everyone's taste and negotiates on their behalf. Travel today. Restaurant tonight. Concert next week. One app, one taste graph, any group decision Qloo supports.
How it works at scale — incremental extraction, not batch processing. The naive approach — passing the entire chat history to an LLM when someone asks for a recommendation — doesn't scale for a real chat app with months of conversation. The production architecture runs extraction as a background job: messages land in a time-series store (PostgreSQL or Cassandra for high-write, time-ordered chat at scale), a scheduled worker periodically processes unprocessed messages in small batches, and the compressed taste graph is upserted into DynamoDB as structured anchor records. By the time the group asks "where should we go?", the taste graph is already built — the LLM never sees the full chat history at query time, only the anchors extracted from it.
Most Qloo integrations query transiently — ask once, get an answer, forget the user. Palatable accumulates signal. A group using it for six months has a taste graph that's genuinely hard to replicate. That's the product.
And every accept and reject feeds back to improve Qloo's taste graph itself. The accept/reject UI is already built — every time a group accepts a recommendation, that's a signal that those taste anchors translated into a real decision. Every reject is a signal they didn't. Routed back to Qloo, it makes the model better — not just for this group, but for every group with overlapping taste signals. The more Palatable is used, the smarter Qloo gets. That's the feedback loop that makes this a platform.
Palatable is that world, starting now.
What it does
Palatable reads taste signals from a group and negotiates a recommendation everyone can actually get behind.
Those signals come from real group chat — every artist mention, restaurant recommendation, place someone vetoed. To try it now without weeks of chat history, describe each person in a sentence or two. The extraction pipeline is identical either way: freeform natural language in, structured taste anchors out.
You pick a pre-built group to see it in action (with real seeded chat conversations), or describe your own group and run the full pipeline live — and the system does three things:
1. Extracts taste anchors from natural language. The extraction agent reads participant descriptions (or real chat history) and pulls out taste signals per participant: artists they mention, places they've been, cuisines they love, vibes they describe. Each signal is mapped to a Qloo entity ID where possible and assigned a confidence score. Only anchors that resolve to a Qloo entity with cross-domain signal enter the negotiation pipeline.
2. Queries Qloo for taste-aware affinities. With each participant's taste profile, we call the Qloo Insights API to get destination affinities. This is where generic AI falls apart and Qloo is indispensable: a group where two people are on a fado kick and one keeps mentioning natural wine bars gets completely different destination scores than a group whose anchors are mainstream pop and chain restaurants. Qloo's taste graph encodes those cross-cultural signals in a way no LLM training corpus can replicate.
3. Negotiates and synthesizes. Four agents total — the Extraction Agent pulls taste signals, then Negotiator + Advocate + Skeptic debate on the group's behalf. The Negotiator proposes candidates based on Qloo scores, the Advocate argues for the destinations that best match the group's strongest shared signals, and the Skeptic vetoes weak proposals and forces honest trade-off reasoning. When the loop settles, a synthesis step writes the final recommendation with per-person reasoning and honest trade-offs — not a corporate-speak compromise.
The whole pipeline streams live over SSE. Each agent's output appears as it completes. You watch the negotiation happen.
And you see the difference Qloo makes in real time. Every negotiation runs two paths simultaneously — Qloo-powered on the right, plain LLM with no taste data on the left. Same group, same anchors, both fire automatically. The comparison is built into the UI, not a separate demo step.
How we built it
Next.js 14 + TypeScript on the frontend and SSR layer. Three API routes: /api/extract for the extraction agent, /api/negotiate for the full Qloo → Negotiator + Advocate + Skeptic pipeline with SSE streaming, and /api/llm-only for the side-by-side comparison without Qloo.
AWS Mantle for all LLM inference — deepseek.v3.2 via OpenAI-compatible endpoint. The agent loop is a direct HTTP call, no SDK overhead on the inference path.
Qloo Insights API as the taste graph backbone. We query /v2/insights with entity affinity sets per participant, then use the scored results as the ground truth for negotiation. The trade-off agent works with Qloo scores, not LLM-generated preferences.
Amazon DynamoDB for three tables: groups, messages, and taste anchors. The anchors table is the core data model — it stores entity IDs, vibe context, confidence scores, and lifecycle status. Every extraction run adds new anchors and updates confidence on existing ones.
AWS Amplify for SSR deployment. Next.js SSR on Amplify requires careful env var handling — secrets go through SSM Parameter Store and need explicit IAM permissions on the Amplify service role.
Content safety via a custom guardrail layer — PII is stripped by regex (SSN, credit card, email, phone, address), and topic scope is checked by an LLM call via AWS Mantle before any content enters the negotiation pipeline.
The extraction prompt is the most important piece of engineering in the project. It classifies each mention as either a named_entity (artist, band, film, TV show, global brand, public figure) or a vibe (descriptive preference like "rooftop bars" or "live music"). Named entities are resolved to Qloo entity IDs via a priority-ordered type resolution — so "Con Todo El Mundo" resolves to the artist, not the TV show. The data model supports confidence scoring and lifecycle management (reinforced, contradicted, deprecated) — the schema is built for taste memory that persists and evolves.
Challenges we ran into
Anchor extraction fidelity. The hardest extraction problem is distinguishing "not feeling EDM lately" from "EDM is dead to me." One is a mood, one is a taste shift. Getting the LLM to correctly skip low-signal noise and extract only stable preference signals required significant prompt iteration. We designed the data model with confidence scoring and lifecycle states (active, deprecated, contradicted) to encode exactly that distinction — extraction is clean and the schema is ready for reinforcement and decay logic as the group keeps talking.
Qloo entity mapping from freeform text. Participants don't say "I enjoy music by urn:entity:artist:khruangbin." They say "just discovered Khruangbin, completely obsessed." Bridging freeform chat signal to Qloo entity IDs required an intermediate extraction step that maps natural language mentions to entity types before the Qloo query.
SSE streaming from Next.js SSR on Amplify. Amplify's SSR runtime doesn't behave identically to a raw Node server for long-lived streaming responses. Getting true SSE flushing to work through the Amplify SSR layer required explicit header handling and response flushing that wasn't obvious from the docs.
npm registry on Amplify. Our development machine's global npm config pointed to an internal registry. package-lock.json had baked in resolved URLs from that registry, causing ETIMEDOUT on every Amplify build. The fix was regenerating the lockfile with the public registry and adding an explicit registry override to amplify.yml — but tracing this took longer than it should have.
Env vars in Amplify SSR. Amplify SSR injects secrets via SSM Parameter Store at runtime, not as static build-time env vars. The Amplify service role needs ssm:GetParametersByPath — a permission that isn't added by default and isn't surfaced clearly in the Amplify console.
Accomplishments that we're proud of
The taste anchor data model is genuinely novel. Most "personalization" features are one-shot preference queries — ask what you like, query for it, forget it. The anchor schema we built — with entity IDs, confidence scores, and lifecycle states (active, deprecated, contradicted) — is a real foundation for persistent taste memory. Anchors are extracted and stored in DynamoDB per participant today. The reinforcement and decay logic builds directly on that schema — it's an extension of what's already running, not a redesign.
Qloo is doing real work here — and we prove it automatically. Every negotiation runs two paths simultaneously: the Qloo-powered result on the right panel, and an "AI ONLY" baseline on the left — same group, same taste anchors, plain LLM with no Qloo data. Both fire when the panel loads. You watch them arrive side by side: Qloo returns a culturally specific, affinity-scored pick with per-person attribution; the LLM-only path returns a generic guess. The taste graph is the product, and the comparison makes that visible without any extra clicks.
Four-agent pipeline with a deliberate negotiation loop. The Extraction Agent pulls taste anchors from natural language; then the Negotiator, Advocate, and Skeptic run the negotiation. Each of the three negotiation agents makes one focused LLM call with pre-assembled Qloo data — the intelligence is in the coordination layer, not inside each agent. Negotiator proposes, Advocate and Skeptic critique in parallel, and if either pushes back, the Negotiator revises with the critique as context, up to two rounds. And watching it stream live — anchor resolution, Qloo scores, agent verdicts, final synthesis — is a genuinely compelling demo moment.
A behavioral eval suite, not just unit tests. Beyond unit tests, we wrote a full eval suite that runs the end-to-end pipeline against fixture groups and checks eight behavioral criteria per case: at least one named anchor extracted per participant, destination affinity ≥ 0.60, attribution balance (no single participant driving more than 70% of anchor weight), convergence within 2 critique rounds, both Advocate and Skeptic rendering verdicts, and venue affinity above threshold. These aren't snapshot tests — they catch regressions in Qloo integration, prompt behavior, and agent coordination that unit tests can't reach.
Production-grade inference reliability. Every LLM call goes through exponential backoff retry logic — ECONNRESET, ETIMEDOUT, 429, 5xx, and truncated or non-JSON responses all trigger up to 3 retries. The negotiation loop doesn't surface a failure to the user unless all retries are exhausted. For a streaming demo where agents run in sequence and in parallel, this makes the difference between a compelling live demo and a broken one.
We built the foundation, not just the demo. The taste anchor model, the confidence lifecycle, the four-agent pipeline (Extraction → Negotiator + Advocate + Skeptic), the guardrails layer — none of this is demo scaffolding. It's a real architecture that extends to any group decision Qloo's taste graph supports. The demo is travel. The code is general.
What we learned
Taste is a graph problem, not a text problem. LLMs are good at understanding that someone likes "indie music with good bass lines." Qloo is good at knowing that someone who likes Khruangbin and Hiatus Kaiyote would probably love Tame Impala and might love Melbourne. That cross-entity, cross-domain signal is the hard part of taste modeling, and it requires a real taste graph — not more prompt engineering.
Signal quality matters more than signal volume. Early versions treated every taste mention as equal weight. The resulting recommendations were noisy — anchors from offhand comments competed with deeply-held preferences. The fix was improving extraction fidelity — better prompt logic to skip noise, and type-priority resolution to pick the right Qloo entity. The confidence and lifecycle model is the natural extension of the same insight: weak signals decay, strong ones persist — the schema supports it today.
Streaming changes the demo experience entirely. Showing the final recommendation is fine. Showing the agents negotiate in real time — extraction pulls out anchors, Qloo returns scores, trade-off agent reasons through vetoes, synthesis writes the conclusion — is genuinely compelling in a way that a static result page isn't.
Infrastructure friction is real. The Amplify SSR + SSM + IAM permission chain is non-obvious, underdocumented, and took a meaningful chunk of Day 2. For any Next.js SSR project on Amplify: set up IAM permissions for the SSR logging role on Day 1, not Day 2.
What's next for Palatable
Palatable is live and working. Here's the roadmap.
Stage 2 — Persistent Taste Memory. Anchors accumulate across conversations, not just one session. Confidence evolves — reinforced signals strengthen, contradicted ones decay. The taste graph grows with the group every time they talk.
Stage 3 — Any Decision Type. Restaurants tonight. Concerts this weekend. Activities on a trip. Same extraction pipeline, same agents, same Qloo integration — just different entity types. Travel is the demo. The architecture is general.
Stage 4 — Feedback Loop to Qloo. The accept/reject flow is already built — every time a group accepts or rejects a recommendation, that decision is captured. In Stage 4, those signals route back as taste data: an accept reinforces the anchors that drove the pick, a reject weakens them. Aggregate signal across groups makes Qloo smarter for everyone.
Stage 5 — The Full Product. A persistent group chat app where taste data accumulates naturally. Planning intent triggers the pipeline automatically. Any group decision Qloo supports, handled by a system that already knows the group. The moat: a taste graph built from real group behavior that gets harder to replicate every day.
Built With
- amazon-dynamodb
- amazon-web-services
- amplify
- deepseek
- mantle
- multiagent
- next.js
- node.js
- qloo
- react
- rest
- tailwindcss
- typescript

Log in or sign up for Devpost to join the conversation.