Canadian public-service question answering at https://gov.buildcanada.com. This is an independent Build Canada application, not a Government of Canada service.
- Cloudflare Worker serves a responsive EN/FR interface and a server-sent-event chat API. Answer text streams as it is generated; reasoning and JSON control fields are never displayed.
- Tavily Search uses a server-controlled
include_domainslist andinclude_domains_mode: "restrict"(up to 300 domains). Fast search is the default for all questions (one Tavily credit on a cache miss). Detailed, local and foreign requests retain up to eight source pages; only simple SIN and domestic passport navigation use four-page narrowing. A server-sideSEARCH_DEPTHoverride can select advanced or basic search. The model cannot add domains or invoke unrestricted browsing. - The curated registry includes every province/territory and 118 municipalities. Cloudflare IP geolocation selects nearby official sources; explicit locations in questions and validated manual selection override inference. Distance/population ranking and data limitations are documented in data-provenance.md.
- Native Workers AI and OpenRouter are interchangeable answer providers.
MODEL_PROVIDER/MODELinwrangler.jsoncselect the public default. Eval requests can compare providers through the protected endpoint. - The initial public model is Gemini 3.8 Flash through OpenRouter. Native Cloudflare GLM-5.3 Flash and DeepSeek V4 Flash were cheaper but made consequential source/jurisdiction mistakes in the synthetic evaluation. See eval/model-comparison.md for results, prices and methodological limits.
- Source IDs, bilingual agency metadata, canonical page deduplication and cited/consulted status are generated by application code.
canada.capaths identify departments independently of shared domains.
Matches the durations disclosed by America.gov's September 29, 2026 privacy policy:
| Data | Cloudflare KV retention |
|---|---|
| Search results and source excerpts | 2 hours (7,200 seconds) |
| Completed answers and source metadata | 2 hours (7,200 seconds) |
| Suggested follow-up questions | 30 days (2,592,000 seconds) |
| Explicitly submitted feedback (answer and sources included) | 1 year |
SHA-256 cache keys include relevant query/history, language, selected domains, jurisdiction context and registry/prompt/model versions. Answers also include the current date. Search caches are shared across answer-model comparisons; answer caches are model-specific. An embedded expiry is checked in addition to KV's TTL. Cache keys are hashed; cached answers/search text themselves are not encrypted by hashing the key.
Structured personal identifiers (emails, phone numbers, SIN-like numbers and long account-number strings) receive best-effort masking before retrieval/model calls. Detected identifiers bypass shared answer/search/follow-up caching. This is not complete personal-information detection. Users should not supply sensitive identifiers or personal documents. Conversation history lives in browser memory and is sent with followups; there is no account or saved chat history. No application request-body logging or analytics is enabled. Cloudflare security/infrastructure and provider retention have separate policies; this app does not promise Canadian data residency or universal zero retention. OpenRouter requests require data_collection: "deny" and zdr: true; Workers AI follows Cloudflare's applicable service policy.
Returned source URLs are checked against exact approved hostnames or dot-boundary subdomains, including protocol/credential/port checks. HEAD redirect chains are inspected concurrently with a total one-second budget per chain; outside-registry destinations and known error pages are rejected. Some agencies block HEAD or automated access; indexed text may still be used when live verification is unavailable or times out, and urlChecked records that distinction. A successful URL check is not a factual accuracy guarantee. Query-relevant Tavily excerpts and bounded official page text form the source pack. Simple navigation prioritizes program pages and uses at most four sources; complex questions can use eight.
The answer model must support each consequential statement with assigned source IDs. Responses use a strict JSON schema; the prompt requires method-specific conditions and preserves the logical direction of eligibility rules. Code validates IDs and removes model-supplied URLs; this prevents invented clickable links but does not prove that every claim is supported. The eval suite separately reviews claim support against the actual source excerpts. Government websites and user-supplied text are treated as evidence, not instructions.
Nearby cities are candidate sources, not a determination of municipal boundaries, residence or eligibility. Unknown/non-Canadian locations receive national federal/provincial coverage without a guessed municipality. The registry is intentionally incomplete for small municipalities, regional bodies and Indigenous governments; local questions may require clarification and additional curated coverage.
The UI supports descriptive official links, bottom source cards and searchable agency dialogs, suggested prompts, followups, copy/feedback, stop/restart, location correction and optional browser speech recognition with a disclosure. Preparation, source discovery and answer deltas are real server events. Streamed text is a draft until the completed answer passes source-ID validation. Provider requests are cancelled when the browser disconnects; cancellation of work already completed by a provider is not guaranteed.
PDF.js is lazy loaded from this site and extracts text locally using a same-origin worker. File bytes are never uploaded. Limits are 10 MB, 25 pages and 24,000 extracted characters, with a visible truncation notice. The user can preview or remove an attachment before sending. Scanned/image-only, password-protected, corrupt or oversized PDFs receive actionable errors; OCR is not performed.
Only after Send does the extracted text go to the Cloudflare Worker and answer model as separate, untrusted document context. Document text is not added to Tavily search queries. Document requests and subsequent turns in that conversation bypass shared search, answer and follow-up caches, receive best-effort identifier masking, and retain no server-side document record. This bypass remains active after removing the attachment, until a new conversation starts, because the history may include document-derived facts. Provider policies still apply. The browser keeps the attached text in page memory for followups until it is removed or the conversation is restarted. The model may describe what the document says but must use official retrieved evidence to establish government requirements. PDF statements and embedded instructions are never treated as official rules.
npm ci
npm test
npm run check
npm run devUse Node.js 22 (nvm use). Copy .dev.vars.example to the ignored .dev.vars file and fill in your local credentials. Production secrets are Worker secrets:
TAVILY_API_KEYOPENROUTER_API_KEY(required for OpenRouter serving or comparisons; not needed for native Workers AI)EVAL_TOKEN(private synthetic eval endpoint authorization)
Never commit credentials. Existing local .secrets.json and .dev.vars are ignored and permission restricted. wrangler secret bulk .secrets.json uploads secrets without including them in configuration/static assets. The evaluator reads local secrets without printing them.
npm run deploy
node scripts/eval.mjs --base https://gov.buildcanada.com \
--provider cloudflare --model @cf/zai-org/glm-5.3-flash --judgeRead eval/README.md for case selection, provider comparisons, actual source-evidence judging and report interpretation. Rule-check success must not be reported as answer-quality success.
GET /api/context: coarse inferred context and validated location options. Optionalprovince/municipalityquery parameters select registry entries.POST /api/chat:{messages, language, location, document?}wheredocumentis{name, pages, text, truncated}extracted locally. SSE events:context,status,sources,answer.delta,answer,done,error.Accept: application/jsonreturns the completed result.POST /api/feedback: explicit rating, answer, source metadata and optional comment.GET /api/health: public current model and TTLs.POST /api/eval: private bearer-token endpoint; synthetic geographic fixtures, model/provider overrides, uncached generation and optional approved-domain source-injection fixtures. Returns the exact source excerpts used for judging. Public callers cannot override models, geography or sources.
Public POST endpoints reject cross-origin browser calls and are limited to 12 requests/minute per Cloudflare client IP. This is a beta abuse control, not a global spending cap.
- America.gov About: https://america.gov/about
- America.gov cache/privacy disclosure: https://america.gov/privacy-policy
- Tavily Search API: https://docs.tavily.com/documentation/api-reference/endpoint/search
- Cloudflare request geolocation: https://developers.cloudflare.com/workers/runtime-apis/request/
- Workers AI pricing: https://developers.cloudflare.com/workers-ai/platform/pricing/
Private document conversations and responses containing recognized identifiers have no feedback controls and bypass feedback storage as well as shared answer/search caches. Identifier masking is best effort, not comprehensive anonymization. Pure document summaries skip web search; verification questions still retrieve official pages.
Repository: https://github.com/BuildCanada/gov
GitHub Actions installs locked dependencies with npm ci, runs the tests, builds the browser assets, and validates the Worker using wrangler deploy --dry-run. These checks run on pull requests and pushes to main. Deployment follows successful checks on main; a manual run on main also deploys. Pull requests cannot run the deployment job. Actions are pinned to commit SHAs.
Configure the repository secret CLOUDFLARE_API_TOKEN with a dedicated Cloudflare Edit Cloudflare Workers API token restricted to the Build Canada account and relevant zone permissions for buildcanada.com. The account ID, Worker name, domain and existing KV binding are in wrangler.jsonc. A fork needs its own bindings and deployment configuration.
gh secret set CLOUDFLARE_API_TOKEN --repo BuildCanada/govRuntime secrets (TAVILY_API_KEY, OPENROUTER_API_KEY, EVAL_TOKEN) stay in Cloudflare Worker secrets and are preserved by deployments. They are not needed for CI tests/builds and must not be added to the repository. Production deployments are serialized, skip superseded commits, and check /api/health after publishing.