docs: add agent codebase guide

This commit is contained in:
jjbrunton committed 2026-08-09 16:21:57 +01:00
1 parent 742c06d0c4
commit c630303b47
12 files changed
+881 -4483

No files matched your search

+69 -469
View File
@@ -1,504 +1,104 @@
# Configuration Guide
# Configuration
Complete reference for configuring RedditBot.
## Precedence and lifecycle
---
Configuration is validated by `ConfigSchema` in `src/config/index.ts`.
## Table of Contents
1. `DATABASE_PATH` identifies the database before application settings can be read.
2. If `app_config` exists, the application-owned configuration is loaded from SQLite.
3. Otherwise, `config.yaml` is loaded once and the validated application-owned subset is seeded into SQLite.
4. Environment-owned secrets and database location are injected on every start.
1. [Configuration Overview](#configuration-overview)
2. [Configuration Storage](#configuration-storage)
3. [Environment Variables](#environment-variables)
4. [Application Settings](#application-settings)
5. [Runtime Configuration](#runtime-configuration)
6. [Configuration Validation](#configuration-validation)
7. [Troubleshooting](#troubleshooting)
After the initial seed, editing `config.yaml` does not replace an existing `app_config` row. Use the authenticated settings/API workflow or a deliberate database migration. The config route validates the complete result before saving.
---
## Environment-owned values
## Configuration Overview
| Variable | Required | Purpose |
| --- | --- | --- |
| `OPENROUTER_API_KEY` | Yes | OpenRouter authentication. |
| `WEB_DASHBOARD_TOKEN` | When web is enabled | Administrator UI/API credential. |
| `DATABASE_PATH` | No | SQLite location; defaults to `./data/reddit-bot.db`. |
| `REDDIT_BOT_AGENT_READ_TOKEN` | No | Read-only API/SSE bearer credential. |
| `REDDIT_BOT_AGENT_CONTROL_TOKEN` | No | Read plus versioned control/job bearer credential. |
| `REDDIT_BOT_API_URL` | MCP only | Base URL; defaults to `http://127.0.0.1:3000`. |
| `REDDIT_BOT_AGENT_TOKEN` | MCP only | Preferred MCP bearer credential override. |
RedditBot uses a layered configuration approach:
Never document real values. `.env` is ignored and may contain production credentials; do not print or blindly source it.
```
┌──────────────────────────────────────────────────────────────┐
│ Priority Order (High → Low) │
├──────────────────────────────────────────────────────────────┤
│ 1. Environment Variables │
│ (OPENROUTER_API_KEY, WEB_DASHBOARD_TOKEN) │
│ │
│ 2. config.yaml │
│ (Primary configuration file) │
│ │
│ 3. Defaults │
│ (Hardcoded defaults in code) │
└──────────────────────────────────────────────────────────────┘
```
## Application sections
Configuration is loaded at startup by `loadConfig()` in `src/config/index.ts`, which:
### `reddit`
1. Reads the singleton `app_config` record from SQLite
2. Imports `config.yaml` only when upgrading an existing database that has no settings record
3. Validates the entire configuration tree using Zod
4. Returns a strongly-typed `Config` object
- `subreddits`: at least one item with `name`, `enabled`, `type`, optional `weight`, `tone`, optional scripted opener, and conversation policy.
- `pollIntervalMs`: legacy/scheduled polling cadence.
- `userAgent`: Reddit JSON request identifier.
- `targetGenders`: allowed parsed values.
- `filterSellers`, `sellerKeywords`, `sellerDomains`: seller policy inputs.
- `prioritizeRecentPosts`, `recentWindowMinutes`: recency/fallback behavior.
- `maxReadyCandidates`: maximum prepared backlog admitted before screening pauses; valid range 1–500.
Application-owned settings are edited from the dashboard and saved to SQLite. `OPENROUTER_API_KEY`, `WEB_DASHBOARD_TOKEN`, `REDDIT_BOT_AGENT_READ_TOKEN`, `REDDIT_BOT_AGENT_CONTROL_TOKEN`, and `DATABASE_PATH` remain environment-owned bootstrap values and are never stored in `app_config`.
Subreddit `type` is `image` or `chat`. Conversation policy contains `mode`, `maxAutomatedTurns`, and `rapportTurnsBeforeMediaAsk`; hybrid rapport turns must be lower than the maximum.
---
### `openrouter`
## Configuration Storage
Contains image/chat primary and fallback models plus the analysis, message, and chat-opener prompt templates. The API key is environment-only. Model fallback and prompt changes affect cost, behavior, and experiment attribution and need focused tests plus operator-visible metadata.
### Legacy config.yaml
### `rateLimit`
`config.yaml` is retained temporarily as a one-time migration source for existing installations. Once every deployed database contains an `app_config` row, it can be removed in a follow-up release.
- `minDelayBetweenMessagesMs`;
- `maxMessagesPerHour`;
- `jitterMs`.
```
RedditBot/
├── config.yaml # ← Main configuration
├── package.json
├── tsconfig.json
└── src/
```
These are per account. Reddit JSON request pacing is separately controlled by `RedditRequestCoordinator` and is process-global.
### Data Directory
### `browser`
Runtime data is stored in `data/` (created automatically):
- `sessionDir`: root for persistent account browser profiles.
- `dryRun`: when true, candidate preparation may run but remote sends must not.
```
data/
├── reddit-bot.db # SQLite database
├── browser/ # Browser session data (cookies, storage)
├── media/ # Downloaded media files
│ ├── index.json # Media index (SHA-1 → filename)
│ ├── abc123.jpg # Media file 1
│ └── def456.png # Media file 2
└── chats/ # Cached conversation JSON files
├── chat_abc123.json
└── chat_def456.json
```
Treat changing `dryRun` from true to false as enabling a real side effect.
---
### `database`
## Environment Variables
Contains `path`, but runtime ownership is `DATABASE_PATH`, not the persisted application config.
Environment variables provide secrets and bootstrap settings that are intentionally excluded from SQLite.
### `media`
| Variable | Required | Type | Description | Example |
|---|---|---|---|---|
| `OPENROUTER_API_KEY` | Yes | string | OpenRouter API key for AI services | `sk-or-v1-abc123...` |
| `WEB_DASHBOARD_TOKEN` | Yes when dashboard enabled | string | Dashboard access token | `long-random-secret` |
| `REDDIT_BOT_AGENT_READ_TOKEN` | No | string | Least-privilege GET/SSE agent access | `separate-read-secret` |
| `REDDIT_BOT_AGENT_CONTROL_TOKEN` | No | string | Read access plus `/api/v1` controls/jobs | `separate-control-secret` |
| `REDDIT_BOT_API_URL` | MCP only | URL | HTTP base URL used by `npm run mcp` | `http://127.0.0.1:3000` |
| `REDDIT_BOT_AGENT_TOKEN` | MCP only | string | Token selected for the MCP process | `separate-read-secret` |
| `DATABASE_PATH` | No | string | SQLite database location | `./data/reddit-bot.db` |
Controls periodic sync enablement and interval. Media work remains authentication-bound.
### Setting Environment Variables
### `strategies` and `chatStrategies`
**Option 1: `.env` file** (recommended for development)
Each has exploration rate, minimum mature samples, response window, and weighted variants. Variants include an ID and prompt override. Image and chat sources use separate sections so their results are not compared as one experiment.
Create a `.env` file in the project root:
### `web`
```env
OPENROUTER_API_KEY=sk-or-v1-abc123...
WEB_DASHBOARD_TOKEN=long-random-secret
REDDIT_BOT_AGENT_READ_TOKEN=separate-read-secret
REDDIT_BOT_AGENT_CONTROL_TOKEN=separate-control-secret
```
> **Note**: Add `.env` to `.gitignore` - never commit credentials.
**Option 2: Shell export**
```bash
export OPENROUTER_API_KEY=sk-or-v1-abc123...
export WEB_DASHBOARD_TOKEN=long-random-secret
export REDDIT_BOT_AGENT_READ_TOKEN=separate-read-secret
export REDDIT_BOT_AGENT_CONTROL_TOKEN=separate-control-secret
```
**Option 3: Docker**
Pass via `docker-compose.yml` or Docker CLI:
```yaml
environment:
- OPENROUTER_API_KEY=sk-or-v1-abc123...
- WEB_DASHBOARD_TOKEN=long-random-secret
- REDDIT_BOT_AGENT_READ_TOKEN=separate-read-secret
- REDDIT_BOT_AGENT_CONTROL_TOKEN=separate-control-secret
```
---
## Application Settings
Use **Settings** in the dashboard for monitoring, rate limits, dry-run behaviour, subreddits, models, prompts, filtering, media sync, and strategy configuration. The advanced JSON editor exposes settings that do not yet have dedicated controls; its payload is validated by the server before it is saved.
The YAML examples below document the legacy field layout used during one-time migration.
Complete reference for every configuration option.
### Reddit Settings
Each subreddit also has a conversation behaviour in the dashboard:
- `legacy` preserves the existing three-turn media-request flow.
- `r4r` continues general conversation autonomously and never requests media.
- `r4r-media` continues general conversation for `rapportTurnsBeforeMediaAsk`, makes one optional non-explicit image request, and never repeats it.
- `manual` records the reply for human handling without sending an automated response.
`maxAutomatedTurns` is configurable for `r4r` and `r4r-media` up to 20. In hybrid mode, `rapportTurnsBeforeMediaAsk` must be lower than `maxAutomatedTurns`. A conversation snapshots these values when its first verified reply arrives, so later settings changes affect new conversations only.
```yaml
reddit:
# Subreddits to monitor for new posts
subreddits:
- gonewild
- ratemyboobs
- HomemadeNsfw
- realgirls
- plussize_gonewild
# ... add or remove subreddits
# Minutes between polling cycles
pollInterval: 15
# Target gender for image analysis filtering
# Options: "F" (female), "M" (male)
targetGender: "F"
# Keywords used to detect sellers/spam profiles
# Posts from users matching these keywords are skipped
sellerKeywords:
- seller
- onlyfans
- link in bio
- check my profile
- promo
- discount
- affiliate
```
### OpenRouter AI Settings
```yaml
openrouter:
# OpenRouter API key (can also be set via OPENROUTER_API_KEY env var)
apiKey: "sk-or-v1-abc123..."
# Primary AI model for image analysis
# Models must be available on OpenRouter
model: "meta-llama/llama-3.2-90b-vision-preview"
# Fallback models tried if the primary model refuses or fails
fallbackModels:
- "google/gemini-2.0-flash-001"
- "meta-llama/llama-3.2-11b-vision-instruct"
# Quick detection model (cheaper/faster, used for gender pre-filter)
quickModel: "google/gemini-2.0-flash-lite-001"
# System prompt for image analysis
# The AI will describe the image and detect gender
systemPrompt: |
Analyze this image and provide:
1. Gender detected (F or M)
2. Detailed description of the image content
3. Notable features (hair color, clothing, setting, etc.)
Return as JSON: {"gender": "F", "description": "...", "features": [...]}
# System prompt for message generation
# Used when crafting DMs to post authors
messagePrompt: |
Generate a natural, friendly DM for this Reddit post.
Image analysis: {description}
Post title: {title}
Strategy: {strategy}
Guidelines:
- Be complimentary but not creepy
- Reference something specific from the image
- Keep it under 2 sentences
- Sound like a genuine person, not a bot
- Do NOT ask for anything in return
```
### Rate Limiting
```yaml
rateLimit:
# Minimum delay between messages (milliseconds)
# Higher values reduce detection risk
minDelayMs: 30000
# Maximum messages per hour
# Bot will pause when this limit is reached
maxMessagesPerHour: 20
# Random jitter added to delay (milliseconds)
# Adds unpredictability to timing
jitterMs: 15000
```
**Timing example with defaults:**
```
Minimum delay: 30,000ms (30 seconds)
Jitter: 0 to 15,000ms (0-15 seconds)
Effective delay: 30-45 seconds between messages
Max messages: 20 per hour
```
### Browser Settings
```yaml
browser:
# Persistent managed-browser data populated only by verified imports
sessionDir: "./data/browser-session"
dryRun: false
```
**Browser session persistence:**
```
data/browser/
├── Default/
│ ├── Cookies # Reddit session cookies
│ ├── Local Storage # Local storage data
│ └── Preferences # Browser preferences
├── Login Data # Saved login info
└── Preferences # Browser state
```
> **Important**: Keep this directory intact. Deleting it requires re-login.
### Database Settings
```yaml
database:
# Path to SQLite database file
path: "./data/reddit-bot.db"
```
The database is auto-created on first run. Migrations run automatically on startup.
### Media Sync Settings
```yaml
mediaSync:
# Enable periodic media synchronization
enabled: true
# Interval between sync cycles (minutes)
intervalMinutes: 30
```
**Sync behavior:**
- **Incremental sync** (automatic): Downloads media from top 10 conversations
- **Full sync** (manual via dashboard): Downloads media from all conversations
### Auto-Reply Settings
```yaml
autoReply:
# Enable automatic chat replies
enabled: true
# Auto-send replies (true) or mark as pending for approval (false)
autoSend: false
# Minutes between checking for new messages
checkInterval: 10
```
**Reply modes:**
| autoSend | Behavior |
|---|---|
| `true` | Bot sends AI-generated replies automatically |
| `false` | Bot suggests replies, user approves via dashboard |
### Strategy Variants (A/B Testing)
```yaml
strategyVariants:
# Variant 1: Compliment-based
- name: "compliment"
prompt: |
Write a friendly compliment about the person's appearance.
Be genuine and specific. Keep it short and natural.
enabled: true
# Variant 2: Question-based
- name: "question"
prompt: |
Write a friendly message that includes a question.
Engage them in conversation naturally.
enabled: true
# Variant 3: Casual observation
- name: "observation"
prompt: |
Write a casual observation about the photo or setting.
Sound like you're commenting to a friend.
enabled: true
```
### Web Dashboard Settings
```yaml
web:
# Port for the web dashboard
port: 3000
```
### Global Settings
```yaml
# Global dry run mode
# When true, all actions are logged but not actually performed
dryRun: false
# Number of successful message examples to include in prompts
fewShotExamples: 3
# Minimum responses needed before trusting strategy stats
minStrategySamples: 10
# Exploration rate for A/B testing (0.0 to 1.0)
# 0.15 = 15% of messages use random variants (exploration)
explorationRate: 0.15
```
Controls enabled state, port, optional tokens after environment injection, and dangerous debug endpoint enablement. Production secrets should never live in YAML.
---
## Runtime Configuration
The following settings can be modified via the web dashboard without restarting the bot:
| Setting | Endpoint | Type | Description |
|---|---|---|---|
| `rateLimit.minDelayMs` | `PUT /api/config` | number | Message delay |
| `rateLimit.maxMessagesPerHour` | `PUT /api/config` | number | Hourly cap |
| `rateLimit.jitterMs` | `PUT /api/config` | number | Jitter range |
| `reddit.pollInterval` | `PUT /api/config` | number | Poll frequency |
| `dryRun` | `PUT /api/config` | boolean | Dry run mode |
### Updating Runtime Config
Via dashboard:
1. Open the settings modal
2. Modify desired values
3. Click "Save"
Via API:
```bash
curl -X PUT http://localhost:3000/api/config \
-H "Content-Type: application/json" \
-d '{
"rateLimit": {
"minDelayMs": 45000,
"maxMessagesPerHour": 15,
"jitterMs": 20000
},
"dryRun": true
}'
```
> **Note**: Runtime changes are in-memory only. They are lost when the bot restarts. To make changes persistent, update `config.yaml`.
## Runtime-editable subset
---
## Configuration Validation
The dashboard/API exposes a deliberate subset: rate limits, dry-run, recency and ready-funnel limits, subreddit definitions/conversation behavior, and selected advanced configuration. Shared transport schemas live in `packages/api-contracts/src/config.ts`; the backend source schema is still the final validator.
All configuration is validated using Zod at startup. Invalid configuration will cause the bot to exit with a descriptive error.
### Validation Rules
| Field | Validation |
|---|---|
| `reddit.subreddits` | Must be non-empty array of strings |
| `reddit.pollInterval` | Must be positive integer |
| `reddit.targetGender` | Must be "F" or "M" |
| `openrouter.apiKey` | Must be non-empty string |
| `openrouter.model` | Must be non-empty string |
| `openrouter.fallbackModels` | Must be non-empty array |
| `rateLimit.minDelayMs` | Must be positive integer |
| `rateLimit.maxMessagesPerHour` | Must be positive integer |
| `rateLimit.jitterMs` | Must be non-negative integer |
| `browser.sessionDir` | Must be non-empty string |
| `browser.dryRun` | Must be boolean |
| `database.path` | Must be non-empty string |
| `mediaSync.enabled` | Must be boolean |
| `mediaSync.intervalMinutes` | Must be positive integer (if enabled) |
| `autoReply.enabled` | Must be boolean |
| `autoReply.autoSend` | Must be boolean |
| `autoReply.checkInterval` | Must be positive integer (if enabled) |
| `strategyVariants` | Must be array with name + prompt |
| `web.port` | Must be integer 1-65535 |
| `dryRun` | Must be boolean |
When adding a runtime-editable setting, update all of:
### Common Validation Errors
1. `ConfigSchema` and defaults.
2. `config.yaml` seed/example when appropriate.
3. `ConfigStore` persistence rules if ownership changes.
4. Shared request/response schemas.
5. The config route's merge/write/read-back behavior.
6. Dashboard model/control.
7. backend, contract, route, and dashboard tests.
8. this document.
**Error**: `Invalid config: openrouter.apiKey must be a string`
**Fix**: Set `OPENROUTER_API_KEY` environment variable or add `apiKey` to `config.yaml`
## Validation and change safety
**Error**: `Invalid config: reddit.subreddits must be a non-empty array`
**Fix**: Add at least one subreddit to `reddit.subreddits` in `config.yaml`
- Use Zod for every external or persisted shape.
- Reject unknown transport fields with strict contracts.
- Validate relationships (not just types), such as the hybrid turn constraint.
- Do not log the full config because it contains injected secrets in memory.
- Configuration that affects running account objects may not be fully hot-reloaded; inspect the owning service before claiming immediate effect.
- A production `PUT /api/config` should be followed by an authenticated GET read-back. Do not infer persistence from a 2xx alone.
- A subreddit listing 404 is persisted as disabled so every account runtime stops polling it.
**Error**: `Invalid config: browser.sessionDir must be a string`
**Fix**: Ensure `browser.sessionDir` is set in `config.yaml`
## Local safe setup
---
## Troubleshooting
### Configuration Not Loading
**Symptom**: Bot fails to start with validation errors.
**Steps**:
1. Verify `config.yaml` is in the project root
2. Check YAML syntax (use a YAML linter)
3. Verify environment variables are set correctly
4. Check bot logs for specific validation errors
### Account Authentication Not Working
**Symptom**: Account work remains paused.
**Steps**:
1. Sign in to Reddit in local Chrome
2. Open Reddit Chat and wait for the extension badge to show `OK`
3. Export and upload a fresh combined session dump
4. Check dashboard logs for Reddit or Matrix identity verification errors
### AI Not Working
**Symptom**: Image analysis fails or returns errors.
**Steps**:
1. Verify `OPENROUTER_API_KEY` is valid
2. Check API key has sufficient balance
3. Verify `openrouter.model` is available on OpenRouter
4. Check fallback models are set correctly
5. Review logs for specific AI errors
### Browser Session Expired
**Symptom**: Bot stops with "Browser session expired" error.
**Steps**:
1. Open Reddit Chat in the local Chrome profile containing the extension
2. Export and upload a fresh session dump
3. Delete the downloaded dump after verification
### Port Already in Use
**Symptom**: Dashboard fails to start with "Port already in use".
**Steps**:
1. Change `web.port` in `config.yaml`
2. Or kill the process using the port: `lsof -i :3000`
Install dependencies and run builds/tests without starting the application. If a local backend is explicitly needed, use a dry-run configuration and verify the database path/port before starting. Importing a real session or calling the start control is a separate live-capable action.