Getting started
Architecture and tooling
The monorepo, the locked stack, the two system paths, and how to work with coding agents on this project.
Before producing any code, you need to know how the services fit together, which technologies are locked in and why, and how the repo is set up so coding agents can work in it productively. All of it lives in two files you will keep coming back to, AGENTS.md at the repo root and docs/architecture.md.
The outcome of this stage is orientation. When it is done you can explain the two paths through the system, name every piece of the stack and its job, and know the working rhythm Dave uses with AI throughout the build.
daveebbelaar/document-copilot0:18:00One repo, agent instructions at every level
Document Copilot is a monorepo. Frontend and backend live in a single GitHub repository.
document-copilot/
├── AGENTS.md # source of truth for any coding agent
├── README.md
├── data/ # local corpus + download script (payloads gitignored)
├── docs/ # specs, briefs, design notes
├── backend/ # FastAPI service (see backend/AGENTS.md)
└── frontend/ # React SPA (see frontend/AGENTS.md)The reason is coding agents. When both services live in one repo, the agent can reason over the entire context. It sees the API contract from both sides, and a change to a backend endpoint and the frontend call that uses it can happen in one session.
There are three AGENTS.md files. One sits at the root with universal rules, and backend/ and frontend/ each have their own with stack-specific rules. These get injected into the agent's context automatically. Cursor picks up AGENTS.md out of the box; if you follow along with Claude Code, rename each file to CLAUDE.md in the same locations.
The root file opens with the point of the whole exercise:
This file is the source of truth for any coding agent (Claude Code,
Cursor, Codex, etc.) working in this repo. Read it before touching code.Dave notes in the video that these instruction files encode about three years of decisions from real client projects. Having them in place before the first prompt is a large part of why the AI output in this build stays consistent.
The locked stack
AGENTS.md pins the stack and states, "Stack is locked unless explicitly changed. Don't propose alternatives without a stated reason." That single line stops the agent from swapping technologies mid-build.
| Piece | Choice | Why |
|---|---|---|
| Backend | Python + FastAPI | The API layer; Python is where the AI and retrieval ecosystem lives |
| Frontend | Vite + React SPA + TypeScript | A plain single-page app; no Next.js, SSR, or server components |
| Database | Supabase Postgres + pgvector | One database for users, chats, documents, chunks, and vectors; no separate vector store |
| Auth | Supabase Auth | Email login and session handling without building auth yourself |
| LLM + embeddings | OpenAI | Generation and embedding models behind one SDK |
| Orchestration | Pydantic AI | Typed agent boundaries: explicit dependencies, typed outputs, bounded tools |
| Hosting | Railway | One frontend service, one stateless backend service, minimal ops |
Supabase pulls double duty, and that is deliberate. It is a real Postgres database (so SQLAlchemy, Alembic, and full-text search all work normally) and it handles the entire sign-in flow. Railway is chosen over AWS or Azure because it keeps deployment simple enough for a tutorial while still producing a real production app; for client projects Dave's team uses the major cloud providers, but that depth would overload this build.
Two paths through the system
docs/architecture.md opens with a service-level diagram, and the main thing to see is that there are two separate paths:
- The live chat path. Analyst opens the browser app (served by Railway), signs in through Supabase Auth, and sends a chat request with their JWT to the FastAPI backend. The backend verifies the user, retrieves passages from Postgres, generates a grounded answer with OpenAI, streams it back with citations, and persists the conversation.
- The ingestion path. A local, offline pipeline takes the downloaded SEC filings, parses and chunks them, creates embeddings with OpenAI, and stores documents and chunks in Supabase. There is no online upload or syncing mechanism. The corpus is prepared once, locally, and the live app only reads it.
Keeping these separate keeps the backend stateless and the deployment simple. Everything durable lives in Supabase, so Railway services can restart freely. Dave reasons through the diagram from the user story ("an analyst opens the browser") and recommends you do the same whenever you read an architecture diagram.
The boundaries are strict. The browser stays thin. It renders chat state and holds the user's session, and it never sees the service role key or calls OpenAI directly. The backend is authoritative. Retrieval, grounding, citation checks, and database writes all happen in FastAPI.
Dependency guardrails
The root AGENTS.md sets a hard default, "write it yourself. Reach for a library only when the alternative would be non-trivial, error-prone, or reinvention of a standard." Every dependency costs you something in bundle size, supply-chain risk, and future upgrade work.
The project goes one step further and enforces this at the package-manager level, a direct response to the recent npm supply chain attacks. The frontend sets this in frontend/.npmrc:
# Block packages younger than 7 days (10080 minutes). Defends against
# the typosquat / compromised-release window where a newly published
# version of a popular package turns out to be malicious or yanked.
minimum-release-age=10080And the backend mirrors it in backend/pyproject.toml, alongside exact version pins:
[tool.uv]
add-bounds = "exact"
exclude-newer = "7 days"Together these mean no package published in the last seven days can enter the project, and every Python dependency is pinned to an exact version. A freshly compromised release of a popular package simply cannot be installed during its most dangerous window.
These guardrails are pre-configured
Both files ship in the starter repo on main. Do not remove them when dependency installs feel slow or a brand-new package version is unavailable. That friction is the feature.
How to work with AI on this project
Dave is explicit about the working style for the whole build, and it is the opposite of firing off one huge plan and letting an agent run for two hours:
- Small, incremental, bite-size prompts. When you deeply understand the problem, short focused prompts are usually faster than mega-plans, and you stay in control of the code.
- Plan before building. The next stage starts by generating
docs/todos.md, a phased checklist, before any application code exists. - Intermediate review points. In Dave's words, no matter which skills, prompts, or models you use, "if you don't have intermediate review points and decide what good looks like, it's going to get messy." Read the diff after every step.
- The architecture doc is the map.
docs/architecture.mdis referenced again and again throughout the build, both by you and in prompts to the agent, so decisions stay anchored to the design instead of drifting.
The AGENTS.md files do the standing-instruction work (stack, dependency policy, config rules, code style), which frees your prompts to be short and task-specific.
Checkpoint
Before moving on, make sure you can answer:
- Why is this a monorepo, and where do the three
AGENTS.mdfiles live (and what do you rename them to for Claude Code)? - What are the two paths through the system, and why is ingestion a local offline pipeline?
- Which two files enforce the 7-day dependency age rule, and what attack does it defend against?
- What does the backend own that the browser must never do?
Next: Setup
Create the Supabase project, configure environment files, download the SEC corpus, and generate the build checklist.