# Project export: Tessera

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: Every answer is a tile your team already cut
- Devpost: https://devpost.com/software/tessera-h4vfaj
- GitHub: https://github.com/hectar-glitches/tessera
- Video: https://www.youtube.com/embed/ScWQCgKri6U?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Madiyar Zhunussov (15 commits), hectar-glitches (5 commits)

## Devpost submission (written by the team)

### Overview

Every answer is a tile your team already cut.

### Inspiration

It started with a small, familiar moment: asking an AI assistant something — and realizing you'd asked nearly the same thing yesterday, but forgot the answer. You just paid for that answer twice. One of our teammates kept watching a bigger version of this play out at their company. The same questions, again and again — "How do I get staging access?" "Which service owns the billing webhook?" "What's the deploy command for the legacy repo?" Sometimes a senior engineer answered for the fifth time that month; increasingly, an AI assistant answered instead. But the assistant had no memory that the organization had already solved it. Every repeat was a brand-new call — full cost, full latency — to regenerate an answer the company already had. At the scale AI now runs, that waste is enormous. Google alone processes 3.2 quadrillion tokens a month; the whole LLM API market runs roughly 1.5 quadrillion tokens a month — about 50 trillion a day. Enterprises spent $37 billion on AI in 2025, and budgets are already breaking: Uber's CTO said the company burned through its entire 2026 AI budget in four months. The problem isn't that the models are bad. It's that we keep paying them to regenerate what we already know. That gap is what Tessera closes.

### What it does

Tessera is a shared, segmented knowledge layer that sits in front of a team's AI assistant and reuses an answer the moment someone has already given it — instead of regenerating it. The flow is straightforward: A developer asks a question. Before the question reaches the LLM, Tessera embeds it and checks Redis for a semantically similar question that has already been answered. Cache hit — Tessera returns the institutional answer instantly, with context such as "3 engineers at your level asked this last week — here's what worked." Cache miss — the question goes to the LLM, and the new answer is stored as a fresh tile for everyone who comes after. This wins on two fronts at once. The infrastructure win: a cache hit skips the entire LLM call, cutting cost and returning in milliseconds instead of seconds. The business win: the same engine means engineers stop re-answering each other, with fewer interruptions and smoother onboarding. Each answer is a single tessera — a tile — and together they form a living mosaic of what the team already knows.

### How we built it

Tessera intercepts a question before it ever reaches the model, rather than answering it after the fact like a typical search tool or chatbot. Redis + RedisVL power the vector search and semantic cache. RedisVL's SemanticCache provided meaning-based matching, tunable distance thresholds, and TTLs out of the box. Its filterable_fields enabled one of our favorite features — answers segmented by tenure and seniority, so a new hire and a staff engineer asking the "same" question receive answers pitched at the right level. Python ties the embedding, similarity search, and fallback-to-LLM path together. Sentry catches the failure modes that matter most in a cache: incorrect matches and errors in the answer path. Redis was the natural core: semantic caching needs both fast vector similarity search and a key-value store to fetch the stored response, and Redis handles both in one place.

### Challenges we ran into

Threshold tuning. Too loose, and the cache returns a near question with the wrong answer; too strict, and it almost never hits. Finding the right distance threshold — and adding a confidence gate on top — was our hardest correctness problem. Staleness. A cached answer can go out of date the moment a service is renamed or a process changes. We relied on TTLs and tile invalidation so the mosaic stays trustworthy. Privacy and segmentation. A shared cache cannot leak answers across permission boundaries. Segmenting by team and seniority had to respect access, not just adjust the tone of the response. Sizing the value honestly. Semantic caching's savings are real but depend on workload: repetitive, FAQ-style traffic hits 40–70% of the time, while creative or multi-turn work barely caches at all. We were careful to claim savings only where the data supports them.

### Accomplishments we're proud of

We built a working semantic cache that reuses real answers before the LLM is called — not just a search box that retrieves them afterward. We made segmentation by tenure and seniority a first-class feature, so the same question returns the right answer for the right person. We grounded the whole pitch in independent research and a transparent model, rather than optimistic claims. We delivered a clear, demoable moment — the "3 engineers at your level asked this last week" experience — that makes the value obvious in seconds. We built it on Redis as core infrastructure, using the sponsor's technology for exactly what it does best.

### What we learned

