Edge-native AI workspace for technical work.
Persistent workspaces · Reusable file memory · Retrieval with citations · Streaming chat · Output board
Live Demo · Architecture Notes
LYTA is a portfolio-grade AI workspace built on Cloudflare Workers, Durable Objects, and Workers AI. It is designed to demonstrate more than a prompt box: it models user state, file memory, retrieval, streaming, and output capture as a real product system.
The current product experience is intentionally minimal. The interface focuses on a quiet workspace rail, readable chat, reusable files, cited answers, and a document-style output board.
- Starts immediately in guest mode with temporary server-backed workspace state.
- Supports account workspaces for persisted chats, files, profile data, and preferences.
- Ingests PDFs, DOCX, TXT, Markdown, CSV, JSON, HTML, XML, and images.
- Stores uploaded documents in a reusable file library instead of treating every upload as a one-off attachment.
- Retrieves relevant file snippets and returns source citations in chat and on the output board.
- Streams assistant responses over Server-Sent Events.
- Provides
Instant,Deep, andCreativeresponse modes. - Generates short chat titles and follow-up prompts.
- Lets users pin strong assistant responses to a board for copy or Markdown export.
Most AI demos are stateless chat screens. LYTA is structured around explicit ownership boundaries:
- Worker Router resolves guest/account identity, validates requests, coordinates retrieval, and forwards normalized chat work.
- AuthDirectory Durable Object owns account records, password hashes, auth sessions, and token validation.
- Workspace Durable Object owns profile data, preferences, chat index, file metadata, document chunks, embeddings, and library search.
- Conversation Durable Object owns per-chat memory, ordered writes, summarization, title generation, follow-ups, and streaming persistence.
- Workers AI provides both chat generation and embedding generation.
The streaming path is hardened for demo reliability. /chat/stream establishes a valid SSE response before the model call runs, and model/retrieval failures degrade into clean LYTA errors instead of leaking Cloudflare 1101 HTML into the UI. Chat responses also include X-Lyta-Request-Id for log correlation, while logs avoid prompts, file text, emails, tokens, and full user data.
- Open the live demo.
- Start as a guest and ask a technical question.
- Upload a document and ask LYTA to summarize risks, decisions, or next steps.
- Reuse the uploaded file from the library in another chat.
- Compare
Instant,Deep, andCreativeresponse modes. - Pin a response to the output board and copy or download it.
- Sign in to move from temporary guest state to a saved account workspace.
Prefer not to type your own material? fixtures/demo-architecture-change.md
is a short, clearly-fictional sample change proposal you can upload in step 3 instead — safe public
material for demoing retrieval and citations.
flowchart TD
UI[Browser UI<br/>Chat, Files, Board, Settings] --> Router[Cloudflare Worker Router]
Router --> Auth[AuthDirectory DO<br/>Accounts + Sessions]
Router --> Workspace[Workspace DO<br/>Profile, Preferences, Chat Index, File Library]
Router --> Conversation[Conversation DO<br/>Per-Chat Memory + Streaming]
Router --> Embeddings[Workers AI Embeddings]
Conversation --> ChatModel[Workers AI Chat Model]
Embeddings --> Workspace
Workspace --> Router
Conversation --> Workspace
sequenceDiagram
participant User
participant UI as Browser UI
participant Router as Worker Router
participant Workspace as Workspace DO
participant Chat as Conversation DO
participant AI as Workers AI
User->>UI: Send message and optional files
UI->>Router: POST /chat/stream
Router->>Workspace: Import/search reusable library
Router->>Chat: Forward message, context, citations, request id
Chat-->>UI: Open SSE response
Chat->>AI: Run streaming chat model
AI-->>Chat: Token stream
Chat-->>UI: SSE chunks, metadata, citations
Chat->>Workspace: Touch/rename session
| Layer | Responsibility |
|---|---|
pages/ |
Vanilla HTML/CSS/JS workspace UI, uploads, auth modal, streaming chat, board |
src/router.ts |
Principal resolution, route validation, guest/account routing, library retrieval orchestration |
AuthDirectory |
Account records, password hashing, session tokens |
Workspace |
Profile, preferences, chat index, reusable file library, vector search |
Conversation |
Per-chat state, summaries, titles, follow-ups, SSE streaming, persistence |
services/ai.ts / services/embeddings.ts |
Workers AI model and embedding calls with normalized failures |
More detail lives in ARCHITECTURE.md.
- Guest and account workspace modes
- Email/password account registration and login
- Persistent chat sessions for account workspaces
- Temporary guest sessions with isolated server-backed state
- File upload and browser-side document text extraction
- Reusable workspace file library
- Embedding-backed file retrieval with citations
- SSE streaming chat responses
- Response modes:
Instant,Deep,Creative - Chat title generation, summarization, and follow-up prompts
- Minimal output board with copy and Markdown download
- Request-id based chat error correlation
- Graceful degradation for AI/retrieval failures
- Guest state is temporary and scoped to the guest cookie.
- Auth is email/password based, not OAuth or magic link.
- Retrieval uses Durable Object state rather than a dedicated external vector database.
- Browser-side extraction keeps the architecture compact but does not replace server-side OCR.
- The output board is a focused Markdown capture pane, not a full artifact runtime.
assets/
architecture.svg Source for the README architecture visual
architecture.png Rendered README architecture visual
demo.svg Source for the README product preview
demo.png Rendered README product preview
scripts/
render_asset.swift Renders SVG assets to PNG on macOS
pages/
index.html Main UI shell
styles.css Minimal workspace visual system
app-core.js Generated browser utility bundle
app-attachments.js Browser-side attachment preparation
app.js Client logic for auth, chat, uploads, board, settings
pages-src/
app-core.ts TypeScript source for app-core.js
src/
index.ts Worker entry point
router.ts Request orchestration and workspace routing
auth/crypto.ts Password and token helpers
chat/messages.ts Prompt shaping and message normalization
durable/
authDirectory.ts Account and session storage
workspace.ts Workspace state, file library, preferences
conversation.ts Chat memory, streaming, follow-ups, summaries
library/chunks.ts Document chunking and citation formatting
services/
ai.ts Workers AI chat calls
embeddings.ts Embedding generation
retriever.ts Small built-in knowledge retriever
utils/
serverErrors.ts Sanitized server error logging helpers
- Cloudflare Workers
- Cloudflare Durable Objects
- Cloudflare Workers AI
- TypeScript
- Vanilla HTML, CSS, and browser JavaScript
- PDF.js
- Mammoth
- Server-Sent Events
- Node.js 18+
- Wrangler CLI
- macOS only for the optional SVG-to-PNG asset renderer
npm installnpm run build:clientwrangler dev --remoteOpen:
http://localhost:8787
swift scripts/render_asset.swift assets/demo.svg assets/demo.png 1600 960
swift scripts/render_asset.swift assets/architecture.svg assets/architecture.png 1600 900npm run format:check
npm run build:client
./node_modules/.bin/tsc --noEmit
./node_modules/.bin/tsc -p tsconfig.client.json --noEmit
node --check pages/app-core.js
node --check pages/app.js
node --check pages/app-attachments.js
git diff --checkOr run everything at once with npm run check — it's the exact gate CI and
the deploy workflow both run.
npm run test:unit # pure-function tests, no Durable Object involved
npm run test:integration # drive a Durable Object's fetch() against faked storage
npm run test:eval # scripted-model evaluation suite (eval/dataset.json)
npm run test:smoke # register -> upload -> chat -> stream through the real routernpm test runs unit, integration, and smoke (the eval suite runs as part of
test:integration's glob and separately via test:eval for CI visibility —
see below). Coverage areas called out in issue #9:
| Area | Test file |
|---|---|
| Auth/session isolation | tests/integration/abuse-controls.test.ts |
| Rate limits | tests/integration/abuse-controls.test.ts |
| Streaming persistence | tests/integration/streaming-persistence.test.ts |
| Prompt boundaries | tests/unit/retrieval-boundary.test.ts |
| File lifecycle | tests/integration/workspace-artifacts.test.ts |
| Citation identity | tests/unit/retrieval-boundary.test.ts, tests/integration/project-context.test.ts |
| History retention | tests/integration/conversation-history.test.ts, tests/integration/workspace-artifacts.test.ts |
| Log redaction | tests/unit/log-redaction.test.ts |
| Full request pipeline | tests/smoke/smoke.test.ts |
src/config/modelProfiles.ts is the single source of truth for per-mode
model selection: model id, version label, timeout, retry budget, fallback
model, and documented latency/cost/quality targets for Instant and Deep
(Deep Review). src/services/ai.ts's callWithPolicy() wraps every
Workers AI call with that timeout/retry/fallback policy and records the
outcome as non-sensitive telemetry (model, version, retry count, fallback
used, timed out) — never prompt or response content.
eval/dataset.json is a versioned evaluation baseline (citation accuracy,
insufficient-evidence compliance, cross-mode sanity checks) that CI runs on
every PR as a pipeline-correctness regression gate. See
eval/README.md for what that suite does and doesn't
prove, and the model-promotion policy: no model or prompt change is called
"stronger" until it beats the documented baseline against this suite, run
against a real model.
GitHub Actions runs formatting, the client build, unit tests, integration
tests, the evaluation suite, the smoke test, TypeScript checks, browser
bundle syntax checks, a generated-client consistency check, a production
dependency audit, a model/prompt configuration change report, and a Worker
deployment bundle validation as separate steps for every pull request and
merge to main (npm run check runs the same gates locally in one
command).
Production deployment is deliberately manual through the Deploy production
workflow. Protect the repository's production environment with required
reviewers, then add these environment secrets:
CLOUDFLARE_API_TOKEN— scoped to deploy this Worker and write to its R2 bucket.CLOUDFLARE_ACCOUNT_ID— the Cloudflare account that owns LYTA.
The workflow never prints either value. It installs from package-lock.json,
reruns the complete quality gate, and only then calls Wrangler to deploy.
Before dispatching it, walk through RELEASE_CHECKLIST.md
for the things CI can't verify automatically — deployed-header verification,
migration review, an AI-evaluation comparison, and a manual accessibility
smoke test.
LYTA stores workspace source payloads in private R2 objects and treats chunks, findings, decisions, and user-approved summaries as provider-independent context. A model request receives only retrieval-selected evidence, never a full workspace dump. Each selection creates an ID-only context manifest with a policy version; it contains no source text or query.
Authenticated workspace APIs support inspection and management:
GET /contextlists context metadata; add?includeContent=trueto inspect saved findings, decisions, and summaries.POST /context/recordscreates or updates a finding, decision, or approved summary with source/chunk provenance.POST /context/records/deletedeletes a saved context record.GET /context/manifestslists non-sensitive selection audit records.
Deleting a source also removes derived context records linked to that source and returns the affected-record count. Historical ID-only manifests remain as audit evidence; deleted source text is not retrievable.
- OCR for scanned PDFs and image-heavy documents
- Web research mode with explicit external citations
- Shareable board outputs or published artifact pages
- OAuth or magic-link authentication
- Retrieval quality metrics and latency dashboards
- Richer artifact generation beyond Markdown export
LYTA is structured to be readable by reviewers:
- clear product story and live demo path
- explicit state ownership boundaries
- architecture diagrams that match implementation
- minimal frontend without framework overhead
- failure handling that preserves product polish during AI or retrieval issues

