Drawer 05 · Interfaces

The reading room

Two consumable surfaces. The service speaks HTTP and publishes an OpenAPI document at /openapi.json. The client is a Next.js 16 application that rewrites most paths to the backend unchanged, keeps its own copy of the chat path, and puts an authentication gate in front of everything except the health check.

05.1Endpoint register

Register I — every declared operation, in router order
Method Path Body / query Source State
GET/main.py:288live
GET/healthmain.py:303live
POST/embedEmbedRequestembed.py:24live
POST/searchSearchRequestsearch.py:16live
POST/search/repos/{repo_id}SearchRequestsearch.py:117live
GET/search/repossearch.py:187live
POST/indexIndexRequestindex.py:17counts files only
POST/reposAddRepoRequest + 3 headersrepos.py:79live
GET/repos?limitrepos.py:152live
GET/repos/{repo_id}repos.py:178live
DELETE/repos/{repo_id}repos.py:201live
POST/repos/{repo_id}/reindexReindexRequestrepos.py:224live
GET/repos/{repo_id}/files/treerepos.py:363used by client
GET/repos/{repo_id}/files/content?pathrepos.py:393used by client
GET/files/{repo_id}/treefiles.py:122duplicate
GET/files/{repo_id}/content?pathfiles.py:152duplicate
POST/chatChatRequest + X-Anthropic-Keychat.py:473live
POST/chat/streamChatRequestchat.py:599Claude path not streamed
GET/chat/modelschat.py:674live
GET/chat/healthchat.py:721live
POST/navigate/definition{symbol, file?}navigate.py:86needs prior indexing
POST/navigate/references{symbol, repo_path}navigate.py:114regex scan
GET/navigate/symbols/{file_path:path}navigate.py:147in-memory only
POST/navigate/symbol-at-position{file, line, column}navigate.py:173live
POST/navigate/index-file?file_path&relative_pathnavigate.py:194live
GET/navigate/statsnavigate.py:230live
POST/navigate/clearnavigate.py:248live
GET/embeddings/providersembeddings.py:60live
POST/embeddings/configure{provider, api_key?, model?}embeddings.py:84destructive
POST/embeddings/test3 headersembeddings.py:147mutates global config
GET/embeddings/statsembeddings.py:201live
POST/memory/skillsStoreSkillRequestmemory.py:179StubAMEM
POST/memory/skills/searchSearchSkillsRequestmemory.py:195StubAMEM
POST/memory/errorsStoreErrorRequestmemory.py:223StubAMEM
POST/memory/errors/searchSearchErrorRequestmemory.py:244StubAMEM
POST/memory/buildsStoreBuildRequestmemory.py:273StubAMEM
GET/memory/buildsmemory.py:292StubAMEM
POST/memory/contextGetContextRequestmemory.py:330StubAMEM
POST/webhook/githubraw + X-Hub-Signature-256github.py:93router not included

The /memory family attempts from autopilot.memory import AMEM and, when that fails, installs an in-process StubAMEM (memory.py:53). autopilot is not a dependency of this distribution, so on any clean install the stub is what answers. Its data does not survive a restart.

05.2Health as a self-report

Terminal 4 · GET /healthresponse shape from main.py:303-352
$ curl -sS http://localhost:8080/health | python3 -m json.tool
{
    "status": "healthy",
    "service": "compute-service",
    "version": "0.1.0",
    "gpu": {
        "available": true,
        "device": "cuda"
    },
    "model": {
        "loaded": true,
        "embedding": "BAAI/bge-m3"
    },
    "features": {
        "faiss_vector_store": true,
        "ast_chunking": true,
        "cross_encoder_reranking": true,
        "query_expansion": true,
        "embedding_cache": true,
        "contextual_retrieval": true,
        "code_navigation": true
    },
    "services": {
        "embedding": true,        "vector_store": true,
        "zoekt": true,            "indexer": true,
        "hybrid_search": true,    "repo_manager": true,
        "cross_repo_search": true
    },
    "memory": {
        "rss_mb": 3182.44,
        "vms_mb": 9614.02,
        "percent": 4.87
    },
    "errors": null
}
Read the two blocks differently