Before writing a line of code, we checked whether this was a real, measurable problem. It is — on both the cost side and the human side. The cost is exploding. The LLM API market processes ~1.5 quadrillion tokens a month, enterprises spent $37 billion on AI in 2025 (up 3.2x in a year), and companies are already hitting budget ceilings. Provider prompt caching helps, but only partially — it discounts the input tokens (Anthropic charges 0.1x on cache reads) while still regenerating every output. Reusing the whole answer requires a semantic cache. Semantic caching works, and it's fast. Redis LangCache reports up to ~73% cost reduction on high-repetition workloads, with hits returning in milliseconds versus seconds. In one benchmark, a 7-second model call became a 27 ms cache hit — a 250x speedup. A peer-reviewed, Redis-based semantic cache reported 61–69% hit rates with over 97% accuracy on repetitive queries. The human cost is just as real. The average knowledge worker spends 8.2 hours a week finding, recreating, and duplicating information (APQC). Three out of four developers re-answer questions they've answered before (Stack Overflow). Developers spend only about 16% of their week actually coding (Atlassian). And individual AI tools don't fix the team problem — only 17% of agent users said agents improved team collaboration. The model, in plain terms. For a team of \(n\) engineers asking \(q\) repeated questions per week, each costing \(m\) minutes at a loaded hourly cost \(c\), the annual cost of repeated questions is: $$\text{Annual cost} = n \times q \times \frac{m}{60} \times c \times 52$$ and the value Tessera recovers at capture rate \(r\) is: $$\text{Value saved} = \text{Annual cost} \times r$$ For 50 engineers asking 5 questions a week at 10 minutes each and $100/hr, that's about $217,000 a year; capturing 30% recovers roughly $65,000 — well above the cost of the tool. This gave us our framing: token savings are the infrastructure win, and engineering time is the business win — and Tessera delivers both from one cache.

### What's next

Expanding to high-volume and customer-facing AI workloads, where token savings compound into hundreds of thousands of dollars a year. Automatically promoting frequently-hit tiles into a curated, human-verified FAQ board. Smarter staleness detection tied to repository and infrastructure changes. Deeper segmentation and routing, with richer "who asked this and what worked" context. Pilots with real teams to measure capture rates, dollars saved, and time recovered in practice. Tessera started with one engineer answering the same question for the fifth time. That frustration turned out to be shared across the entire industry — and the fix isn't a smarter chatbot. It's a system that stops paying to regenerate what it already knows.

## README (from the GitHub repository)

# Tessera

**Token-aware FAQ infrastructure for orgs.** A semantic-cache-backed RAG assistant
that lets an org safely cut LLM API costs while serving accurate, source-grounded
answers — and never serving one across a permission boundary. Demoed as **Ask Ddoski**
for AI Hackathon 2026.

> Semantic caching exists as developer infrastructure. Tessera turns it into a
> budget-and-trust tool a non-technical org admin can actually own, solves the
> false-positive problem that makes naive caching unsafe to deploy, and shows the
> accuracy live.

## Why it's safe (the core idea)

Naive semantic caching serves the wrong answer on near-miss queries — same sentence
shape, different entity ("Saturday lunch" vs "Sunday lunch"). Tessera extracts
entities (numbers, dates, days, track/sponsor names) on both ingest and query, and
only auto-serves a cached answer when **vector similarity is high AND the entities
match**.

When similarity is high but entities disagree (the dangerous gray zone), Tessera does
**not** silently serve. Instead it surfaces the close matches to the user as a
"did you mean one of these previously answered questions?" popup — the human
disambiguates, and the false positive never reaches them as a confident wrong answer.

## Architecture

```
ingest doc -> chunk -> embed -> extract entities -> Redis vector index
                                                      + chunk hash + reverse index

query -> embed -> extract entities -> Redis hybrid search (vector KNN + entity tag)
      -> decide:
           high sim + entity match      -> CACHE HIT  (instant, $0)
           high sim + entity mismatch    -> SUGGEST    (popup, user picks)
           low sim / no match            -> CACHE MISS (call Claude, store entry)
```

