feat: initial docs repo with project inventory and all documentation
- Added REPO_INVENTORY.md with all repos, branches, remotes, and staging info - Added .gitignore - Synced all existing docs from local workspace - Centralized documentation hub for GrowQR team
This commit is contained in:
463
runbook.md
Normal file
463
runbook.md
Normal file
@@ -0,0 +1,463 @@
|
||||
# GrowQR Demo Runbook
|
||||
|
||||
Use this when restarting the full local demo after everything has been stopped. It should bring back the workflows dashboard, growqr-app frontend session launchers, backend, Rivet workflow actor runner, interview-service, roleplay-service, QScore service, resume-builder, matchmaking, and the per-user Gitea/OpenCode stack.
|
||||
|
||||
## What Must Be Running
|
||||
|
||||
- Docker Desktop
|
||||
- `qscore-service`: Postgres, Redis, API, worker
|
||||
- `interview-service`: API, Postgres, MinIO, bucket setup
|
||||
- `roleplay-service`: API, Postgres, MinIO, bucket setup
|
||||
- `resume-builder`: Docker container with Postgres (port 5433), API (port 8002)
|
||||
- `matchmaking`: API on host port 8006
|
||||
- `growqr-backend`: backend Postgres plus local Node backend
|
||||
- `workflows--dashboard`: Next.js frontend
|
||||
- `growqr-app/frontend`: Next.js frontend for the new interview/roleplay session UI
|
||||
- Per-user OpenCode containers, created by backend provisioning (Gitea is central/shared)
|
||||
|
||||
The known demo ports are:
|
||||
|
||||
- Workflows dashboard: `http://localhost:3000`
|
||||
- GrowQR app frontend session launchers: `http://localhost:3002`
|
||||
- Backend: `http://localhost:4000`
|
||||
- Backend Rivet proxy: `http://localhost:4000/api/rivet`
|
||||
- Interview service: `http://localhost:8007`
|
||||
- Roleplay service: `http://localhost:8008`
|
||||
- QScore service: `http://localhost:8000`
|
||||
- Resume builder: `http://localhost:8002`
|
||||
- Matchmaking service: `http://localhost:8006`
|
||||
- Resume builder DB: `localhost:5433`
|
||||
- QScore Redis: `localhost:6379`
|
||||
- Central Gitea: `http://localhost:3001` (shared org-wide, changes.md §2A)
|
||||
- Backend Postgres: `localhost:5432`
|
||||
- Per-user OpenCode containers: dynamically allocated from `20000-29999`
|
||||
|
||||
## Required Env Files
|
||||
|
||||
Before starting, verify these files exist and contain the local demo values:
|
||||
|
||||
- `growqr-backend/.env`
|
||||
- `growqr-backend/.env.local`
|
||||
- `workflows--dashboard/.env.local`
|
||||
- `growqr-app/frontend/.env.local`
|
||||
- `interview-service/.env`
|
||||
- `roleplay-service/.env`
|
||||
- `growqr-app/resume-builder/.env.local` (created by setup script)
|
||||
|
||||
Do not print secrets into chat or commit them. The important non-secret shape is:
|
||||
|
||||
- Backend service URLs should point to `127.0.0.1`:
|
||||
- `INTERVIEW_SERVICE_URL=http://127.0.0.1:8007`
|
||||
- `ROLEPLAY_SERVICE_URL=http://127.0.0.1:8008`
|
||||
- `QSCORE_SERVICE_URL=http://127.0.0.1:8000`
|
||||
- `RESUME_SERVICE_URL=http://127.0.0.1:8002`
|
||||
- `MATCHMAKING_SERVICE_URL=http://127.0.0.1:8006`
|
||||
- `GROWQR_APP_FRONTEND_URL=http://localhost:3002`
|
||||
- Backend per-user containers should use:
|
||||
- `OPENCODE_IMAGE=ghcr.io/anomalyco/opencode:latest`
|
||||
- `USER_CONTAINER_HOST=127.0.0.1`
|
||||
- `USER_DATA_ROOT=./.data/users`
|
||||
- Frontend should use:
|
||||
- `NEXT_PUBLIC_RIVET_ENDPOINT=http://127.0.0.1:4000/api/rivet`
|
||||
- `NEXT_PUBLIC_GROWQR_BACKEND_URL=http://127.0.0.1:4000` (NOT `GROWQR_BACKEND_URL` — must have NEXT_PUBLIC_ prefix for client-side)
|
||||
- `OPENCODE_API_KEY=<from backend .env>` (must match backend)
|
||||
- `LLM_BASE_URL=https://opencode.ai/zen/v1`
|
||||
- `LLM_MODEL=kimi-k2.6`
|
||||
- Resume builder should use:
|
||||
- `DATABASE_URL=postgresql+asyncpg://postgres:postgres@host.docker.internal:5433/growqr_resume` (host.docker.internal when running in Docker, localhost when running locally)
|
||||
- `API_PORT=8002`
|
||||
- `OPENCODE_API_KEY=<from backend .env>` — **CRITICAL: must be the EXACT same key as backend.** Both services share the same OpenCode account.
|
||||
- `OPENCODE_BASE_URL=https://opencode.ai/zen/v1`
|
||||
- `AI_MODEL=kimi-k2.6`
|
||||
- `CLERK_JWKS_URL=https://noted-elephant-23.accounts.dev/.well-known/jwks.json`
|
||||
- `CLERK_ISSUER=https://noted-elephant-23.accounts.dev`
|
||||
- `CORS_ORIGINS="http://localhost:3000,http://localhost:3001,http://127.0.0.1:3000"`
|
||||
- `A2A_ALLOWED_KEYS=dev-a2a-key`
|
||||
- `REDIS_URL=redis://host.docker.internal:6379/0`
|
||||
- `DEBUG=true`
|
||||
- `SERVICE_TOKEN` and `A2A_ALLOWED_KEY` must match across backend and services.
|
||||
- Clerk keys must match between frontend and backend.
|
||||
- Gemini and OpenCode keys must be present for the audio/session and LLM paths.
|
||||
|
||||
### OpenCode API Key Sync (IMPORTANT)
|
||||
|
||||
The backend and resume-builder share the same OpenCode API key. When copying values, always use `growqr-backend/.env` as the **source of truth**. The resume-builder's `.env.local` was previously out of sync (25-char dummy key vs 67-char real key), causing silent LLM failures.
|
||||
|
||||
To verify keys are synced:
|
||||
```bash
|
||||
diff <(grep OPENCODE_API_KEY growqr-backend/.env | head -1) <(grep OPENCODE_API_KEY growqr-app/resume-builder/.env.local | head -1)
|
||||
# Should show no differences
|
||||
```
|
||||
|
||||
Note: interview-service and roleplay-service use Gemini (not OpenCode) — they don't need an OpenCode key.
|
||||
|
||||
## Start Order
|
||||
|
||||
Run all commands from `/Users/divyansh/Desktop/growQR` unless a command changes directory.
|
||||
|
||||
### 0. Start Central Gitea first (NEW per changes.md §2A)
|
||||
|
||||
The central Gitea is now a shared compose service in the backend's docker-compose. **Note:** Gitea health check uses port 3000 internally (container), mapped to 3001 on host. Port mapping is `3001:3000`.
|
||||
|
||||
```bash
|
||||
docker compose -f growqr-backend/docker-compose.yml up -d gitea
|
||||
```
|
||||
|
||||
Wait until Gitea is healthy (~30 seconds on first startup):
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:3001/api/v1/version
|
||||
# Expected: {"version":"1.22.6"}
|
||||
```
|
||||
|
||||
Per-user Gitea containers are NO LONGER spawned dynamically. All users share the central Gitea instance with one repo per user under the `growqr` org.
|
||||
|
||||
### 1. Start QScore first
|
||||
|
||||
QScore owns the Redis instance exposed on `localhost:6379`. The interview service currently uses that host Redis for workflow task dispatch, so QScore should come up first.
|
||||
|
||||
```bash
|
||||
docker compose -f qscore-service/docker-compose.yml up -d --build
|
||||
```
|
||||
|
||||
Wait until all QScore containers are healthy/running:
|
||||
|
||||
```bash
|
||||
docker compose -f qscore-service/docker-compose.yml ps
|
||||
curl http://127.0.0.1:8000/health
|
||||
# Expected: {"status":"ok"}
|
||||
```
|
||||
|
||||
### 2. Start Interview Service
|
||||
|
||||
```bash
|
||||
docker compose -f interview-service/docker-compose.yml up -d --build
|
||||
```
|
||||
|
||||
Verify:
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8007/health
|
||||
# Expected: {"status":"ok","service":"interview-service","a2a":true,"agent":true}
|
||||
```
|
||||
|
||||
### 3. Start Roleplay Service
|
||||
|
||||
```bash
|
||||
docker compose -f roleplay-service/docker-compose.yml up -d --build
|
||||
```
|
||||
|
||||
Verify:
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8008/health
|
||||
# Expected: {"status":"ok","service":"roleplay-service","a2a":true,"agent":true}
|
||||
```
|
||||
|
||||
### 4. Start Resume Builder (NEW)
|
||||
|
||||
The resume builder runs as a Docker container built from `growqr-app/resume-builder/Dockerfile`. It uses its own Postgres database on port 5433.
|
||||
|
||||
```bash
|
||||
# Start resume-builder Postgres
|
||||
docker compose -f growqr-app/resume-builder/docker-compose.yml up -d postgres
|
||||
|
||||
# Build resume-builder image (first time only)
|
||||
docker build -t growqr-resume-builder growqr-app/resume-builder
|
||||
|
||||
# Run migrations
|
||||
cd growqr-app/resume-builder
|
||||
uv run --env-file .env.local alembic upgrade head
|
||||
|
||||
# Start the service with synced OpenCode key from backend
|
||||
cd /Users/divyansh/Desktop/growQR
|
||||
OPENCODE_KEY=$(grep OPENCODE_API_KEY growqr-backend/.env | head -1 | cut -d= -f2)
|
||||
|
||||
docker rm -f growqr-resume-builder 2>/dev/null
|
||||
docker run -d --name growqr-resume-builder \
|
||||
-p 8002:8002 \
|
||||
-e DATABASE_URL=postgresql+asyncpg://postgres:postgres@host.docker.internal:5433/growqr_resume \
|
||||
-e OPENCODE_API_KEY="${OPENCODE_KEY}" \
|
||||
-e OPENCODE_BASE_URL=https://opencode.ai/zen/v1 \
|
||||
-e AI_MODEL=kimi-k2.6 \
|
||||
-e CLERK_JWKS_URL=https://noted-elephant-23.accounts.dev/.well-known/jwks.json \
|
||||
-e CLERK_ISSUER=https://noted-elephant-23.accounts.dev \
|
||||
-e CORS_ORIGINS="http://localhost:3000,http://localhost:3001,http://127.0.0.1:3000" \
|
||||
-e A2A_ALLOWED_KEYS=dev-a2a-key \
|
||||
-e REDIS_URL=redis://host.docker.internal:6379/0 \
|
||||
-e DEBUG=true \
|
||||
growqr-resume-builder uvicorn app.main:app --host 0.0.0.0 --port 8002
|
||||
|
||||
# Verify
|
||||
curl http://127.0.0.1:8002/health
|
||||
# Expected: {"status":"healthy","service":"resume-builder","mcp":true,"agent":true,"a2a":true}
|
||||
```
|
||||
|
||||
### 5. Start Backend Postgres
|
||||
|
||||
For the demo path that avoids Rivet `no_capacity`, run the backend locally with its embedded Rivet runner and start only the backend Postgres from compose:
|
||||
|
||||
```bash
|
||||
docker compose -f growqr-backend/docker-compose.yml up -d postgres
|
||||
```
|
||||
|
||||
**Important:** If the backend was previously running with older schema, you may need to add missing columns:
|
||||
|
||||
```bash
|
||||
docker exec growqr-postgres psql -U growqr -d growqr -c "
|
||||
ALTER TABLE user_stacks ADD COLUMN IF NOT EXISTS gitea_repo_name TEXT;
|
||||
ALTER TABLE user_stacks ADD COLUMN IF NOT EXISTS gitea_repo_owner TEXT;
|
||||
ALTER TABLE user_stacks ADD COLUMN IF NOT EXISTS image_version TEXT;
|
||||
ALTER TABLE user_stacks ADD COLUMN IF NOT EXISTS migration_version TEXT;
|
||||
ALTER TABLE user_stacks ADD COLUMN IF NOT EXISTS prompt_version TEXT;
|
||||
"
|
||||
```
|
||||
|
||||
Then migrate:
|
||||
|
||||
```bash
|
||||
cd /Users/divyansh/Desktop/growQR/growqr-backend
|
||||
npm run db:migrate
|
||||
```
|
||||
|
||||
Start the backend:
|
||||
|
||||
```bash
|
||||
cd /Users/divyansh/Desktop/growQR/growqr-backend
|
||||
RIVET_RUN_ENGINE=1 npm run dev
|
||||
```
|
||||
|
||||
Keep this terminal open. Verify in a separate terminal:
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:4000/healthz
|
||||
# Expected: {"ok":true}
|
||||
```
|
||||
|
||||
The backend will auto-provision OpenCode containers for existing users on boot (reconcileOnBoot).
|
||||
|
||||
### 6. Start Matchmaking
|
||||
|
||||
The matchmaking service powers the Scout/job-search handoff. It runs on port 8000 internally and port 8006 on the host.
|
||||
|
||||
```bash
|
||||
docker compose -f matchmaking/docker-compose.yml up -d --build
|
||||
curl http://127.0.0.1:8006/api/v1/health
|
||||
# Expected: {"status":"healthy", ...}
|
||||
```
|
||||
|
||||
### 7. Start Frontends
|
||||
|
||||
Start the dashboard on port 3000:
|
||||
|
||||
```bash
|
||||
cd /Users/divyansh/Desktop/growQR/workflows--dashboard
|
||||
npm run build
|
||||
npm run start
|
||||
```
|
||||
|
||||
Start the growqr-app frontend on port 3002. This app now owns the product UI for interview and roleplay sessions launched from workflow chat.
|
||||
|
||||
```bash
|
||||
cd /Users/divyansh/Desktop/growQR/growqr-app/frontend
|
||||
npm install --legacy-peer-deps
|
||||
npm run dev -- -p 3002
|
||||
```
|
||||
|
||||
Keep both terminals open. Open:
|
||||
|
||||
```text
|
||||
http://localhost:3000/v2/workflows
|
||||
```
|
||||
|
||||
## Provision OpenCode Containers
|
||||
|
||||
OpenCode containers are per-user and spawned dynamically by the backend Docker manager. Gitea is NO LONGER per-user — it's a central shared service (changes.md §2A).
|
||||
|
||||
After provisioning, `docker ps` should show containers with names like:
|
||||
|
||||
```text
|
||||
growqr-opencode-user_<userId>
|
||||
```
|
||||
|
||||
If they are missing, sign into the dashboard and allow the user stack bootstrap to run. The local data is under:
|
||||
|
||||
```text
|
||||
growqr-backend/.data/users
|
||||
```
|
||||
|
||||
That folder is intentionally gitignored and should stay local.
|
||||
|
||||
## Demo Path — Interview-to-Offer Accelerator
|
||||
|
||||
Use this path for the Interview-to-Offer workflow demo:
|
||||
|
||||
1. Open `http://localhost:3000/v2/workflows`.
|
||||
2. Type in the chat: `I have an interview at Google for SWE`
|
||||
3. The AI responds with a conversational message and the **WorkflowDiscovery card** (red, 4 agents, 4 steps) appears below.
|
||||
4. Click **"Start this workflow"** on the card.
|
||||
5. The workflow progresses step-by-step:
|
||||
- **Step 1**: AI asks for job description → you respond with details
|
||||
- **Step 2**: AI asks for resume → you respond
|
||||
- **Step 3**: Resume Agent analyzes and tailors (shows "DONE")
|
||||
- **Step 4**: Sara interview session ready → shows **"Open" button**
|
||||
- **Step 5**: Emily roleplay session ready → shows **"Open" button**
|
||||
- **Step 6**: Quinn computes Q-Score → shows results
|
||||
6. Click **Open** on Sara's card → new tab opens `http://localhost:3002/service-sessions/interview?session_id=<uuid>...`
|
||||
7. On the new GrowQR interview launcher, click **Start interview** and allow microphone/camera access.
|
||||
8. Click **Open** on Emily's card → new tab opens `http://localhost:3002/service-sessions/roleplay?session_id=<uuid>...`
|
||||
9. On the new GrowQR roleplay launcher, click **Start roleplay** and allow microphone/camera access.
|
||||
10. Completed steps are marked with **green checkmarks** in the chat.
|
||||
|
||||
### Alternative Workflow Trigger Queries
|
||||
|
||||
| Workflow | Trigger Query |
|
||||
|---|---|
|
||||
| Interview-to-Offer | `I have an interview at Google for SWE` |
|
||||
| Career Switch | `I want to switch from marketing to product management` |
|
||||
| Resume Boost | `My resume isn't getting any responses` |
|
||||
| Job Search | `Find me backend engineering jobs` |
|
||||
| Job Preparation | `Prepare me for a role at Stripe` |
|
||||
|
||||
Direct service demo pages are for low-level service debugging only. Product workflow links should use the new `growqr-app/frontend` launchers:
|
||||
|
||||
```text
|
||||
http://localhost:3002/service-sessions/interview?session_id=<uuid>
|
||||
http://localhost:3002/service-sessions/roleplay?session_id=<uuid>
|
||||
```
|
||||
|
||||
## Health Checks
|
||||
|
||||
Use these after startup:
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:4000/healthz # Backend
|
||||
curl http://127.0.0.1:8007/health # Interview
|
||||
curl http://127.0.0.1:8008/health # Roleplay
|
||||
curl http://127.0.0.1:8000/health # QScore
|
||||
curl http://127.0.0.1:8002/health # Resume builder
|
||||
curl http://127.0.0.1:8006/api/v1/health # Matchmaking
|
||||
curl -I 'http://localhost:3002/service-sessions/interview?session_id=demo-interview'
|
||||
curl -I 'http://localhost:3002/service-sessions/roleplay?session_id=demo-roleplay'
|
||||
curl http://127.0.0.1:3001/api/v1/version # Gitea
|
||||
docker ps # All containers
|
||||
```
|
||||
|
||||
If the workflow actor fails with `no_capacity`, restart the backend with:
|
||||
|
||||
```bash
|
||||
cd /Users/divyansh/Desktop/growQR/growqr-backend
|
||||
RIVET_RUN_ENGINE=1 npm run dev
|
||||
```
|
||||
|
||||
## Shutdown
|
||||
|
||||
Stop frontend and backend with `Ctrl+C` in their terminals.
|
||||
|
||||
**Also kill any lingering backend Node processes:**
|
||||
```bash
|
||||
pkill -f "tsx watch" 2>/dev/null
|
||||
pkill -f "tsx.*src/index" 2>/dev/null
|
||||
```
|
||||
|
||||
Stop compose services:
|
||||
|
||||
```bash
|
||||
docker compose -f growqr-backend/docker-compose.yml down
|
||||
docker compose -f interview-service/docker-compose.yml down
|
||||
docker compose -f roleplay-service/docker-compose.yml down
|
||||
docker compose -f qscore-service/docker-compose.yml down
|
||||
docker compose -f matchmaking/docker-compose.yml down
|
||||
docker compose -f growqr-app/resume-builder/docker-compose.yml down
|
||||
docker rm -f growqr-resume-builder
|
||||
```
|
||||
|
||||
Stop per-user OpenCode containers if still running:
|
||||
|
||||
```bash
|
||||
docker ps --filter "name=growqr-opencode-user"
|
||||
```
|
||||
|
||||
Then stop/remove the shown containers by name:
|
||||
|
||||
```bash
|
||||
docker stop <growqr-opencode-container>
|
||||
docker rm <growqr-opencode-container>
|
||||
```
|
||||
|
||||
They can be recreated later by the backend provisioning flow. The local persistent data remains in `growqr-backend/.data/users`.
|
||||
|
||||
## Build Rule
|
||||
|
||||
Whenever code changes are made, run the relevant build before calling the demo ready:
|
||||
|
||||
```bash
|
||||
cd /Users/divyansh/Desktop/growQR/growqr-backend && npm run build
|
||||
cd /Users/divyansh/Desktop/growQR/workflows--dashboard && npm run build
|
||||
cd /Users/divyansh/Desktop/growQR/growqr-app/frontend && npm run build
|
||||
```
|
||||
|
||||
For Dockerized Python services, rebuild with their compose commands:
|
||||
|
||||
```bash
|
||||
docker compose -f interview-service/docker-compose.yml up -d --build
|
||||
docker compose -f roleplay-service/docker-compose.yml up -d --build
|
||||
docker compose -f qscore-service/docker-compose.yml up -d --build
|
||||
```
|
||||
|
||||
## Agent Architecture & Flow
|
||||
|
||||
The system has 5 real service-backed agents:
|
||||
|
||||
| Agent | Name | Service | Port | Provider | Creates |
|
||||
|---|---|---|---|---|---|
|
||||
| Resume Agent | Mira | resume-builder | 8002 | OpenCode (kimi-k2.6) | Resume analysis + tailoring |
|
||||
| Interview Agent | Sara | interview-service + growqr-app frontend launcher | 8007 + 3002 | Gemini (audio) | Live interview sessions in new UI |
|
||||
| Roleplay Agent | Emily | roleplay-service + growqr-app frontend launcher | 8008 + 3002 | Gemini (audio) | Roleplay scenario sessions in new UI |
|
||||
| Q-Score Agent | Quinn | qscore-service | 8000 | Internal formula | Readiness scores |
|
||||
| Job Search Agent | Scout | matchmaking | 8006 | Internal ranking engine | Ranked opportunity feed |
|
||||
|
||||
### Chat Flow (Conversational Step-by-Step)
|
||||
|
||||
1. User types message → Frontend sends to `POST http://127.0.0.1:4000/api/chat` (with Clerk auth)
|
||||
2. Backend tries Rivet actor first, falls back to direct LLM with tools
|
||||
3. LLM responds conversationally OR calls a tool (`start_interview_session`, `analyze_resume`, etc.)
|
||||
4. If tool is called, backend executes it against the real microservice
|
||||
5. Response includes `{ reply, sessions[], workflow }` — sessions contain real URLs
|
||||
6. Frontend renders agent cards with photos, status badges, and **Open** buttons
|
||||
|
||||
### Trigger Queries (what to type)
|
||||
|
||||
| For this agent | Type this |
|
||||
|---|---|
|
||||
| Resume analysis | `analyze my resume for Google SWE` |
|
||||
| Interview session | `launch a behavioral interview for Software Engineer at Google` |
|
||||
| Roleplay session | `launch a roleplay for negotiating a Google offer` |
|
||||
| Q-Score | `compute my Q-Score` |
|
||||
| Full workflow | `I have an interview at Google for SWE` |
|
||||
|
||||
### Session URLs
|
||||
|
||||
When a session is created, the backend preserves the `session_id` from the service configure response and returns one of these product URLs:
|
||||
|
||||
- Interview: `http://localhost:3002/service-sessions/interview?session_id=<uuid>&role=<goal>&type=<type>`
|
||||
- Roleplay: `http://localhost:3002/service-sessions/roleplay?session_id=<uuid>&goal=<goal>&type=<type>`
|
||||
|
||||
The old direct demo pages still exist for service debugging, but workflow chat should not link to them.
|
||||
|
||||
### Key Files
|
||||
|
||||
- System prompt: `growqr-backend/prompts/system.txt`
|
||||
- Agent definitions: `growqr-backend/agents/*.md`
|
||||
- Service probes: `growqr-backend/src/services/service-agents.ts`
|
||||
- Chat route (with tool dispatch): `growqr-backend/src/routes/chat.ts`
|
||||
- Frontend chat component: `workflows--dashboard/src/components/home-v2/GrowChat.tsx`
|
||||
- New service launchers: `growqr-app/frontend/src/app/service-sessions/interview/page.tsx`, `growqr-app/frontend/src/app/service-sessions/roleplay/page.tsx`
|
||||
- Product flow requirements: `.growqr_memory.md`
|
||||
|
||||
### Known Quirks
|
||||
|
||||
- **Roleplay configure is slow** (30-60s): The roleplay service calls an LLM to generate scenarios. Be patient.
|
||||
- **Rivet `/actors` returns 400**: Cosmetic console warning from the Rivet frontend SDK. Does not affect chat — chat uses HTTP fallback.
|
||||
- **Rivet proxy vs handler conflict**: When `RIVET_RUN_ENGINE=1`, the backend proxies `/api/rivet/*` to the engine at `localhost:6420` instead of using `registry.handler()`. This avoids the "Runtime already started as runner" error.
|
||||
- **Multiple backend processes**: `tsx watch` sometimes spawns orphans. Always `pkill -f "tsx watch"` before restarting.
|
||||
Reference in New Issue
Block a user