How the agent decides
Every check, every flag, and the rules that turn them into a decision.
A submission goes through four stages. Only the third uses a model.
- Fetch. One request for the submitted resource: the X API for posts, the GitHub API for pull requests and commits, and a guarded HTTP fetch for articles. For an X post with replies, one more request reads the author's own thread (see Threads on X). Nothing else is crawled.
- Deterministic checks. Code compares the resource with the contributor, the round and every earlier submission in the program, and raises flags.
- Judge. Claude Haiku 4.5 scores the work against your rubric through a strict tool schema (
judge-v4). It returns a category, a score per criterion, a confidence, a recommendation and its reasons. It doesn't set amounts. - Decision engine. Plain TypeScript rules (
rules-v6) combine the flags and the judgment into an action and an amount, and write the explanation the contributor sees.
Threads on X
A submitted post is read together with the author's own self-reply chain: the author's replies to the post, their
replies to those, and so on, in order. The chain stops at the first post that isn't the author replying to
themselves, so nobody else's words are ever judged as the contributor's. The judge sees every post, each headed
[Post i of n], plus the post count, and judges the thread as one piece of work (so "at least 3 posts" rules
work). The whole thread is what's hashed into the decision record and compared for duplicates.
- Cost. One X recent search, billed per post returned and capped at 25 posts (at most $0.125 on top of the $0.015 post lookup). Posts without replies skip it. Every call and its cost is logged.
- Cached. The fetched thread is cached with the post, so it's read once. Payout re-checks read only the post.
- Gaps are stated, not hidden. A deleted post in the middle ends the thread at the gap, a thread longer than 25 posts is judged on its first 25, and replies older than X's 7-day search window can't be read. In each case the judge and the reviewer see a note saying so. A quoted post shows as a link, never as the contributor's text.
Flags
Hard flags are facts. Most of them reject on their own. Soft flags are signals that something needs a person's eyes; they send the submission to review.
| Flag | Severity | Raised when |
|---|---|---|
OWNERSHIP_MISMATCH | hard, rejects | The post belongs to a different X account, or it's a repost; or the PR/commit author isn't the GitHub account the contributor connected (checked by GitHub's numeric id, never a typed username). |
OUT_OF_WINDOW | hard, rejects | The work was published before the round started or after it ended. |
DUPLICATE_URL | hard, rejects (soft for articles) | The same resource was already submitted in this program by someone whose ownership was verified. It never fires for the verified author, so submitting someone else's post first doesn't lock its real author out. For an article, who wrote it is for a person to decide. |
NEAR_DUPLICATE | hard or soft | The text is similar to an earlier submission (pg_trgm similarity, or SimHash within 3 bits). Earlier means created earlier on X or GitHub, not submitted earlier. At 80% or more against someone else's earlier work it's hard and rejects; otherwise (60%+, or your own earlier work) it's soft. An article's date comes from its own page, so a match involving an article is always soft (a person decides). If the original arrives after its copy was approved, the copy is held for review: the agent never rejects or replaces an earlier decision on its own. |
NOT_MERGED | hard, rejects | A pull request isn't merged and the category pays only for merged work, or a commit isn't on the repository's default branch (a fork or side branch). A commit's date is when its pull request merged into the default branch, never the author-set commit date. |
DELETED | hard, rejects | The content no longer exists. Also checked again before payout. |
PROMPT_INJECTION_ATTEMPT | hard, escalates | The content tries to instruct the grader, including in hidden article text. Always goes to a person, even if the model wasn't fooled. |
OWNERSHIP_UNVERIFIED | soft | An article's structured author metadata (twitter:creator, author meta tags, <link rel="author">, JSON-LD author links) doesn't name the contributor's X handle. Bylines, comments, replies, display names and body text don't count. |
MISSING_REQUIRED | soft | The work doesn't include a link, @mention or #hashtag the program requires (expanded links count). |
LOW_FOLLOWERS | soft | The X account has fewer followers than the program's minimum. |
OFF_TOPIC | soft (rejects when the judge is sure) | Not about what the program's brief describes. |
CONTRADICTS_BRIEF | soft | A claim conflicts with a key fact in the brief; the flag quotes both ("says X, but the brief says Y"). |
DATE_UNVERIFIED | soft | No publication date was found, so the round window can't be confirmed. |
NEW_ACCOUNT | soft | The X account is younger than the program's minimum account age. |
ENGAGEMENT_ANOMALY | soft | Likes and reposts far beyond the account's reach (more than 5× followers and over 200), or more than half of impressions. |
WALLET_CHANGED_RECENTLY | soft | The payout wallet changed within the program's cooldown. |
FETCH_FAILED | soft | The content couldn't be fetched after retries. |
Rules
The engine applies these in order and stops at the first match. The rule that decided is part of the signed record.
| Rule | Action |
|---|---|
R1_REJECT_FLAG | Any hard flag in the reject set → reject. |
R2_INJECTION | PROMPT_INJECTION_ATTEMPT → review, whatever the model said. |
R3_NO_JUDGMENT | The judge produced no valid output → review. |
R2B_JUDGE_INJECTION | The judge itself noted an attempt to influence or instruct it (asking for full marks, claiming pre-approval, gaming the rubric, addressing the AI reviewer, …) → review, even if the code-level screen saw nothing. |
R4_CATEGORY_INVALID | The judged category doesn't exist or doesn't accept this source type → review. |
R5A_CLEAR_SPAM | The judge recommends rejecting with confidence ≥ 0.9 and every criterion scores ≤ 1 → reject (you can override). |
R5B_OFF_TOPIC | The judge is confident the work is off topic for the brief → reject. |
R5_AGENT_RECOMMENDS_REVIEW | The judge recommends rejecting or reviewing → review. |
R6_SOFT_FLAGS | Any soft flag → review. |
R7_LOW_CONFIDENCE | Confidence below the program's auto-approve confidence → review. |
R8_ZERO_AMOUNT | The work scores zero points → review. |
R9_ABOVE_AUTO_CAP | The amount is above the program's auto-approve cap per item → review. |
R9B_ARTICLE_REVIEW | An article → review, always: its author and date come from the page itself, not a platform. |
R10_AUTO_APPROVE | Otherwise → approve (or partial when the judge recommends it). |
Amounts
There is one points rule, applied in code: points = max points × (sum of criterion scores) ÷ (10 × number of criteria), and amount = points × rate per point. The judge's own point total is recorded but never used. When the
judge recommends rejecting the work, points and amount are both 0, whatever the individual scores: the agent never
suggests paying for work it says doesn't qualify, and a reviewer who disagrees sets the amount themselves. The
explanation always states the final action and amount. The amount is capped at the per-payout limit. Items are paid whole: if a contributor's total for a round would pass a cap, the items
that don't fit carry over to the next round, with the reason recorded.
Untrusted content
Submitted content is data, never instructions. The judge receives it inside a per-request random boundary, look-alike boundary tags are neutralized, and the output is re-validated against the schema. Injection is detected in code before the model sees anything, so being fooled can't approve a submission: R2 runs before any approval rule.
Overrides and re-processing
Owners can approve, change the amount, or reject any decision before it's paid. An override is a new signed record that references the agent's decision, so the history is never rewritten.
When the agent itself improves (say, it learns to read whole threads), a decided submission that isn't in a payout
yet can be re-processed: the content is fetched fresh, and the new signed agent decision names the one it
supersedes and why. The audit trail records both, decision.superseded on the old decision and
decision.reprocessed on the new one, and the old record stays verifiable.
The program's brief
Each program can give the agent context: what the project is, what to post about and what to avoid, up to three links, and anything every post must include. The agent reads the links once (through the same guarded fetcher as articles; a page that contains instructions for the agent is left out) and drafts a summary, key facts and on/off topic themes, which the owner checks and edits. The saved brief is versioned.
When judging, the brief is a separate, trusted section; the submission stays untrusted data. The judge rates relevance, checks the work's factual claims against the key facts only, and marks a claim "unverifiable" rather than guessing. Every decision records the brief's version and hash in its signed record, so it's always clear what the work was judged against.
Second looks
A contributor can ask for a second look at a rejected or partly paid decision, once per submission, with a short note. It goes to the owner under "Needs you"; their answer is a signed human decision, recorded in the audit log.