Every request is logged with its decision path, tokens saved, and dollars saved. Both
the cache search and RAG retrieval are access-scoped to the requester's identity (see
[IAM / access-control governance](#iam--access-control-governance)), so neither a hit
nor a suggestion can leak across a permission boundary.

## Stack

- **Backend:** FastAPI, redis-py (Redis Stack / RediSearch), Anthropic SDK,
  sentence-transformers (local embeddings, with a deterministic fallback).
- **Governance:** an IAM/RBAC layer (clearance levels + team boundaries) on top of the
  role/seniority/tenure segmentation, with sensitivity-tiered cache TTLs.
- **Observability:** Sentry (tracing + AI-governance issues) and Arize (decision logs),
  both optional and no-op without keys.
- **Clients:** a React + Vite + Tailwind dashboard, a VS Code extension, and a Node MCP
  server.
- **Storage:** Redis Stack — vector search, the chunk-to-cache-key reverse index
  (Redis beyond caching), and Lua-atomic writes.

## Quick start

### 1. Redis Stack

```bash
docker compose up -d redis
```

### 2. Backend

```bash
cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env   # add ANTHROPIC_API_KEY (optional; falls back to a stub)
uvicorn app.main:app --reload --port 8000
```

Then seed the demo org:

```bash
curl -X POST http://localhost:8000/api/orgs/ask-ddoski/ingest/seed
```

### 3. Frontend

```bash
cd frontend
npm install
npm run dev   # http://localhost:5173
```

## Notes on degradation (works without external services)

- **No Redis?** The store falls back to an in-memory implementation with the same
  interface (vector search, reverse index, atomic writes). Use Redis for the demo —
  the reverse index is a prize talking point.
- **No `ANTHROPIC_API_KEY`?** Generation falls back to a deterministic
  context-stitching stub so the full flow stays demoable offline.
- **No `sentence-transformers`?** Embeddings fall back to a hashed bag-of-words
  vector so tests and CI run without heavy ML deps.

## Confidence check

The `/api/orgs/{org}/confidence-check` endpoint runs the hand-built test suite two
ways — vector-similarity-only baseline vs entity-filtered hybrid — and reports which
pairs each gets right. The baseline visibly fails the near-miss-by-entity bucket; the
hybrid passes all four. This is re-runnable live from the dashboard.

## Multi-tenant & storage design

- Every key namespaced by org: `org:{org_id}:cache:{hash}`, `org:{org_id}:chunk:{id}`.
- Source of truth = most recent completed ingestion per org (last-write-wins).
  Multi-document conflict resolution is explicitly out of scope (future work).
- Cache-entry writes and their reverse-index updates are wrapped in a single Lua
  script so a concurrent re-ingest can't open a stale-write window.

## Role-aware cache (OrgCache)

OrgCache builds on Tessera with a role + seniority + tenure segmentation layer so an
org's shared cache serves *role-appropriate* answers.

**New cache-entry fields:** `role` (engineer/designer/pm/devops/manager),
`seniority` (junior/mid/senior/staff/principal), `tenure` (onboarding/experienced),
`min_seniority_level` (1–5), plus `hit_count`, `created_at`, `last_asked_at`.

**Hierarchy rule:** a user at `user_level = L` only sees entries with
`min_seniority_level <= L` (junior=1 … principal=5). Tenure adds a soft re-rank boost
(onboarding favors setup/tooling; experienced favors architecture/patterns).

**Endpoints (org `acmecorp`):**

- `POST /api/orgs/{org}/query` (and alias `/check`) accept optional
  `{ role, seniority, tenure, user_level }`; omitting them preserves legacy behavior.
- `GET /api/orgs/{org}/trending?role=&seniority=&tenure=&limit=` — top entries by
  `hit_count` for a segment, hierarchy-filtered.
- `GET /api/orgs/{org}/entries`, `PATCH /api/orgs/{org}/entries/{hash}`
  (`answer`, `min_seniority_level`), `DELETE /api/orgs/{org}/entries/{hash}` — dashboard
  entry management.

**Seed:** `POST /api/orgs/acmecorp/ingest/seed` loads 60 role-tagged AcmeCorp Q&As
(Next.js + PostgreSQL + AWS) from `backend/data/acmecorp_seed.json`.

**Tests:** `cd backend && python -m scripts.smoke` (legacy) and `pytest -q`
(role-filtering suite in `backend/tests/`).

## IAM / access-control governance

Beyond entity-safety, Tessera enforces a second boundary: **who is allowed to see an
answer.** A shared org cache is dangerous if an answer generated from a manager-only or
finance-only source can be served to anyone who asks a similar question.
`backend/app/acl.py` is the governance core.

**Two axes**, declared per source section via an inline directive
(`<!-- acl: level=manager team=finance -->`):

- **level** — an ordered clearance tier: `public < employee < manager < exec`.
- **team** — an unordered cache-sharing boundary (teammates share; `exec` sees across
  all teams).

A cached answer **inherits the most-restrictive label** of the chunks it was generated
from (`acl.combine`). A requester — an `Identity` (`user` / `team` / `level`), sent as
the optional `identity` field on `/query` — may see an entry iff
`identity.level >= entry.level` **and** (`entry` has no team restriction, the identity's
team is allowed, or the identity is `exec`).

Enforced on **both cache hits and suggestions**, and RAG retrieval is itself
access-scoped — so a low-clearance user can never be served, *see the existence of*, or
have an answer grounded on, content above their clearance.

**Demo personas** (`GET /api/identities`): Maya (intern), Leo (engineer), Raj (eng
manager), Priya (finance manager), Dana (CEO) — the intern-vs-CEO and same-team-sharing
story.

## Label-aware cache TTL

Cache entries expire on a sensitivity-tiered schedule (`config.cache_ttl_for`): the
more restrictive an answer's ACL level, the sooner it expires. Correctness on source
edits is handled event-driven by the reverse-index invalidation; these TTLs are a
*risk ceiling* that bounds staleness and the blast radius of any mislabel.

| Level | TTL |
|-------|-----|
| `public` | 7 days |
| `employee` | 24 hours |
| `manager` | 1 hour |
| `exec` | 15 minutes |

The served/written entry's absolute expiry is surfaced as `expires_at` on the query
response; `0` disables expiry for a tie

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 97 recognized source files, 402 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Redis (technology) — detected in the code
- SQL (language) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- AI coding agent: Claude Code — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (120 of 122)

```
.gitignore
backend/.env.example
backend/app/__init__.py
backend/app/acl.py
backend/app/arize_logger.py
backend/app/auth.py
backend/app/config.py
backend/app/embeddings.py
backend/app/engine.py
backend/app/entities.py
backend/app/eval.py
backend/app/ingest.py
backend/app/llm.py
backend/app/main.py
backend/app/models.py
backend/app/redis_store.py
backend/app/roles.py
backend/app/seed.py
backend/app/store.py
backend/app/telemetry.py
backend/conftest.py
backend/data/acmecorp_seed.json
backend/data/ask_ddoski_guide.md
backend/data/test_pairs.json
backend/railway.toml
backend/requirements.txt
backend/scripts/eval_numbers.py
backend/scripts/smoke_iam.py
backend/scripts/smoke.py
backend/scripts/tune.py
backend/scripts/warmup.py
backend/tests/test_acl_safety.py
backend/tests/test_arize_logger.py
backend/tests/test_auth.py
backend/tests/test_role_filtering.py
backend/tests/test_ttl.py
DEPLOYMENT.md
devin/api-contract.md
devin/coordinator.md
devin/orchestrate.sh
devin/PR_BODY.md
devin/README.md
devin/task-1-backend.md
devin/task-2-extension.md
devin/task-3-dashboard.md
devin/task-4-observability-mcp.md
docker-compose.yml
extension/.gitignore
extension/.vscode/launch.json
extension/.vscode/tasks.json
extension/.vscodeignore
extension/build.js
extension/LICENSE
extension/mock/after.py
extension/mock/before.py
extension/mock/demo-repo/.env.example
extension/mock/demo-repo/docker-compose.yml
extension/mock/demo-repo/docs/rfc/RFC-001-workspace-billing-v2.md
extension/mock/demo-repo/infra/ecs.tf
extension/mock/demo-repo/package.json
extension/mock/demo-repo/packages/api/src/cache/ttl.ts
extension/mock/demo-repo/packages/api/src/middleware/auth.ts
extension/mock/demo-repo/packages/db/migrations/0001_initial.sql
extension/mock/demo-repo/packages/db/migrations/0002_add_workspaces_members.sql
extension/mock/demo-repo/packages/db/package.json
extension/mock/demo-repo/packages/db/src/schema.ts
extension/mock/demo-repo/packages/jobs/src/email-notification.ts
extension/mock/demo-repo/packages/shared/src/cache.ts
extension/mock/demo-repo/packages/shared/src/env.ts
extension/mock/demo-repo/packages/shared/src/flags.ts
extension/mock/demo-repo/packages/shared/src/logger.ts
extension/mock/demo-repo/packages/web/src/app/api/webhooks/clerk/route.ts
extension/mock/demo-repo/packages/web/src/app/dashboard/page.tsx
extension/mock/demo-repo/packages/web/src/app/dashboard/workspace/[slug]/members.test.ts
extension/mock/demo-repo/packages/web/src/app/layout.tsx
extension/mock/demo-repo/packages/web/src/app/page.tsx
extension/mock/demo-repo/pnpm-workspace.yaml
extension/mock/demo-repo/README.md
extension/mock/demo-repo/scripts/gdpr-delete.mjs
extension/mock/demo-repo/turbo.json
extension/mock/requirements.txt
extension/mock/server.js
extension/mock/simulate.js
extension/package.json
extension/README.md
extension/src/api.ts
extension/src/extension.ts
extension/src/helpers.ts
extension/src/webview.ts
extension/test/helpers.test.ts
extension/tsconfig.json
frontend/.env.production
frontend/.gitignore
frontend/index.html
frontend/package.json
frontend/postcss.config.js
frontend/README.md
frontend/src/api.js
frontend/src/App.jsx
frontend/src/components/CacheHealth.jsx
frontend/src/components/Chat.jsx
frontend/src/components/Dashboard.jsx
frontend/src/components/EntryManager.jsx
frontend/src/components/FilterBar.jsx
frontend/src/components/IdentitySwitcher.jsx
frontend/src/components/RedisPanel.jsx
frontend/src/components/TrendingTable.jsx
frontend/src/components/ui.jsx
frontend/src/index.css
frontend/src/main.jsx
frontend/src/mockData.js
frontend/src/mockData.test.js
frontend/tailwind.config.js
frontend/vercel.json
frontend/vite.config.js
mcp-server/index.js
mcp-server/lib.js
mcp-server/mock-backend.js
mcp-server/package.json
mcp-server/README.md
[2 more files omitted for size]
```

### Dependencies

- backend/requirements.txt: anthropic@==0.39.0, arize@==7.19.0, fastapi@==0.115.0, httpx@==0.27.2, numpy@==1.26.4, pydantic@==2.9.2, pydantic-settings@==2.5.2, pytest@==8.3.3, python-dotenv@==1.0.1, redis@==5.0.8, sentence-transformers@==3.1.1, sentry-sdk[fastapi]@==2.18.0, uvicorn[standard]@==0.30.6
- extension/mock/demo-repo/package.json: @playwright/test@^1.44.0, turbo@^2.0.6, typescript@^5.4.5
- extension/mock/demo-repo/packages/db/package.json: @neondatabase/serverless@^0.9.3, drizzle-kit@^0.22.7, drizzle-orm@^0.31.2, tsx@^4.15.5
- extension/mock/requirements.txt: anthropic@>=0.40.0, arize-phoenix@>=4.0.0, openinference-instrumentation-anthropic@>=0.1.0, opentelemetry-exporter-otlp-proto-http@>=1.24.0, opentelemetry-sdk@>=1.24.0, requests@>=2.31.0
- extension/package.json: @types/node@^20.14.0, @types/vscode@^1.85.0, @vscode/vsce@^2.31.0, concurrently@^9.0.0, esbuild@^0.23.0, typescript@^5.5.4, vitest@^2.1.1
- frontend/package.json: @vitejs/plugin-react@^4.3.1, autoprefixer@^10.4.20, lucide-react@^0.439.0, postcss@^8.4.47, react@^18.3.1, react-dom@^18.3.1, tailwindcss@^3.4.13, vite@^5.4.8, vitest@^2.1.1
- mcp-server/package.json: @modelcontextprotocol/sdk@^1.0.4, zod@^3.23.8

### Recent commits (newest first)

- Merge pull request #4 from hectar-glitches/updates
- Merge branch 'main' into updates
- added auth and trust boundary
- Update server.js
- config
- Update redis_store.py
- Tessera Hook Error Fix + mock repo added
- fix(backend): handle redis-py 5.x IndexType import across all known paths
- Merge pull request #3 from hectar-glitches/feat/orgcache-v1
- Merge branch 'main' into feat/orgcache-v1
- test and readme updates
- push
- added new integrations
- Merge pull request #2 from hectar-glitches/redis
- add redis confidentials
- feat(ext): rebrand extension OrgCache -> Tessera (config keys, icon, webview)
- Merge pull request #1 from hectar-glitches/feat/orgcache-v1
- Merge remote-tracking branch 'origin/main' into feat/orgcache-v1
- feat: Tessera premium UI redesign + production deploy artifacts
- docs: OrgCache v1 PR description

## Key source files (fetched from GitHub, selected and truncated for size)

### DEPLOYMENT.md

```markdown
# Tessera — Deployment Guide

Production stack:

| Component | Platform | URL |
|-----------|----------|-----|
| Redis | Redis Cloud (free) or Railway Redis plugin | _(connection string only)_ |
| Backend (FastAPI) | Railway | `https://REPLACE-WITH-RAILWAY-URL.up.railway.app` |
| Dashboard (Vite/React) | Vercel | `https://REPLACE-WITH-VERCEL-URL.vercel.app` |

> Fill in the two URLs above after the first deploy. The MCP server runs **locally**
> per-developer and points at the deployed backend.

All required config files are already committed:
- `backend/railway.toml` — Railway build/start/healthcheck
- `backend/app/main.py` — CORS now reads `CORS_ORIGINS` (env-driven, locks down in prod)
- `frontend/vercel.json` — Vite framework + SPA rewrites
- `frontend/.env.production` — `VITE_API_URL`, `VITE_DEFAULT_ORG`

---

## A. Redis (do first)

**Option 1 — Redis Cloud (recommended; has RediSearch):**
1. Sign up at <https://redis.io/cloud> (free 30MB tier).
2. Create a database named `tessera-prod`.
3. Copy the connection string → this is your `REDIS_URL`
   (format: `redis://default:<password>@<host>:<port>`).

**Option 2 — Railway Redis plugin (if Redis Cloud signup is blocked):**
```bash
railway add            # choose "Redis" (or: railway add --plugin redis)
```
Copy the plugin's `REDIS_URL` from the Railway dashboard → Variables.

> ⚠️ **RediSearch required.** Tessera uses a vector index (RediSearch module).
> Redis Cloud includes it. The Railway Redis plugin is vanilla Redis **without**
> RediSearch — if you use it, the backend automatically falls back to its in-memory
> store (works, but cache is not shared across instances / restarts). For a real
> shared cache, use Redis Cloud (or Redis Stack / AWS ElastiCache Redis Stack).

---

## B. Backend → Railway

```bash
npm install -g @railway/cli
railway login                      # interactive (opens browser)
railway init                       # name: tessera-backend
```

Set the service **root directory** to `backend/` (Railway dashboard → Settings →
Source → Root Directory = `backend`). This makes nixpacks pick up
`backend/requirements.txt` and `backend/railway.toml`.

Add environment variables (Railway dashboard → Variables):

| Variable | Value |
|----------|-------|
| `REDIS_URL` | _(from step A)_ |
| `ANTHROPIC_API_KEY` | _(from your `.env`; optional — falls back to stub)_ |
| `ARIZE_API_KEY` | _(optional)_ |
| `ARIZE_SPACE_KEY` | _(optional)_ |
| `CORS_ORIGINS` | `https://REPLACE-WITH-VERCEL-URL.vercel.app` |

> Do **not** set `PORT` manually — Railway injects `$PORT` and the start command in
> `railway.toml` already binds to it.

Deploy:
```bash
railway up
```
Note the generated URL (Settings → Networking → Generate Domain), e.g.
`https://tessera-backend-production.up.railway.app`.

Seed production data:
```bash
curl -X POST https://<railway-url>/api/orgs/acmecorp/ingest/seed
# -> {"org":"acmecorp","entries":60,...}
```

Verify:
```bash
curl https://<railway-url>/api/health
# -> {"status":"o
[truncated — 3285 more characters]
```

### devin/task-3-dashboard.md

```markdown
# Devin Task — Sub-agent 3: Admin Dashboard Upgrade (Frontend)

> Paste this entire file as the prompt for a fresh Devin session. Self-contained.

## Repo & setup
- Repo: `https://github.com/hectar-glitches/tessera`
- Branch: **`feat/dashboard-upgrade`** off `main`.
- Read `devin/api-contract.md` — the endpoints you consume. Build against a **mock
  data layer** so you are not blocked by Sub-agent 1.
- Frontend: `cd frontend && npm install && npm run dev` (Vite, React 18, Tailwind,
  lucide-react already configured).

## Existing code you are extending (DO NOT rebuild)
- `frontend/src/components/Dashboard.jsx` — current admin dashboard.
- `frontend/src/components/Chat.jsx` — chat UI (leave as-is).
- `frontend/src/api.js` — API client. **Important:** it currently hardcodes
  `export const ORG = "ask-ddoski"`. Add support for `acmecorp` (e.g. an org selector
  or a second exported client) WITHOUT breaking the existing `ask-ddoski` calls.
- `frontend/src/App.jsx` — tab shell (`chat` | `dash`).

## What to build (add to Dashboard, keep existing functionality intact)

### 1. Role/Seniority/Tenure filter bar (top of dashboard)
Three dropdowns: Role | Seniority | Tenure (values from the contract enums, plus an
"All" option). Selection is the single source of truth that all sections below filter by.

### 2. Cache Health panel — 4 metric cards (with lucide icons)
- Total entries in cache.
- Hit rate % (last 24h).
- Tokens saved = `cache_hits × avg_tokens_per_answer × 1` (use stats from
  `/api/orgs/{org}/stats`; if a field is missing, derive from activity).
- Dollar savings = `tokens_saved × $0.000015`.

### 3. Trending FAQ table
Columns: Question | Answer Preview | Role | Seniority | Hits | Last Asked.
Source: `GET /api/orgs/{org}/trending` with the selected filters. Click a row → expand
to full answer. This enhances (does not delete) the existing activity view.

### 4. Cache Entry Manager
List entries from `GET /api/orgs/{org}/entries` with: Question, Answer preview, Role,
Seniority, Hit count. Per row:
- **[Edit]** → inline edit answer → `PATCH /api/orgs/{org}/entries/{hash}` `{answer}`.
- **[Delete]** → `DELETE /api/orgs/{org}/entries/{hash}` (confirm first).
- **[Set Level]** → change `min_seniority_level` (1–5) → `PATCH {min_seniority_level}`.
- **Staleness badge**: if `created_at` > 30 days ago, show a ⚠️ warning pill.

> If the `entries`/PATCH/DELETE endpoints are not live yet, render from mock data and
> feature-flag the Edit/Delete/Set-Level buttons (disabled with a tooltip "backend
> pending"). The table must still render.

### 5. Activity feed (enhance existing)
Show timestamp, question, HIT/MISS, role, seniority. Color-coded: green=hit, red=miss.
Source: existing `GET /api/orgs/{org}/activity`.

## Mock data layer (so you are independent)
Add `frontend/src/mockData.js` with realistic AcmeCorp entries/trending/stats matching
the contract shapes, and a `VITE_USE_MOCK` flag in `api.js`: when set (or when a fetch
fails), serve mock data so the da
[truncated — 1005 more characters]
```

### docker-compose.yml

```yaml
services:
  redis:
    image: redis/redis-stack:7.2.0-v9
    container_name: tessera-redis
    ports:
      - "6379:6379"   # Redis
      - "8001:8001"   # RedisInsight UI
    volumes:
      - tessera-redis-data:/data

volumes:
  tessera-redis-data:

```

### mcp-server/package.json

```
{
  "name": "orgcache-mcp-server",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "description": "MCP server exposing OrgCache (check_cache / store_answer / get_trending) to any MCP-compatible agent.",
  "bin": {
    "orgcache-mcp": "index.js"
  },
  "scripts": {
    "start": "node index.js",
    "mock": "node mock-backend.js",
    "test": "node test/smoke.mjs"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.4",
    "zod": "^3.23.8"
  }
}

```

### frontend/package.json

```
{
  "name": "tessera-frontend",
  "private": true,
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "test": "vitest run"
  },
  "dependencies": {
    "lucide-react": "^0.439.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^4.3.1",
    "autoprefixer": "^10.4.20",
    "postcss": "^8.4.47",
    "tailwindcss": "^3.4.13",
    "vite": "^5.4.8",
    "vitest": "^2.1.1"
  }
}

```

### backend/requirements.txt

```
fastapi==0.115.0
uvicorn[standard]==0.30.6
redis==5.0.8
anthropic==0.39.0
pydantic==2.9.2
pydantic-settings==2.5.2
numpy==1.26.4
python-dotenv==1.0.1
# Optional but recommended for real semantic quality. If absent, a hashed
# fallback embedder is used automatically.
sentence-transformers==3.1.1

# Observability — optional. Without ARIZE_* keys, decisions log to stdout instead.
arize==7.19.0
# Telemetry is a no-op unless SENTRY_DSN is set, so this stays optional at runtime
# even though it's pinned here.
sentry-sdk[fastapi]==2.18.0

# Testing
pytest==8.3.3
httpx==0.27.2

```

### extension/package.json

```
{
  "name": "tessera",
  "displayName": "Tessera",
  "description": "Role-aware semantic cache for engineering teams — instant answers before they hit your coding agent.",
  "version": "1.0.0",
  "publisher": "madiyarzhunussov",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/madiyarzhunussov/tessera"
  },
  "private": true,
  "engines": {
    "vscode": "^1.85.0"
  },
  "categories": ["Other"],
  "main": "./dist/extension.js",
  "activationEvents": ["onStartupFinished"],
  "contributes": {
    "configuration": {
      "title": "Tessera",
      "properties": {
        "tessera.serverUrl": {
          "type": "string",
          "default": "http://localhost:8000",
          "description": "Tessera backend base URL."
        },
        "tessera.org": {
          "type": "string",
          "default": "acmecorp",
          "description": "Organization id to query."
        },
        "tessera.userName": {
          "type": "string",
          "default": "",
          "description": "Your name (for activity attribution)."
        },
        "tessera.role": {
          "type": "string",
          "enum": ["engineer", "designer", "pm", "devops", "manager"],
          "default": "engineer",
          "description": "Your role."
        },
        "tessera.seniority": {
          "type": "string",
          "enum": ["junior", "mid", "senior", "staff", "principal"],
          "default": "junior",
          "description": "Your seniority level."
        },
        "tessera.joinDate": {
          "type": "string",
          "default": "",
          "description": "Your join date (ISO, e.g. 2026-06-01). Used to derive onboarding vs experienced tenure."
        },
        "tessera.hookPort": {
          "type": "number",
          "default": 7778,
          "description": "Local port for the Claude Code PreToolUse hook listener."
        },
        "tessera.hookHoldTimeoutMs": {
          "type": "number",
          "default": 600000,
          "description": "How long (ms) to pause Claude Code on a cache hit while waiting for your sidebar decision before automatically continuing the agent."
        },
        "tessera.similarityThreshold": {
          "type": "number",
          "default": 0.85,
          "description": "Minimum similarity to show the cache-hit popup."
        },
        "tessera.cacheTtlDays": {
          "type": "number",
          "enum": [7, 14, 30, 60, 90, 0],
          "enumDescriptions": ["7 days", "14 days", "30 days", "60 days", "90 days", "Never expire"],
          "default": 30,
          "description": "How long cached entries are considered fresh. 0 = never expire."
        }
      }
    },
    "commands": [
      {
        "command": "tessera.checkSelection",
        "title": "Tessera: Check selection against cache"
      },
      {
        "command": "tessera.openTrending",
        "title": "Tessera: Refresh trending FAQs"
      },
      {
        "command": "tessera.setProfile",
        "title": "Tessera: Set my role and seniority"
      }
    ],
    "viewsContainers": {
      "activitybar": [
        {
          "id": "tessera",
          "title": "Tessera",
          "icon": "media/icon.svg"
        }
      ]
    },
    "views": {
      "tessera": [
        {
          "type": "webview",
          "id": "tessera.trending",
          "name": "Trending FAQs"
        }
      ]
    }
  },
  "scripts": {
    "compile": "tsc --noEmit && node build.js",
    "build": "node build.js",
    "watch": "node build.js --watch",
    "mock": "node mock/server.js",
    "dev": "concurrently -n mock,watch -c cyan,yellow \"node mock/server.js\" \"node build.js --watch\"",
    "test": "vitest run",
    "package": "vsce package --no-dependencies"
  },
  "devDependencies": {
    "concurrently": "^9.0.0",
    "@types/node": "^20.14.0",
    "@types/vscode": "^1.85.0",
    "@vscode/vsce": "^2.31.0",
    "esbuild": "^0.23.0",
    "typescript": "^5.5.4",
    "vitest": "^2.1.1"
  }
}

