Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Winnow

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.


Status

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.

What the first live run taught us

  • A false negative on run one. Applicant-type code 99 is "Unrestricted"; 25 is "Others — see the eligibility text". Assuming 25 meant unrestricted killed an opportunity open to everyone. Codes now come from the live facet, and 25 goes 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".

What it is, and what it refuses to be

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.


The cascade

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."

Hours protected

Σ estimated_prep_hours for 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.


The provenance contract

A hard requirement, not a quality goal.

  1. No unsourced factual claims. Every assertion about the org, the funder, or the RFP carries a source_id resolving to a retrieved span.
  2. A validator node runs after Gate 5. Two regeneration attempts, then the verdict downgrades to WATCH with an explicit insufficient-evidence note.
  3. Never infer organisational facts. If the 990 doesn't say it and the user didn't enter it, the agent does not know it.
  4. Absence is reported as absence. "No prior awards found in USASpending for this ALN" is a valid, useful output.
  5. 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.


Running it

Requires Node 20+.

cd web && npm install && npm run dev

Then 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

Interruption policy

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.


Architecture

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.

Data sources

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.


Explicitly out of scope

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.


Stack

  • Agent — Strands Agents SDK (Graph), Amazon Bedrock AgentCore (Runtime, Memory, Observability)
  • Web — Next.js 16, React 19, Tailwind 4, Motion
  • Type contractweb/src/lib/types.ts is the boundary. The agent writes this JSON; the UI reads it; neither needs to know about the other.

Pre-existing code

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.

Licence

MIT — see LICENSE.

About

The grant agent that tells a small nonprofit what not to apply for. AWS x Devpost Agents for Humans - Good Neighbor track.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages