Quorum
Tell Quorum the issue. It generates your advocacy playbook.
A civic advocacy generator built in Jac for JacHacks SF 2026. Give it a civic action — a project, proposal, agenda item, or ballot measure — and Quorum generates what a community actually needs to be heard: a plain-language issue brief, a cited playbook, bilingual outreach drafts, and strategic advice on where that advocacy should actually be aimed.
Inspiration
When a government action harms a local community, the community usually loses — not from apathy, but from going down the wrong paths. Two real San Francisco cases show the two ways it happens.
Portsmouth Square, Chinatown. A ~$73M renovation included demolishing the historic park and bridge. Real opposition existed: the 176-year-old CCBA says it was never invited to project meetings; a five-decade board member opposed the removal; 150+ merchants protested. Yet the District 3 supervisor's office reported no communication in support of keeping the bridge. Outreach never reached the associations connected to the elderly residents who used the park daily. The community held a position; the decision-maker's record held nothing. Nobody generated the ask: submit this comment, in this format, to this person, before this date.
Prop K / Great Highway, Nov 2024. The measure to close the highway passed citywide at 54.7%, while the neighborhoods that used it voted heavily to keep it open — 60/40 in the Sunset, 70/30 in the Richmond. The Great Highway supporters weren't disorganized, they spent $269,000, filed a CEQA challenge, and ran an active campaign. But their own post-mortem was blunt: they never went east. Nobody told them the one thing that mattered: your outreach was reaching people who already agreed, while the persuadable audience was the citywide electorate.
Both sides had motivation, networks, even money. What they lacked was output and advice — a drafted, deadlined, correctly-addressed submission, and a strategic read on who actually needed persuading.
What it does
Point Quorum at a civic action. It generates:
- A plain-language issue brief — what's happening, who's affected, what's contested — pulled from local and in-language press, forum threads, and public records.
- A procedural playbook — who decides, who they answer to, where to submit, by what deadline. Every fact carries a source URL, or is flagged as an explicit gap: "No verified source found. Quorum will not guess."
- Three recourse paths —
recorded_comment,decision_zone_contact, andescalation. Each is source-backed; none is ever marked legally reviewed. - Targeting advice — Quorum compares the impact zone against the decision zone and flags when they diverge: "You are not the electorate for this decision — here's who is." The sentence the Prop K opposition paid $269,000 to learn too late.
- Precedent-based strategy — matched by how a community lost, not what got built. Boston's Parcel C garage fight informs Portsmouth Square because both involved an in-language community excluded from an official process.
- A bilingual outreach packet — per-organization English and Chinese drafts built around the playbook's real, deadlined ask.
- A campaign dry-run spec — an outreach and media plan, staged for human review, never sent automatically.
The target user is a volunteer at a family association who shares her community's position, knows the elders who share it, and has no idea a comment period closes in eleven days. Quorum hands her the finished draft and tells her where to send it.
How we built it
Generation grounded in a fact graph. A confident wrong deadline costs an organizer the one resource they can't recover — so Quorum splits the work. An LLM writes briefs, proposes evidence bindings, and drafts outreach copy. Every procedural fact underneath is retrieved, not generated, into a persistent Jac graph — with a source URL or an explicit gap.
The pipeline is one-way: structured APIs and scrapers feed a cache, which feeds extraction, which feeds one schema-validated file (fixture.json), which feeds the Jac graph, which walkers traverse to produce the playbook and outreach packet. Nothing downstream calls back upstream.
- Fetch: Socrata (DataSF 311) and Legistar go first since they're structured and free; Firecrawl handles pages with no API (agendas, press); Apify pulls Reddit for context only, never testimony. Everything is cache-first and budget-guarded, so the whole pipeline runs offline.
- Extract: four extractors turn raw fetches into records, and their real job is to refuse bad data — dead links get filtered, unclear dates become
"unknown"rather than guessed, private individuals get reduced to roles. - Contract:
fixture.jsonis the only interface downstream — seven collections, every record source-tagged and timestamped. An empty comment-channel field isn't missing data, it's a finding. - Graph: the fixture materializes into a Jac workspace — 15 node types, 13 edge types, across seven semantic layers. Fetched edges (
located_in,decided_by) are automatic; judgment edges (accountable_to,inside_process) are hand-curated and never model-set. Where a link can't be verified, the graph gets an explicitProcedureGapnode instead of a guess. - Walk: six walkers assemble the output with no HTTP or model calls inside the traversal.
DivergenceCheckis the core trick — it forks one traversal toward impact zones and another toward decision zones, and the mismatch is the advice; no single node stores it. - Validate:
RecommendRecoursechecks any LLM-proposed paths against a strict schema (three paths, three steps each, real source IDs) and discards the whole thing for a deterministic fallback if anything fails. It never returns fewer than three paths.
The honesty boundary is structural, not a prompt: EvidenceMatcher can only write potentially_relevant_to — no satisfies or proves edge type even exists — and legal_reviewed is hardcoded false. The generator can't claim a legal standard is met, structurally.
A Jac fullstack UI renders the output verdict-first, with citation chips and live/cached badges. Nothing is hardcoded, and nothing sends automatically.
Challenges we ran into
- Real procedure resists resolution. Rec & Park's and the D3 supervisor's comment channels never resolved — every candidate agenda page was dead by scrape time.
DecisionWalkerreports aProcedureGapinstead of guessing, and in the lead case, the gap proves the thesis. - 311 data lies by category. "Portsmouth Square" returns 994 cases, 609 of them routine maintenance. SF's 311 taxonomy has no category for project opposition, so we shipped an honest zero.
- Frozen upstream data. Legistar's API is stuck at ~Dec 2018; we built a portal scraper for current data and disclosed the split.
- Building substantially in Jac. Keeping graph and validation logic in a young language, while hitting a 40%+ Jac source-line share, meant compiling constantly and reading compiler output instead of assuming.
Accomplishments we're proud of
- Zero fabricated procedural facts: 3 verified comment channels, 9/9 organizations resolved or explicitly blanked, 22 sourced testimony records.
- Targeting advice that's computed, not scripted — it comes from a graph fork, not a field.
- Gaps reported as output, not hidden — the refusal is what makes the rest trustworthy.
- Offline-by-design: 42/42 tests pass with no network.
- New cases are YAML config, not code.
What we learned
- Communities don't need another awareness tool, they need the finished output: the drafted comment, the named recipient, the deadline, and one honest piece of strategic advice.
- Generated advice needs a retrieval floor. Structural constraints (no edge to write) beat prompt-level promises.
- Object-Spatial Programming fits when the advice lives in the topology — divergence is a comparison of traversal endpoints, not a field.
What's next for Quorum
- Wire remaining generators fully into the UI.
- Richer per-path advice: expected effort, historical success signals, what "enough" input looks like.
- DataSF live mode with cache fallback.
- A Chinese-language entry point and text zoom.
- More cases as verified manifests.
Built with
Jac/jaclang, Python, Socrata (DataSF 311), Legistar, Firecrawl, Apify, LLM (by llm()), pytest.
Built With
- firecrawl
- jac
- python

Log in or sign up for Devpost to join the conversation.