```

### extension/mock/requirements.txt

```
anthropic>=0.40.0
arize-phoenix>=4.0.0
openinference-instrumentation-anthropic>=0.1.0
opentelemetry-sdk>=1.24.0
opentelemetry-exporter-otlp-proto-http>=1.24.0
requests>=2.31.0

```

### extension/mock/demo-repo/package.json

```
{
  "name": "acmecorp-platform",
  "private": true,
  "version": "2.14.0",
  "packageManager": "pnpm@9.4.0",
  "scripts": {
    "dev": "turbo run dev",
    "build": "turbo run build",
    "test": "turbo run test",
    "test:e2e": "playwright test",
    "clean": "turbo run clean",
    "clean:all": "pnpm clean && rm -rf node_modules/.cache .turbo",
    "db:migrate": "pnpm --filter @acme/db migrate",
    "db:reset": "pnpm --filter @acme/db reset",
    "trigger:dev": "pnpm --filter @acme/jobs dev",
    "gdpr:delete": "node scripts/gdpr-delete.mjs"
  },
  "devDependencies": {
    "@playwright/test": "^1.44.0",
    "turbo": "^2.0.6",
    "typescript": "^5.4.5"
  }
}

```

### extension/mock/demo-repo/docker-compose.yml

```yaml
services:
  web:
    build:
      context: .
      dockerfile: packages/web/Dockerfile
    ports:
      - "3000:3000"
    env_file: .env.local
    depends_on: [postgres, redis]

  api:
    build:
      context: .
      dockerfile: packages/api/Dockerfile
    ports:
      - "8000:8000"
    env_file: .env.local
    depends_on: [postgres, redis]

  worker:
    build:
      context: .
      dockerfile: packages/jobs/Dockerfile
    env_file: .env.local
    depends_on: [postgres, redis]

  postgres:
    image: postgres:16-alpine
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: acmecorp_dev
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

```

[87 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]