Inspiration
Before RAVENCRY had a database, it had a bird.
We watched the raven: alert, social, watchful. A raven does not make a warning for itself. Its call makes the flock look again. A missing-person report works the same way, it is a call that asks a community to look.
In Nigeria, those calls now often arrive as photos. A bystander captures a motorcycle passing a checkpoint. A shopkeeper photographs riders outside their shop. Someone screenshots a WhatsApp image of two people on an okada. Each photo is a call that asks someone to look closer.
But looking takes time, and time is the one thing a kidnapping response does not have. So we asked a narrow question for this hackathon: can a small model, running entirely offline on an ordinary laptop, look at that photo first, and write down only what it is sure of?
The raven taught us the constraint. It calls what it sees. It does not invent. Our agent had to earn its name the same way.
What it does
RAVENCRY Intake turns a single photo into one structured JSON record:
- People — how many, rider or passenger, apparent presentation, clothing, distinguishing features (a blue helmet, a headwrap, a patterned dress).
- Vehicle — type, color, make, distinguishing marks (a yellow jerrycan strapped to the frame).
- License plate — transcribed only when every character can be read with high confidence. Otherwise the record says
unreadableorabsent, and the text field stays empty. Always.
It runs through llama.cpp on CPU, fully offline, we proved this by running inference inside a Linux network namespace where every outbound connection fails. The agent doesn't notice. Nothing about the job needs the internet.
Two examples from our validated test set:
- A single rider on a gold scooter comes back with
blue helmet,white shirt, plate honestly marked absent. - A sharp close-up of a real Nigerian plate (
TSF-565QA) — legible to any human — comes backunreadable. Not because the photo is bad, but because our gate demands certainty before it writes characters into a case file. We'll explain why below.
How we built it

We benchmarked five vision-language models on identical photos and prompts before choosing one: Gemma 3 4B-it (Q4_K_M base + F16 vision projector) won on extraction quality and honesty, and fit the memory budget at 4.06 GB peak. Qwen2.5-VL and SmolVLM2 exceeded usable context during image encoding; InternVL2.5 ran but extracted less; Gemma 4 E2B spent its token budget thinking out loud.
The system prompt went through two measured iterations: classify what's visible, extract per schema, then a hard confidence gate on plates. Every rule in it exists because a validation run caught its absence — including a run where the model copied the literal word "string" into a clothing field, and another where it wrapped JSON in markdown fences.
We shipped a checksum-verified download script and did the thing teams skip: cloned our own repo fresh onto a clean machine and ran it. That test caught a Windows line-ending bug that would have crashed the judges' very first command. We also ran the official ADTC profiler end-to-end — Gate 1 passed, and the numbers in this page come from its JSON output, not from vibes.
Challenges we ran into
The near-miss problem. This was the finding that reshaped the project. On that sharp plate close-up, models that did attempt a transcription got it almost right: InternVL2.5 and Gemma 4 E2B both read TSF-5650A instead of TSF-565QA, one character off. We verified this by eye against the full-resolution photo. A near-miss plate in a case file is operationally identical to a fabricated one: it sends searchers to the wrong vehicle with full confidence. Refusal, by contrast, is recoverable — a human looks at the photo.
Small models have wildly different image appetites. The same photo cost InternVL ~15 image tokens-chunks and SmolVLM2 179. Our first smoke-test context size worked for one model and crashed two others mid-encode. We learned to budget context per model, not per project.
Honesty sometimes looks like weakness. On cluttered scenes, the model occasionally over-claimed legible on tiny background-car plates — zoom-only reads like 187A57 rendered as 187-457. Five out of six characters right is still wrong. Rather than hide it, we documented these verified near-misses in REPORT.md and made the mitigation explicit: post-hoc validation plus mandatory human review of any legible claim.
Accomplishments that we're proud of
- Zero fabricated motorcycle plates, across every evaluation run, across three model families. The gate never once invented characters.
- Human-verified honesty findings in the report — including the ones that don't flatter us.
- 4.06 GB peak RSS against a 7 GB budget, measured by the official profiler, with no thermal throttling.
- A clean-clone reproduction path: fresh clone → one script → checksummed weights → working agent, proven end-to-end.
- Test prompts where every expected value came from a real observed model run, not from hope.
What we learned
Near-miss OCR is more dangerous than refusal. This is the sentence the whole project hangs on. A missing plate can be added later by a human; a wrong plate sends a search team to the wrong address with confidence.
Safety needs a mechanism, not a disclaimer. "Please be careful" is not a design. A schema field that forces an explicit choice, legible, unreadable, or absent, turns honesty into something testable.
Benchmark five models instead of arguing about one. Every model choice we made was settled by a table, not a debate.
Test the judge's first command first. The clean-clone test found the crash; nothing else did.
What's next for Ravencry (ADTC)
- Ship the post-hoc validator as a real component: schema normalization, plate-format checks, and a confidence re-check before any
legibleclaim enters a case file. - Add a human-review workflow so
legibleclaims from cluttered scenes get a second pair of eyes by default. - Evaluate dedicated OCR models now hosted in llama.cpp as a plate sub-pipeline — a RAM question, now that we know the honesty floor holds.
- Re-run the five-model comparison on target-class hardware (4 vCPU), where throughput has room to move.
- Fold this intake step into RAVENCRY's WhatsApp/Telegram/USSD channels, keeping victim imagery on-device.
Built With
- adtc-profiler
- aws-ec2
- bash
- cpu-inference
- edge-ai
- evidence-extraction
- gemma-3
- gguf
- hugging-face
- json
- llama.cpp
- lm-eval-harness
- machine-learning
- multimodal
- offline-first
- public-safety
- python
- q4-k-m
- safety
- vision-language-model
Log in or sign up for Devpost to join the conversation.