Engineering
From Moderation Queues to Inline Policy Checks
Move policy checks before publication. Compare queues, user reports, and inline Antaeus evaluation with explicit outcome handling.
Inline policy evaluation checks a submission before your application acts on it. For suitable workloads, Antaeus lets the application return a policy-based result in the submission request, rather than putting every submission through a separate approval queue or waiting for someone to report it.
Consider a seller listing a used camera. A queue-based approval flow might accept the submission immediately but leave it marked “pending.” A reactive flow publishes it immediately and relies on later reports to identify violations. An inline flow evaluates the listing first, so the application can publish an eligible submission or return a different result before completing the request.
This article compares these three designs for a marketplace publication endpoint. It is an architecture guide with a runnable application simulation, not a latency benchmark or a claim that semantic evaluation catches every violation. Antaeus’s hosted service is an experimental beta with shared capacity and no production SLA; qualify your workload before depending on inline decisions.
Queued approval: accept now, decide later
In a pre-publication approval flow, the endpoint saves a pending listing and writes a job to a queue or stream. A worker reads the job, evaluates the content, and updates its publication state. The browser learns the result through polling, a notification, or another request.
The first response can be quick because it acknowledges receipt. Time to publication includes the wait for a worker, evaluation, and the publication-state update. Notification adds time before the seller learns the result. Under backlog, those are different user experiences: “We received your listing” and “Your listing is published.” Asynchronous request-reply makes that separation explicit.
This design has useful properties. A queue can absorb bursts, let intake and processing scale independently, and keep work available while consumers recover. If arrivals exceed processing capacity for long enough, the backlog grows. These are the tradeoffs described in the queue-based load-leveling pattern.
A stream can play a similar role when publication waits for a consumer’s result. Here, “asynchronous” describes processing outside the submission request; the worker may run continuously and call an online model. It does not necessarily mean a nightly batch or an evaluator disconnected from the network.
User reports: publish now, investigate later
A reactive design publishes first. Another user may flag counterfeit goods, spam, or harassment, and a reviewer or automated process investigates the report.
This gives the submitting user immediate publication. In a system relying on reports as its primary detection mechanism, however, a violation can stay visible until someone notices it, submits a report, and the system acts. A listing that nobody reports might never enter that workflow.
Reports remain useful after adding automated checks. They capture missed cases, changed context, and disputes about a decision.
Inline evaluation: check before the application acts
The main flow is deliberately small:
Submit → Antaeus → Application action
Your endpoint captures the submitted content and its applicable policy, asks Antaeus to evaluate it, and handles the returned outcome before attempting publication. Existing identity, permission, and deterministic validation checks remain part of your application.
Antaeus separates the language judgment from the application action. An evaluator assesses the policy’s conditions; the engine combines that evidence according to the authored outcomes. The policy is a versioned artifact identified by a digest. See the policy-authoring guide for the portable format.
For example, your marketplace might prohibit listings that offer counterfeit goods. A seller’s phrase “1:1 replica with a fake certificate” requires interpreting the description. Checking whether the price is positive or a required field is present belongs in ordinary code. The marketplace tutorial develops the policy example; this article focuses on where the evaluation sits.
An inline design can remove the separate approval-worker wait for submissions that receive a usable decision within the request’s budget. It still performs evaluation. A queue worker could call the same Antaeus engine or API; moving that call into the request changes orchestration, not the evaluator’s inference speed or accuracy.
Make the application action explicit
Antaeus returns four outcomes. The application chooses the corresponding workflow:
| Decision outcome | Example marketplace action |
|---|---|
allow |
Attempt to publish the exact evaluated revision after the remaining checks. |
review |
Hold the listing and create a human-review task. |
deny |
Reject publication and return an appropriate explanation. |
failure |
Hold the listing and enter an operational recovery path. |
Completed uncertainty can produce review. An accepted evaluation that fails to produce a policy judgment can produce failure. Preserve that distinction even if both lead to someone investigating. A matched deny takes precedence in the engine’s reduction; applications consume the final outcome rather than reconstructing it from one rule. The v0.7.0 Decision guide defines these semantics.
A successful HTTP response is not permission to publish. The hosted API returns a Decision directly for accepted evaluations; your backend must inspect it. A timeout, rejected request, or malformed response might leave you without a usable Decision at all. In this example, that path also keeps the listing unpublished, while recording an operational error separately from any policy judgment.
Bind the result to the content you publish
Suppose the seller submits revision 7. While evaluation runs, they edit the listing to revision 8. Publishing revision 8 using revision 7’s allow result would check one object and act on another.
Capture the tenant, listing, immutable revision, exact evaluation input identity, and policy digest before the call. If trusted context contributes to the judgment, include it in that input identity. Keep this request metadata in your own trusted operation record; the public Decision does not supply your application’s revision fields.
Before acting, validate the complete returned Decision against the supported schema and expected policy. Associate it with the exact request through a trusted backend integration. A policy digest identifies a policy; it does not authenticate the producer or prove which input was evaluated. Reject synthetic fixture evidence in an enforcement integration.
At the publication boundary, condition the update on the evaluated identity still being current. Check any permissions that must still hold, and deduplicate retries. A database transaction or conditional write should connect the identity check, state transition, and durable publication intent. If publication triggers an external side effect, use your normal reliable-delivery mechanism, such as a transactional outbox.
A separate “read, then publish” sequence leaves a race. An edit between those operations must invalidate the old publication attempt. An application deadline must also close the original attempt: a late result can only enter a deliberate, version-checked recovery workflow.
Run a small application simulation
The following function illustrates routing after a trusted integration has validated a Decision and associated it with the evaluated submission. Its evidence argument is an application projection, not the public Antaeus response format. Save it as publication-plan.mjs:
export function planAction(evidence, current) {
const keys = ["tenantId", "listingId", "revision", "policyDigest", "inputDigest"];
const digest = /^sha256:[a-f0-9]{64}$/;
const valid = (value) => value &&
["tenantId", "listingId"].every((key) =>
typeof value[key] === "string" && value[key].trim().length > 0) &&
Number.isSafeInteger(value.revision) && value.revision > 0 &&
["policyDigest", "inputDigest"].every((key) =>
typeof value[key] === "string" && digest.test(value[key]));
if (!valid(evidence) || !valid(current)) {
return { action: "hold", cause: "invalid-identity" };
}
if (!keys.every((key) => evidence[key] === current[key])) {
return { action: "hold", cause: "stale-or-mismatched-evaluation" };
}
switch (evidence.outcome) {
case "allow": return { action: "publish", cause: "policy-allow" };
case "review": return { action: "hold", cause: "policy-review" };
case "deny": return { action: "reject", cause: "policy-deny" };
case "failure": return { action: "hold", cause: "evaluation-failure" };
default: return { action: "hold", cause: "invalid-outcome" };
}
}
Save this second file as demo.mjs in the same directory:
import assert from "node:assert/strict";
import { planAction } from "./publication-plan.mjs";
// Invented identities and outcomes: no evaluator call or real publication.
const current = {
tenantId: "demo-marketplace", listingId: "demo-camera", revision: 1,
policyDigest: `sha256:${"a".repeat(64)}`,
inputDigest: `sha256:${"b".repeat(64)}`,
};
for (const [outcome, action] of [
["allow", "publish"], ["review", "hold"],
["deny", "reject"], ["failure", "hold"],
]) {
const plan = planAction({ ...current, outcome }, current);
assert.equal(plan.action, action);
console.log(outcome, plan);
}
const oldAllow = { ...current, outcome: "allow" };
assert.equal(planAction(oldAllow, { ...current, revision: 2 }).action, "hold");
assert.equal(planAction({ ...current, outcome: "unknown" }, current).action, "hold");
console.log("Stale content and unknown outcomes stay held.");
Run it with Node.js 24:
node demo.mjs
publish in this example is a proposed application action. The function has no side effects and does not authorize a state change. It omits schema validation, provider calls, permissions, deadline management, durable deduplication, and transactional writes; those belong at the surrounding boundaries described above.
For this article, the copyable demo and an additional in-memory commit simulation were run with invented outcomes. The commit cases exercise all four branches, changed identities, duplicate delivery, and competing evaluations. They test application handling, not semantic classification, network latency, or distributed correctness. No provider evaluation or real publication was performed.
Give “real-time” a measured meaning
For this architecture, real-time means evaluation before the application action within the interactive request’s budget. It is not a hard real-time guarantee or a promise of an instant answer.
Model the seller’s time to publication with separate terms:
Queued approval ≈ queue wait + evaluation + publication/update work
Inline approval ≈ request overhead + evaluation + conditional publication
These are conceptual breakdowns, not measured equations; notification can add further delay in the queued design. Removing an approval queue does not remove internal queues, model inference, network overhead, or provider limits.
Set an end-to-end deadline, reserve time for validation and the final write, and bound retries and backoff within the remaining budget. Antaeus’s evaluator profiles configure evaluation deadlines, retry behavior, and fallback routes. A profile timeout is only one part of the application’s full request budget.
Measure from submission to the user-visible result as well as from submission to publication. Report p50, p95, and p99 over a stated workload and concurrency, alongside timeout, failure, review, and throttling rates. Keep unsuccessful and held cases visible instead of reporting only the fastest allowed submissions. This article establishes no numeric latency target or production speedup.
Keep an asynchronous path for the cases that need it
| Workload condition | Appropriate starting point |
|---|---|
| A bounded submission and an evaluator qualified within the interactive budget | Consider inline evaluation. |
| Long documents, expensive multi-stage analysis, or unpredictable bursts | Consider queued processing or a hybrid. |
| An uncertain result or significant consequence requiring human judgment | Hold for human review. |
| A later report, appeal, or changed policy | Re-evaluate through a separate lifecycle. |
Start in shadow mode: record advisory decisions while retaining the existing workflow. Freeze example inputs and expected judgments, evaluate the chosen policy and evaluator, and measure under representative load before changing publication behavior. Gradually enable inline handling only for a bounded, qualified path. Keep reports and appeals available.
The useful change is that an ordinary, qualified submission no longer has to wait for a separate approval worker just because that is where moderation has always lived. Antaeus makes the policy decision explicit; your application decides whether it can act now or needs another workflow.
Follow the Antaeus API quickstart to connect a backend and begin in shadow mode. For the broader boundary between policy judgment and application responsibility, see semantic policy enforcement.
Questions and answers
Does inline policy evaluation remove every moderation queue?
No. Uncertainty, failures, appeals, and heavier checks can still need queues or human review.
Does real-time policy evaluation mean an instant response?
No. Evaluation takes time. Measure end-to-end latency and qualify capacity, deadlines, and recovery.