This is a submission for Weekend Challenge: Earth Day Edition
TL;DR β Snap a photo of an injured animal, the right licensed rehabber gets paged in under 60 seconds. Backboard remembers every accept, decline, and "at capacity" outcome, so the next case re-ranks before it's dispatched. Memory is the product; the ranking is a pure function that cannot compute without it.
What I Built
Last spring I found a stunned songbird on the sidewalk and spent forty minutes cold-calling vets that don't take wildlife. By the time I reached an actual rehabber, the bird was gone. That's the problem I wanted to solve in a weekend.
Most dispatch apps pick the closest rehabber. Terra Triage picks the one who will actually say yes, because Backboard remembers who said no last time.
Terra Triage is a three-agent web app for people who just found an injured animal and have no idea who to call. You snap a photo, approve a single consent prompt, and under 60 seconds later a licensed wildlife rehabilitator within range has an email in their inbox with the photo, the GPS, and a one-click "accept / decline / at capacity" magic link. No account, no app, no phone tree.
The interesting part is not the first dispatch. It's the second one. Every outcome a rehabber returns (accepted, declined, at capacity, unreachable) is written back as a signal into Backboard, and the very next case reranks because of it. If Rehabber A just declined a raptor at 9:42, the 9:51 raptor won't go to them first. The memory is the product.
Three agents, one narrow job each:
| Agent | Job | Model / Service |
|---|---|---|
| Finder | Vision triage: species, severity 1-5, safety advice | Groq Llama-4 Scout (vision), JSON mode |
| Dispatcher | Rank rehabbers, send the email, mint magic-link | Auth0 scoped agent token + Resend |
| Memory | Read and write rehabber signals that drive the ranking | Backboard (primary), Supabase mirror as fallback |
Demo
Live URL: https://terra-triage.vercel.app/
60 second walkthrough:
The flow:
- Open the website on a phone, snap a photo of an injured animal, and approve the location prompt.
- The Finder agent returns a triage card with species, severity, and first-aid advice.
- A ranked list of nearby rehabbers appears. Each card shows the Backboard-aware score, distance, capacity, and a one-tap Call button for the listed 555-01xx number.
- Tap Send referral on the top pick. Auth0 asks for the
referral:sendscope, you consent once, and the dispatcher fires. - The success pane shows "Referral sent" next to a scoped-token badge and a View captured email link.
- Open the captured email in
/demo/inbox/<id>. Everything a real rehabber would see is there: photo, GPS, triage summary, accept and decline buttons. - Click Decline, at capacity from inside that email. The magic-link records the outcome and redirects to a thank-you page.
- Switch to
/admin. The memory timeline shows the new signal landing in Backboard, and the same case re-ranks with that rehabber demoted.
No email leaves the server during this flow. Delivery is gated behind a demo switch for this submission; why, and what the real launch path looks like, are in the sections below.
Code
Terra Triage
Snap a photo of an injured wild animal and a multi-agent system identifies the species, triages the injury, and dispatches the referral to the rehabber most likely to say yes, in under 60 seconds.
What it does
Terra Triage collapses the chaotic gap between "I just found a hurt animal" and "a trained rehabber is on the way" into a single guided 60-second flow. It pairs a Groq-powered vision Finder agent, an Auth0-scoped Dispatcher agent, and a Backboard-backed Memory agent so that every referral outcome improves the next ranking. Most dispatch apps pick the closest rehabber. Terra Triage picks the one who will actually accept, because Backboard remembers who said no last time.
Nationwide coverage is seeded (250 licensed rehabbers, 5 per US state, fictional .example.org contacts using the NANPA 555-01xx block reserved for fiction) so the ranker has something to rank from day one. Everyβ¦
Project structure (trimmed):
src/
βββ app/
β βββ report/ # Anonymous intake (photo + geo)
β βββ case/[id]/ # Reporter-visible case page
β βββ rehabber/outcome/[token]/ # Magic-link outcome form
β βββ admin/cases/ # Ops console + memory timeline
β βββ api/
β βββ admin/seed-demo-case/ # Idempotent demo seeder
β βββ auth/[auth0]/ # Auth0 login / callback / profile
βββ lib/
β βββ agents/
β β βββ finder.ts # Groq vision call, JSON mode
β β βββ dispatcher.ts # Rank + Resend + magic-link
β β βββ rank-with-memory.ts # Fuses memory signals into the rank
β βββ memory/
β β βββ backboard.ts # Real Backboard API client
β β βββ index.ts # Backboard-primary, local fallback
β βββ auth/
β βββ agent-token.ts # Scoped agent token (PAR or M2M)
β βββ magic-link.ts # HMAC-signed, single-use tokens
How I Built It
Backboard as the protagonist
Most "memory" integrations I see treat the memory service as a prompt-context bucket: fetch recent history, stuff it into the system message, let the LLM figure it out. Terra Triage does the opposite. The ranker is a pure scoring function that cannot compute without memory first β no LLM in the hot path, no prose interpretation, just signals driving weights.
// src/lib/agents/rank-with-memory.ts
export async function rankRehabbersWithMemory(
input: CaseInput,
rehabbers: PublicRehabber[],
): Promise<RankedRehabber[]> {
const signals = await getMemory().query(rehabbers.map((r) => r.id));
return rankRehabbers(input, rehabbers, signals);
}
The scorer weights species match (0.35), distance (0.25), capacity (0.20), accept rate (0.15), and response time (0.05). Every weight except distance is sourced from Backboard. When a rehabber submits an outcome, applyOutcomeToSignals mutates the relevant keys (capacity, accept_rate, species_scope, response_ms) as a pure function and writes them back. The next ranking reflects it immediately.
The engineering lesson I did not expect. My first Backboard integration used semantic /memories/search once per rehabber, per case. That is correct-looking code and costs about $0.80 per triage at hackathon volumes.
Because all of our memory writes are structured and attributable to a rehabber id, the correct access pattern is a single paginated GET /memories and filter in application code. I rewrote it that way and the cost dropped roughly 800x (to fractions of a cent) with no change in ranking quality. Signals are encoded as TERRA_SIGNAL rehabber=<id> key=<k> value=<json> so the filter is trivial.
The final detail: FallbackMemory is a tiny proxy that prefers Backboard and mirrors every upsert to a local memory_entries table tagged source='backboard' | 'local_fallback'. If Backboard is down mid-demo, the app keeps working and the admin timeline shows a red chip so you can see the failover instead of it hiding behind a stack trace.
Auth0 for Agents: scoped consent for a destructive action
"Send referral" is the one button in this app that can annoy a real human being (emails a licensed rehabber). I treated it as an agent action that must be authorized, not a server-side formality.
// src/lib/auth/agent-token.ts (excerpt)
export async function getAgentToken(): Promise<AgentToken> {
const session = await getSession();
if (session?.tokenSet?.scope?.split(" ").includes("referral:send")) {
return { token: session.tokenSet.accessToken, mode: "user-consented", scope: "referral:send" };
}
return mintM2MToken({ audience: env.AUTH0_AGENT_AUDIENCE, scope: "referral:send" });
}
PAR is on when the tenant allows it (AUTH0_PAR=1), so the browser never sees the full authorization params, only a request_uri handle. The custom consent_context query parameter carries human-readable context ("email Marcus at Hudson Valley Raptors on your behalf") into the consent screen. If consent is unavailable, we fall back to a scoped machine-to-machine token rather than silently downgrading the action to a service call.
The UI surfaces which mode was used with an on-screen badge. The narrator can literally point at it on camera and say "scoped." That visibility is the Auth0 story for me: agents should explain themselves, not hide.
Rehabbers do not have accounts. Their outcome submission goes through an HMAC-signed, single-use, 72-hour magic link (src/lib/auth/magic-link.ts). Single-use is enforced with a conditional UPDATE ... WHERE outcome IS NULL, so concurrent submissions for the same token are atomic at the database layer.
The rest of the stack
-
Finder: Groq's
meta-llama/llama-4-scout-17b-16e-instructover the OpenAI-compatiblechat/completionsendpoint, withresponse_format: { type: "json_object" }. Sub-second vision triage. Prompt shape is inlined in the system message because Groq does not support strict JSON schemas. -
Supabase: Postgres, RLS, private
photosbucket with short-lived signed URLs. The Finder hashes the resized JPEG bytes and caches triage results, so demo retries are free. -
Resend: transactional email, gated behind a
DEMO_MODEflag for this submission (more on that below). - Next.js 16 (app router) + server actions, Tailwind + shadcn/ui, Leaflet for the rehabber map.
Seeding 250 rehabbers without spamming any of them
The list you see in the demo is 250 fictional licensed rehabilitators, five per US state, generated from a deterministic script (scripts/generate-rehabber-seed.ts). Every record uses real capital and largest-city coordinates so the distance math is honest, but every email ends in .example.org (reserved under RFC 2606, can never resolve) and every phone uses the NANPA 555-0100..555-0199 block reserved for fiction. Not one of those addresses can receive mail. That is deliberate.
Two switches control delivery in production:
-
DEMO_MODE=1shorts the dispatcher before Resend is ever called. The rendered email is written to asent_emails_logtable and surfaced at/demo/inbox/<referral_id>, a server-rendered viewer behind admin basic-auth. The success pane grows a View captured email link so judges can click straight from the app into the message that would have been sent. Zero outbound traffic, real referral row, real memory signal, real magic-link outcome loop. -
DEMO_REDIRECT_TO=you@example.comkeeps Resend in the loop but rewrites every recipient to a single verified inbox and prefixes the subject[DEMO -> original@address]. Useful for recording a live walkthrough where you want a real email to arrive on your phone.
Both paths delete the referral row if the send actually fails, so the case page never shows a phantom "awaiting response" card for a message that never left the server.
What I cut, and the real path to launch
The biggest thing I cut: real rehabber contacts.
There is no global registry of licensed wildlife rehabilitators. US coverage is fragmented state-by-state, sometimes county-by-county, and most other countries (mine included) have no centralized list at all.
The tempting fix is to scrape state-agency PDFs and let an LLM parse them into rows. I refused to ship that for three reasons: (1) scraping public directories into a third-party product violates most of those agencies' terms of use, (2) the data is stale the moment you capture it (licenses lapse, phones change), and (3) language models invent plausible-looking email addresses. Sending a real referral to a hallucinated inbox is worse than returning no results.
So the 250 rows in this build are honest placeholders that exercise the ranking math without lying to anyone. Production needs a different sourcing path, and I think there are only three real options:
- Partner with the Animal Help Now 501(c)(3). AHN already runs a consented, maintained database of thousands of rehabbers across the US. A partnership integration (their pipeline, our ranking and memory layer) is the only path that ships real coverage without recreating two decades of stewardship work. This is what I would pursue first, post-hackathon.
- A self-serve rehabber portal. Licensed rehabbers sign up, verify their license number against the relevant state registry, accept a Terra Triage ToS, and opt in to receive referrals. Growth is slow but consent is unambiguous and the data stays fresh because each rehabber owns their own row. This is the right fallback if #1 does not pan out.
- Per-state agency MoUs. Some state wildlife agencies distribute their rehabber lists under explicit terms. Where those terms permit a downstream dispatcher, you sign a memorandum and import. Slow, jurisdiction-by-jurisdiction, but legally clean where it applies.
What will not change is the consent requirement. Regardless of sourcing path, every rehabber in the live system needs a signed agreement covering referral delivery, PII handling, license verification, and a clear opt-out before they can be ranked. That is table stakes, not a feature.
The data model for all three paths already exists in this repo (rehabbers table with active flag, species_scope, license metadata). The discovery pipeline and ToS flow are the next weekend.
Prize Categories
Primary: Best Use of Backboard. Memory drives a computed decision, not an LLM prompt. Every rank reads signals first; every outcome writes them back; the admin timeline makes the loop visible on screen. A FallbackMemory proxy keeps the app alive if Backboard is unreachable and tags the origin so failover is auditable. The cost model went from $0.80 per triage to fractions of a cent after rewriting from per-rehabber semantic search to a single filtered list read.
Secondary: Best Use of Auth0 for Agents. The Dispatcher is a first-class OAuth client scoped to referral:send, with PAR when available and an M2M fallback, and the UI labels which mode was used. Rehabbers authenticate through HMAC-signed, single-use magic links with DB-level replay protection.
Built solo in a weekend with GitHub Copilot CLI as co-author with zero paid services.












![Defluffer - reduce token usage π by 45% using this one simple trick! [Earthday challenge]](https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiekbgepcutl4jse0sfs0.png)


