Add Matrix NL-query Q&A surface (W2 step 5)

Read-only natural-language query over the curated nl_query endpoint, answered
in-thread. Two entry points (room-per-purpose model): a dedicated Q&A room
(MATRIX_QUERY_ROOM) where every top-level message is a question, plus the
?/@bot trigger in the intake room as a cross-room convenience. Both routes hit
the same handle_query -> crm_client.nl_query -> POST /api/query/nl; translation
runs on the box's local model, nothing leaves the box, and there is no write
path so no approval gate applies.

Pure logic (trigger parsing, answer rendering) in query.py with offline tests;
async room wiring in bot.py (live-smoke only, per the bot's convention).

Bot-side only, ships on the Spark via git pull + restart. Depends on the
box-side /api/query/nl endpoint, which lands with the v93 s9pk (reminders + W2):
until v93 is installed the Q&A surface 404s, so the bot deploy is staged to
follow that install.
This commit is contained in:
Keysat
2026-06-18 19:46:54 -05:00
parent 6c29c22601
commit 68106d7a5a
10 changed files with 458 additions and 5 deletions
+10
View File
@@ -68,3 +68,13 @@ def team_roster():
# intake flow. Unset/empty disables the whole email-review poll loop (the bot just does intake).
def email_review_room():
return os.environ.get("MATRIX_EMAIL_REVIEW_ROOM", "").strip()
# Dedicated Q&A room for read-only natural-language queries (W2). In this room EVERY top-level
# message is treated as a question — no '?'/'@bot' trigger needed (the trigger only exists to
# disambiguate question-vs-note when Q&A shares the intake room; here that's unnecessary). The
# '?'/'@bot' trigger still works in the intake room too, as a cross-room convenience. Unset/empty
# just means no dedicated room (questions then go through the intake-room trigger). The bot must be
# a member of this room. Read-only — no approval gate, no redaction, no special power level needed.
def query_room():
return os.environ.get("MATRIX_QUERY_ROOM", "").strip()