11 KiB
VerdictTank — Technical Architecture
Version: v1.0 Status: 3/3 Unanimous Conditional Go (Aug 10, 2026) Owner: Germaine Brown Built by: Sho'Nuff (Hermes Agent)
Overview
VerdictTank is an AI-powered business proposal review platform. A user submits a business proposal, product spec, or architecture document. Five specialized agents process it across three phases, three independent judges deliver a majority-rules verdict, and a branded PDF report lands in the user's inbox.
Pipeline
User submits proposal
│
▼
Phase 1: Research Agent
Live web verification — competitors, trademarks, domain WHOIS,
market data, pricing benchmarks. Citations attached to every claim.
│
▼
Phase 2: Critic Agent
10-dimension review (Name, Pricing, PMF, Competition, Financials,
GTM, Risk, Missing Elements, Founder Fit, Overall Verdict).
Scored 1-10 per dimension.
│
▼
Phase 3: Three judges in parallel (majority rules)
Judge A — Independent architecture
Judge B — Independent architecture
Judge C — Independent architecture
│
▼
PDF verdict delivered by email
Verdict System
| Result | Meaning |
|---|---|
| 3/3 Unanimous | Maximum confidence — all judges agree |
| 2/3 Majority | Majority verdict with dissenting opinion |
| 1-1-1 Split | Human (Germaine) breaks the tie |
| Degraded (2 judges) | One judge unavailable — runs with remaining two |
Component Architecture
Server: Core (netcup RS 2000 — 152.53.192.33)
| Component | Technology | Port | Role |
|---|---|---|---|
| Caddy | Caddy 2 | 80/443 | Reverse proxy, TLS termination, static file serving |
| VerdictTank API | FastAPI (Python) | 8201 | Review orchestration, corpus queries, email dispatch |
| Corpus DB | SQLite | — | Proposal corpus, benchmark data, outcome tracking |
| PDF Generator | WeasyPrint | — | HTML-to-PDF rendering from pdf-template.html |
| Gitea Actions Runner | Gitea Act Runner | — | CI/CD for VerdictTank repo |
AI Models (via admin-ai)
| Phase | Model | Role |
|---|---|---|
| Research | Worker model | Web search, claim verification |
| Critic | Reviewer model | 10-dimension critical review |
| Judge A | Independent model | Architecture A — validation |
| Judge B | Independent model | Architecture B — cross-check |
| Judge C | Independent model | Architecture C — operational realism |
All model calls route through admin-ai.itpropartner.com with the verdicttank-prod API key. Multi-provider architecture ensures no single vendor lock-in and genuine cross-model disagreement.
Caddy Routing
verdicttank.com, www.verdicttank.com {
handle /api/verdicttank/* {
reverse_proxy 127.0.0.1:8201
}
handle /reports/* {
root * /var/www/verdicttank
file_server
}
redir /reports /reports/ permanent
}
Crucial detail: use handle (not handle_path) for /reports/* — handle_path strips the prefix, breaking file resolution for /reports/Client-Report.pdf.
Data Model
Review Record
Review
├── id: UUID
├── client_name: str
├── proposal_text: str (full submitted document)
├── pricing_tier: enum[free, pro, enterprise, whitelabel]
├── status: enum[pending, research, critique, judging, done, failed]
├── created_at: datetime
├── completed_at: datetime?
│
├── research_brief: ResearchBrief?
├── critic_review: CriticReview?
├── judge_verdicts: [JudgeVerdict]
├── final_verdict: enum[go, no_go, conditional_go]?
├── pdf_path: str?
│
├── cost_breakdown: CostBreakdown
└── turnaround_seconds: int?
ResearchBrief
ResearchBrief
├── verified_claims: [{claim, source_url, confidence}]
├── discrepancies: [{claimed, actual, severity, evidence}]
├── competitor_analysis: [{name, url, pricing, features, notes}]
├── domain_check: {domain, registered, expiry, registrar}
├── trademark_risk: enum[none, low, medium, high, blocked]
└── market_data: [{statistic, value, source, verified}]
CriticReview
CriticReview
├── dimension_scores: {name: str, score: int, notes: str}[10]
├── fatal_flaws: [{description, severity, condition_to_fix}]
├── strengths: [str]
├── blind_spots: [str]
├── overall_verdict: enum[go, no_go, conditional_go]
└── conditions: [str]
JudgeVerdict
JudgeVerdict
├── judge_id: str (Judge 1/2/3)
├── verdict: enum[go, no_go, conditional_go]
├── confidence: float (0.0-1.0)
├── agreements_with_critic: [str]
├── disagreements_with_critic: [str]
├── novel_insights: [str] ← findings no other judge caught
├── second_order_effects: [str]
└── conditions: [str]
PDF Generation
The PDF pipeline at /root/projects/verdicttank/:
-
HTML Template (
pdf-template.html) — 11-section branded report:- Cover page with verdict badge
- Executive summary
- Dimension score table
- Research findings with citations
- Fatal flaws ranked by severity
- Priority-ranked action plan
- Judge cards (anonymized — "Judge 1/2/3", no model names)
- Judge notes section
- Disclaimer (Not Legal Advice, Not Financial Advice, AI-Generated Content Disclosure, No Warranty, Limitation of Liability, Confidentiality Notice)
- Citations
-
Generator (
generate-pdf.py) — Reads structured JSON data, fills the template via WeasyPrint, outputs branded PDF -
Quality Gate (post-generation):
from pypdf import PdfReader
r = PdfReader('report.pdf')
text = ''.join(p.extract_text() or '' for p in r.pages)
assert '—' not in text, 'EM DASH FOUND'
assert 'dispatch' not in text.lower(), 'DISPATCH FOUND'
assert 'Disclaimer' in text, 'DISCLAIMER SECTION MISSING'
assert len(r.pages) >= 10, f'Expected 10+ pages, got {len(r.pages)}'
Sanitization Rules (hard constraints)
| Rule | Enforcement |
|---|---|
| No model names anywhere in PDF | build_judge_cards() uses data name field — expects "Judge 1/2/3" |
| No internal methodology exposed | No "pipeline", "conductor", "Opus", "Sonnet", "Qwen", "Gemini" |
| No em dashes | clean() function strips \u2014 and \u2013 |
| 11-section report (was 10 before Aug 8) | Disclaimer is Section 10, Citations is Section 11 |
Filename: <Client>-VerdictTank-Report.pdf |
Safe-filename generator strips non-alphanumeric |
Pricing Integration
| Tier | Price | Reviews/Month | Model Access |
|---|---|---|---|
| Free | $0 | 1 | Limited model set |
| Pro | $79/mo | 10 ($8 overage) | Full model set |
| Enterprise | $499/mo | 50 ($12 overage) | API access, prediction tracking |
| White-Label | $1,999+/mo | Unlimited | Multi-tenant, custom branding |
Cost per review: $0.70-0.75 (v3 corpus-backed). Turnaround: 5-15 minutes.
Cost Model
Per-Review Breakdown
| Phase | Tokens (est.) | Cost |
|---|---|---|
| Research Agent | ~5K in, ~3K out | ~$0.10 |
| Critic Agent | ~15K in, ~5K out | ~$0.25 |
| Judge A | ~20K in, ~3K out | ~$0.12 |
| Judge B | ~20K in, ~2K out | ~$0.05 |
| Judge C | ~20K in, ~2K out | ~$0.05 |
| PDF Generation | N/A | ~$0.01 |
| Total | ~$0.58-0.75 |
At 10 reviews/month (Pro tier): ~$7.50 cost, $79 revenue = ~90% gross margin. At scale (100+ reviews/month): corpus caching reduces research costs significantly.
Security Model
Attack Surface
| Surface | Risk | Mitigation |
|---|---|---|
| Prompt injection via proposal text | HIGH | Input sanitization, structured parsing, no raw tool execution from user input |
| Research agent web access | MEDIUM | URL allowlist, rate limiting, response size caps |
| PDF injection via WeasyPrint | LOW | No user-controlled HTML in template, all content sanitized |
| API key exposure | LOW | verdicttank-prod key scoped to admin-ai, usage caps |
| Report access | LOW | /reports/ directory served by Caddy, no directory listing |
| Corpus poisoning | LOW | SQLite read-only for judge queries, write path gated |
Input Sanitization Pipeline
Raw proposal submission
→ Strip control characters
→ Validate UTF-8
→ Size cap (100KB)
→ MIME type validation
→ Pass to Research Agent as structured data, not raw prompt
Failure Modes & Recovery
| Failure | Impact | Recovery |
|---|---|---|
| Single judge timeout/crash | Degraded to 2 judges | Majority of 2 rules; note missing judge in addendum |
| All judges fail | No verdict produced | Free re-run; investigate admin-ai |
| Research Agent web failure | Incomplete citations | Flag as "limited research" in brief; proceed with available data |
| PDF generation failure | Report not delivered | Retry once; email plain-text summary on persistent failure |
| Core server outage | Platform unavailable | Warm standby (app1-bu) can take over Caddy + API |
| admin-ai outage | All phases blocked | Model failover chain (Flash → Gemini → Grok → Sonnet) |
| SQLite corruption | Corpus lost | Nightly backup to S3; rebuild from backup |
Operations
Monitoring
| Metric | Source | Alert Threshold |
|---|---|---|
| API response time | Prometheus (Core:9090) | >30s p95 |
| Review success rate | App-level counter | <90% over 1h |
| PDF generation failures | App-level counter | Any failure |
| admin-ai latency | LiteLLM metrics | >10s p95 |
| Disk usage (reports) | node_exporter | >80% |
Backup
- Corpus DB: Nightly to S3 via
hermes-backup.sh - PDF reports:
/var/www/verdicttank/reports/included in full backup - API config: In git repo, pushed to Gitea
Known Limitations
- Single-box architecture — Core is a single VPS. If Core goes down, VerdictTank is down until app1-bu failover completes (~5-10 min). A dedicated VerdictTank instance would eliminate this dependency.
- No payment integration — Currently manual billing. Stripe integration planned for Phase 2.
- No user accounts — Each review is standalone. User dashboard and review history planned for Phase 2.
- Corpus freshness — Benchmark data is static until manually refreshed. Automated crawl planned.
- PDF email delivery — Via MXroute SMTP, no delivery tracking. Postmark/SendGrid integration planned.
Roadmap
| Phase | Deliverable | Status |
|---|---|---|
| Phase 0 | PDF pipeline, corpus DB, basic API | DONE |
| Phase 1 | Outcome tracking, accuracy engine, corpus refresh | Proposed |
| Phase 2 | Stripe integration, user dashboard, API access | Proposed |
| Phase 3 | Multi-tenant, white-label, SSO | Proposed |
Deployment
# Deploy site
scp -i /root/.ssh/itpp-infra /tmp/verdicttank-architecture.md \
root@152.53.192.33:/root/itpp-docs/docs-source/verdicttank/architecture.md
# Build and deploy
ssh core "cd /root/itpp-docs && mkdocs build --clean && \
tar czf - site/" | ssh app3 "tar xzf - --strip-components=1 \
-C /home/ippadmin/htdocs/docs.itpropartner.com/"
Last updated: August 10, 2026 Pipeline verdict: 3/3 Unanimous Conditional Go Commit: see git.itpropartner.com/ippadmin/itpp-docs