Situation-first, grounded, multilingual assistant for Vienna municipal processes.
A newcomer describes their situation in plain language — in English, German, Ukrainian, Arabic, or Bosnian/Croatian/Serbian — and Wegweis returns an ordered, verified checklist of what to do next: offices, documents, appointment links, freshness badges, and a direct NGO handoff when the case needs a human.
Most "chat with the city" tools answer with plausible prose. Newcomers don't need plausible — they need verified, in the right order, with source URLs they can forward to an employer or a school. Wegweis is built around that constraint:
- Two-stage, prose-free. Stage 1 is a small Anthropic Haiku call in forced tool-use mode that returns closed-vocabulary structured facets (topic, situation tags, language, ambiguity score) — never free text. Stage 2 is a deterministic router that maps those facets to a checklist assembled from author-controlled scenario metadata. Document names, phone numbers, and URLs are never synthesised. Hallucinated enum values are Zod-rejected and the route falls through to a deterministic retrieve baseline.
- Cached. A 6 h KV-backed prompt cache makes repeat queries return in tens of milliseconds. Only fully grounded answers are cached; clarifier cards, no-coverage fallbacks, and safety-blocked queries are never cached.
- Situation-first, not topic-first. Users describe their situation ("I just arrived from Ukraine with two children") rather than guessing the right form name. Sequences encode the real journeys; scenarios are their steps.
- Honest about coverage. When the system doesn't know, it says so and hands off to an NGO list — rather than confidently hallucinating an answer to a stressed newcomer. When the question is genuinely ambiguous, it surfaces a clarifier card rather than guessing.
┌────────────────┐ ┌────────────────────────────────────────┐
│ apps/web │ │ apps/api (Cloudflare Worker) │
user ──▶ │ Next.js │ ──▶ │ │
│ static export │ SSE │ safety regex → cache lookup │
│ │ ◀── │ → Stage-1 (Anthropic Haiku, tool) │
│ checklist | │ │ → Stage-2 router (pure) │
│ clarifier | │ │ direct | sequence | clarifier | │
│ NGO handoff │ │ retrieve (embed + rerank) │
└────────────────┘ │ → buildChecklist → cache write │
└────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Anthropic │ │ Vectorize │ │ KV │
│ Haiku 4.5 │ │ bge-m3, │ │ scenarios │
│ enum-only │ │ 1024 cos │ │ sequences │
│ tool-use │ │ + reranker │ │ ngos │
└────────────┘ └────────────┘ │ cache:* │
│ feedback │
└────────────┘
Knowledge base ──▶ packages/content/scripts/index.ts ──▶ Vectorize + KV
(21 scenarios, 7 sequences, 5 locales)
See docs/ARCHITECTURE.md for the full pipeline, the coverage gates, and the content model.
Cloudflare Workers · Cloudflare Pages · Vectorize · Workers AI
(bge-m3 embeddings + bge-reranker-base) · Anthropic Haiku 4.5
(forced tool-use, prompt-cached) · Next.js 15 + Tailwind · pnpm monorepo.
pnpm install
cp apps/api/.dev.vars.example apps/api/.dev.vars # Anthropic key (reserved)
cp apps/api/wrangler.toml.example apps/api/wrangler.toml # paste KV namespace ID
pnpm dev:api # Cloudflare Worker on http://localhost:8787
pnpm dev:web # Next.js frontend on http://localhost:3000Requires Node 20+, pnpm 9+, Wrangler, and a Cloudflare account with Workers, Vectorize, KV, and Workers AI enabled.
pnpm lint:content # validate KB content (schema, URL allowlist, tags)
pnpm test # Vitest
pnpm test:e2e # Playwright
pnpm test:adversarial # adversarial prompt suite against a deployed preview
pnpm index # embed content and upsert to Vectorize + KV
pnpm deploy:api # deploy Worker
pnpm deploy:web # build + deploy Pagesapps/api Cloudflare Worker — /api/query, /api/feedback, /api/health
apps/web Next.js 15 static export — landing, checklist, language switcher
packages/shared Zod schemas + TypeScript types + pure helpers (shared contract)
packages/content Knowledge base Markdown + indexer + content lint + NGO list
docs/ ARCHITECTURE.md
Scenarios and sequences live in packages/content/. Each scenario is a
Markdown file with YAML frontmatter; each sequence references scenarios by
ID and can branch on situation tags. The schema is enforced by
pnpm lint:content, the URL allowlist is in
packages/content/scripts/lint-content.ts, and the situation-tag vocabulary
is closed (see packages/content/SITUATION_TAGS.md).
For conventions, invariants, and the agent contract see CLAUDE.md and docs/ARCHITECTURE.md.
Licensed under the Apache License, Version 2.0. Content under
packages/content/ is authored for the Wegweis project; reuse outside it
should credit the source URLs listed on each scenario.