Inspiration

Every autumn Lund University admits international students who arrive to systems nobody writes down. Which bin the coffee grounds go in. How to book the laundry room, and what happens socially if you overrun your slot. That bottles are worth money if you take them to the right machine. The signs are in Swedish, the rules are unwritten, and the people who know them learned by living here.

There is a worse problem underneath. Ask a general-purpose assistant how to get a residence permit and it will answer — confidently, with steps and citations. Some of it will be right. A student acting on the wrong part can jeopardise their right to stay.

What it does

undra explains the everyday systems: housing and rental scams, arrival logistics from Copenhagen airport, waste sorting, the bottle deposit, regional transport, laundry etiquette. Photograph a Swedish sign — a booking board, a recycling label, a notice on a door — and ask what it means.

It also refuses. Immigration, tax and civil registration, tenancy law and medical questions are routed to the authority that owns the decision: Migrationsverket, Skatteverket, AF Bostäder and the LU International Desk, 1177 Vårdguiden and 112.

The line is not the topic. It is the kind of statement:

  • answered — "1177 Vårdguiden is where you book non-emergency care"
  • refused — "you should see a doctor about that"
  • answered — "Migrationsverket decides residence permit applications"
  • refused — "your residence permit remains valid while it is processed"

Explaining how a system works is the product. Deciding something about the person asking is not.

How we built it

The product is a FastAPI service on Cloud Run with a single-file mobile web interface, calling gemini-3.6-flash through the official Google GenAI SDK for both text and image questions.

The unusual part is who built it. An autonomous agent called Coral wakes every four hours, reads its own ledger, decides what to do next, commissions code from a build agent, reviews it against a written charter, merges it, records what it spent, and goes back to sleep. It has no memory between cycles — a SQLite ledger is the only continuity there is. It cannot log in anywhere, holds no payment instrument and cannot agree to terms; when it needs one of those it asks a human and waits, and every request states what happens if nobody answers.

Its decision log is public at log.undra.nu, rendered from the same ledger.

Challenges we ran into

The refusal guardrails were wrong twice, in opposite directions, and both were found by measurement rather than review.

Too broad: every medical pattern was a bare institution noun, so "what is 1177?" returned a refusal card telling the user to contact 1177. The product refused to explain the service it was recommending.

Too narrow: nine of eleven tenancy questions phrased the way people actually phrase them — "my landlord kept my deposit, what are my rights?" — passed straight through, because every pattern in that category was written in the vocabulary of the category name. Nobody in trouble writes "tenancy dispute".

Then the fix for the first one did not reach users for a day. The same rule was written down in three places — a prompt, an input pattern set, an output pattern set — and changing one left the other two contradicting it. Meanwhile a deploy shipped a six-day-old tree while the build, the deploy, Cloud Run and the health check all reported success, because none of them look at what is actually running.

Accomplishments that we're proud of

The instrumentation was built before the product, and it caught things a review would not. A deterministic auditor checks every money figure, model name, date and hostname in the submission against the ledger — it exists because an earlier draft, written by an agent, contained invented financial figures and passed CI. That failure is documented in the submission rather than removed.

Refusals are deterministic and run before any model call, so a refused question costs nothing, returns in about 0.2 seconds and cannot be talked around by rephrasing.

What we learned

Deterministic guardrails are not one thing. On the way in they are everything they claim: free, fast, unarguable. On the way out they are asked a question that often has no answer in the text — "You should call 112 in an emergency" is general information in one context and a determination about a specific person in another, and the difference is not in the sentence. We now say so in the code rather than implying a pattern list is more capable than it is.

The other lesson was about the human. The system had a deterministic briefing for the agent, regenerated every four hours precisely because a stateless instance cannot remember — and nothing equivalent for the person supervising it. She deliberately let herself lose the thread to see what would happen, and found that re-entering was hard because every document had been written for someone already inside the system.

What's next for undra

Arrival Day for autumn 2026 is 18 August, which is the week this is submitted — the moment the product was built for. The immediate work is real users rather than one, and a classifier to replace the pattern matching on the output path, which is the known limitation of the current design.

Built With

  • artifact-registry
  • cloud-build
  • cloud-run
  • fastapi
  • gemini
  • gemini-3.6-flash
  • github-actions
  • google-genai
  • jules
  • pillow
  • python
  • secret-manager
  • sqlite
  • tailwindcss
  • telegram-bot-api
  • uvicorn
Share this project:

Updates