From 89f76cc286f4fcc5ee4580a12769c104ca51acd8 Mon Sep 17 00:00:00 2001 From: SemianiakaVY Date: Thu, 17 Sep 2026 21:07:32 +0000 Subject: [PATCH] chore(template): sync SPEC.md --- SPEC.md | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 SPEC.md diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..db7902f --- /dev/null +++ b/SPEC.md @@ -0,0 +1,171 @@ +# AI Agent App — product specification + +This is the contract the project is built against. It is written for the agent that scaffolds +the repository, but it doubles as the human-readable brief: every requirement below is meant to +be implementable and verifiable. + +Values in angle brackets (``, ``, ``) come from the +project-creation dialog and are recorded in the project record; replace them as you read. + +## 1. Goal + +A chat application built around an LLM agent: a user holds a conversation, the agent streams its +answer token by token, may consult the workspace's own documents through ``, and may +call tools. Answers that use documents cite the source chunks. It must be **runnable +end-to-end on day one** (even with a mock provider and seeded documents), not a set of screens +wired together later. + +Non-goals (do not build unless the must-have list says otherwise): multi-agent orchestration, +fine-tuning, a public model marketplace, voice I/O, autonomous background agents. + +## 2. Roles + +| Role | Can do | +|------|--------| +| End user | Create conversations, chat with streaming answers, upload attachments, see citations and their own usage. | +| Workspace admin | Manage the knowledge base (upload, re-index, delete documents), configure the agent (system prompt, model, temperature, tools) and review usage/cost. | +| Platform operator | Set provider credentials and per-tenant quotas, inspect provider health and the audit log. | + +Authentication is required for every conversation and for the whole admin surface; there is no +anonymous chat. Provide a seeded admin account and a seeded user for local development. + +## 3. Information architecture (routes) + +- `/` — conversations list: newest-first, title, last message snippet, model, new-conversation + action. +- `/conversations/:conversationId` — one conversation: streaming chat, message history, + attachments, stop/regenerate, rename/delete. +- `/knowledge` — knowledge base: document list with status (uploaded → processing → indexed → + failed), upload, re-index, delete, per-document chunk count. +- `/knowledge/:documentId` — one document: metadata, ingestion progress, chunk preview. +- `/agent` — agent configuration: system prompt, model, temperature, retrieval on/off, tool + selection. +- `/usage` — usage and cost: tokens and cost per period, per conversation and per model. +- `/settings` — profile, provider keys (BYOK), default model, export/delete account data. +- `/login`, `/logout` (admin surface guarded by the admin role). + +## 4. Data model + +Minimum viable entities (add fields the requirements imply; keep them typed and validated): + +- **User** — id, email, passwordHash, name, role (`user` | `admin` | `operator`), createdAt. +- **Conversation** — id, userId, title, model, systemPromptSnapshot, createdAt, updatedAt, + archivedAt?. +- **Message** — id, conversationId, role (`system` | `user` | `assistant` | `tool`), content, + status (`pending` | `streaming` | `complete` | `error` | `cancelled`), inputTokens, + outputTokens, model, toolCallId?, createdAt. +- **Attachment** — id, messageId, kind (`image` | `file`), filename, mimeType, sizeBytes, + storagePath. +- **Document** — id, workspaceId, filename, mimeType, sizeBytes, status (`uploaded` | + `processing` | `indexed` | `failed`), error?, chunkCount, uploadedBy, createdAt. +- **DocumentChunk** — id, documentId, ordinal, text, tokenCount, embeddingRef (vector column for + `pgvector`, else the external collection/point id), createdAt. +- **AgentConfig** — id, workspaceId, systemPrompt, model, temperature, topP, retrievalEnabled, + retrievalTopK, enabledToolIds[], updatedAt. +- **ToolDefinition** — id, workspaceId, name, description, parameters (JSON schema), handler, + sideEffecting (boolean), enabled. +- **ToolCall** — id, messageId, toolId, arguments (JSON), result (JSON), status (`pending` | + `awaiting_confirmation` | `running` | `succeeded` | `failed` | `denied`), latencyMs. +- **UsageRecord** — id, userId, conversationId?, messageId?, model, inputTokens, outputTokens, + costMinor, currency, createdAt. + +Retrieval storage is chosen in the dialog: `pgvector` keeps `embeddingRef` as a vector column; +`Qdrant`/`OpenSearch` store the external collection and point id; `None (chat only)` disables +ingestion and citations. Keep this behind one retrieval interface so the choice is a +configuration, not a rewrite. + +Embeddings and model calls always run **server-side**; the browser never holds a provider key. + +## 5. Key flows + +1. **Streamed reply.** The user sends a message; the server persists it, calls the model through + the provider abstraction, and streams tokens back over the response stream. The assistant + message is `streaming` while tokens arrive and `complete` when the stream ends. The user can + cancel mid-stream (`cancelled`, partial text kept); a provider error ends the message as + `error` with a retry action. +2. **Document ingestion.** The admin uploads a document; the server stores it, extracts text, + chunks it, embeds each chunk, writes it to ``, and marks the document `indexed`. + Progress is visible per document (status + chunk count); a failure records the error and the + document stays re-indexable. +3. **Retrieval-augmented answer.** When retrieval is enabled, the question is embedded, the top-k + chunks are fetched, and they are passed to the model as untrusted context. The answer includes + **citations** that map back to the source chunk (document + ordinal), shown next to the + message and linking to `/knowledge/:documentId`. +4. **Tool calling.** The model may request a tool. Read-only tools run immediately; a + side-effecting tool pauses the turn in `awaiting_confirmation` and the user confirms or denies + before it executes. The result is fed back to the model and the turn continues; every call and + its status is recorded on the message. +5. **Conversation lifecycle.** A conversation is auto-titled from its first exchange, can be + renamed, and can be deleted (with confirmation) along with its messages and usage links. + +## 6. Functional requirements + +- **Chat:** streaming token-by-token with visible typing state; stop/regenerate; markdown + rendering with code blocks; per-message status; retry after an error without losing history. +- **Provider abstraction:** one interface with `DeepSeek`, `OpenAI`, `Anthropic` and `BYOK` + implementations, plus a **mock provider** used when no key is configured, so the app runs + without secrets. +- **Knowledge base:** upload (multiple files), list with status and filters, re-index, delete; + extraction for at least text/PDF/markdown; chunk size and overlap documented. +- **Retrieval:** configurable top-k; citations on every grounded answer; a clear "no relevant + documents" state instead of an invented answer; retrieval can be toggled per workspace and per + conversation. +- **Tools:** a registry with JSON-schema parameters; per-workspace enable/disable; the + side-effecting confirmation step; timeouts on every handler. +- **Usage:** record input/output tokens and cost per request; aggregate by period, conversation + and model; surface remaining quota to the user. +- **Admin:** edit the system prompt, model, temperature and tools; guard the surface with the + admin role; no destructive action without confirmation. +- **Seed data:** one workspace, one admin, one user, a few documents already indexed, and a + couple of conversations so the app is presentable on first run. + +## 7. Non-functional requirements + +- **Secrets:** never expose model/API keys to the client — all provider calls are server-side and + proxied; keys live in server env only and are documented in `.env.example`; BYOK keys are + encrypted at rest and never returned to the browser. +- **Limits:** per-user and per-tenant rate limits and token/cost quotas enforced server-side; a + request over quota is rejected with a clear message, not a silent failure. +- **Untrusted input:** documents, tool output and retrieved chunks are untrusted data — never + concatenated into instructions, delimited and labelled as data in the prompt, and never allowed + to trigger side-effecting tools without the confirmation step. +- **Resilience:** provider timeouts, rate-limit and partial-stream failures degrade with a clear + message and a retry path; a dropped connection keeps the partial answer. +- **Context budget:** conversation history sent to the model is bounded with a documented + truncation/summarisation strategy; the full transcript is always kept server-side. +- **Observability:** structured logs for model calls, retrieval and tool calls (no prompt or key + material in logs); a health endpoint that reports provider reachability. +- **Accessibility:** semantic landmarks, labelled inputs, keyboard-operable chat, visible focus, + contrast at least AA, and an announced streamed message for screen readers. + +## 8. Acceptance criteria (definition of done) + +- [ ] Install, dev server, lint, typecheck, tests and production build all pass. +- [ ] A streamed answer works end-to-end: tokens appear progressively, cancellation stops the + stream, and a provider error shows a retry without losing history. +- [ ] A question over an ingested document returns a grounded answer with citations to the source + chunks. +- [ ] An unavailable provider degrades with a clear, actionable message instead of a blank screen. +- [ ] Model/API keys are never present in the client bundle or any client response. +- [ ] Usage (input/output tokens and cost) is recorded per request and visible in `/usage`. +- [ ] A side-effecting tool cannot run without an explicit user confirmation. +- [ ] Seeded data makes the app presentable immediately; `.env.example` documents every secret. +- [ ] README quickstart (install, run, test, env) is accurate; empty/loading/error states exist. +- [ ] CI runs install + lint + typecheck + tests + build. + +## 9. Suggested build order + +Follow this order and finish (and verify) a layer before starting the next: + +1. **Scaffold** the chosen stack, install dependencies, get the dev server and the empty shell + running, set up env handling for model keys, and commit the skeleton. +2. **Streaming chat shell**: the message list, the composer and token streaming end-to-end with + the mock provider. +3. **Conversation persistence**: conversations and messages in the database, auto-titling, + rename/delete, history loading. +4. **Document ingestion + retrieval**: upload → chunk → embed → index over ``, and + retrieval-augmented answers with citations. +5. **Tool calling**: the tool registry, the run loop, and the confirmation step for side-effecting + tools. +6. **Usage + limits**: token/cost accounting, quotas and rate limits. +7. **Quality**: tests for the flows above, CI, README, `.env.example`, accessibility pass.