AGORA+ Taste Council
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@AGORA+ Taste CouncilWhat should we watch for family movie night?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
AGORA+ Taste Council
A household assistant that turns what it remembers about each person into Qloo signals, ranks one shortlist for each of them, and picks the option nobody ranks low.
Live demo: https://agora-plus-295478535981.us-central1.run.app (household passcode: tastecouncil — a fictional family is preloaded)
Submitted to the Qloo Agentic Hackathon ("Agents, but with taste"). Source code is MIT (LICENSE). The deep dive is docs/QLOO.md. Copy-ready submission text is docs/SUBMISSION_QLOO.md.
The problem
AGORA+ is a self-hosted household assistant. It keeps facts about each family member in a file on your server, every write comes back with a receipt, and anything with side effects waits for a person's yes. It is reachable as an MCP server (Claude, ChatGPT, Alexa+-style clients) and from a voice and text assistant on its Home page.
It already knew "Mia loves Encanto", "Dadi is 74" and "Arjun is vegetarian". But when someone asked "what should we watch tonight?", a language model alone could only guess. It tends to pick something generic, or something for whoever spoke last. It has no grounded way to say how a 74-year-old who loves Raj Kapoor films and a 9-year-old who loves Encanto would each rank the same five films.
That is a daily question in multigenerational homes, which are common in India and in many other places: movie night, dinner out, a road-trip playlist, a show to watch together, a family book club, a weekend outing. The Taste Council answers it with Qloo, and shows its work.
Related MCP server: Swiggy Syndicate
Try it in 60 seconds
On the live demo (a fictional family: Mia 9, Rohan 16, Arjun 41, Dadi 74):
Open the link and enter the household passcode. Home opens.
Find the Taste Council card. There is one chip per person, with their age band, what they love, and what Keeper watches for (for example "Keeper watches: vegetarian").
Leave everyone in, choose Family movie night, and press Ask the council.
The result sheet shows the pick, each person's rank for it ("#2 of 10"), who it suits least ("Lowest fit"), Qloo's tags, and the runners-up.
Open Qloo calls at the bottom. Every call is listed: the tool, who it was for, the status, the result count, the time in ms, and the exact arguments sent.
Choose Dinner out with the location "Bandra, Mumbai". Arjun's Qloo calls ask for places with vegetarian options, and if a candidate is clearly meat-centric (a kebab or fried-chicken place) or a satay place, Keeper sets it aside under "Keeper removed" and quotes the fact ("Arjun is vegetarian", "Mia is allergic to peanuts").
Tap a chip to leave someone out, for example only Mia and Dadi, and ask again.
In the assistant box, type "What should we watch tonight?". The model calls the
taste_councilMCP tool, and Under the hood shows thattools/call.
How the council decides
flowchart TD
mem["Household memory: 'Mia loves Encanto', 'Dadi is 74', 'Arjun is vegetarian'"]
parse["profile.ts: per person loves, likes, age band, constraints (no model)"]
rec["qloo_recommend, once per person: their loves as signals, plus age band and location"]
short["Shortlist: round-robin across people, deduplicated, at most 10"]
rank["qloo_rank, once per person, same shortlist: ordinal ranks"]
maximin["Maximin: best worst-rank, then best mean rank"]
keeper["Keeper: drop picks that clash with a diet, allergy or 'no X' fact, and quote it"]
out["Top 5 picks, one spoken sentence, every Qloo call"]
mem --> parse --> rec --> short --> rank --> maximin --> keeper --> out
home["Home page: POST /api/taste/council"] --> parse
mcp["MCP tool taste_council: Claude, ChatGPT, the Gemini voice assistant"] --> parseFacts become signals. server/taste/profile.ts reads household memory with plain rules. "Dadi loves Raj Kapoor films, Lata Mangeshkar and Mughal-e-Azam" gives three named loves. "Arjun likes thrillers" gives a tag. "Dadi is 74" gives the band
55+. "Mia is allergic to peanuts" gives a constraint.Candidates per person. One
qloo_recommendcall per person, for the target type of the occasion (movie, tv_show, artist, book, or place). Dinner asks for Qloo's restaurant genre, an outing for attractions such as parks and museums. A vegetarian's or vegan's dinner calls also carry Qloo's vegetarian or vegan options tag. When someone is under 18, films, shows, music and books rated for adults are left out, and the result says so.One shared shortlist. Round-robin across people, so everyone's candidates get in. At most 10 options, because
qloo_rankaccepts at most 10.Rank the same list per person. One
qloo_rankcall per person over that shortlist. Qloo says scores from separate calls are not comparable, so the council keeps only each person's order (1st, 2nd, ...).Maximin. The winner is the option whose worst rank across people is best. Ties go to the better mean rank. Nobody gets their 9th choice so that someone else gets their 1st.
Keeper. Picks that clash with a diet, allergy, or "no horror" fact are removed, and the fact is quoted. Here Keeper is plain code (a keyword check), not the model agent of the same name in the household team.
Receipts. The result carries every Qloo call. The Home page keeps the last few councils after a reload.
The algorithm is in server/taste/council.ts. The contract shared by server and page is shared/taste.ts.
Qloo tools used, and why
The council talks to Qloo through Qloo's official harness, qloo mcp (package @qloo/qloo-harness@0.1.26), started as a child process over stdio (server/taste/qlooClient.ts). That is the route Qloo's hackathon kit recommends. There are no raw HTTP calls to Qloo in this repository.
Tool | When | What we send | Why this tool |
| Once per person |
| Turns each person's favourites into candidates of the right type, across domains: an artist can be the signal for films. |
| Once per person | The same | Ranks one fixed list against one person's taste. Comparing positions across people is what makes the choice fair. |
| Before each council, and for the card's "ready" state | Nothing | Checks that |
The harness offers nine workflow tools. The council uses the two that fit a group decision. qloo_describe, qloo_entity_tags, qloo_find_tags, qloo_compare_audiences, qloo_audience_demographics, qloo_where_popular and qloo_trends are not used yet.
When Qloo cannot resolve a name, the harness returns needs_input with candidate matches. The council chooses one (a film or artist over an album of the same name), retries once with its Qloo id, and remembers it in taste-entities.json, so later calls send the id (server/taste/entityMemo.ts). With AGORA_DEMO_SEED=1 the demo family starts out resolved. Calls are cached for 30 minutes and run one at a time by default (AGORA_QLOO_IN_FLIGHT, 1 to 4). A rate-limited (429) call is retried after 1.5 s and 4 s, within the council's 40-second deadline. A location such as "Bandra, Mumbai" filters places to Bandra and sends Mumbai as the audience location. See docs/QLOO.md, which also has live results from the deployed demo.
What is Qloo, what is code, what is Gemini
Part | Done by |
Resolving "Encanto" or "A. R. Rahman" to Qloo entities | Qloo (inside the harness) |
Candidate options per person | Qloo |
Each person's ordering of the shared shortlist | Qloo |
Tags, year, rating, address and images on a pick | Qloo result metadata |
Reading facts into loves, likes, age bands and constraints | Deterministic code, no model |
Shortlist, maximin, tie-breaks, Keeper vetoes, the spoken sentence | Deterministic code, no model |
Hearing "what should we watch tonight?", choosing | The agent's model. On the server that is Gemini on Vertex AI ( |
No model chooses the winner. The Home page's Ask the council button runs with no model at all.
Why it needs Qloo
Take Qloo out and the council has nothing to rank with.
Cross-domain taste. Dadi's signals are a filmmaker, a playback singer and a 1960 film. Mia's are two animated films and a pop star. Qloo's taste graph turns both into ranked films, books or restaurants. A model can name films, but it has no measured affinity behind the order.
Audience demographics. An age band and a location become a Qloo demographic signal, so "seniors in Bengaluru" and "teens in Bengaluru" pull the ranking in different directions.
Rank against a profile.
qloo_rankputs one fixed list in order for one person. That per-person order is the input to the fairness rule. A model has no grounded per-audience ranking to give.
Responsible use
What goes to Qloo: entity names as written in memory ("Encanto"), abstract likes as tags ("thrillers"), an age-band phrase ("seniors", "adults 35-44"), a location string, the target type, and the shortlist's Qloo entity ids. An "avoid" fact such as "no horror" is sent as an exclude tag for films, shows, music and books. At dinner, a vegetarian or vegan diet is sent as Qloo's "vegetarian options" or "vegan options" place tag, with no name attached.
What stays on the server: first names, exact ages, allergies, other diets, and the text of the facts. Names appear only in the local call log ("for Mia"). Keeper checks diets and allergies locally.
The key is read by the
qloo mcpchild fromQLOO_API_KEYor the harness's own config. Our code never logs it or returns it, and scrubs it from error text.Aggregate, not personal. Qloo results are aggregate cultural affinities, not predictions about any one person. The card and the result sheet both say so.
Nothing is booked or bought. The council suggests. Adding a pick to the to-do list is a separate button.
The demo family is fictional. It is written only when
AGORA_DEMO_SEED=1and memory is empty, and it is taggeddemo.
Limits
What a council result does not establish:
That a person will enjoy the pick. "#2 of 10 for Dadi" means Qloo placed it second among those ten options for a profile built from Dadi's saved favourites.
That the pick is the best option anywhere. Ranks are only within a shortlist of at most 10.
That the pick is suitable for a child. With someone under 18, only adult ratings (R, TV-MA, A, 18) and explicit titles are left out. Only an age band is known, so TV-14 and PG-13 still pass for a 9-year-old, and books have no rating. A parent should check.
That a place is safe for an allergy or a diet. Keeper is a keyword check on names, tags and descriptions, not a menu or ingredient check. The diet tag only says whether Qloo lists vegetarian or vegan options.
That "#2 for Mia" and "#2 for Dadi" mean the same strength of liking. Only order within one person's call is used.
Other limits:
Under-18 maps to the phrase "teens", which Qloo's harness turns into its youngest age bucket (24 and younger). There is no "children" demographic, so a 9-year-old and a 16-year-old get the same age signal.
Age and location signals are sent only when a location is known (the request, or
AGORA_TASTE_LOCATION). Only the first comma part of a location filters places.A person with only abstract likes ("street food") and no named favourite or location cannot be ranked. The result says so.
The fact parser is English pattern rules. Unusual phrasing is missed.
taste_profileshows what it understood.Maximin is one fairness rule. It can choose a pick everyone ranks third over one most people rank first.
When
needs_inputoffers several matches, the council chooses one without asking and remembers it. It can be the wrong "Coco", and stays wrong untiltaste-entities.jsonis cleared.One household per server.
Run it yourself
Full setup from a clean machine, environment variables, and the Cloud Run command are in docs/QLOO.md. The short version:
npm install --global @qloo/qloo-harness@0.1.26
qloo setup --qloo # paste the key; it is hidden
qloo config set base-url https://hackathon.api.qloo.com
git clone https://github.com/Ritesh-Root/agora-taste-council.git && cd agora-taste-council
npm ci
AGORA_DEMO_SEED=1 AGORA_TASTE_LOCATION=Bengaluru npm run devOpen http://127.0.0.1:3000/. The Taste Council card works without any language model. The assistant box needs one (see Quick start).
From Claude Code, with AGORA_MCP_TOKEN set (at least 32 characters):
claude mcp add --transport http agora http://127.0.0.1:3000/mcp --header "Authorization: Bearer $AGORA_MCP_TOKEN"Then ask "What should the family watch tonight?" or "Where should Arjun and Mia eat in Bengaluru?".
The rest of AGORA+
The household layer was first built for the Amazon Developer Hackathon (Alexa+ track), on top of the AGORA office. The sections below describe that base. They are still accurate for this build.
Household memory is a file on a server you run. Every write returns a receipt whose verified flag is a second read of that file. Nothing with side effects starts until a person says yes, and that wait is in the tool result, not only in a prompt.
The surfaces that were run for the Alexa+ entry are the unofficial voice page at /alexa and the MCP server at /mcp, used from Claude Code with AGORA_MCP_TOKEN. MCP clients such as Alexa+ add-ons, Claude, and ChatGPT can connect to that same server. ChatGPT was not run against it, and a live Alexa+ add-on was not deployed. The add-on package in alexa-addon/addon.json is still a template: every URL contains <host>.

