Files
RedditBot/docs/configuration.md
Jamie BruntonandClaude Opus 5 1ac6ad22ad feat: grow new accounts by commenting on active threads
A new Reddit account cannot start chats until Reddit considers it
established, and nothing in the app previously did anything about that.
Account growth posts a small number of AI-written comments per day on
active threads in an operator-chosen list of communities, per account,
so the history that lifts the hold actually accumulates.

Growth is account-scoped rather than part of the shared outreach funnel:
karma, comment history and pacing all belong to one identity. It runs as
the lowest-priority work in the account runtime, after inbound replies,
and deliberately keeps running while an account is held out of outbound
messaging.

Comments publish without operator review, so the guards are the feature:

- Both screening stages fail closed. A deterministic gate rejects links,
  contact solicitation, user tags, filler and any text that reveals the
  generator; the classifier must actively approve with confidence, and
  an outage stalls growth rather than publishing unreviewed text.
- Thread text reaches the model only as fenced untrusted data, since
  Reddit posts do contain instructions aimed at automated readers.
- The daily allowance is adaptive, not configured: it starts at two per
  day and rises only while comments survive and earn upvotes.
- Posted comments are swept at ~15m/2h/24h with one batched info.json
  request, plus a single signed-out probe that detects a comment the
  public cannot see. Two consecutive removals, an invisible comment, or
  a captcha challenge pauses the account until an operator clears it. A
  failed probe never pauses anything.
- One comment per thread across every owned account, enforced by a
  unique index, because two of our accounts on one post is the easiest
  pattern to correlate.
- A comment interrupted mid-post is abandoned, never retried: we cannot
  know whether the write landed and a duplicate cannot be withdrawn.
- Reddit's refusals are authoritative — a ban disables that community
  for that account, a throttle backs off at the scope Reddit named, and
  a signed-out response invalidates the session.

Comments are submitted through the account's own browser session against
the old.reddit form endpoint, which returns machine-readable error codes
and needs no credentials beyond the cookies already imported.

API-first: /api/growth exposes state, settings, the community list and
pause/resume, all verified against a local instance. Route responses are
parsed with the shared contracts in tests so the server-side mirror
cannot drift from what the dashboard expects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 20:08:52 +01:00

130 lines
9.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Configuration
## Precedence and lifecycle
Configuration is validated by `ConfigSchema` in `src/config/index.ts`.
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.
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
| 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`. |
| `NTFY_AUTH_TOKEN` | No | Bearer token for a protected ntfy topic. |
| `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. |
Never document real values. `.env` is ignored and may contain production credentials; do not print or blindly source it.
## Application sections
### `reddit`
- `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; see also the `conversations` section, whose off-platform kill is independent of this flag. The defaults treat Telegram references, including `t.me` and `telegram.me` links, as seller signals alongside paid-content platforms and payment prompts.
- `prioritizeRecentPosts`, `recentWindowMinutes`: recency/fallback behavior.
- `maxReadyCandidates`: hard ceiling for the active candidate target; valid range 1–500. Runtime lowers the actual target to what authenticated running accounts can send within `recentWindowMinutes` under current rolling-hour and message-spacing limits.
- `physiqueTargeting`: optional image-candidate filter with `enabled`, inclusive `minScore`, and inclusive `maxScore` values from 1 to 10. It is disabled by default. When enabled, a model score outside the range or an image without enough visible body evidence to score is rejected before opener generation. Saving the setting also reconciles prepared image candidates in the ready and delivery-retry stages: candidates that no longer qualify are rejected, while retained candidates receive the newly applied range snapshot shown in the funnel. A candidate already leased for sending is not changed mid-send. The setting is hot-read by existing account runtimes; chat sources are unaffected.
Physique targeting is owned only by its dedicated Monitoring controls. It is excluded from the advanced JSON response. Older cached dashboards may still submit a duplicate advanced copy; the server accepts but ignores that copy so it cannot overwrite the dedicated value persisted 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.
### `conversations`
- `offPlatformKill` (default `true`): end a conversation outright when the reply pushes it to another app, to contact details, or to a payment rail. Independent of `reddit.filterSellers`.
- `killOnSellerKeyword` (default `true`): a configured seller keyword or domain also ends the conversation. Set `false` to hand those to an operator instead.
- `aiReplyScreening.enabled` (default `false`): second-stage classifier on inbound replies, run only when the deterministic rules pass. Uses `openrouter.chatModel` and its fallbacks, and fails open.
- `aiReplyScreening.killConfidence` (default `0.8`) / `flagConfidence` (default `0.6`): thresholds at which a seller/off-platform/scam/bot verdict kills or hands off.
### `openrouter`
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.
`growthCommentPrompt` is the template the account grower uses to write a public comment on someone else's thread. It supports `{subreddit}`, `{title}`, `{selftext}`, and `{flair}`. The thread is appended separately as fenced untrusted data and the deterministic safety gate runs on the result regardless of prompt wording, so editing this template changes tone, not the safety boundary.
### `rateLimit`
- `minDelayBetweenMessagesMs`;
- `maxMessagesPerHour`;
- `jitterMs`.
These are per account. Reddit JSON request pacing is separately controlled by `RedditRequestCoordinator` and is process-global.
### `browser`
- `sessionDir`: root for persistent account browser profiles.
- `dryRun`: when true, candidate preparation may run but remote sends must not.
Treat changing `dryRun` from true to false as enabling a real side effect.
### `database`
Contains `path`, but runtime ownership is `DATABASE_PATH`, not the persisted application config.
### `media`
Controls periodic sync enablement and interval. Media work remains authentication-bound.
### `pushNotifications`
Publishes selected application events to a single ntfy topic so they reach a phone. Holds the enabled flag, server URL, topic, ntfy priority, and a boolean per event (`imageReceived`, `replyReceived`, `messageFailed`, `botStopped`, `rateLimited`, `sessionExpired`, `syncFailed`). Editable from Settings → Notifications, which writes through `PUT /api/push-notifications`; `POST /api/push-notifications/test` sends a fixed test notification. The access token is environment-owned (`NTFY_AUTH_TOKEN`), is never persisted to the settings record, and is never returned by the API — the response only reports `hasAuthToken`.
Delivery is best-effort: an unreachable or rejecting ntfy server is logged and never propagated into the path that produced the event.
### `strategies` and `chatStrategies`
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.
### `web`
Controls enabled state, port, optional tokens after environment injection, and dangerous debug endpoint enablement. Production secrets should never live in YAML.
### Account growth
Growth is configured per account through `/api/growth`, not through `config.yaml` or `app_config`, because the community list, allowance, and pause state belong to one Reddit identity. The stores are `account_growth_settings` and `account_growth_subreddits`; see `docs/database.md`.
Operator-settable per account: enabled, minimum spacing, jitter, quiet hours, NSFW opt-in, the community list with per-community weight and daily cap, and pause/resume. The daily allowance itself is **not** operator-settable — it is derived from an adaptive trust level that the verification sweep maintains. `browser.dryRun` also suppresses growth posting.
## Runtime-editable subset
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.
When adding a runtime-editable setting, update all of:
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.
## Validation and change safety
- 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.
## Local safe setup
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.