Add account browsing simulation

This commit is contained in:
jjbrunton committed 2026-08-11 21:40:31 +01:00
1 parent e2d7dc4b82
commit 27012399ec
31 files changed
+1476 -28

No files matched your search

+1
View File
@@ -108,6 +108,7 @@ an administrator credential.
| `GET` | `/api/growth` | Get per-account comment growth state | read | no |
| `GET` | `/api/growth/{accountId}` | Get one account's growth settings, communities and comment history | read | no |
| `PATCH` | `/api/growth/{accountId}` | Enable, disable or re-pace comment growth for an account | admin | yes |
| `PATCH` | `/api/growth/{accountId}/browsing` | Configure simulation-only browsing selections for an account | admin | yes |
| `POST` | `/api/growth/{accountId}/pause` | Stop an account from posting further comments | control | yes |
| `POST` | `/api/growth/{accountId}/resume` | Clear a growth pause after reviewing the cause | admin | yes |
| `DELETE` | `/api/growth/{accountId}/queue` | Clear queued and retryable growth threads that have not been posted | admin | yes |
+2 -1
View File
@@ -66,7 +66,7 @@ SIGINT/SIGTERM abort shared loops, stop account runtimes, stop the web server, c
- verified Reddit and Matrix identity;
- persistent Playwright browser profile;
- posts, messages, outcomes, activity, outbox, and conversation state repositories;
- growth settings, community list, comment history, and adaptive comment allowance;
- growth settings, community list, comment history, adaptive comment allowance, and isolated browsing-simulation policy/audit;
- Matrix chat cache and sync cursor;
- media index and files;
- rate limiter and invitation cooldown;
@@ -137,6 +137,7 @@ docs/ Maintainer and agent documentation
- Shared operator media: keep it under `MediaLibraryService`/`MediaLibraryRepository`; never merge it with account-owned received media.
- New account behavior: place it in the account runtime or a service injected into it; do not add mutable cross-account identity to a singleton.
- New account-growth behavior: keep it account-scoped and lowest-priority, fail closed before anything is published, and make every outcome visible in the growth audit trail.
- Simulation-only account behavior: keep the decision sink separate from Reddit transports so future remote-write authority cannot appear as a hidden setting.
- New candidate stage: make transitions durable and idempotent, expose counts/reasons, and define lease/retry behavior.
- New dashboard feature: add a feature slice and route, use shared schemas and query keys, and treat HTTP as authoritative.
+2
View File
@@ -105,6 +105,8 @@ A per-community `dailyCap` only distributes the account allowance; caps are not
Community weights feed a new weighted-random pick for each discovered growth thread. The grower queues only one new thread at a time, drains due retries and existing queue work before polling another community, and exposes an account-scoped queue clear that removes only unposted discovered/retryable rows.
Browsing simulation is configured separately per account through `/api/growth/{accountId}/browsing` and reuses the enabled growth-community list and weights. It defaults off, targets 3–6 hypothetical selections per local day with a 60/40 post/comment split, enforces at least 45 minutes between sessions, respects quiet hours, excludes NSFW unless enabled, and has a non-overridable 12/day ceiling. `nextDueAt` is service-owned durable state and cannot be set through the API. Browser dry-run does not change the behavior because simulation never performs a remote write.
## 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.
+6
View File
@@ -60,6 +60,12 @@ Every runtime shares one `RedditRequestCoordinator`, because Reddit can enforce
Do not add local retries around this layer without preserving the shared schedule. Per-account bursts defeat the coordinator.
## Browsing simulation flow
After reply handling, response checks, and comment-growth work, an enabled account may run one due browsing simulation session. The durable `next_due_at` gate survives restarts; a deterministic per-account daily target and randomized waking-hours spacing avoid recalculating a flapping schedule on each minute tick. The session picks one configured growth community by weight, reads its hot listing through the shared Reddit request coordinator, optionally reads typed top-level comments, applies deterministic exclusions, and only then selects randomly.
The terminal operation records `simulated-upvote` in SQLite and operator activity. There is no vote endpoint or Reddit write dependency. Scan, running, and browser dry-run all perform the same simulation; stopped mode performs no discovery or decision work. Reddit 429 responses extend the shared cooldown, while ordinary read failures remain isolated from replies and comment growth.
## Reply-first conversation automation
Explicit bot clients can send to a username through `POST /api/v1/messages` or the MCP `send_message_to_user` tool without selecting an account. The selector compares cached conversations case-insensitively and reuses the account that most recently interacted with that recipient. If no conversation exists, it chooses an authenticated running account with no invitation cooldown, preferring the lowest hourly send count. The selected account's normal rate limiter still owns the send. An API reply to an existing conversation marks automation as user-overridden so autonomous replies do not race the external caller.
+4
View File
@@ -20,6 +20,8 @@ The application uses one `better-sqlite3` database, normally `data/reddit-bot.db
- `account_growth_settings`: per-account commenting switch, adaptive trust level, optional operator daily-limit override (`daily_limit`, null means adaptive), spacing/jitter, quiet hours, NSFW opt-in, and the operator-clearable pause reason.
- `account_growth_subreddits`: per-account community list with weight, daily cap, and any Reddit-imposed disable reason or cooldown.
- `account_growth_comments`: durable growth comment records from discovery through posting and verification, including the exact text posted, score history, removal and public-visibility verdicts.
- `account_browsing_settings`: disabled-by-default per-account simulation policy, conservative daily range, post/comment weights, quiet hours, spacing, NSFW policy, and durable next due time.
- `account_browsing_decisions`: minimal post/comment identity, permalink, decision/reason, and scheduled-session audit. Reddit text is deliberately not retained.
The special legacy-unassigned account exists only to preserve rows created before account ownership was introduced. New runtime data must use a verified account ID.
@@ -60,6 +62,8 @@ The special legacy-unassigned account exists only to preserve rows created befor
9. A library image content hash has at most one reservation per account conversation, even if the same file is later re-uploaded under a new asset ID; an inbound request also has at most one selected asset. Failed or uncertain attempts remain reserved to prevent duplicates.
11. Exactly one account may comment on a given thread. `account_growth_comments` enforces this with a unique index on `post_id` across all accounts, because two owned accounts commenting on the same post is the easiest pattern for Reddit to correlate.
12. A growth comment interrupted while posting is rejected, never retried. The process cannot know whether the write reached Reddit, and a duplicate comment cannot be withdrawn.
13. Browsing decisions are unique per account and thing. Before selecting, the service also checks for a `simulated-upvote` by any other owned account, but account-specific rejections do not unnecessarily reserve an item globally.
14. Browsing simulation rows never contribute to adaptive comment-growth promotion, and no schema or repository operation represents a submitted Reddit vote.
10. Recipient account status changes only after a definitive Reddit profile response. Transient lookup failures retain the previous value, and the shared check timestamp prevents duplicate lookups across account runtimes during the four-hour interval.
+3 -1
View File
@@ -19,13 +19,15 @@ Shared HTTP/SSE schemas live in `packages/api-contracts`. The server and client
| `/media-library` | Upload, classify, correct, enable, preview, and delete shared exchange images. |
| `/performance` | Mature outcome trends, strategy/subreddit results, and model usage. |
| `/operations` | Background jobs, sync state, events, rate limits, and technical logs. |
| `/growth` | Per-account comment growth: switch, communities with daily caps, adaptive allowance, and the posted-comment audit trail. |
| `/growth` | Per-account comment growth plus simulation-only browsing: switches, communities, pacing/allowance, next browsing session, and both audit trails. |
| `/settings` | Validated runtime configuration and account/session management. |
Navigation is declared in `app/navigation.ts`; route elements are composed in `app/router.tsx`.
The account-growth page states plainly that comments are AI-written and posted without review, shows the adaptive allowance and trust level rather than a settable rate, and surfaces every posted comment's exact text alongside its score and removal state. A paused account replaces the pause control with a resume control and explains why it stopped, because resuming is the one action that can compound the damage.
Its browsing section separately states that no Reddit votes were submitted, shows today's deterministic target, post/comment split, exclusion counts, durable next session, and recent simulated selections with Reddit permalinks. Enabling browsing changes only the simulation policy; it does not enable comment growth or bot processing.
The Monitoring settings section owns the optional physique-targeting toggle and inclusive 1–10 range. It explains that the signal is subjective, applies only to image posts, and screens out unscorable images while enabled.
Prepared cards in the authenticated outreach funnel expose the exact opener, grounded image analysis, attractiveness and physique scores, current target range, detected gender, strategy/tone/model metadata, and original source image. The live range is returned through the existing preparation field so cached dashboard bundles remain compatible during a rolling deployment. The durable queue still owns the historical preparation snapshot used for reconciliation. The source image uses the shared `OriginPostCard` privacy behavior: blurred initially, first click reveals it, and a later click opens the full-screen preview. Candidates from older persisted snapshots degrade cleanly when newer optional metadata is absent.
+2
View File
@@ -70,6 +70,8 @@ This is inauthentic account activity under Reddit's content policy, and many sub
- Growth deliberately continues while an account is held out of outbound messaging, because building the history is what lifts that hold. It is always the lowest-priority work an account performs, after inbound replies.
- Dry run prepares and records a comment but never posts it. Every posted comment, and every comment refused by either gate, is kept with its reason in the growth audit trail.
Browsing simulation is a separate, disabled-by-default account-growth experiment. It selects a conservative number of eligible posts or comments from the same configured communities and records a hypothetical upvote, but it cannot submit a vote: the service has no Reddit vote method or write transport. Owned-account content, outreach recipients, cross-account duplicates, growth-comment threads, promotion experiments, moderator content, closed or removed threads, old or low-context items, and NSFW content unless explicitly allowed are excluded before randomness is applied. Simulation records never affect the adaptive comment-growth trust level.
## Operator exchange media
The exchange library contains only operator-uploaded images. Upload requires confirmation that every depicted adult consented to storage and bot delivery. Received conversation media is account-owned evidence and can never be promoted into this shared library.
+2
View File
@@ -41,6 +41,8 @@ Comment-based growth exists so a new account accumulates the age/karma history R
| `services/growth-comment-safety.ts` | Deterministic pre-publication gate on generated comment text | Rejects links, contact solicitation, user tags, filler, and any text revealing the generator. Independent of prompt wording. |
| `reddit/comment-client.ts` | Comment submission and comment-state lookups for one account | Classifies Reddit's error codes into retry/abandon/disable/re-auth/stop; runs inside the account's own browser session and shares the request coordinator. |
| `database/growth-repository.ts` | Growth settings, per-account community list, durable comment records | One comment per thread across every account; mutations require the expected worker lease; queue clearing deletes only unposted discovered/retryable rows. |
| `services/account-browsing-simulation.ts` | Durable pacing, eligibility, post/comment weighting, and hypothetical selection | Runs below comment growth; its only terminal capability is a simulation decision sink, never a Reddit vote. |
| `database/browsing-simulation-repository.ts` | Per-account browsing policy, cross-account selection checks, and minimal decision audit | Stores identity/permalink and reasons without copying Reddit content; simulation never feeds comment trust. |
Verification is the safety mechanism that matters most. Posted comments are swept at roughly 15 minutes, 2 hours, and 24 hours using one batched `info.json` request, and a single signed-out probe on the second sweep decides whether the comment is publicly visible at all. Two consecutive removals, or one invisible comment, hard-pauses the account until an operator clears it; a failed probe never pauses anything.