feat(ai): per-bot AI persona - a custom /ask system prompt per assistant bot (LC-695) #662

Merged
longjacksonle merged 2 commits from feat/LC-695-bot-personas into main 2026-08-09 19:40:17 +02:00

What

Gives each assistant bot its own /ask persona (a custom system prompt), building on LC-693 (which chose which bot the assistant posts as, but left one fixed behavior for all). Implements LC-695.

How

  • Composed prompt with a fixed safety envelope. The /ask system prompt is now persona + an invariant ASSISTANT_RULES half (safety refusal, the "answer from the room's context, don't invent facts" contract, formatting). effective_system_prompt(persona) always appends the rules, so a custom persona shapes tone/role but can never remove the safety refusal or the retrieval contract. A blank persona falls back to the default opener (mirrors LC-693's identity fallback).
  • Storage: settings KV keyed bot_persona:<username> - no schema change, consistent with LC-693's assistant_bot_username.
  • Scope: applies to /ask only. Catch-up, recap, triage, and weekly-recap keep their own task-specific prompts (they don't use the assistant persona prompt).
  • Admin UI: a persona textarea in the existing "AI assistant identity" card, editing the persona of whichever bot is the active assistant. The target bot is re-resolved server-side on save (effective_assistant_username, which does not lazily create the built-in bot), so a stale client can't write another bot's persona. Site-admin only.

Tests

New unit tests: the default prompt still carries the safety/retrieval clauses; a custom persona is applied but cannot drop the envelope; a blank persona falls back to the default.

Scope

routes/assistant.rs (prompt split + composer + persona lookup + non-creating username helper), routes/admin.rs (persona read, POST /admin/bots/persona, route), views/admin.rs (BotsPage.assistant_username / .assistant_persona), templates/admin/bots.html (persona textarea), locales/en+es/admin.ftl. No schema change.

Verification

  • cargo check / cargo clippy --lib: clean.
  • cargo fmt --check: clean.
  • Full lib suite: 319 passed (incl. i18n en/es parity + the 2 new persona tests).

🤖 Generated with Claude Code

https://claude.ai/code/session_011M7CNRsGzgzc48KinZUAQ8

## What Gives each assistant bot its own `/ask` persona (a custom system prompt), building on LC-693 (which chose which bot the assistant posts *as*, but left one fixed behavior for all). Implements LC-695. ## How - **Composed prompt with a fixed safety envelope.** The `/ask` system prompt is now `persona` + an invariant `ASSISTANT_RULES` half (safety refusal, the "answer from the room's context, don't invent facts" contract, formatting). `effective_system_prompt(persona)` always appends the rules, so a custom persona shapes tone/role but can **never** remove the safety refusal or the retrieval contract. A blank persona falls back to the default opener (mirrors LC-693's identity fallback). - **Storage:** settings KV keyed `bot_persona:<username>` - no schema change, consistent with LC-693's `assistant_bot_username`. - **Scope:** applies to `/ask` only. Catch-up, recap, triage, and weekly-recap keep their own task-specific prompts (they don't use the assistant persona prompt). - **Admin UI:** a persona textarea in the existing "AI assistant identity" card, editing the persona of whichever bot is the active assistant. The target bot is re-resolved server-side on save (`effective_assistant_username`, which does *not* lazily create the built-in bot), so a stale client can't write another bot's persona. Site-admin only. ## Tests New unit tests: the default prompt still carries the safety/retrieval clauses; a custom persona is applied but cannot drop the envelope; a blank persona falls back to the default. ## Scope `routes/assistant.rs` (prompt split + composer + persona lookup + non-creating username helper), `routes/admin.rs` (persona read, `POST /admin/bots/persona`, route), `views/admin.rs` (`BotsPage.assistant_username` / `.assistant_persona`), `templates/admin/bots.html` (persona textarea), `locales/en+es/admin.ftl`. No schema change. ## Verification - `cargo check` / `cargo clippy --lib`: clean. - `cargo fmt --check`: clean. - Full lib suite: 319 passed (incl. i18n en/es parity + the 2 new persona tests). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_011M7CNRsGzgzc48KinZUAQ8
feat(ai): per-bot AI persona - a custom /ask system prompt per assistant bot (LC-695)
Some checks failed
check-secrets / Nosey parker (pull_request) Successful in 4s
Check / clippy + fmt + tests (pull_request) Failing after 1m20s
check-secrets / Nosey parker (push) Successful in 2m22s
check-secrets / TruffleHog (push) Successful in 2m22s
check-secrets / Kingfisher (pull_request) Successful in 2m10s
check-secrets / Kingfisher (push) Successful in 2m25s
check-secrets / TruffleHog (pull_request) Successful in 2m11s
92439fa953
Follow-up to LC-693, which let an admin choose which bot the AI assistant posts AS. Its behavior was still one fixed persona: a single hard-coded system prompt, so a "standup-bot" answered exactly like "assistant". This lets each bot have its own /ask personality.

The /ask system prompt is now composed: a persona half (the personality/role) plus an invariant ASSISTANT_RULES half (the safety refusal, the "answer from the room's context, don't invent facts" contract, and the formatting rules). effective_system_prompt(persona) puts the operator's persona in front and always appends the rules, so a custom persona can shape tone and role but can NEVER remove the safety refusal or the retrieval contract - the envelope is not overridable from the admin UI. A blank persona falls back to the default opener, mirroring how LC-693 falls back to the built-in identity.

The persona is stored in the settings KV keyed bot_persona:<username> (no schema change, consistent with LC-693's assistant_bot_username), and handle_ask looks it up for the bot the assistant actually posts as. It applies to /ask only; the catch-up, recap, triage, and weekly-recap surfaces keep their own task-specific prompts. Admin > Bots gains a persona textarea in the existing "AI assistant identity" card, editing the persona of whichever bot is the active assistant; the target bot is re-resolved server-side on save so a stale client cannot write another bot's persona. Site-admin only (the Bots page gate).

New tests cover the composition: the default prompt still carries the safety/retrieval clauses, a custom persona is applied but cannot drop the envelope, and a blank persona falls back to the default. No schema change. i18n en/es both updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M7CNRsGzgzc48KinZUAQ8
fix(ai): silence saas-build dead-code on effective_assistant_username (LC-695)
All checks were successful
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / TruffleHog (pull_request) Successful in 4s
check-secrets / Nosey parker (push) Successful in 5s
check-secrets / Kingfisher (push) Successful in 6s
check-secrets / Kingfisher (pull_request) Successful in 8s
Check / clippy + fmt + tests (pull_request) Successful in 5m50s
Create release / Create release from merged PR (pull_request) Has been skipped
f85c2f13fe
routes::admin is standalone-only (#[cfg(feature = "standalone")]), and it is the sole caller of effective_assistant_username (which lives in the always-compiled assistant module). The saas build therefore fails -D dead-code. Add #[cfg_attr(not(feature = "standalone"), allow(dead_code))], the same guard the other standalone-only helpers use (see routes/auth.rs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M7CNRsGzgzc48KinZUAQ8
longjacksonle deleted branch feat/LC-695-bot-personas 2026-08-09 19:40:17 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!662
No description provided.