Why memory with receipts
Other assistants already advertise family memory, shopping lists, and device control. This server keeps household facts on the machine you run, and a plan does not start until a person says yes. Lights, locks, calendars, and countdown timers are not in this build.
The Alexa+ entry was shaped by four complaints from an 8 Oct 2026 research pass: jobs that stop halfway, slow replies, stored facts that disappear, and people who want a checkpoint before an agent acts. Quotes and sources are in docs/research/alexa-plus-voice-of-customer.md and docs/research/sources.json. In this build:
remember,recall, andforgetare backed bymemory.jsonand the MCP resourceagora://memory. A fact can name a person (who). The Household panel can edit it.forgetandplan_and_runreturnneeds_approval. Remembering a fact and adding a task do not wait. On/alexathe model cannot callapprove_action. A person says an exact yes or no, or uses a button.approve_actionstays an MCP tool for external clients.A plan shows its steps and waits for a yes before the team starts. The receipt on a write is a separate thing:
verifiedis a read-back from disk.
What it does
Household Home at
/(also/alexa). This is the page to open first. A greeting with the briefing, the assistant, the Taste Council, Today's reminders, the Shopping and To-do lists (tick, untick, remove, clear the basket), Family facts grouped by person with Allergy, Diet, and Medical badges, and Plans. While a plan runs, the Plans card shows each team member's step and its status; a finished plan opens in the page. Connect shows the/mcpendpoint and theclaude mcp addcommand. Under the hood opens the MCP trace beside the page.Voice assistant on Home. Unofficial simulated Alexa+ experience. Type, or tap or hold to talk. Remember, recall ("What is Mia allergic to?"), the briefing, the shopping list, reminders, and an exact yes or no are routed with no model call (server/alexaSim/fastPath.ts). Other turns go to the server model, which calls tools through the real
/mcpendpoint. A plan asks the model once for the step list, and only after that preview returns steps does the page wait for a yes. Orders, purchases, bookings, payments, and phone calls are refused, with no approval id. The page shows an "Under the hood" trace of each MCPtools/call. While it is open it pollsGET /api/householdabout every 30 seconds and speaks "Reminder: <title>." once per due task per page load.MCP server at
/mcp. Thirteen tools always (includingtaste_councilandtaste_profile), andweb_researchas a fourteenth whenFIRECRAWL_API_KEYorEXA_API_KEYis set. Three resources (agora://memory,agora://tasks,agora://runs/{id}/document) and two prompts (morning-briefing,plan-a-week-of-meals). Streamable HTTP. The server negotiates protocol 2025-11-25 and newer (server/mcp/mcp.test.ts). Alexa's published client sample sends2025-03-26. The specification index on 8 Oct 2026 names 2026-07-28 as current (docs/council/reports/g4-spec-followup.md). OAuth is in server/mcp/oauth.ts.daily_briefingandlist_tasksput the MCP Apps card on the tool result as well as the tool definition (ui://daily_briefing/household-card.html). The tool reference is skills/agora-household/references/TOOLS.md.People, lists, and reminders. A fact can store
who(a leading name such as "Mia is allergic to peanuts" is enough). A task can storewho,list(shoppingis its own list; absent means the main to-do list), anddueAt(ISO 8601 with a numeric offset). The free-textduestays. The briefing speaks one memory line, allergies and medicine first ("Remember: Mia is allergic to peanuts."). Recall says both facts when they tie.Agent Skill skills/agora-household/SKILL.md. How to connect, which tool to call, and the rule that a
needs_approvalresult waits for a person.Server model.
AGORA_SERVER_LLMpicks it.vertexcalls Gemini on Vertex AI through its OpenAI-compatible endpoint, with Application Default Credentials (server/llm/vertexProvider.ts).bedrockcalls Amazon Bedrock through the Claude Messages endpoint (server/llm/bedrockProvider.ts), only when AWS credentials are present.openaicalls any OpenAI-compatible endpoint (AGORA_LLM_*in .env.example, server/llm/openaiProvider.ts).Household team: Agora, Scout, Planner, and Keeper (server/household/team.ts), on whichever server model is set. Keeper checks plans against memory. A step whose model call fails is marked for help and the rest of the team finishes. The plans recorded during the Alexa+ hackathon used the
openaimode with open Qwen models on Featherless, because no AWS credentials were present. A live Bedrock call was not recorded in this workspace.Polly speaks when AWS credentials are present (server/alexaSim/tts.ts). The browser voice speaks when Polly returns 503. A successful Polly synthesize was not recorded, so time to first audio was not measured.
Approvals and receipts.
forgetandplan_and_runwait. Remembering and adding a task do not. Every write can show{ action, id, verified, at }.verifiedis a follow-up read. A failed yes leaves the approval pending so it can be retried. Waiting approvals still live in the process and expire after ten minutes.3D office at
/office. The Household cast is the roster. A household run is written toruns/<id>.jsonwithstatus: "running"when it starts, and again when it ends. The Society panel showsspokenSummarywhen the run has one.Alexa+ add-on package in alexa-addon/addon.json, plus public/privacy.html and public/terms.html. Account linking is enabled in the file (authorization code, PKCE S256). The manifest lists the United States only, and every URL is still
https://<host>/....alexa-aiwas not deployed from this workspace.
The file map is docs/ARCHITECTURE.md.
Screenshots
Captured from the running app on 10 and 12 Oct 2026, before the Taste Council was added.
Claude Code, a separate MCP client, answering from the same household. The output of a real claude -p run, set as text:

A forget request sent over MCP comes back as needs_approval and waits on the Home page for a person:

Quick start
You need Node.js and npm. The Dockerfile uses Node 22. Qloo's harness asks for Node 22.19.0 or newer. The repository does not declare an engines field.
git clone https://github.com/Ritesh-Root/agora-taste-council.git
cd agora-taste-council
npm ci
cp .env.example .envThe voice page, remember, the briefing, the shopping list, recall, and the Taste Council card do not need a language model. The Taste Council needs Qloo (docs/QLOO.md).
For Gemini on Vertex AI, sign in with gcloud auth application-default login and start the server with these in the shell. npm run dev copies only AGORA_*, AWS_*, BEDROCK_*, POLLY_*, QLOO_* and a few other named settings from .env into the server, so GOOGLE_CLOUD_PROJECT and VERTEX_* must come from the shell:
AGORA_SERVER_LLM=vertex GOOGLE_CLOUD_PROJECT=your-project npm run devDefaults are google/gemini-3.8-flash, and google/gemini-3.5-flash-lite for spoken turns. Override with VERTEX_MODEL and VERTEX_FAST_MODEL, or set ALEXA_SIM_MODEL=google/gemini-3.8-flash to use Flash for spoken turns too.
For Amazon Bedrock instead, with AWS credentials on the standard chain (aws login, a profile, or a task role), in .env (values only, never commit the file):
AGORA_SERVER_LLM=bedrock
AWS_REGION=us-east-1Bedrock defaults are anthropic.claude-sonnet-5 and anthropic.claude-haiku-4-5. Override with BEDROCK_MODEL and BEDROCK_FAST_MODEL if you need to.
Claude Code needs a development token of at least 32 characters. A shorter value is ignored.
AGORA_MCP_TOKEN=choose-at-least-32-random-charactersOAuth also needs AGORA_OAUTH_CLIENT_ID, AGORA_OAUTH_CLIENT_SECRET, and AGORA_TOKEN_SECRET (at least 32 characters). A public process also needs AGORA_PUBLIC_URL and AGORA_HOUSEHOLD_PASSCODE. If the OAuth secrets are empty, those routes return 503 and the development token is the one that can call tools. The token is ignored when NODE_ENV=production or AGORA_PUBLIC_URL is https, unless AGORA_ALLOW_DEV_TOKEN=1. The voice assistant reaches its tools through /mcp, so it needs OAuth or an accepted development token too.
npm run devnpm run dev starts Vite on port 3000 and binds 127.0.0.1, with the room relay and /mcp. Open http://127.0.0.1:3000/ for the household Home. The optional 3D team view is http://127.0.0.1:3000/office. /alexa is the same Home. npm run dev:lan binds 0.0.0.0. On that bind, /mcp and OAuth return 503 until AGORA_PUBLIC_URL is set, and household routes from another machine return 403 until AGORA_HOUSEHOLD_PASSCODE is set.
Environment
Names the server reads. Empty in .env.example means unset. The Qloo and Vertex names are in docs/QLOO.md. The full table, including office and deploy names, is docs/DEPLOY.md. Do not commit real values.
Variable | What it does |
| When set, household HTTP routes need the |
| Development bearer for Claude Code and the Inspector. At least 32 characters. Ignored in production or behind an https public URL unless |
| Set to |
| HMAC secret for access and refresh tokens. OAuth stays off when it is shorter than 32 characters. |
| OAuth client id. |
| OAuth client secret. HTTP Basic or the form body, never both. |
| Comma-separated exact |
| Public HTTPS origin, no path. Required before OAuth or |
| Set to |
| Household files. Default |
| Calendar day for "due today" and reminder times. The server reads it. |
|
|
| Set to |
| Default location for the Taste Council. Enables age-band signals and filters places. |
| Room host code. On an open bind with no passcode, the office has no first-caller host. |
Other scripts:
npm run build # client in dist/, and the household card
npm run preview # static client only; no room and no MCP
npm run lint # client typecheck
npm run lint:server # server typecheck
npm test # unit tests
npm run test:e2e # Playwright; starts its own server on port 3000./run-agora.sh is an optional Chrome launcher for hardware WebGPU on Linux. The office runs without it.
Hosting (named tunnel, ECS, Cloud Run) is docs/DEPLOY.md. The Cloud Run command with Qloo and Vertex is in docs/QLOO.md.
Connect from Claude Code or any MCP client
The skill is skills/agora-household/SKILL.md. With the development token:
claude mcp add --transport http agora http://127.0.0.1:3000/mcp --header "Authorization: Bearer $AGORA_MCP_TOKEN"Any other client uses Streamable HTTP POST http://127.0.0.1:3000/mcp with Accept: application/json, text/event-stream and Authorization: Bearer <token>. A client-credentials token (scope=mcp:service) can list tools. Calling them needs mcp:tools, from the authorization-code grant or from AGORA_MCP_TOKEN.
Try: "What is Mia allergic to?" after you have saved that fact on /alexa, or "What should the family watch tonight?" once Qloo is set up.
History: the Alexa+ hackathon (Aug 31 – Oct 23, 2026)
AGORA existed before that window. The baseline tag pre-window is commit 4f61371 (2026-08-20). git diff --stat pre-window..HEAD on 8 Oct 2026, at commit fa7a9c0, was 288 files changed, 29768 insertions, 2318 deletions, over 40 commits dated 2026-09-23 through 2026-10-08. September kept building the office. 8 Oct added the household brain and the fix wave: people on facts and tasks, the shopping list, reminders, the approval gate in the voice loop, the household lock, persisted OAuth refresh, the MCP Apps card on tool results, and the Household cast in the office. The phase log is docs/HACKATHON_CHANGES.md.
That entry's tracks were Alexa+ (primary), AWS Builder, and Open Source. Its Devpost text is docs/SUBMISSION.md. Also: docs/ARCHITECTURE.md, docs/AWS.md, docs/DEMO_SCRIPT.md, docs/FEEDBACK.md, docs/FRICTION_LOG.md.
The number to read on /alexa is time to first audio. It was not measured: the browsers on the build machine had no speech voices, so the fast path only ever showed "text only", and Polly and the model path need credentials. Older local timings were acknowledgement and screen times, not first audio: phase 4 saw the acknowledgement on the wire in 5 ms and on screen in 50 ms and 63 ms; phase 6 logged 55–58 ms to that acknowledgement, about 100 ms for a full remember turn, and about 800 ms from "plan dinners" to the approval card when a preview existed.
The Taste Council, the Vertex AI provider, the demo family, and the Qloo docs were added after that window, for the Qloo Agentic Hackathon.
Household limits
One household per server, and one office room in the memory of that process (server/roomState.ts, docs/DEPLOY.md). Waiting approvals and authorization codes are in that same process. Approvals expire after ten minutes. OAuth refresh records and the simulator's linked account are files and survive a restart.
Household files are on the task disk (
.agora-dataorAGORA_DATA_DIR). Replacing the task deletes them unless that directory is on a volume.POST /api/memoryandPOST /api/tasksdo not yet acceptwho,list, ordueAt. A fact that starts with a capitalised name still storeswho. The MCP tools accept the fields.The Alexa+ add-on manifest distributes to the US only and was not deployed (alexa-addon/addon.json).
No device control, no calendar, and no countdown timer. Lights, locks, and Hue routines stay out of the demo (docs/research/feature-matrix.md).
The microphone uses Web Speech (src/alexa/useSpeechInput.ts). Record in Chrome. The text box works when that API is missing.
A live Bedrock call and a successful Polly clip were not recorded here.
Taste Council limits are above and in docs/QLOO.md.
The shared office

AGORA is Ritesh-Root’s open-source workspace where people and AI agents share tasks, chat, and one Markdown document. The session is drawn as a 3D office. When WebGPU and WebGL2 are both unavailable, the same session uses a 2D office.
This repository is a working local workspace, not a finished hosted product. A room lives in the memory of one server process. The current source includes a production process and host controls. The office has no public deployment URL of its own; the live demo link at the top is the household Home.
What you can do
Implemented in the current source:
Join one shared room, up to five people, with a display name and a color. There are no accounts.
Talk in the room.
@allasks the team to discuss the idea.@nameaddresses one teammate. Chat does not start the document.The host submits a brief and starts the run with Run Swarm, then Execute Swarm.
Watch planning, worker tasks, a debate when the run calls for one, and the assembled Markdown document.
See the session in a 3D office (WebGPU, or WebGL2) or the 2D fallback.
Save the document to your own machine as
agora-document.md.Leave on your own, or, as host, end the session for everyone.
Save more than one OpenAI-compatible model connection in this browser. One is the default. An agent can use another connection and override its model. Keys stay in the browser and are not stored in the room.
Start the header’s benchmark control. That automated comparison is not a user study. See Validation and performance.
Not in this build: saved cloud rooms, accounts, spending caps, and host approval before a document edit becomes final. See Roadmap.

Joining the office
Install and run from Quick start. Open http://127.0.0.1:3000/ for the household Home. The optional 3D team view is http://127.0.0.1:3000/office.
On loopback, with no DEMO_HOST_SECRET, the first person to join is the host. On an open bind (0.0.0.0) with that secret unset, the first caller is not the host. The host seat is a household session, which exists only when AGORA_HOUSEHOLD_PASSCODE is set. With neither the secret nor the passcode, that room has no host. With the secret set, only a matching host code, or the server-issued resume token, makes someone the host. A display name does not. When a passcode is set, the office socket needs the agora_household cookie.
A browser-supplied model key stays in localStorage under Model connections. The room does not store it. With a server model set (AGORA_SERVER_LLM), the office can call it when the browser sent no key. See docs/AWS.md.
How a session works
People join the room. Everyone connected can see the brief, the tasks, the document, and public chat.
@alland@namesend chat. The named agents can reply. That discussion does not create the document.The host runs Execute Swarm. A second Execute while a run is active is refused with “A run is already in progress.”
Planning splits the brief into tasks. Workers run those tasks. An invalid worker output can be retried once.
Debate is conditional. With fewer than two usable worker outputs, there is no debate. Otherwise the first two are debated for up to two rounds. A referee scores each round, and a clear winner ends it early. Without one, the referee sums up the decision the host has to make. Then the lead writes one Markdown document.
If the brief says “under 400 words,” the document may be at most 399 words. A word is a whitespace-separated span after trimming, and headings count. Up to two shortening passes may run. A draft that is still over the limit is kept and labelled “Needs revision: exceeds word limit.” If both the word limit and required sections fail, the word-limit label is the one shown. Repair calls are counted separately from the other model calls.
Section checks run only when the brief names problem, audience, priorities, and next steps. A miss is labelled “Needs revision: missing required sections.”
Save a copy downloads the Markdown. The room copy disappears when the process restarts.
Leave room drops only that person. End session, confirmed with End for everyone, is host-only. The server rejects anyone else with “Only the room host can end the session.” and does not wipe the room. A successful end stops scheduling, clears the room and the run, tells every client, and closes the sockets. A late model result cannot restore that document.
After you leave or the host ends the session, the page stays disconnected. An unexpected drop shows a reconnect screen. The separate Watch a live run control is an explicit local replay.
System architecture
flowchart LR
ui[Browser UI]
http[HTTP and WebSocket server]
room[In-memory room and run]
orch[Orchestrator]
model[OpenAI-compatible chat API]
ui --> http
http --> room
room --> orch
orch --> model
model --> orch
orch --> room
room --> uiThe browser shows the office, chat, task board, and document. In development, the Vite server attaches the relay and the /api/cors-proxy route. The production entry, server/prod.ts, listens on PORT (8080 if unset). It binds 0.0.0.0 only when AGORA_HOUSEHOLD_PASSCODE is set, and 127.0.0.1 otherwise. It serves dist/ and attaches the same relay and proxy.
One process holds one room: roster, the last 100 public chat messages, cabin assignment, and a single run. Stages reported on that run are planning, working, debating, assembling, and repairing.
The orchestrator asks an OpenAI-compatible /chat/completions endpoint. The browser proxy forwards only an https URL whose path ends in /chat/completions, and only when the caller sends Authorization. It refuses private, loopback, and link-local targets, and it refuses redirects.
The office room is still the memory of one process. A restart clears the roster, the chat, and the live run. Household facts, tasks, and finished runs are files under .agora-data (or AGORA_DATA_DIR). See docs/ARCHITECTURE.md.
Model configuration
Open Model connections in the header. Each saved connection has a name, an OpenAI-compatible base URL, an API key, and a default model ID. Test connection calls that endpoint and shows a short error. It does not print the key.
One connection is the default for this browser. An agent uses that default unless you pick another connection for it. A model ID on the agent replaces the connection’s model only when it differs. Every browser connection uses the same chat-completions protocol. The server's Bedrock path is separate and uses the Claude Messages endpoint. See docs/AWS.md.
A single configuration saved earlier under byok-config is copied into the connection list on the next load. The key, URL, and model are kept. The copy is stored in this browser under agora-model-connections.
Key handling:
Your connections and keys stay in this browser. Room state stores chat, tasks, and the document. It does not store keys.
A caller key is sent as-is and is never replaced by the server key or by another connection’s key.
A call without a caller key fails with “Add an API key for that model endpoint.” when no server model is set. With
AGORA_SERVER_LLMset tobedrock,openai, orvertex, server/relay.ts uses that server model instead.A failed call does not switch to another person’s key, or to a different saved connection.
The browser proxy copies the caller’s
Authorizationheader and does not attach the server key.Provider failures shown in the room are short messages, such as “The API key was rejected.” or “That model is not available.” The provider body and the key are not shown.
Do not commit keys. .env is gitignored.
Validation and performance
These are reported local validation results from clean commit 6983ddc (“Keep a swarm document inside the brief and one active run per room.”). They are functional gate checks. They are not real-user benchmarks and they are not proof of usefulness. This README pass did not rerun them. Later commits, including the production entry and host controls, are not covered by this table.
Check | Run | Recorded result |
Consecutive film brief 1 |
| 258 words |
Consecutive film brief 2 |
| 349 words |
Consecutive film brief 3 |
| 396 words; one repair |
WebGL-disabled completion |
| 302 words; 2D fallback |
Workers reload, first attempt |
| State restored and duplicate Execute refused; model timed out after three HTTP attempts; no document |
Workers reload, second attempt |
| State restored; 1,136-word document on a brief without a word cap |
Debate reload |
| Same run restored at a later stage; 378 words; 55.06 seconds; one repair |
Assembly reload |
| Same run restored; 1,490 words; 114.86 seconds; brief had no stated word cap |
For the debate-reload run, reported usage was 13 model calls: eight planning/worker/merge, four debate/other, one repair; 7,176 input tokens and 4,651 output tokens.
For the assembly-reload run, reported usage was 11 calls: seven planning/worker/merge and four debate/other; 13,239 input tokens and 13,573 output tokens.
How to read the numbers:
A word is a whitespace-separated span, including headings. The current source still counts that way in server/society/documentCheck.ts.
“Under 400 words” means at most 399.
Up to two shortening passes are allowed.
A draft still over the limit is preserved and labelled “Needs revision: exceeds word limit.”
Repair usage is counted separately.
The reload checks did not create a second run id.
Cost was unavailable. It is not zero.
The timings are single observations on different briefs and conditions. They are not averages and they are not a speed ranking.
No controlled single-agent versus swarm comparison, and no independent user study, has been completed.
Swarm Bench’s automated judge is not evidence that the workspace is useful to a person.
Deployment
Verified from this repository:
npm run devserves the app and the room together.npm run buildwrites the client todist/.npm run previewserves that client and does not start the room.Dockerfile builds the client, bundles server/prod.ts to
dist/server.mjs, and runsnode dist/server.mjs.That process listens on
PORT, default 8080, and binds0.0.0.0only whenAGORA_HOUSEHOLD_PASSCODEis set. It servesdist/and attaches the relay.
The static prototype is https://agora-gamma-seven.vercel.app. Joining there opens a local preview in the browser. It does not start the shared room, and it does not call a model.
For the Alexa+ entry, a container image and a Cloud Run service were not verified from that workspace, and the tunnel, ECS, and Cloud Run commands in docs/DEPLOY.md were not run. The Qloo build's Cloud Run command is in docs/QLOO.md. There is no static Pages deploy, because a static copy has no /api, /mcp, or room. .github/workflows/ci.yml type-checks the client and server, runs the unit tests, and builds on every push and pull request.
When every browser disconnects, the sockets close. With a platform that stops an idle process, the in-memory room is gone. A model call still in flight is not a background job. This repository does not promise that a document finishes after everyone leaves, or that it survives a restart.
Current limitations and security
One process, one room, at most five people.
Everyone in the room can see the brief, the tasks, the document, and public chat.
There are no accounts. On loopback, with no
DEMO_HOST_SECRET, the first joiner is the host. On an open bind without that secret, the household session holds the host seat when a passcode is set, and the room has no host when it is not. With the secret set, host authority is the host code or the resume token.Only the host can start a swarm, start a benchmark, or end the session.
Room state is memory. A restart or an idle shutdown clears it. Save a copy if you want a file.
There is no spending cap.
A provider can reject a key, lack a model, or time out. The room shows a short error and does not echo the upstream body.
Browser keys remain in
localStorageon that origin. They are not part of the room snapshot.The server key must not be used against an arbitrary base URL. The proxy is not an open forwarder.
Bundled 3D models are CC BY-NC 4.0. They are placeholders and are not licensed for commercial use. See License and maintainer.
Roadmap
The household MCP server, approvals, receipts, and on-disk memory are built. See What it does. No dates are set for the office items below.
Persist a cloud room across restarts, and keep an in-flight run when nobody is connected.
Hold lasting document edits until the host accepts them.
Replace the CC BY-NC office models before any commercial use.
Planned, not built: a temporary workspace, separate from the room server, where a participant can install a CLI and connect a compatible tool to the room. Installing a CLI does not make it an agent. The deliverable today remains one Markdown document.
License and maintainer
Source code is MIT. See LICENSE.
Third-party code and assets have their own notices. Those notices stay in the files the licenses require:
NOTICE.md — MIT attribution for the upstream office shell, and the CC BY-NC terms for the bundled models.
public/models/ASSETS-LICENSE.md — CC BY-NC 4.0 for the 3D assets.
public/models/README.txt — the same asset terms, next to the model files.
Maintainer: Ritesh-Root <workstationritalks@gmail.com> · https://github.com/Ritesh-Root
Repository: https://github.com/Ritesh-Root/agora-taste-council (forked from https://github.com/Ritesh-Root/agora-plus) · built on AGORA, https://github.com/Ritesh-Root/agora
This server cannot be deployed
Maintenance
Related MCP Connectors
Pick one movie, show, book, artist or restaurant a whole group will enjoy, using Qloo taste data.
Plans a shared evening for two people: places both would enjoy, ranked by Qloo's taste graph.
Free London beta meetup planner. Ranks curated Placelists by journey fairness for the whole group.
AI-native restaurant discovery: verified/menu-indexed/discovered tiers + signed allergy-safety data.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceRecommends fair and enjoyable meeting places for group gatherings by balancing travel distance equity and nearby amenities like restaurants, cafes, and cultural facilities.-
- AlicenseBqualityBmaintenanceEnables autonomous multi-person group food ordering on Swiggy by solving dietary, budget, and delivery-SLA constraints to pick a restaurant and allocate dishes, then consolidating the items into a single cart. It also computes exact proportional bill splits including taxes, fees, and discounts, and generates UPI payment links for reimbursement.2MIT
- AlicenseNot gradedqualityBmaintenanceExposes two Qloo-grounded tools, resolve_taste_anchor and recommend_for_group, so any agent can turn what individual people like into per-person signals instead of stereotypes. Group picks are ranked by how many people each option covers, then by affinity, letting agents answer 'what should this group do?' with grounded recommendations.MIT
- AlicenseNot gradedqualityBmaintenanceEnables MCP clients to submit a group’s favorite movies, books, restaurants, artists, or TV shows and receive a fairness-ranked common pick, three alternatives, and per-person reasons using Qloo affinity insights.28 npmMIT