Build Bazzly

KINDAreplaces $99/mosaves $1,188/yrback to the verdict

0%0 of 18 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A Reddit listener that finds threads worth replying to and drafts the reply, whose hard problems are staying inside the documented API and its published limits, keeping model spend below the subscription it replaces, and drafting something that does not invent a feature.

estimated effort weekendthe files for this build are in the project pack

RuntimeOne process with a scheduled poll on a machine that stays awakeAPI accessReddit's OAuth data API through a registered script applicationStorageSQLite, one row per thing the API returned, keyed on its fullnameScoringA local keyword prefilter, then one model call per survivorReviewA server-rendered queue on localhost with copy, dismiss and mark-postedSpendA daily cost ceiling checked by the scoring loop, not a warning in a log

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the poller, the scorer and the review page in one process.

    Get it python.org, or your package manager. open ↗

    Verify python3 --version prints 3.12 or higher

  2. free

    Why The only account this build ever touches, and the one a person will post from by hand.

    Get it Create a script-type application in your account's app preferences and note the client id, secret and redirect.

    Verify A token request succeeds and an authenticated listing returns with rate limit headers

  3. API keya few pounds a month at the ceiling suggested here

    Why Relevance scoring and drafting both go through it, and it is the only recurring cost in the build.

    Get it Any provider whose pricing you can read per token.

    Verify One call returns valid JSON and its cost is recorded

  4. have readyfree

    Why It is the only source a draft may make a claim from, so without it the drafting step is disabled rather than creative.

    Get it Write what the product does, what it costs, and three things it does not do.

    Verify Every claim in a generated draft maps to a line in it

  5. have readya few pounds a month, or free if you own the box

    Why A thread is worth answering in its first hours, and a laptop that sleeps turns the listener into a weekly digest.

    Get it A small always-on box, a virtual machine, or a home server.

    Verify A poll scheduled for four in the morning has rows by breakfast

  6. decidefree

    Why Both are asserted by tests, and deciding them after seeing a bill or a ban means choosing whatever already happened.

    Get it Write the promotion policy for each subreddit you watch, with the date you read its rules, and pick a ceiling in pennies per day.

    Verify Both appear in configuration and are named in the tests

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- subreddits(name, rules_fetched_at, promo_policy, allows_links, reviewed_by, reviewed_at) · what each community permits, recorded by a person
- seen(fullname, kind, subreddit, created_utc, first_seen_at) · the dedup table, keyed on Reddit's own identifier
- candidates(fullname, permalink, title, body_excerpt, author, subreddit, matched_terms_json, prefilter_score)
- scores(fullname, model, relevance, asking_for_recommendation, reason, draft_reply, tokens_in, tokens_out, cost_cents, at)
- decisions(fullname, action, acted_at, note) · queued, dismissed, irrelevant, posted_by_hand
- api_calls(at, endpoint, status, ratelimit_remaining, ratelimit_reset, retry_after) · the evidence when you get throttled
- facts(id, claim, source) · what your product actually does, and the only claims a draft is allowed to make
- There is no posts table and no send function, because this build never writes anything to Reddit.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_PATHrequired./data/radar.dbCandidates, scores, decisions and the call log.
REDDIT_CLIENT_IDrequireda1B2c3D4e5F6g7From your script application's settings.
REDDIT_CLIENT_SECRETsecretrequiredsk-...redactedRevocable on its own, which is the point of a script application.
REDDIT_REFRESH_TOKENsecretrequiredsk-...redactedRevoking this is the test for the pause behaviour in the last phase.
REDDIT_USER_AGENTrequiredreddit-radar/0.1 by your-usernameNames the tool and the account. An anonymous agent is a blocked agent.
LLM_API_KEYsecretrequiredsk-...redactedScoring and drafting. Every call's cost is recorded against it.
DAILY_COST_CEILING_PENCErequired50Checked by the scoring loop. Reaching it stops scoring for the day.
POLL_INTERVAL_SECONDSrequired900Set against the currently published rate limit, not against this example.

The build, in order

  1. Reading Reddit the documented way

    The listener polls inside the published limits and never counts the same thread twice.

    1. The documented route, with a credential you can revoke without changing your password.

      Files src/reddit/auth.py.env.example

    2. Titles change, links get crossposted, identifiers do not.

      Files src/reddit/poll.pysrc/db/schema.sql

    3. Record remaining, reset and retry-after on every call so throttling is visible.

      Files src/reddit/client.pytests/test_backoff.py

    done when · tick each as it passes
    watch out
    • Deduping on the title or the link means an edited title, a crosspost and a resubmission all arrive as new, and the queue fills with one thread three times while a genuinely new one scrolls off the end. Key everything on the fullname the API returns, store it before you score it, and treat a fullname you have seen as finished whatever changed about it since.
  2. Two stages, because the model is the bill

    Only plausible candidates reach a model call and the day's spend has a ceiling.

    1. The cheapest call is the one you did not make.

      Files src/score/prefilter.pytests/test_prefilter.py

    2. An unparseable answer leaves the row unscored, never scored zero.

      Files src/score/model.py

    3. A spend limit that only logs is a spend limit that does nothing.

      Files src/score/budget.pytests/test_budget.py

    done when · tick each as it passes
    watch out
    • Sending every new post from a busy subreddit to a model looks fine on the first evening and costs more by the end of the month than the subscription you were replacing, and nothing in the interface shows it until the invoice does. Prefilter locally, price each call as it happens, and make the ceiling a condition the scoring loop checks rather than an alert somebody is supposed to read.
  3. Drafts that cannot invent a feature

    A draft only makes claims traceable to a fact sheet you wrote.

    1. It is the only source a claim is allowed to come from.

      Files data/facts.yamlsrc/draft/prompt.py

    2. Highlighted in the queue, so the person reads them before copying.

      Files src/draft/verify.pytests/test_claims.py

    3. Asserted in the suite, not left to the prompt's good behaviour.

      Files src/draft/prompt.pytests/test_disclosure.py

    done when · tick each as it passes
    watch out
    • A model writing a helpful recommendation will confidently attribute a feature you do not have, and because the draft reads well the person posts it, and the correction then happens in public underneath it. Ground the draft in the fact sheet, mark untraceable claims in a second pass, and treat an unflagged draft as the exception rather than the default.
  4. Community rules as data

    A subreddit that forbids promotion never produces a draft.

    1. Stale rules are worse than none, so the date is shown.

      Files src/reddit/rules.py

    2. The safe default is the one that costs you nothing but a review.

      Files src/policy.pytests/test_policy.py

    3. The person copying the text should read them at that moment.

      Files src/templates/queue.html

    done when · tick each as it passes
    watch out
    • Treating every community the same gets the account banned in the one that bans on a first link, and the ban is not undone by deleting the comment or by explaining that a tool wrote it. Default unreviewed communities to no drafting, keep the rule text beside the draft so the decision is made with it in view, and record who reviewed each policy and when.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more social media.