The Anti-Slop Stack: how TenderWright stops AI from inventing fees
Five guardrails that turn an off-the-shelf LLM into a tender drafter you can actually ship to a Crown agency. None of them are 'just prompt better'.
The fastest way to lose a tender is to submit an AI-drafted proposal
that names a project the firm never delivered. The second-fastest is
to ship one with [Lead Engineer] still visible in the executive
summary. We’ve watched both happen on competitors’ demos this year.
TenderWright is built around the assumption that the AI will try to fabricate or leave placeholders, and that the only defence is a stack of guardrails between the model and the export button. Here is the actual stack, in execution order.
1. The 412 pre-flight gate
The first thing the AI drafter checks is whether the proposal has linked RFP source material with at least 500 characters of body text. If not, the endpoint returns HTTP 412 Precondition Failed with a typed error code:
{
"code": "rfp_context_too_thin",
"message": "Not enough RFP context to draft this section.
Upload the RFP PDF/DOCX (or paste the brief into the
proposal description) before generating, otherwise the
model has nothing to write against.",
"rfp_text_length": 0,
"min_required": 500
}
This is not a soft warning — the model is never invoked. We do not spend Sonnet tokens on a proposal where the AI has nothing to ground itself against. The frontend renders the error as a “Missing RFP Context” banner with a one-click jump to the upload step.
Why this matters: every “AI hallucinated a project that doesn’t exist” story we’ve heard starts with the model being given a five-line tender title and asked to “draft an Executive Summary”.
2. Brand voice + entity grounding
Once the AI is allowed to run, it doesn’t see “the world”. It sees a context window with your data only:
- The parsed RFP body (full text, not summary)
- Your linked reference projects with structured fields (name, client, year, value, sector, disciplines, status)
- Your linked team members with full credentials (name, title, years experience, qualifications array, role on proposal)
- Your fee estimate structure (rates, hours, disciplines, location multiplier)
- Your brand voice training corpus (5 of your past winning proposals)
When the model writes “The proposed team brings together…”, the next words are constrained to people that exist in your database with real credentials. There is no “Andrew Smith, Lead Engineer” because that string isn’t in the context.
3. The post-generation fabrication scanner
After the model returns, every draft is run through a regex sweep looking for known fabrication signatures:
_FABRICATION_PATTERNS = [
(r"\[[A-Z][A-Za-z ]{2,}\]", "Bracket placeholder ([Lead Engineer])"),
(r"lorem ipsum", "Lorem ipsum filler"),
(r"\bsample (text|name|client|project)", "'Sample X' placeholder"),
(r"your (client|firm|company)'s name here", "Template marker"),
(r"\b(TBD|TODO|FIXME|XXX)\b", "TBD/TODO marker"),
(r"\*\*\s*\*\*", "Empty bold marker"),
(r"\bOur 0\b", "Empty {{team_size}} residue"),
(r"[.!?:]\s+will\s+(?:serve|direct|lead)", "Missing {{lead_engineer}} subject"),
(r"\(\s*\)", "Empty parens — name removed"),
# ...plus 21 more
]
Hits get surfaced in a quality_warnings array on the response. The
frontend renders them in the section editor as red chips so the
reviewer sees what to fix before the draft hits the export pipeline.
(The bracket detector has a small allowlist of legitimate section
cross-references — [Methodology], [Health & Safety],
[Risk Management] — so we don’t false-positive on the compliance
traceability block.)
4. The pre-export readiness gate
When you click DOCX / PDF / PPTX, the readiness check fires before the file is generated. It scores five evidence axes (RFP attached 35, projects linked 25, team linked 20, fee structured 10, NZ Govt signals 10) and one hard-floor check (≥3 sections drafted).
If the evidence score is under 80 or fewer than 3 sections have been drafted, the export button label changes from “Generate DOCX” to “Generate DOCX anyway”. The user can still proceed — they’re adults, they may have a workflow where they generate intentionally sparse drafts. But the friction is real, the warning is visible, and the “Fix this” buttons inline open the exact tab they need.
5. The red DRAFT banner
Even after all of the above, some users will click “anyway”. For those cases, every undrafted section in the exported DOCX renders this in bold red C21E1E:
[ DRAFT — Section content not yet written. This proposal is
NOT ready for client submission until all sections are complete. ]
The section heading is preserved (so the TOC and downstream numbering stay intact), but the body is unmistakably not for the client. A reviewer skimming the printed PDF will see the red on page 3 and stop before the document leaves the office.
This was added after a NAL audit caught the previous (grey, muted) placeholder — “This section has not been drafted yet, use the AI Assistant…” — slipping through five sections of a Won proposal. The muted text read as “noteworthy detail” rather than “do not send”. Bold red doesn’t.
What this adds up to
None of these five guardrails is novel on its own. The point is the stack: a model that can fabricate, served behind five compounding checks, three of which can’t be turned off. By the time a draft reaches a client, it has passed:
- Pre-flight context check (or wasn’t invoked at all)
- Entity-grounding context window
- Post-generation regex sweep with surfaced warnings
- Pre-export readiness modal with friction
- In-document red DRAFT banner on any remaining gap
The thing that separates a generic LLM wrapper from a tender drafter is not the prompt. It’s the five layers of paranoia between the model and the procurement team.
Curious about a specific layer? Email us or book a demo — we can walk through how each guardrail behaved on a real recent draft.