The README told people to clone. For anyone adapting this to their own parliament
that is wrong: they cannot push, so their work is stranded on one machine, and
pulling in later updates is awkward. docs/YOUR-PARLIAMENT.md covers forking, and the
habit that decides whether future updates are painless — add files rather than edit
them, so git never has to merge the same file twice.
Writing it meant walking through a Norwegian adaptation for real, which found two
things that would have stopped a user on their first attempt.
A relative PARLIAMENT_CONFIG resolved against the current directory, so
`PARLIAMENT_CONFIG=parliament.no.yaml` worked when you happened to be standing in the
repository and failed everywhere else, including under systemd. Relative paths now
resolve against the repository, for PROMPTS_DIR and CONTENT_DIR too.
Worse, YAML 1.1 reads unquoted `no`, `yes`, `on`, `off`, `y` and `n` as booleans.
Norway is the worst possible case: `country: NO` and `prompt_language: no` both became
false, and the symptom was a TypeError deep inside a path join that named nothing
relevant. Text fields are now coerced back, and both parliament.yaml and the guide say
to quote such values, since the repair cannot recover the original capitalisation.
Verified by building a Norwegian fork the way the guide describes — adding
parliament.no.yaml, prompts/no/ and an adapter, editing nothing — and confirming the
app loads it. Sweden is unaffected and production still serves.
The README's documentation index is now grouped by what you are trying to do, with a
sentence on each entry rather than a bare filename.
Sixteen system prompts lived as module-level string constants across chat.py,
board.py, synthesis.py, trip.py and llm_tools.py. They are the main thing a fork
for another parliament has to rewrite, and editing them meant editing Python.
They now live under prompts/sv/ as Markdown. Placeholders use string.Template
($name), not str.format: three of these prompts embed literal JSON braces that
str.format raises on, and safe_substitute leaves an unknown placeholder alone
rather than killing a live chat turn over a typo.
Technical configuration that had leaked into the prompt text is now templated:
websearch_to_tsquery('swedish', ...) became '$fts_config', "Answer in Swedish"
became "Answer in $answer_language", and the åäö preservation rule became
$preserve_characters. Domain vocabulary from parliament.yaml is available too, so a
prompt can say $speech_plural and read naturally in any language.
PROMPTS_RELOAD=1 re-reads the files per call, so prompt iteration no longer needs
a server restart.
The prompts themselves stay Swedish. That is the intended design: a fork writes
prompts/<lang>/ in its own language, and the loader falls back through
<lang>/ -> shared -> en/.
Verified by snapshotting all sixteen constants before the move and asserting
equality after: 16/16 render byte-identically, including after parameterization —
which is what proves the templating substitutes exactly what was there before.
tests/test_prompts_golden.py keeps that guarantee going forward.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>