trails/docs/demo-persona.md
Ullrich Schäfer fc4485f6ef
Apply configurable-demo-persona: per-instance demo identity + voice
Adds DEMO_BOT_PERSONA env var (inline JSON or file:<path>) so
self-hosted instances can replace Bruno-in-Berlin with their own
demo account identity and content pools. No-op for trails.cool — the
built-in Bruno persona remains the default.

- DemoPersona type + Zod schema validation
- loadPersona() cached at boot; falls back to default on any failure
- ensureDemoUser throws DemoPersonaUsernameClashError when the
  persona username is already a real user; server declines to
  schedule demo jobs for that process
- isDemoUser flag moved from hardcoded "bruno" check to loader-supplied
  boolean computed from the running persona
- docs/demo-persona.md explains the schema + operator flow

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 10:30:32 +02:00

87 lines
3.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Demo persona
The Journal's demo bot (see the `demo-activity-bot` spec) ships with a default Bruno-in-Berlin persona. Self-hosted instances can override the identity and voice without touching code by supplying `DEMO_BOT_PERSONA` to the journal container.
Region is separate (`DEMO_BOT_REGION`) — an operator may mix-and-match a non-Berlin region with the default Bruno persona, or vice versa, though it usually reads better to keep persona and region stylistically consistent.
## Supplying a persona
Two forms are accepted:
1. **Inline JSON**`DEMO_BOT_PERSONA='{"username":"hamish",...}'`. Workable for short personas; gets ugly fast once the pools are realistic.
2. **File path**`DEMO_BOT_PERSONA=file:/etc/trails-cool/persona.json`. The journal container reads the file once at boot; mount the file via a volume or Docker secret.
A missing / invalid / unreadable persona falls back to the built-in default with a single warn log line. The bot never crashes over a bad persona.
## Schema
```json
{
"username": "<kebab-case, 232 chars, matches /^[a-z0-9][a-z0-9_-]{1,30}$/>",
"displayName": "<string, 1200 chars>",
"bio": "<string, 0200 chars>",
"locales": ["en"] | ["de"] | ["en","de"],
"content": {
"names": {
"en": ["<3 to 50 non-empty strings>"],
"de": ["<3 to 50 non-empty strings>"]
},
"descriptions": {
"en": ["<3 to 50 non-empty strings>"],
"de": ["<3 to 50 non-empty strings>"]
}
}
}
```
Every locale listed in `locales` MUST have both a `names` pool and a `descriptions` pool. Locales omitted from `locales` may also omit their pools.
Sensible pool size is 1015 entries per pool. Fewer than 3 is rejected; more than 50 is rejected. The generator samples deterministically from the walk's start-time, so very small pools produce visibly-repeating copy in the daily feed.
## Example: Hamish the English-only Scottish collie
```json
{
"username": "hamish",
"displayName": "Hamish",
"bio": "Border collie. Arthur's Seat regular.",
"locales": ["en"],
"content": {
"names": {
"en": [
"Arthur's Seat summit patrol",
"Holyrood Park morning rounds",
"Royal Mile crumb inspection",
"Dean Village bridge audit",
"Inverleith perimeter check",
"Blackford Hill squirrel count",
"Water of Leith investigation",
"Hamish vs. the seagulls",
"Portobello beach reconnaissance",
"Cramond causeway survey"
]
},
"descriptions": {
"en": [
"Sniffed an impressive number of bins. All catalogued.",
"Weather: dreich. Ears: flat.",
"One dropped scone located. Consumed as evidence.",
"Diplomacy established with the resident Labrador.",
"Route completed. Biscuits expected.",
"Found three sticks. Returned with one."
]
}
}
}
```
## Rollout
1. Write `persona.json`, mount it via your compose override or Docker secret.
2. Set `DEMO_BOT_PERSONA=file:/path/to/persona.json` in your SOPS env.
3. Set `DEMO_BOT_ENABLED=true`.
4. Restart the journal container.
On boot, the worker logs `demo-bot user ensured` with the persona's user id. If that username is already in use by a real human user, the worker logs a `demo persona username clash` error and declines to schedule the demo jobs — pick a different username and restart.
Rolling back is the same in reverse: unset `DEMO_BOT_PERSONA`, restart. The built-in default returns.