services is computed — each entry is x is not None against the object graph built in lifespan(). features is a dictionary of seven hard-coded True literals (main.py:332-340). It reports that contextual retrieval is enabled whether or not Ollama is reachable, and that cross-encoder reranking is on even when the resolved reranker is Cohere. status is "healthy", "degraded" if anything appended to startup_errors, or "initializing" before the model finishes loading.

05.3Issuing a search

Terminal 5 · POST /searchfields from models/schemas.py:36
$ curl -sS -X POST http://localhost:8080/search \
    -H 'Content-Type: application/json' \
    -d '{
          "query": "reciprocal rank fusion",
          "search_type": "hybrid",
          "repos": ["4f2a91c7"],
          "limit": 5
        }' | python3 -m json.tool
{
    "results": [
        {
            "repo": "REPO",
            "file": "compute_service/services/hybrid_search.py",
            "line": 183,
            "score": 0.883,
            "snippet": "    def _reciprocal_rank_fusion(\n        self,\n …",
            "match_type": "hybrid"
        },
        …4 more…
    ],
    "query": "reciprocal rank fusion",
    "total": 5,
    "searched_repos": ["4f2a91c7"]
}

# search_type accepts exactly three values; anything else falls through to hybrid
$ curl -sS -X POST http://localhost:8080/search \
    -H 'Content-Type: application/json' \
    -d '{"query":"embed_query","search_type":"keyword","limit":3}'
{"results":[],"query":"embed_query","total":0,"searched_repos":[]}
# empty because no repository has status "ready" yet — CrossRepoSearch skips
# anything not ready and returns before touching the retrieval core.
query
Required. Passed unmodified to the semantic leg; expanded before the keyword leg.
repos
null or omitted searches every repository whose status is ready. A list restricts to those ids — and ids not in the list are also excluded from the FAISS post-filter.
search_type
"keyword", "semantic" or "hybrid". Only the first two short-circuit; any other string takes the hybrid branch.
limit
Defaults to 20. Applied per repository and again to the merged list, so a five-repository search fetches up to 5 × limit rows before truncating.
project_path
Marked deprecated in the schema. Still honoured as a fallback branch at search.py:66 when cross_repo_search is missing from app.state.

05.4The client, reconstructed

