gpt-5.6-sol through Surf’s pure Codex OAuth Responses route, with no enterprise API-key or hybrid fallback. Deduplication does not change raw scores, select a publishing quota, or publish anything.
Selection excludes confirmed duplicates only. For newly finalized cycles, each tier’s ranked list contains every scored candidate except its confirmed duplicates. Unknown, unfinished, timed-out and unavailable assessments remain eligible in score order. Their unchecked entries and assessment warnings are preserved: eligibility is not proof of uniqueness, and this policy can allow actual duplicates through. The client must not subtract unchecked or reject the entire ranking solely because deduplication is partial or unavailable. Existing score freshness, response deadlines and non-deduplication selection rules still apply.
Published feed context
Supplypublished_windows.main and .light independently. Each array is the complete rolling window of at most 20 actually published posts for that tier, current as of cycle_time. The API evaluates the same pending candidate pool against both windows; candidates do not need a target-tier field. A duplicate of a Main post does not automatically become a duplicate for Light.
gall_id, post_no and placed_at are required per item. This reference-only form reuses content already delivered to the API. placed_at requires an explicit offset and cannot be after cycle_time. Repeated identities within one tier or more than 20 entries are rejected. The same post may appear in both tiers; its semantic comparisons can be shared without coupling the two tier decisions. A candidate already present in a tier window is a duplicate by identity without an LLM call.
Omitting a tier or using null means its feed context is unknown. That tier remains incomplete, with every scored candidate both ranked and unchecked. The retained wire label candidate_only means missing published context; it does not request candidate-to-candidate deduplication. An explicit empty array means the feed is known to be empty, so candidates pass that tier’s published-window check without semantic calls.
The server does not infer publication from saved removals, recommendations or outcomes. It does not append recommendations to either window or reuse an old snapshot when the current one is absent. The sender supplies complete snapshots as of each cycle. An old placed_at does not itself make a quiet feed stale; there is no invented publication age expiry.
Bootstrap the initial published posts once
Candidate text and image metadata are archived when received and retained after removal. For a published post that was never delivered to this API, provide its original content once inside the published-window entry:PUT /v1/images/{gall_id}/{post_no}/{NN}. Image counts, positions and URLs in HTML do not supply image evidence; the API does not fetch those URLs. Use accurate metadata, and omit fields that are genuinely unavailable rather than inventing content or declaring missing images absent. Image-only posts need usable image evidence for semantic assessment.
Later cycles can return to references alone. Do not insert an already-published post into candidates merely to provide its content. Bootstrap fills missing archive fields without replacing known candidate evidence, survives restarts, and is retained while background checks are unfinished. Later bootstrap or image uploads make changed evidence eligible for assessment. Any resulting verdict can appear in a new cycle; it does not rewrite an earlier response.
If a supplied publication has no delivered text or image evidence, its tier reports published_content_missing. The affected comparisons remain unchecked: reducing the comparison count cannot compensate for missing content. A candidate is excluded only on a confirmed match to a publication. Missing evidence does not block selection, but the candidate is not certified different from that unknown publication.
Work order and reuse
Surf runs missing comparisons while cycle requests are waiting and between requests, using candidate scores and exact Main/Light windows from the most recently prepared cycle by cycle time. Recovering an older cycle does not replace that current work snapshot; its final report still assesses its own prepared evidence. Among work eligible to run, higher raw scores are scheduled first, with gallery and post number breaking ties. Up to four provider calls run concurrently, with at most 20 pairs per call. Each completed batch commits independently and allows its free slot to take more eligible work without waiting for a slower batch. New cycles can refresh priorities and publication context without discarding reusable pair verdicts. A valid unknown (U) verdict is retained for the exact evidence assessed. Full-evidence here means all evidence currently selected by the input policy was delivered; it does not make image uploads mandatory. Surf does not periodically ask the model to reconsider unchanged evidence: the pair stays unchecked until changed content/images or a model/prompt version changes its evidence key. Unknown is never a confirmed difference or duplicate, but does not exclude a candidate from ranking. Transient failures still use bounded retry backoff so other eligible comparisons can advance. If a request could not deliver all selected evidence, that incomplete attempt is retryable rather than a durable verdict on the full input. Priority cannot guarantee that every high-scoring candidate receives a conclusive assessment.
Validated same/different pair verdicts and full-evidence unknown decisions are stored durably and reused across cycles and restarts. Only same/different verdicts count as checked. The cache uses both posts’ content/image fingerprints and the model, prompt and evidence policy. New scores, engagement counts, cycle IDs, publication times or batch composition do not invalidate unchanged semantic evidence. New content/images or a changed model/prompt invalidate the affected verdicts. Unknown, failed or timed-out comparisons are never cached as evidence that posts are different. Reusing a retained unknown means leaving it unchecked, not increasing comparisons_checked.
The background worker owns semantic inference; the HTTP path waits for progress and reads validated verdicts for its final report. An unchanged warm queue with all required verdicts cached needs no new background inference. Once a candidate has a confirmed same-event match in a tier, further comparisons needed only for that tier can stop. Its other tier still needs its own match or completed negative checks. duplicate_of identifies one confirmed match, not an exhaustive list. A new candidate requires its still-needed comparisons with the current published posts; a new publication requires its comparisons with the candidate queue. Shared Main/Light pairs are checked once. There is no candidate-to-candidate quadratic term: 578 candidates against two disjoint 20-post windows require at most 23,120 semantic pairs, before identity resolution or cached reuse. Actual new work depends on window changes and available evidence; this is not a per-cycle quota or a latency guarantee.
Read the response
results retains every original raw score row and its provenance. In each deduplication.tiers.main and .light report, ranked and duplicates partition all scored candidates. unchecked is an overlapping diagnostic subset of ranked, not a third selection list.
Non-scored rows remain only in
results. A confirmed duplicate remains excluded even if some other comparisons are unfinished. Historical completed responses retain their original checked-only ranking policy and disjoint lists; they are never rewritten. They may also contain same_event_candidate and candidate representatives from the previous algorithm; the legacy enum is retained for reading them. New cycles do not produce candidate-to-candidate exclusions. Coordinate the client policy update with deployment: clients that continue dropping unchecked candidates will retain the old behavior.
Status and context describe assessment coverage, not eligibility. One tier’s missing evidence does not erase the other tier’s completed verdicts. Unavailable/disabled assessments have all scored posts ranked and unchecked, with no confirmed duplicate decisions. Keep the existing freshness and response deadline checks even when candidates remain eligible.
The report records model/prompt provenance and coverage:
A tier can have a completed verdict for every candidate even when
comparisons_checked is less than comparisons_total: one positive match is enough to exclude a duplicate, and skipped pairs are never counted as checked. Existing evidence warnings can still make its status partial.
Background request counts, timing and outcomes are operational metrics, separate from these frozen response counters. A zero provider_calls value does not mean the background processor is idle. Verdicts completed during the bounded wait can improve the pending cycle’s report. Progress after finalization can improve a new cycle’s report; it never updates the response already returned.
Evidence and time limits
Images may arrive one or two cycles after text; raw scoring never waits for them. Deduplication reads uploaded immutable local bytes within its input budgets. Missing, oversized or insufficient evidence stays explicit. Text-bearing posts are assessed only to the extent supported by their evidence; image-only posts without usable images remain unchecked. The client allows 60 seconds from sending including at most one retry. The default server processing budget is 25 seconds from request-handler entry for queue work, raw scoring, bounded waiting and finalization. Preparation commits before the wait and releases the database writer so background facts can become visible. The wait ends when this cycle’s still-needed work has settled or its remaining wait budget expires, reserving time to finalize; admitting unchecked candidates does not shorten this wait. Retained full-evidence unknowns or unavailable evidence can leave a settled assessment unchecked. Transient failures, cooldown and in-flight work are not terminal decisions. Background provider requests have an independent bound of at most 30 seconds and can continue after the response; this does not extend the server or client budgets. Facts completed after finalization can appear only in a new cycle. This does not guarantee conclusive checks for the entire queue within one cycle. Client-visible latency, conclusive assessment coverage and duplicate misses must be measured separately from the number of eligible candidates. The final report is persisted with its prepared raw scores. An unchanged retry after interruption resumes durable preparation without repeating ingestion or scoring. Once finalized, same-ID POST retries and GET return the original completed response, including a partial or unavailable report. Later changed evidence, provider recovery or background completion can affect a new cycle only. GET is recovery of a frozen cycle, not polling for a fresher deduplication result. Older completed cycles may omitdeduplication entirely.