TrustIntern:作者用 5.5 小時打造實習邀約風險調查工具
TrustIntern: I built a scam detective for internship offers in 5.5 hours, and my agents kept receipts
作者用 5.5 小時打造 TrustIntern,供用戶輸入公司、招聘人員、網站及訊息內容,並上載聘書 PDF 或截圖,以調查實習邀約風險。Gemma 4 負責安排調查智能體及讀取文件,分數則由固定規則計算並逐項連結證據,報告亦註明分數不是詐騙概率。
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
What I Built
Every placement season, a lot of students in India get some version of this message. (This one is a composite I wrote; the pattern is the real part.)
"Congratulations! You have been selected for our internship. Please pay a ₹1,999 registration fee to confirm your seat. Share your Aadhaar and the OTP you receive for verification. Offer valid for 24 hours only."
It comes from a Gmail address, for a company whose website is a month old. And it works, because a student chasing their first résumé line is the person least likely to stop and check.
TrustIntern is an investigation desk for internship offers. You paste in the company name, the recruiter's email, the website and the message, and you can attach the offer letter as a PDF or a screenshot. TrustIntern then:
- runs fixed checks (email domain vs. company, domain age via RDAP, the website itself, the wording of the message),
- sends a small team of research agents to look into the company, the recruiter and public complaints, with a Gemma 4 planner deciding which agents to launch,
- has Gemma 4 vision read the offer letter or screenshot,
- and returns a risk score you can recompute by hand. Every point is tied to a specific piece of evidence.
Every report says: "Heuristic indicator score, not a probability of fraud." It shows evidence and calls nobody a scammer.
The side hustle
Who would pay for this? Not students, and that's on purpose.
- Students: free. The people being targeted shouldn't have to pay for protection. With local Gemma it costs almost nothing to run.
- Colleges and placement cells: paid. Bulk verification of the offers circulating in student WhatsApp groups, with a shareable report per offer.
- Job boards and internship platforms: paid API. Screen listings and recruiter accounts before they go live.
- Postmortem report: a one-off paid report for someone who has already been scammed. It's an evidence-backed write-up of what happened, which they can attach to a complaint to their college, the platform, or the cybercrime portal.
That last idea didn't come from me. It came from my first tester (more on that below).
Demo
🔗 Live: https://trustintern.charancodes.com
In the recording I typed in a fresh fake offer from "Global Talent Solutions Ltd": a Gmail recruiter, a ₹1,999 "refundable registration fee" via UPI within 24 hours, and a request for Aadhaar plus OTP. It scored 80/100: Upfront fee +25, Email domain mismatch +20, Sensitive data request +20, Urgency or guarantee +10, Personal email provider +5. The company name did appear in the domain, so that check came back clear and added nothing. Live lookups were on, but the fake domain didn't resolve, so the website and domain-age checks say Not checked instead of guessing. The built-in "Consistent offer" sample scored 0/100.
And here's the built-in Suspicious sample (a fictional "Aurora Tech Pvt Ltd" offer):
Hit investigate, and the run streams live over SSE: checkers, then spawned agents, then findings.
The finding leads with the number, then shows the arithmetic:
90 = 25 upfront fee
+ 20 email domain mismatch
+ 20 sensitive data request
+ 10 company not in domain
+ 10 urgency or guarantee
+ 5 personal email provider
Each chip is tied to the sentence that triggered it. Nothing is subtracted, and the total is capped at 100.
Download it all as a PDF:
Code
compteccharan
/
TrustIntern
TrustIntern is an AI-powered platform that evaluates the authenticity and scam risk of internship and job opportunities by analyzing company details, recruiters, websites, communications, documents, and digital evidence to generate an explainable risk assessment.
TrustIntern 🕵️
An investigation desk that tells students whether an internship offer is a scam, and shows its working.
TrustIntern looks at the company name, recruiter email, website, message text, and an offer-letter PDF or screenshot. It runs a small team of research agents and returns an explainable risk score: every point is tied to a specific piece of evidence. Reports download as a PDF.
Heuristic indicator score, not a probability of fraud.
Live demo: https://trustintern.charancodes.com
How it works
- Fixed checkers look at the email domain, domain age (RDAP), website and message text.
- Gemma 4 plans which research agents to launch: company, recruiter, complaints, domain, document, follow-up. Only allowlisted agent types run; at most 8 agents and depth 2.
- Gemma 4 vision reads the offer letter (PDF or screenshot) and flags inconsistencies.
-
Deterministic scoring (
backend/app/scoring.py) adds up fixed indicator points: upfront fee 25, domain mismatch 20, sensitive…
MIT licensed. The API contract, including the scoring rules, lives in backend/CONTRACT.md.
How I Built It
Architecture
┌──────────────────────────────────────────┐
Student ─▶ │ Investigation desk (Vite + React, jsPDF)│
└───────────────┬──────────────────────────┘
│ POST run · SSE event stream
┌───────────────▼──────────────────────────┐
│ FastAPI (Vercel services: /api → py) │
│ │
│ 1. Fixed checkers ──┐ │
│ domain, RDAP, │ evidence ledger │
│ wording, site ▼ │
│ 2. Planner ──▶ allowlisted agents │
│ Gemma 4 company · recruiter · │
│ (fallback: complaints · domain · │
│ heuristics) document · follow-up │
│ max 8 agents, depth 2 │
│ 3. Gemma 4 vision: offer PDF/screenshot │
│ │ │
│ 4. scoring.py ◀─────┘ deterministic, │
│ (Gemma never recomputable │
│ touches this) │
└──────────────────────────────────────────┘
Gemma 4: local gemma4:e2b via Ollama
hosted demo → Gemma 4 via Google's Gemini API
Stack: FastAPI, Python agents (requests, BeautifulSoup, pypdf, pypdfium2), Gemma 4, React 18 + Vite, jsPDF, Vercel, GitHub Actions CI.
Gemma 4 does the thinking, rules do the math
Gemma 4 has two jobs:
-
Planner. It reads what the fixed checkers found and decides which research agents to launch next. If a recruiter's name turns up, a recruiter agent goes after it. If a strange domain turns up, a domain agent does. The planner can only pick from an allowlist: in
registry.py, "Unknown types from the planner are never executed." - Multimodal reader. Scam offers rarely arrive as tidy PDFs. They arrive as WhatsApp screenshots, so Gemma 4 vision reads either and pulls out claims to compare against everything else.
Locally, I run gemma4:e2b through Ollama on my own laptop, and it was fast. The hosted demo can't reach my laptop, so it falls back to Gemma 4 served through Google's Gemini API (gemma-4-26b-a4b-it). Same open-weight family. If no model is reachable at all, a heuristic planner takes over and the investigation still finishes.
The one rule I never let bend, written into CONTRACT.md under "Scoring boundary (non-negotiable)":
Gemma / OSINT never invent indicator points.
The docstring on gemma_client.py says it in four words: "Never scores." Gemma interprets evidence; scoring.py sums fixed points. Run the Suspicious sample with live lookups off and you get 90 every time, pinned by tests.
The pivot I didn't plan for
When I asked my agent to "run a real test case and check if the osint ai and hueristics hold" against real Reddit and LinkedIn data, reality pushed back:
"DuckDuckGo and Reddit's own API are blocked from this network. Switching OSINT to PullPush (Reddit archives) and Wikipedia, then running a live end-to-end case."
A few minutes later: "Live OSINT worked." That fix now lives in agents/workers/_fetch.py, and the reason for it is one search away (next section).
The Agent Sessions Behind It
I built TrustIntern in a single day, inside a 5.5-hour hackathon plan, by directing Cursor agents, subagents and parallel workers. I saved the sessions with DevRelay, and they're public on my DEV profile. Here's the build, told through five moments from them.
1. "It would not win"
I opened with a plan and a demand: "this plan must win me an hackathon , so plan the best , i need the best plan." The agent's answer changed the whole project:
"The current plan would finish. It would not win. The score a judge can recompute, with the exact sentence beside each point, is the part other teams will not have."
That's the reason scoring.py exists the way it does.
2. A locked plan, then workers in parallel
I split the work ("build just the backend , another member builds the frontend"), and the agent turned the split into a contract: "Backend owns the number." Then I locked it: "lock in with the final plan , I will do a final review and lets get building." Each worker agent got the plan file with one instruction in capitals: "Do NOT edit the plan file." The Gemma worker also had to keep "locked sample scores (suspicious=90 with live_lookups=false)."
You
Implement the full Gemma multimodal multi-agent OSINT + SSE plan. Do NOT edit the plan file.
Plan (source of truth): [home].cursor\plans\gemma_multi-agent_osint_fbf50735.plan.md
Workspace: [home]\Documents\CODEWORKS\trustintern
Existing backend already works under backend/ (FastAPI, analyze.py, scoring.py, CONTRACT.md). Extend it — do not break the sync POST /api/investigations or locked sample scores (suspicious=90 with live_lookups=false).
Complete ALL todos; update them via TodoWrite merge=true as you go:
1. agent-schema — AgentSpec/AgentEvent, registry, SSE bus
2. orchestrator — fixed checkers first, planner spawn loop, max 8 agents, depth 2, ~45s wall clock
3. gemma-planner — google-genai, GEMINI_API_KEY, GEMMA_MODEL=gemma-4-26b-a4b-it, heuristic fallback, multimodal PDF page path
4. osint-workers — company, recruiter, domain, document_mm, search, followup with safe fetch
5. sse-api — POST /api/investigations/runs + GET .../events SSE; persist report + agent_trace; update CONTRACT.md
6. tests — heuristic spawn, unknown agent_type rejection, SSE sequence, suspicious still 90
Critical rules from the plan:
- Gemma NEVER assigns score points — only scoring.py does
- Unknown agent_type from planner → error event, never execute
- Registry allowlist only
- Keep sync investigations endpoint for samples
You may split internal workstreams if helpful, but deliver one integrated backend.
Install new deps, run pytest from backend/, fix until green.
Return: files created/changed, pytest summary, how to start a run + SSE, env vars needed, confirmation suspicious score still 90.
Agent
I'll implement the full Gemma multi-agent OSINT + SSE plan against the existing backend. Starting by reading the plan and surveying the current code.
I have the plan and existing backend. Implementing the agent stack next: schemas, bus, registry, then workers and orchestrator.
Writing the core agent modules and workers.
Fixing the bad import in followup.py, then adding the Gemma client, planner, and orchestrator.
Updating the API, contract, and dependencies next.
Fixing a race where SSE can subscribe before the run bus exists, then installing deps and running tests.
All 28 tests passed. Cleaning up a few rough edges, then wrapping up.
Gemma multi-agent OSINT + SSE is integrated on the existing FastAPI backend. All todos complete; 28 pytest passed; suspicious sample score still 90.
Files created
-
backend/app/agents/—schema.py,bus.py,registry.py,planner.py,gemma_client.py,orchestrator.py -
backend/app/agents/workers/—fixed.py,company.py,recruiter.py,domain.py,document_mm.py,search.py,followup.py,_fetch.py -
backend/tests/test_orchestrator.py,test_planner_heuristic.py
Files changed
-
backend/app/main.py—POST /api/investigations/runs,GET .../events(SSE),GET .../runs/{id} -
backend/CONTRACT.md— new endpoints, events, scoring boundary, env vars -
backend/requirements.txt—google-genai,pypdfium2
Pytest
28 passed, 1 warning in 1.28s
Suspicious multi-agent heuristic run + sync endpoint both assert score == 90.
Env vars
| Var | Required | Default |
|---|---|---|
GEMINI_API_KEY |
for Gemma path | — |
GEMMA_MODEL |
no | gemma-4-26b-a4b-it |
FORCE_HEURISTIC_PLANNER |
no | off; 1/true forces heuristic |
Without a key, planner emits planner_mode: heuristic and still runs fixed checkers + OSINT.
Start a run + SSE
cd backend
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
curl -s -X POST http://[REDACTED]:8000/api/investigations/runs \
-F company_name="Aurora Tech Pvt Ltd" \
-F recruiter_name="Riya Shah" \
-F recruiter_email="[REDACTED]" \
-F website_url="https://quickplacement-jobs.example" \
-F message="Congratulations. Pay a registration fee of Rs 2000..." \
-F live_lookups=false \
-F force_heuristic=true
curl -N http://[REDACTED]:8000/api/investigations/runs/RUN_ID/events
Guarantees held
- Gemma never assigns score points — only
scoring.py - Unknown
agent_type→error/unknown_agent_type, never executed - Registry allowlist only; max 8 agents, depth 2, ~45s wall clock
- Sync
POST /api/investigationskept for samples
SSE + agent streaming patterns informed by How to Build a LangGraph Agent with FastAPI and SSE Streaming (saladadedados) and Streaming LLM Responses End to End: SSE… (kuldeep_paul). This build fits Hacktoberfest Week 2 — Best Use of Gemma.
Tools used: UpdateCurrentStep, Read, GetDynamicTools, Glob, CallDynamicTool, Shell, Write, StrReplace
You
Briefly inform the user about the task result and perform any follow-up actions (if needed). If there's no follow-ups needed, don't explicitly say that.
Agent
Deps installed and pytest finished cleanly: 28 passed (about 1.3s). The Gemma multi-agent OSINT + SSE backend is green, including the locked suspicious sample score of 90.
3. Choosing the brain
I asked for "a multi agent orchestrator that does real time osint research … dont use api alone." The agent made me choose. I picked an "LLM planner (Gemma or similar) … with heuristic spawn as fallback when the model is down," and replied, with my usual eloquence: "use gemma for the multi modal orchestration :0 btw sse it is ( its cool )."
4. "Is there really multimodal AI?"
Later I got suspicious of my own project: "is there really multimodal ai , and is it using an open source or gemma ai ? because both the conditions are very much required for this to ever win." The agent checked honestly. Gemma was wired up, but it only ever saw a PDF page. So it added screenshot upload into the same Gemma 4 vision call, and noted: "Points still come only from scoring.py." Commit eed6eba: "Judges need a live open-weight Gemma call on an uploaded image, and the desk only sent PDFs."
5. Gemma on my own machine, secrets kept out
"i need to use gemma 4 locally and run this project" got me Ollama and gemma4:e2b:
The deploy prompt opened with guardrails: "Do not print secrets. Do not commit or push git. Do not read or upload backend/.env."
You
Finish deploying TrustIntern to Vercel. Do not print secrets. Do not commit or push git. Do not read or upload backend/.env.
Account: charancodes-dev. Project id: prj_6tDB2c0CN5qcWgfV4algtAko0BYd. Project name: trustintern. Custom domain trustintern.charancodes.com is already added and verified. GEMINI_API_KEY (sensitive) and GEMMA_MODEL=gemma-4-26b-a4b-it are already set for production and preview. Do not change those env vars. Do not pass teamId or slug on Vercel MCP calls; passing teamId returns 403 even though calls without it succeed on this account.
Source root: [home]\Documents\CODEWORKS\trustintern
- Use plugin-vercel-vercel MCP. Call GetDynamicTools for upload_file and create_deployment before using them if you do not already have the schema.
- Re-hash files from disk immediately before upload (the tree may have changed). Include every file needed to build and run, using forward-slash paths:
- vercel.json, requirements.txt, api/index.py, .vercelignore, README.md
- backend/ except backend/.env, backend/data, pycache, .venv, *.pyc
- frontend/ except frontend/node_modules and frontend/.env.development Skip .git, .entire, .cursor, .agents is optional (include if present).
- Upload each file with upload_file: contentLength = raw byte size, xVercelDigest = sha1 hex of raw bytes, requestBody = base64 of raw bytes. Uploads of the same digest are idempotent. These three are already uploaded and can be skipped if unchanged: api/index.py sha b6ed866bc8a830ab5d61dce6ce2e5da7fded019a, requirements.txt sha a3dd95ae29fefcd5ffd90929c41cb708967c6039, vercel.json sha f5a7ef7f887d3f9952bea4fd7e8879be9718b4da.
- create_deployment requestBody:
- name: trustintern
- project: prj_6tDB2c0CN5qcWgfV4algtAko0BYd
- target: production
- files: array of {file, sha, size} for every uploaded file (references, not inline data)
- projectSettings: framework vite, installCommand "npm install --prefix frontend", buildCommand "npm run build --prefix frontend", outputDirectory "frontend/dist" Do not include gitSource.
- Poll the deployment until it is READY or ERROR. Use get_deployment. If the build fails, read the build logs, fix only a hosting issue (missing file, python import, vercel.json), re-upload changed files, and deploy again. Do not weaken scoring.
- After READY, fetch https://trustintern.charancodes.com/api/health and the site root. Report the deployment URL, whether the custom domain responds, and the health JSON (no secrets). If the custom domain does not resolve, report the exact DNS record Vercel expects without inventing a generic CNAME.
Return a short result: deployment url, ready or error, custom domain status, health body.
</div>
<button class="agent-session-expand-btn" type="button">Show more</button>
</div>
</div>
</div>
<div class="agent-session-message agent-session-assistant">
<div class="agent-session-role-badge agent-session-role-assistant">
Agent
</div>
<div class="agent-session-content">
<div class="agent-session-text">
<p>I'll finish the TrustIntern production deploy on Vercel: hash the source tree, upload anything that changed, then create and watch the deployment. Secrets stay out of the output, and I won't touch git or <code>backend/.env</code>.</p>
</div>
<div class="agent-session-text">
<p>Schemas are in. Next I’ll hash the deploy tree from disk and skip the three files whose digests are already on Vercel.</p>
</div>
<div class="agent-session-text">
<p>The first hash missed nested paths because of Windows separators. I’ll rehash with forward-slash paths so backend and frontend are included.</p>
</div>
<div class="agent-session-text">
<p><code>frontend/.env</code> points the client at localhost, so I’ll leave it out of the production upload. The app already uses the same origin when that variable is unset. Next I’ll upload the remaining files.</p>
</div>
<div class="agent-session-text">
<p>One frontend file changed after the first hash. I’ll hash the tree again and upload those exact bytes.</p>
</div>
<div class="agent-session-text">
<p>The first ten payloads are hashed. I’m uploading those now, then I’ll continue through the rest of the tree.</p>
</div>
<div class="agent-session-text">
<p>Those smaller modules are on Vercel. I’m uploading the next batch of backend and frontend source.</p>
</div>
<div class="agent-session-text">
<p>Tools used: UpdateCurrentStep, GetDynamicTools, Read, Shell, Glob, Grep, CallDynamicTool</p>
</div>
</div>
</div>
<div class="agent-session-message agent-session-user">
<div class="agent-session-role-badge agent-session-role-user">
You
</div>
<div class="agent-session-content">
<div data-collapsible>
<div class="agent-session-text agent-session-text-collapse">
<p><available_subagent_types><br>
Available subagent_types and a quick description of what they do:
- generalPurpose: General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. Use when searching for a keyword or file and not confident you'll find the match quickly.
- explore: Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/*/.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.
- cursor-guide: Read Cursor product documentation to answer questions about how Cursor Desktop, IDE, CLI, Cloud Agents, Bugbot, and other features work. Use when the user asks 'In Cursor, how do I...?' or similar questions about Cursor products.
- ci-investigator: Investigate a single failing PR CI check and return a short root-cause summary. Use when the user asks to summarize, explain, diagnose, or investigate a specific failed check from a pull request.
- bugbot: Use only when the user explicitly asks for a Bugbot-like review of local code changes. When launching this subagent, set the Task description to exactly "Bugbot". Launch exactly one Bugbot subagent with
run_in_background: falseunless the user explicitly asks to run in background. Use this fixed prompt form: "Full Repository Path: ...\nDiff: <one of: \"branch changes\", \"uncommitted changes\", \"natural language\">\nChange Description: ...\nCustom Instructions: ..."; default toDiff: branch changes; includeChange Descriptiononly whenDiffisnatural language, formatting it as one block per changed file (a<path> (added|modified|deleted|renamed)header followed by bullets of what changed, mentioning line numbers or ranges inline where helpful), and only usenatural languageas a last resort after a regular diff-based review failed because the diff could not be computed; includeCustom Instructionsonly when the user gave specific review instructions. This subagent is single-shot and does not supportresume; always launch a fresh subagent instead. - security-review: Use only when the user explicitly asks for a security review of local code changes. When launching this subagent, set the Task description to exactly "Security Review". Launch exactly one security-review subagent with
run_in_background: falseunless the user explicitly asks to run in background. Use this fixed prompt form: "Full Repository Path: ...\nDiff: <one of: \"branch changes\", \"uncommitted changes\">\nCustom Instructions: ..."; default toDiff: branch changes; includeCustom Instructionsonly when the user gave specific review instructions. This subagent is single-shot and does not supportresume; always launch a fresh subagent instead. - best-of-n-runner: Run a task in an isolated git worktree. Each best-of-n-runner gets its own branch and working directory. Use for best-of-N parallel attempts or isolated experiments.
- ai-architect: Specializes in architecting AI-powered applications on Vercel — choosing between AI SDK patterns, configuring providers, building agents, setting up durable workflows, and integrating MCP servers. Use when designing AI features, building chatbots, or creating agentic applications.
- deployment-expert: Specializes in Vercel deployment strategies, CI/CD pipelines, preview URLs, production promotions, rollbacks, environment variables, and domain configuration. Use when troubleshooting deployments, setting up CI/CD, or optimizing the deploy pipeline.
-
performance-optimizer: Specializes in optimizing Vercel application performance — Core Web Vitals, rendering strategies, caching, image optimization, font loading, edge computing
…[truncated]
</div> <button class="agent-session-expand-btn" type="button">Show more</button> </div> </div>
You
Finish deploying TrustIntern to Vercel. Do not print secrets. Do not commit or push git. Do not read or upload backend/.env.
Account: charancodes-dev. Project id: prj_6tDB2c0CN5qcWgfV4algtAko0BYd. Project name: trustintern. Custom domain trustintern.charancodes.com is already added and verified. GEMINI_API_KEY (sensitive) and GEMMA_MODEL=gemma-4-26b-a4b-it are already set for production and preview. Do not change those env vars. Do not pass teamId or slug on Vercel MCP calls; passing teamId returns 403 even though calls without it succeed on this account.
Source root: [home]\Documents\CODEWORKS\trustintern
- Use plugin-vercel-vercel MCP. Call GetDynamicTools for upload_file and create_deployment before using them if you do not already have the schema.
- Re-hash files from disk immediately before upload (the tree may have changed). Include every file needed to build and run, using forward-slash paths:
- vercel.json, requirements.txt, api/index.py, .vercelignore, README.md
- backend/ except backend/.env, backend/data, pycache, .venv, *.pyc
- frontend/ except frontend/node_modules and frontend/.env.development Skip .git, .entire, .cursor, .agents is optional (include if present).
- Upload each file with upload_file: contentLength = raw byte size, xVercelDigest = sha1 hex of raw bytes, requestBody = base64 of raw bytes. Uploads of the same digest are idempotent. These three are already uploaded and can be skipped if unchanged: api/index.py sha b6ed866bc8a830ab5d61dce6ce2e5da7fded019a, requirements.txt sha a3dd95ae29fefcd5ffd90929c41cb708967c6039, vercel.json sha f5a7ef7f887d3f9952bea4fd7e8879be9718b4da.
- create_deployment requestBody:
- name: trustintern
- project: prj_6tDB2c0CN5qcWgfV4algtAko0BYd
- target: production
- files: array of {file, sha, size} for every uploaded file (references, not inline data)
- projectSettings: framework vite, installCommand "npm install --prefix frontend", buildCommand "npm run build --prefix frontend", outputDirectory "frontend/dist" Do not include gitSource.
- Poll the deployment until it is READY or ERROR. Use get_deployment. If the build fails, read the build logs, fix only a hosting issue (missing file, python import, vercel.json), re-upload changed files, and deploy again. Do not weaken scoring.
- After READY, fetch https://trustintern.charancodes.com/api/health and the site root. Report the deployment URL, whether the custom domain responds, and the health JSON (no secrets). If the custom domain does not resolve, report the exact DNS record Vercel expects without inventing a generic CNAME.
Return a short result: deployment url, ready or error, custom domain status, health body.
</div>
<button class="agent-session-expand-btn" type="button">Show more</button>
</div>
</div>
</div>
<div class="agent-session-message agent-session-assistant">
<div class="agent-session-role-badge agent-session-role-assistant">
Agent
</div>
<div class="agent-session-content">
<div class="agent-session-text">
<p>The last upload didn’t send the file bytes. I’ll upload the fetch helper on its own.</p>
</div>
<div class="agent-session-text">
<p>The application entry upload timed out. The same digest is safe to send again, so I’ll retry it.</p>
</div>
<div class="agent-session-text">
<p>The FastAPI entry changed on disk, so I’m uploading the current copy.</p>
</div>
<div class="agent-session-text">
<p>Tools used: Read, CallDynamicTool, UpdateCurrentStep, GetDynamicTools, Shell, Glob, Grep, Write</p>
</div>
</div>
</div>
How Entire fits in
Entire is set up in the repo to tie agent work to git:
-
Commit linking is set to
always. 13 commits carry anEntire-Checkpoint:trailer, and the checkpoints live as git refs (refs/entire/checkpoints/*). -
Six trail runners in
.entire/runners/: Risk, Security, Drift, Confidence, Review and Summary. I pasted Entire's gates screen into the chat, typed "set up runners ig", and the agent noticed: "The PR had no CI checks — I'll add GitHub Actions for build, lint, and test so Entire's gates can actually run." - Capture hooks for Cursor, Codex and Antigravity, an
entire-searchskill so agents search history instead of grepping it, anAGENTS.md, and 9 DevRelay skills (sessions, publishing, challenges, community wisdom and more).
My PRs had a second reviewer: Entire runners
Every push to a PR branch ran the six runners and posted their scores to that PR's trail:
| Trail | Confidence | Risk | Drift | Security |
|---|---|---|---|---|
| #3: React investigation desk | 58% (↑ from 48%) | 15% | 28% | 14% (↓ from 18%) |
| #4: Gemma vision reads offer screenshots | 62% | 40% | 32% | 14% |
The movement on trail #3 is the part I like: as I kept iterating, confidence rose from 48% to 58% and security risk fell from 18% to 14%. On trail #4, the risk runner was blunt about the weak spot: it "falls back to phrase rules when no Gemini key is set, but the new worker branches are untested." The security runner signed off on the "5 MB limit and a MIME allowlist... nothing in the diff looks malicious or leaks secrets."
The best part is that the reviewer caught a real bug in my Gemma integration. A medium finding on backend/app/agents/planner.py:141:
"The Gemma planner summary only sets
had_pdfand never reports that an image was uploaded. … when Gemma is the planner it sees onlyhad_pdf=falsefor an image-only upload. It can then decide not to spawn the vision agent, and the screenshot is never read. Addhad_imageto this summary and mention it in the planner prompt."
So an AI reviewer caught a blind spot in code an AI author wrote, in the exact feature I built to win the Gemma category. That's humbling. I merged with my eyes open (trail approved, review passed, CI 3/3), and had_image is at the top of my fix list, along with a low finding that the sync endpoint quietly ignores images and one on trail #3 about sanitizing source_url links against javascript: URLs. It isn't fixed yet. But I know exactly where it is and why, and that's the point of keeping receipts.
Using entire search to explain why code exists. When I forget why something is the way it is, I search the agent history rather than the code:
$ entire search --json --compact fallback
→ 1821b7f Add spawn planner with Gemma path and heuristic fallback.
$ entire search --json --compact vision
→ eed6eba Let Gemma vision read an offer screenshot as well as a PDF.
→ 5c8fe31 Switch Gemma to local Ollama and ship PDF reports on Vercel.
$ entire search --json --compact contract
→ session DevRelay and Multi-Agent OSINT Backend (CONTRACT.md, scoring.py …)
→ d746742 Document multi-agent SSE API and scoring boundary in CONTRACT.
Lessons learned (with some humility)
My capture hooks were Windows-only cmd.exe one-liners. They fired for the early setup session and then went quiet, so the 13 checkpoints came out thin: the commits link correctly, but the transcripts inside hold only the setup prompt. At one point I literally asked an agent, "my entire dashboard registered only one checkpoint … could you make them appear ?", and it told me straight: "You can't retroactively re-register those thin ones." Fair.
So the DevRelay sessions are the real record of how TrustIntern was built, and Entire supplies the bones: commit links, runners, search. Next time: check the hooks in minute one.
Why Does Open Innovation Matter?
- Privacy. Offer letters carry names, addresses and sometimes ID numbers. With Gemma 4 running locally through Ollama, those documents never leave my laptop.
- Cost. The free tier for students only works if checking an offer costs close to nothing. An open-weight model on my own hardware makes that possible.
-
Swappable models. The planner speaks to "a Gemma", not to one vendor's endpoint: local
gemma4:e2b, Gemma 4 via the Gemini API, or no model at all with the heuristic fallback. -
Auditable scoring. The most important part isn't AI at all. It's open, deterministic rules anyone can read in
scoring.pyand recompute. In a tool that says "this offer looks risky", being inspectable matters more than being clever.
First User
I gave an early version to an early tester. Their feedback, lightly cleaned up:
"This would stop internship scams, at least the basic ones, from affecting unsuspecting users. And it could become a postmortem report for the wiser."
There are no paying customers yet; this was a free beta. But the postmortem idea went straight into the pricing plan above: people who already got burned are the ones most motivated to want an evidence-backed report.
What's Next
- Postmortem report mode: a timeline of what happened, formatted for a complaint.
- A WhatsApp or Telegram forwarder, so students can check an offer where it actually arrives.
- Bulk verification for placement cells.
- Fixing what the runners found (
had_imagefirst), and my Entire hooks so the next build's checkpoints carry full transcripts.
Prize Categories
-
Best Use of Gemma: Gemma 4 plans the investigation and reads offer PDFs and screenshots (local
gemma4:e2bvia Ollama; Gemma 4 via the Gemini API in the hosted demo), and it is deliberately kept out of the scoring. -
Best Use of Entire: agent sessions shared above, 13 linked checkpoints, six trail runners gating PRs, and
entire searchused to explain why code exists.
Built by @noahosmont.
來源:DEV Community · dev.to