Reconstruction of the CodeScope web client layout A wireframe of the application shell. A full-width header sits above a three-column body. The left column is a collapsible sidebar containing four navigation links — Search, Browse, Chat and Settings — followed by a checkbox list of indexed repositories with coloured status dots and a status legend pinned to the bottom. The centre column is the search page: a heading, a search input, a three-way toggle between semantic, hybrid and keyword, a repository select, and a stack of result cards each showing a file path, a language badge, a match-type badge, a percentage score and up to ten numbered lines of code. The right column is a chat panel drawn dashed because it is conditional on store state. A command palette dialog is drawn as an overlay in the centre. Numbered callouts around the frame name the component file behind each region. web/src — AppShell composition, reconstructed from the component tree Header h-12 · logo · SignedIn / SignedOut buttons (Clerk) layout/header.tsx Repositories ▸ Search ▸ Browse ▸ Chat ▸ Settings INDEXED REPOS Select All 2 of 4 selected for search compute-service web docs-site legacy-api Ready Indexing Error Search your codebase 🔎 Search your codebase… ✕ ⌕ ⚙ Semantic Hybrid Keyword All repositories ▾ 📄 services/hybrid_search.py python · hybrid · 88% 183 │ def _reciprocal_rank_fusion( 184 │ self, 185 │ keyword_results: List[SearchResult], 186 │ semantic_results: List[VectorDocument], … │ +14 more lines 📄 services/reranker.py python · hybrid · 64% 211 │ final_score = ( 212 │ self.ORIGINAL_WEIGHT * orig_score + 213 │ self.RERANK_WEIGHT * rerank_score 214 │ ) … │ +6 more lines 📄 services/vector_store_faiss.py python · hybrid · 30% 298 │ def search( 299 │ self, query_embedding, limit=10, … … │ +8 more lines Ask about your code rendered only when chatPanelOpen is true chat/chat-panel.tsx chat/chat-message.tsx Ask a follow-up… ➤ Command palette — ⌘K Type a command or search… NAVIGATION Search · Browse Files · AI Chat REPOSITORIES one row per repo — “n files • status” ACTIONS Settings — Configure CodeScope components behind each region 1 layout/app-shell.tsx flex h-screen · bg-slate-950 2 layout/sidebar.tsx w-64 open / w-16 collapsed checkbox drives selectedRepoIds 3 search/search-bar.tsx 300 ms debounce on keystroke language chips are local state and are never sent to the API 4 search/search-result-card.tsx first 10 lines, then a count score rendered as a percentage 5 search/command-palette.tsx Radix Dialog, arrow-key list 6 store/app-store.ts Zustand · 1 flat store, 28 keys sidebarOpen · chatPanelOpen searchType · selectedRepoIds fileTree · expandedPaths · … 7 explorer/ + navigation/ file-tree · code-viewer (Monaco) symbol-panel · symbol-outline used by /browse/[repoId] 8 middleware.ts clerkMiddleware · every route except /sign-in, /sign-up, /health redirects to /sign-in otherwise 9 next.config.ts output: "standalone" 10 rewrite groups → BACKEND_URL default http://localhost:8080 no rewrite for /index or /memory keyboard — hooks/use-keyboard-shortcuts.ts declares five bindings; nothing calls the hook ⌘K / Ctrl-K open command palette active — registered separately inside AppShell Esc close command palette active — same inline handler ⌘B · ⌘⇧C · ⌘⇧F · ⌘⇧E declared in the hook only; no component imports it, so these are inert The duplicate ⌘K listener in app-shell.tsx is the one that runs, which is why the palette works and the other four do not.
Figure 05.1 — Client layout reconstruction Regions and copy are taken from the component sources; the wireframe reproduces the composition in web/src/components/layout/app-shell.tsx and the search page at web/src/app/(main)/page.tsx. Result cards, scores and file paths are illustrative content in a real component shape.

05.5Proxy map

The client is deployed in front of the service, not beside it. Every browser request hits Next.js, which either handles it or rewrites it to BACKEND_URL. The API client uses relative paths throughout (const API_BASE = "", web/src/lib/api.ts).

Register J — next.config.ts rewrites and the one exception
Browser pathHandled byNote
/search · /search/*rewrite → backend
/repos · /repos/*rewrite → backendCovers the file tree and content endpoints.
/chat/*rewrite → backend/chat/stream and /chat/models reach FastAPI.
/navigate/*rewrite → backend
/embedrewrite → backend
/embeddings · /embeddings/*rewrite → backend
/healthrewrite → backendThe only route the auth middleware treats as public.
/api/chatNext.js route handlerNot a rewrite. Runs its own POST /search at limit: 5 and calls Anthropic itself.
/api/github/reposNext.js route handlerLists the signed-in user's GitHub repositories.
/api/github/index-repoNext.js route handlerForwards an add-repository request with the user's token.
/index · /memory/*not proxiedNo rewrite entry; unreachable from the browser.
Two model tables that disagree

chat.py:37 maps claude-sonnet-4-5 to claude-sonnet-4-5-20250929, claude-haiku-4-5 to claude-haiku-4-5-20251001 and claude-opus-4-5 to claude-opus-4-5-20251101. The Next.js handler at web/src/app/api/chat/route.ts maps the same three display names to claude-sonnet-4-20250514, claude-3-5-haiku-20241022 and claude-opus-4-20250514. Selecting “Claude Haiku 4.5” in the UI therefore reaches a different model depending on which of the two paths served the request. The prices shown by GET /chat/models come from the Python table only.