The grant agent that tells a small nonprofit what not to apply for.
Every grant tool on the market sells more: more funders, more matches, more drafts. But a two-person nonprofit doesn't lose money on the grant it never found — it loses money on the grant it did find and spent 40 hours losing. A single federal application takes 80–200 hours. Foundations fund 15–30% of applicants. That arithmetic is absorbed by someone who is also running programmes.
Winnow screens every federal opportunity against an organisation's real profile and real funder history, kills the overwhelming majority with cited evidence, and surfaces only the handful worth a human's week.
The output is not a list. It is a verdict with a paper trail.
This week we screened 312 opportunities. Three are worth your time. Here's why we killed the other 309 — and here's the receipt for each one.
Built for the AWS × Devpost Agents for Humans hackathon, Good Neighbor Agents track.
Day 1 of 22. Being straight about what runs, because the honesty is the product:
| ✅ Kill Sheet, ledger, evidence panel, GO memo, onboarding, sweep view | built and running |
✅ Domain contract (web/src/lib/types.ts) |
the exact JSON the agent emits |
| ✅ Grants.gov, ProPublica, USASpending clients | live, cached, every call returns a source_id |
| ✅ Gates 1–2 — eligibility and feasibility | deterministic, no model, screening live data |
✅ Strands Graph cascade with short-circuit |
deterministic nodes + AgentNodes in one traced run |
| ⬜ Gates 3–5 — funder history, winnability, verdict | stubbed; they pass through and say so |
| ⬜ Provenance validator | contract is expressed in types; nothing enforces it yet |
| ⬜ AgentCore Runtime, Memory, Observability | not started |
python agent/run_ledger.py 56-2466977 screens live opportunities today and prints a real ledger with zero model calls.
The web app still renders a fixed demo corpus for a fictional organisation — real agencies, ALNs and document shapes, invented filings. See web/src/lib/demo/. Wiring the live cascade into it is the next milestone.
- A false negative on run one. Applicant-type code
99is "Unrestricted";25is "Others — see the eligibility text". Assuming25meant unrestricted killed an opportunity open to everyone. Codes now come from the live facet, and25goes to WATCH because only prose can settle it. - Gate 4 will carry more load than planned. ~38% of a sample corpus defers eligibility to prose. The PRD budgets Gate 4 at 3–7%; the real figure is far higher, which changes the cost model.
- Gate 1 deliberately skips geography. Grants.gov exposes no structured eligible-state field, and scanning prose for state names false-kills on "projects benefiting communities in Louisiana are encouraged".
| Not this | Why we refuse it |
|---|---|
| A grant finder | The category's core failure. More matches = more damage. |
| A proposal writer | Crowded, and the direction funders are actively closing. |
| A CRM or pipeline tracker | Solved. Free tools exist. |
| A score out of 100 | Unauditable output is the exact thing nonprofits have been burned by. |
Verdicts are three-valued — GO / WATCH / KILL. No numeric fit score: 78 implies a precision we do not have.
There is no chatbox in this product. That absence is a design argument, not an omission.
Five gates, cheapest first. An opportunity that dies at Gate 1 never costs an LLM token — which is the only reason exhaustive screening is affordable.
| Gate | Method | Kills | Output |
|---|---|---|---|
| 1 · Hard eligibility | deterministic, no model | ~65–75% | INELIGIBLE + the field that failed |
| 2 · Deadline feasibility | deterministic, no model | ~5–10% | INFEASIBLE + the hours math |
| 3 · Funder behaviour | retrieval + model | ~10–15% | POOR_FIT + the actual prior-recipient list |
| 4 · Winnability | model | ~3–7% | LOW_WINNABILITY + quoted RFP requirement |
| 5 · Verdict composition | model, survivors only | — | a memo, every claim cited |
Gate 3 is the moat. It converts "you are eligible" into "this funder has never once funded an org under $2M, and here are the eleven it did fund."
Σ
estimated_prep_hoursfor opportunities that cleared Gates 1 and 2 — so a person could plausibly have started one — and were then killed on evidence at Gate 3 or 4.
Not "hours saved". We do not assume the user would have applied to all 312. Gate 1 kills are excluded, because nobody would have started them. These are the traps: eligible, arithmetically doable, and not winnable.
A hard requirement, not a quality goal.
- No unsourced factual claims. Every assertion about the org, the funder, or the RFP carries a
source_idresolving to a retrieved span. - A validator node runs after Gate 5. Two regeneration attempts, then the verdict downgrades to
WATCHwith an explicit insufficient-evidence note. - Never infer organisational facts. If the 990 doesn't say it and the user didn't enter it, the agent does not know it.
- Absence is reported as absence. "No prior awards found in USASpending for this ALN" is a valid, useful output.
- An AI-use disclosure ships with every GO memo — which tools ran, what they touched, what a human still must verify.
Known limitation, surfaced rather than buried: 990 filings lag 12–24 months. Excellent for funder behaviour patterns, useless for deadlines. Every 990-derived claim renders with its fiscal year, and anything past 18 months carries a staleness warning in the evidence panel. All timing comes from the live announcement.
Requires Node 20+.
cd web && npm install && npm run devThen open http://localhost:3000. Every screen state is linked from /states:
| Route | Screen |
|---|---|
/start |
EIN → profile confirm → three capacity questions |
/sweep |
the live cascade — ?speed=2, ?auto=1, ?degraded=1 |
/ |
Kill Sheet — 3 GO, 9 watching, 300 ruled out |
/zero-go |
the week with nothing worth applying to |
/case/op-epa-66604 |
full GO memo, every claim cited |
/interrupt |
deadline collapse — the only screen that arrives uninvited |
/settings · /states |
— |
Default: one email per week. A mid-week interruption is earned by exactly two events — a deadline about to fall below the user's stated capacity, or a WATCH promoted to GO on new evidence inside 21 days. One interrupt per week maximum, ever. A tool that interrupts twice has taught the user to ignore it.
Explicitly does not interrupt: new opportunities, kills of any volume, WATCH expiries, sweep completion, or anything the system finds interesting about itself.
Sweep Scheduler (weekly, AgentCore Runtime)
↓
Scout (Agent) — fan-out queries by NTEE / ALN / geography
↓ ~300 candidates
Graph: per-opportunity cascade, parallel
[G1 eligibility] ──KILL──▶ ledger
[G2 feasibility] ──KILL──▶ ledger
[G3 funder-history AgentNode] ──KILL──▶ ledger
[G4 winnability AgentNode] ──KILL──▶ ledger
[G5 verdict AgentNode]
[Provenance validator] ──FAIL──▶ regenerate ×2
↓
Kill Sheet → email + web
Graph from strands.multiagent, not Swarm and not a bare agent loop: the cascade is a deterministic DAG with conditional edges and short-circuit termination. Swarm's autonomous handoffs would burn tokens deciding what we already know. Graph also mixes deterministic Python nodes (Gates 1–2) with AgentNodes (Gates 3–5) in one traced execution.
| Source | Auth | Role |
|---|---|---|
Grants.gov search2 / fetchOpportunity |
none | opportunity corpus |
| ProPublica Nonprofit Explorer v2 | none | org profile + 990-PF funder history |
| USASpending.gov | none | federal award history |
Total marginal data cost: $0. A tool that costs a nonprofit nothing to run is part of the pitch.
Proposal drafting of any kind · post-award reporting and compliance · foundation corpus beyond 990-PF (no Candid, no scraping) · multi-user accounts, roles, billing · state and local grant sources · mobile app.
Deliberately absent from the UI: dashboard, analytics, charts, pipeline board, calendar, notification centre, onboarding tour, chat interface.
- Agent — Strands Agents SDK (
Graph), Amazon Bedrock AgentCore (Runtime, Memory, Observability) - Web — Next.js 16, React 19, Tailwind 4, Motion
- Type contract —
web/src/lib/types.tsis the boundary. The agent writes this JSON; the UI reads it; neither needs to know about the other.
None. The repo starts from create-next-app; everything else was written for this hackathon. The web interface was built on 23 August 2026, before the submission window's first working day — noted here for the disclosure requirement.
MIT — see LICENSE.